dsh-plugin-onekey logo # dsh-plugin-onekey **True one-click install / uninstall for DeepSeek Harness plugins** Click "copy" and the plugin is installed. Type `/uninstall` and it is gone. [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/lishLRF/dsh-plugin-onekey/actions/workflows/ci.yml/badge.svg)](https://github.com/lishLRF/dsh-plugin-onekey/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/lishLRF/dsh-plugin-onekey?color=2ea44f)](https://github.com/lishLRF/dsh-plugin-onekey/releases) [![Topic: dsh-plugin](https://img.shields.io/badge/topic-dsh--plugin-0e7490.svg)](https://github.com/topics/dsh-plugin) [![Tests](https://img.shields.io/badge/tests-129%20passing-2ea44f)](https://github.com/lishLRF/dsh-plugin-onekey/actions) [简体中文](README.md) · **English**
--- ## Why this exists The built-in DSH plugin hub (`dsh-plugin`) only **copies** `dsh plugin add ` to the clipboard — everything else is on you: open a terminal, paste, wait for pnpm, restart the backend. Uninstalling is worse: there is no entry point at all. `dsh-plugin-onekey` completes the whole chain: | Before | After | |--------|-------| | Click "copy" → paste it in a terminal yourself | Click "copy" → confirm dialog → progress bar → done | | Manually restart `dsh web` afterwards | "Restart backend now" does it, the page recovers itself | | Copy error logs by hand to ask an AI | "Copy to workspace & ask LLM" writes prompt + logs into the composer | | Uninstall by hand-editing config | Type `/uninstall` → pick → confirm → auto-uninstall + restart | ## ✨ Features ### 🖱️ True one-click install - Clicking **copy** on any plugin in **Settings → Plugin Hub** pops a confirmation dialog showing the exact command; - After confirming, a **live progress bar** (parse → validate → pnpm → config write) runs inside the dialog; - The backend **reads the install command from the OS clipboard** (Windows / macOS / Linux), validates it against a strict whitelist, runs pnpm inside the profile directory and registers the plugin in `dsh.profile.bundles`. ### 🚨 Red failure bar + one-click hand-off - On failure the progress bar turns red and the full error is shown below it; - **"Copy to workspace & ask LLM"**: closes the dialog and writes a prepared prompt (plus the full error log) straight into the current workspace composer (falls back to the clipboard when no session is open); - **"Close"**: just closes. ### 🔄 One-click backend restart on success - Success shows "Configuration complete — restart DSH to refresh"; - **"Restart backend now"**: replays the originally recorded launch command (`execPath + argv + cwd + env`), the frontend probes the port and refreshes automatically once the backend is back; - **"Not now, close"**: closes without doing anything. ### 🗑️ `/uninstall` slash command - Type `/uninstall` in the workspace composer and a native-style candidate menu appears above the input; - Candidates come from the profile dependency list — automatically excluding this plugin, the plugin hub, and DSH's in-box bundles; - Picking one opens a confirmation dialog and **never touches the LLM**: pnpm remove → config rewrite → automatic backend restart → "Restarting, please wait…" until the page reloads itself. ### 🛡️ Security & permissions - **Fully code-driven — no LLM calls anywhere**; - **Strict whitelist parsing**: only `dsh plugin [--profile X] add ` is accepted (npm names / `owner/repo` / `github:` / `git+https` / https tarballs); `file:`, `link:`, paths and all shell metacharacters are rejected (including `./` and `../` path bypasses); - pnpm runs as an argument array, and the command is shown for confirmation before execution; - Every operation is double-checked against live permissions (config-level gateway). ### 🎛️ Settings → Plugins → Plugin configuration | Setting | Default | When off | |---------|---------|----------| | Enable this plugin | ✅ | Slash command, dialogs and backend routes all stop | | Allow one-click install | ✅ | Install route returns 403 | | Allow uninstalling plugins | ✅ | `/uninstall` candidates empty, route 403 | | Allow automatic backend restart | ✅ | "Restart backend now" unavailable | | Allow reading the clipboard | ✅ | Backend no longer reads the OS clipboard | ### 🧭 Zero-config environment detection Profile directory comes from `ctx.baseUrl` (the cordis config-tree anchor); pnpm is resolved from PATH with a corepack fallback; the restart command is a snapshot of the actual launch — **so every official DSH install method (npm global / source via tsx / binary) works out of the box**. Detection results are shown live on the plugin's configuration card. ### 🧩 Capabilities live and die with the plugin Every extension point (HTTP routes, the `shell.overlay` dialog, the `/uninstall` trigger source, the settings card, the clipboard interception) is registered through DSH's public slots/services and torn down with the plugin fiber: **no DSH or third-party code is modified**, disable and the capabilities disappear, enable and they return. ## 🚀 Installation > Requires: a DSH web profile, Node ≥ 20, pnpm available (or corepack). ```bash # npm (recommended) dsh plugin --profile web add dsh-plugin-onekey # GitHub dsh plugin --profile web add github:lishLRF/dsh-plugin-onekey # Local development dsh plugin --profile web add link: ``` Restart `dsh web` afterwards, then open **Settings → Plugins → Plugin configuration** to confirm the plugin is enabled and review its permissions. ## 📖 Usage **One-click install** 1. Open Settings → Plugin Hub and click "copy" on any plugin; 2. Confirm "Configure it into DSH directly?" → "Install"; 3. Wait for the bar to finish → "Restart backend now" → the page recovers on its own. **One-click uninstall** 1. Type `/uninstall` in the workspace composer; 2. Pick the plugin from the candidate list; 3. Confirm → auto-uninstall + restart → the page recovers on its own. **Troubleshooting** On a failed install, click "Copy to workspace & ask LLM" and just press send. ## 🔐 Security model 1. **Command whitelist**: regex plus leading-character rules; 2. **No shell string-building**: argument-array spawn (on Windows, the same trust boundary as the official CLI; arguments are whitelist-sanitized); 3. **Permission gateway**: live config re-checked before and after each operation; 4. **Confirmation dialog**: shows the command and the target before anything runs; 5. **Reversible capabilities**: every registration unloads with the plugin. ## 🧭 How it works ``` Hub "copy" button │ navigator.clipboard.writeText wrapper / document copy capture (signal) ▼ Confirmation dialog (command + permission notice) │ POST /plugins/onekey/install ▼ Backend reads OS clipboard → whitelist parse → permission re-check │ pnpm add (profile dir, argument array) ▼ bundles reconcile → progress polling → success / red failure bar │ success → POST /plugins/onekey/restart ▼ Detached relauncher (waits for old pid → replays original argv) │ frontend probes /plugins/onekey/ping every 2s ▼ Backend up → automatic page reload ``` `/uninstall` reuses the same pipeline: `inputTriggers` slash source → candidates (profile deps − self − hub) → `pnpm remove` → reconcile → auto-restart. ## 🧪 Development ```bash pnpm install pnpm run test # vitest, 129 tests (red-green TDD) pnpm run typecheck # host + client tsconfigs pnpm run build # tsdown dual artifacts: lib/index.js + lib/client.js pnpm run check # full gate (same as CI) ``` ``` src/ Host half (routes / clipboard / pnpm / relauncher / settings namespace) src/client/ Client half (dialog / progress / slash source / settings card / composer bridge) src/*.spec.ts Host pure-logic tests (whitelist / reconcile / relauncher / permission gate…) src/client/*.spec.ts Client state machine and controller tests docs/design.md Design document ``` ## ❓ FAQ **Git-hosted plugins blocked by pnpm?** pnpm ≥ 10 blocks `prepare` scripts of git dependencies. Click "Copy to workspace & ask LLM" on the failure dialog for the exact `allowBuilds` steps. **"pnpm not found"?** Install pnpm or enable corepack and retry. **Page didn't refresh after restart?** The restart dialog has a "Refresh manually" fallback button. **Dialog shows a failure after uninstalling?** Since v0.1.0 a backend disconnect during uninstall is treated as "restarting" and the polling takes over. If it still fails, open an issue with the error details. ## 📄 Related projects - [deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) — DeepSeek Harness itself - [dshplugin/dsh-plugin-hub](https://github.com/dshplugin/dsh-plugin-hub) — the DSH plugin hub ## License [MIT](LICENSE) © dsh-plugin-onekey contributors