dsh-clipboard-menu — a right-click clipboard menu for DeepSeek Harness **The right-click menu the DeepSeek Harness composer was missing** [简体中文](README.md) | [English](README.en.md) [![license](https://img.shields.io/badge/license-MIT-yellow.svg?style=flat-square)](LICENSE) [![release](https://img.shields.io/github/v/release/wuwaka/dsh-clipboard-menu?style=flat-square)](https://github.com/wuwaka/dsh-clipboard-menu/releases) [![CI](https://github.com/wuwaka/dsh-clipboard-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/wuwaka/dsh-clipboard-menu/actions/workflows/ci.yml) [![stars](https://img.shields.io/github/stars/wuwaka/dsh-clipboard-menu?style=flat-square)](https://github.com/wuwaka/dsh-clipboard-menu/stargazers) [![topic](https://img.shields.io/badge/topic-dsh--plugin-4d6bfe?style=flat-square)](https://github.com/topics/dsh-plugin) [![tested](https://img.shields.io/badge/tested%20on-DSH%200.1.5--rc.1-4d6bfe?style=flat-square)](#)
## What it is Right-click the DeepSeek Harness composer and get a menu: the menu that opens on right-click: Cut / Copy / Paste / Select All | 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 ```sh dsh plugin --profile add github:wuwaka/dsh-clipboard-menu ``` From a local checkout: ```sh dsh plugin --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: ```js 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')` | | `` / `