Skip to content
dsh-market Browse plugins GitHub 中文

wuwaka/dsh-clipboard-menu

Adds a right-click menu to DeepSeek Harness inside desktop shells that ship no context menu: cut, copy, paste and select all in the composer, copy and copy-as-plain-text on selected text elsewhere, and a search entry that opens the default browser with a configurable engine.

Stars ★ 0 Category UI Enhancements Listed 2026-09-13

Install

Inside DeepSeek Harness, with dsh-market

dsh plugin --profile web add dshmarket

Or from the command line

dsh plugin --profile web add github:wuwaka/dsh-clipboard-menu

Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.

README

The right-click menu the DeepSeek Harness composer was missing

简体中文 | English

CI

What it is

Right-click the DeepSeek Harness composer and get a menu:

Item Action
✂️ Cut Cut the selected text
📋 Copy Copy the selected text
📥 Paste Insert the system clipboard at the caret
🗂️ Select All Select the whole field
🌐 Search Search the selected text in your default browser, with the current engine shown on the right (greyed out without a selection)
🌐 Search engine… Hovering it expands the engine list directly below: Baidu / Bing / Google / DuckDuckGo / Custom

Select text in a read-only surface — an AI reply, a document preview, a code block — and right-click for three entries:

Item Action
📋 Copy Copy with formatting (paste into Word and the styling comes along)
📝 Copy as plain text Copy the characters only, which pastes cleanly into a terminal or editor
🌐 Search Search the selected text in your default browser
🌐 Search engine… Switch engine

How the engine is chosen

A page cannot read the browser's own default search engine — no web API exposes it, deliberately, so that sites cannot profile visitors. The order is therefore:

  1. Whichever you picked — hover "Search engine…" and click one in the list; it is stored in localStorage and sticks
  2. Otherwise a guess from the UI language: Chinese → Baidu, anything else → Google
  3. window.__DSH_CLIPBOARD_MENU_SEARCH__ overrides outright (developer escape hatch)

The list's "Custom" entry opens a small form, and you can keep as many providers as you like:

Field Meaning
Name Shown in the list and next to Search; defaults to the URL's host
URL The search address, using §{query}§ for the keywords; without it the query is appended

§§§ https://search.example.org/find?q={query} §§§

Enter or Save adds a provider. Right-clicking either field offers the same cut/copy/paste/select-all menu on its own layer, so pasting a URL never tears the form down — which is why there is no separate Paste button. Escape backs out one level rather than closing the whole menu.

Saved providers are listed under the name you gave them; click one to switch. Hover a row and a trash icon slides in at the far right; one click removes that provider (if it was the current engine, the engine falls back to the language guess). Any number of custom providers can coexist and be removed independently.

The browser itself needs no configuration. window.open goes through the desktop shell's shell.openExternal, which hands the URL to your operating system's default browser — whatever you already set.

Cut and Copy grey out when nothing is selected, and a right-click on read-only content with no selection is passed straight through to the app's own menu. Labels follow the interface language (Chinese / English); colours follow the system light or dark theme.

Why it exists

Electron ships no default context menu, and the DSH Desktop shell does not add one, so right-clicking the composer in the desktop window does nothing — you are stuck with Ctrl+C / Ctrl+V.

The desktop main process is a packaged bundle (lib/main.js + electron-runtime), so a plugin cannot hook webContents. The menu is therefore built in the renderer.

🚀 Install

dsh plugin --profile <profile> add github:wuwaka/dsh-clipboard-menu

From a local checkout:

dsh plugin --profile <profile> add /path/to/dsh-clipboard-menu

Restart the host (or the desktop app wrapping it) to load it.

🔒 Scope

It only activates in shells that have no context menu of their own.

A normal browser tab already has a native menu that does more (paste and match style, search, inspect), so the plugin does not install its listener there at all — right-click keeps behaving exactly as the browser intends, rather than being downgraded.

Detection:

Signal Source
dsh-desktop-mode and friends (URL) markers DSH Desktop stamps onto the renderer URL
window.__DSH_DESKTOP_FILE_PATH__ DSH Desktop's preload bridge
User-Agent containing Electron/ other Electron shells
__TAURI__ / __TAURI_INTERNALS__ Tauri shells

None of them (a plain browser) means it stays inert. To use the menu in a browser on purpose, set this before the plugin loads:

window.__DSH_CLIPBOARD_MENU_FORCE__ = true

⚙️ How it works

The DSH composer is a Lexical editor (a contenteditable root), so pasting cannot go through a plain value setter. Three insertion paths are used:

Target Paste path
contenteditable (the Lexical editor) dispatches a synthetic ClipboardEvent('paste') with a DataTransfer, consumed by Lexical's own paste listener; falls back to execCommand('insertText')
<input> / <textarea> (React-controlled) writes through the native value setter and dispatches an input event
xterm dispatches the same synthetic paste event

Cut and copy use document.execCommand('cut' | 'copy') after restoring the selection captured at right-click time (a Range for contenteditable, selectionStart/End for inputs). If copy fails it falls back to navigator.clipboard.writeText.

Exactly two cases are intercepted: editable fields (the full menu) and read-only content with a selection (the two copy entries). Everything else is passed through, so context menus the app or other plugins provide (JSON copy buttons, sidebar previews) keep working.

🧪 Test

npm install --no-save jsdom
node test/smoke.cjs

83 assertions: menu rendering, cut/copy disabled without a selection, textarea caret insertion plus the input event, the synthetic paste event reaching a contenteditable listener, read-only text without a selection not being intercepted, Copy and Copy-as-plain-text on a read-only selection, Copy not stealing focus, the menu surviving a scroll (while an outside mousedown still closes it), the search entry opening the browser with the selection, a Chinese UI defaulting to Baidu and an English one to Google, the hover submenu opening without closing the main menu, a named custom engine appearing in the list, right-clicking a form field opening a clipboard menu without closing the form, pasting into that field, a picked engine being remembered, a custom template's {query} being substituted, one icon per entry, the menu staying uninstalled in a browser, and the force flag opting back in.

⚠️ Limitations

  • If navigator.clipboard.readText() is denied, the menu reports that the clipboard is unavailable and suggests Ctrl+V.
  • No "paste and match style" entry.

📄 License

MIT

Content from the project README on GitHub ↗

Comments

Comments live in GitHub Discussions. Sign in with GitHub to post or react.