diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d92c561..6cca880 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,8 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } python -m json.tool plugins/quicker/.mcp.json > $null if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python -m json.tool plugins/quicker-dsh/package.json > $null + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Test transport, installation and release packaging run: python -m unittest discover -s tests -v - name: Build and verify every release package diff --git a/AGENTS.md b/AGENTS.md index c939021..7efd9b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This repository maintains integrations that let external agents use Quicker. Sta ## Boundaries -- Keep installable packages under plugins/ and make each directory self-contained. The Codex marketplace currently exposes plugins/quicker; declare each future platform's source path in its own catalog. +- Keep installable packages under plugins/ and make each directory self-contained. The Codex marketplace currently exposes plugins/quicker; DSH uses plugins/quicker-dsh as a cordis bundle. Declare each future platform's source path in its own catalog. - Quicker owns action execution, step knowledge, permissions, approvals and Catalog state. Discover runtime knowledge instead of copying a static module catalog here. - Do not add Quicker product source, private repository dependencies, personal configuration, client grants, credentials or machine-specific paths. - Add shared source when a second real consumer needs it. Copy required runtime files into each distributable package; do not use symlinks or paths outside the installed package. diff --git a/README.md b/README.md index e247c02..ede5589 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Quicker Agent Integrations -让 Codex、Cursor 等外部 Agent 调用本机 Quicker,编写和使用自动化动作。 +让 Codex、Cursor、DeepSeek Harness 等外部 Agent 调用本机 Quicker,编写和使用自动化动作。 这个仓库集中维护各平台插件、安装与更新工具、MCP 接入约定、开发指南和契约测试。插件通过 Quicker 的公开接口工作,步骤知识由运行中的 Quicker 实时提供。 -提供 Codex、Cursor、Claude Code 插件及 VS Code / Gemini CLI 的 MCP 配置安装器,采用 [MIT 许可证](LICENSE)。 +提供 Codex、Cursor、Claude Code、DeepSeek Harness 插件及 VS Code / Gemini CLI 的 MCP 配置安装器,采用 [MIT 许可证](LICENSE)。 ## 支持情况 @@ -14,6 +14,7 @@ | Cursor | 本地插件;CLI 真实写动作通过 | Windows;技能、草稿创建/保存/预览 | | Claude Code | 原生插件,v0.2.1 | v0.2.0 公开市场安装和真实 MCP 连接通过;模型写动作待验收 | | VS Code / Copilot、Gemini CLI | 配置安装器 | 默认 Windows 用户配置;尚未完成各客户端写动作验收 | +| DeepSeek Harness | DSH bundle 插件 | Windows;安装包与转接已实现,真实 DSH 会话写动作待验收 | 需要使用设置 → Agent 中带「启用 MCP」入口、并包含默认技能包发现修复的 Quicker 新构建。Release 支持已实现,待包含这些变更的正式版发布;已发布旧版没有该入口时仍不可用。本次已验证 Debug 的草稿编写与预览,Release 配置内核测试和正式前端构建通过,正式安装包端到端仍待验收,详见[兼容性说明](docs/兼容性.md)。 @@ -62,6 +63,26 @@ claude auth status Quicker 原生插件可与官方 [codex-plugin-cc](https://github.com/openai/codex-plugin-cc) 同时安装,前者直接提供 Quicker 工具,后者从 Claude Code 委托 Codex 审查或处理代码。两者不互为依赖,Codex 登录不能代替 Claude 登录。更新、连接超时处理及验收范围见[Claude Code 安装和诊断](docs/客户端安装.md#claude-code-安装和诊断)。 +## 安装 DeepSeek Harness 插件 + +在 Windows PowerShell 中执行(需要 Git): + +```powershell +$quickerInstall = Join-Path $env:TEMP ("quicker-agent-" + [guid]::NewGuid().ToString("N")) +git clone --depth 1 https://github.com/QuickerOrg/quicker-agent-integrations.git $quickerInstall +if ($LASTEXITCODE -eq 0) { + powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $quickerInstall "scripts/install-client.ps1") -Client dsh +} +``` + +安装器把自包含 bundle 复制到 `%USERPROFILE%/.quicker/agent-integrations/dsh`。PATH 中有 `dsh` 时会再执行 `dsh plugin --profile web add link:<该目录>`;否则把打印出的命令贴到目标 profile。开发检出可直接 `dsh plugin --profile web add link:<仓库>/plugins/quicker-dsh`。 + +启用 Quicker 设置 → Agent 中的 MCP 与允许写入,重启 `dsh web` 或 DSH 桌面端。首次连接确认 `dsh-quicker-plugin`。工具名为 `mcp__quicker__skill_load` 这类带命名空间的名称。可以这样开始: + +> 用 Quicker 写一个动作,显示“来自 DeepSeek Harness”,保存到暂存区并打开预览,不运行。 + +更新、卸载和诊断见[客户端安装](docs/客户端安装.md)。本轮完成安装包、转接和隔离测试,尚未声称真实 DSH 会话已经写过动作。 + ## VS Code / Copilot 与 Gemini CLI 使用上述下载步骤,将安装器参数分别改为 `-Client vscode` 或 `-Client gemini`。安装器只合并自己的 MCP 项,保留其他服务与设置,token 始终由本地转接读取。 @@ -143,6 +164,7 @@ codex plugin remove quicker@quicker-agent-integrations marketplace.json Codex Git 市场入口 plugins/ quicker/ Codex 完整安装单元 + quicker-dsh/ DeepSeek Harness bundle docs/ 接入约定.md 跨平台协议、权限、错误及动作编写约定 新增平台.md 新平台开发和验收流程 @@ -153,7 +175,7 @@ tests/ test_quicker_mcp.py 独立的传输契约测试 ``` -Cursor、Claude 和 Codex 分别由自己的 marketplace 清单声明包路径。`shared/` 是传输与写动作技能的唯一源码,`python scripts/sync-packages.py` 同步到各自包含安装包,`--check` 在 CI 验证无漂移。 +Cursor、Claude 和 Codex 分别由自己的 marketplace 清单声明包路径。DeepSeek Harness 使用 `plugins/quicker-dsh` 的 `dsh.bundle` 清单,不走那些市场。`shared/` 是传输与写动作技能的唯一源码,`python scripts/sync-packages.py` 同步到各自包含安装包,`--check` 在 CI 验证无漂移。 ## 开发与验证 diff --git "a/docs/\345\205\274\345\256\271\346\200\247.md" "b/docs/\345\205\274\345\256\271\346\200\247.md" index 8aad843..e03c6f4 100644 --- "a/docs/\345\205\274\345\256\271\346\200\247.md" +++ "b/docs/\345\205\274\345\256\271\346\200\247.md" @@ -20,6 +20,7 @@ | Cursor CLI | 2026-09-07:2026.08.11-e8db854;v0.2.0 本地安装包技能加载、实时知识发现、4 步动作创建/保存/预览通过。IDE 3.7.12 的插件设置界面重载尚未单独操作验收 | | Claude Code | 2026-09-07:2.1.263;v0.2.0 插件与市场通过官方 validator,隔离安装及真实用户配置下的公开市场安装成功;识别 write-action 技能与 MCP,mcp list 连接真实 Quicker 成功。模型调用因未登录退出,未验证 Claude 模型写动作 | | VS Code / Gemini CLI | v0.2.0 默认用户配置安装/更新/卸载和隔离传输通过;尚未完成实际客户端写动作验收 | +| DeepSeek Harness | v0.2.0 bundle 清单、安装器复制、`dsh` Client 转接与隔离传输通过;尚未完成真实 DSH 会话写动作验收 | ## 版本记录 diff --git "a/docs/\345\217\221\345\270\203.md" "b/docs/\345\217\221\345\270\203.md" index 749410f..9da5844 100644 --- "a/docs/\345\217\221\345\270\203.md" +++ "b/docs/\345\217\221\345\270\203.md" @@ -1,6 +1,6 @@ # 发布完整插件集合 -每次 GitHub Release 都必须提供 **Codex、Cursor、Claude Code、通用 MCP 的全部独立 ZIP**,即使本次只修改其中一个插件。最新 Release 应能直接找到所有平台的安装包,不能要求用户到旧版本寻找其他插件。 +每次 GitHub Release 都必须提供 **Codex、Cursor、Claude Code、通用 MCP、DeepSeek Harness 的全部独立 ZIP**,即使本次只修改其中一个插件。最新 Release 应能直接找到所有平台的安装包,不能要求用户到旧版本寻找其他插件。 ## 单一清单与版本 @@ -8,10 +8,10 @@ 文件名统一使用发布批次版本,包内 manifest 保留平台自己的版本。例如批次 `v0.2.2` 会生成 `quicker-cursor-0.2.2.zip`,即使其中的 Cursor 插件仍为 `0.2.0`。不要为凑齐附件擅自递增未变更的插件版本。`release-manifest.json` 分别记录 `releaseVersion`、各包 `packageVersion`、源 tag / commit、附件名、大小和 SHA-256。 -当前每批必须有 7 个附件: +当前每批必须有 8 个附件: - `quicker-agent-integrations-<发布版本>.zip`:完整源码安装包,含各平台包、安装器、市场入口、文档与许可证。 -- `quicker-codex-<发布版本>.zip`、`quicker-cursor-<发布版本>.zip`、`quicker-claude-<发布版本>.zip`、`quicker-mcp-<发布版本>.zip`:所有独立包。 +- `quicker-codex-<发布版本>.zip`、`quicker-cursor-<发布版本>.zip`、`quicker-claude-<发布版本>.zip`、`quicker-mcp-<发布版本>.zip`、`quicker-dsh-<发布版本>.zip`:所有独立包。 - `release-manifest.json`:可审查的批次与包版本记录。 - `SHA256SUMS.txt`:覆盖全部 ZIP 及发布清单。 diff --git "a/docs/\345\256\242\346\210\267\347\253\257\345\256\211\350\243\205.md" "b/docs/\345\256\242\346\210\267\347\253\257\345\256\211\350\243\205.md" index 3dcfe32..6343d42 100644 --- "a/docs/\345\256\242\346\210\267\347\253\257\345\256\211\350\243\205.md" +++ "b/docs/\345\256\242\346\210\267\347\253\257\345\256\211\350\243\205.md" @@ -10,17 +10,19 @@ | Claude Code | plugins/quicker-claude | `.claude-plugin/plugin.json` + `.mcp.json` + skills;原生市场 | | Codex | plugins/quicker | `.codex-plugin/plugin.json` + `.mcp.json` + skills;原生市场 | | VS Code / Gemini CLI | plugins/quicker-mcp | 安装本地 stdio 转接并合并客户端 MCP JSON;编写规则来自 MCP initialize 与 skill_load | +| DeepSeek Harness | plugins/quicker-dsh | `package.json` 的 `dsh.bundle` + `cordis.patch.yml` + `index.js`;安装器复制到本机目录,再 `dsh plugin --profile web add link:…` | Cursor 和 Claude 分别展开 `${CURSOR_PLUGIN_ROOT}`、`${CLAUDE_PLUGIN_ROOT}`,Codex 使用包内相对 cwd。安装后各包独立运行,不访问 shared/ 或开发检出。 ## 安装器的范围 -从本仓库执行 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-client.ps1 -Client cursor`。支持 cursor、vscode、gemini。安装器不会打开 Quicker 权限开关。 +从本仓库执行 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-client.ps1 -Client cursor`。支持 cursor、vscode、gemini、dsh。安装器不会打开 Quicker 权限开关。 - Cursor:`%USERPROFILE%/.cursor/plugins/local/quicker`。 - VS Code 默认用户:`%USERPROFILE%/AppData/Roaming/Code/User/mcp.json` 的 `servers.quicker`。 - Gemini CLI:`%USERPROFILE%/.gemini/settings.json` 的 `mcpServers.quicker`。 - 通用转接安装目录:`%USERPROFILE%/.quicker/agent-integrations/`。 +- DeepSeek Harness:`%USERPROFILE%/.quicker/agent-integrations/dsh`,再链入 profile;默认 `web`。 现有 quicker 项、插件目录不属于此安装器,或已安装文件被手动修改时会拒绝覆盖。安装器保留其他设置,替换前备份原配置/包,已安装包用归属标记和文件 hash 校验。备份保存在本机,不上传。JSON 不能解析时原文件不变;VS Code JSONC(注释、尾逗号)、便携版、自定义用户数据目录和 Profile 不由此安装器自动处理。 @@ -43,7 +45,19 @@ VS Code 的服务项还需 `"type": "stdio"`。不填 URL、Authorization 或 to powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-client.ps1 -Client cursor -Uninstall ``` -VS Code / Gemini 同样替换 Client。卸载只删除归属匹配且未编辑的当前包与对应 MCP 项,保留其他配置与旧备份。Codex 和 Claude Code 使用各自原生插件卸载命令。 +VS Code / Gemini / DSH 同样替换 Client。卸载只删除归属匹配且未编辑的当前包与对应 MCP 项,保留其他配置与旧备份。DSH 还会尝试 `dsh plugin --profile web remove dsh-plugin-quicker`。Codex 和 Claude Code 使用各自原生插件卸载命令。 + +## DeepSeek Harness 安装和诊断 + +安装器复制 bundle 后,若 PATH 中有 `dsh`,会执行 `dsh plugin --profile web add link:<安装目录>`。桌面端或其它 profile 把 `web` 换成实际名称。没有 CLI 时,把安装器打印的命令贴进已初始化的 profile。 + +启用 Quicker 设置 → Agent 中的 MCP 与允许写入,重启 `dsh web` 或桌面端。首次连接确认 `dsh-quicker-plugin`。工具名为 `mcp__quicker__<原名>`。 + +```powershell +powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$env:USERPROFILE\.quicker\agent-integrations\dsh\scripts\quicker-mcp.ps1" -Check +``` + +`-Check` 只验证配置和 TCP。bundle 层改动需要重启 DSH;不要把未重启误报为 Quicker 403。真实会话写动作验收见[兼容性](兼容性.md)。 ## Cursor CLI 验收 @@ -107,3 +121,4 @@ claude plugin uninstall quicker@quicker-agent-integrations --scope user - [Claude Code 插件与 MCP](https://code.claude.com/docs/en/plugins-reference)、[市场 CLI](https://code.claude.com/docs/en/plugin-marketplaces#manage-marketplaces-from-the-cli)、[认证](https://code.claude.com/docs/en/authentication)。 - [VS Code MCP 配置](https://code.visualstudio.com/docs/agent-customization/mcp-servers)。 - [Gemini CLI MCP 配置](https://geminicli.com/docs/tools/mcp-server/)。 +- [DeepSeek Harness 插件打包](https://deepseek-harness.github.io/deepseek-harness/en/develop/basic/publish)、[MCP client](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/mcp/mcp-client/README.md)。 diff --git "a/docs/\346\216\245\345\205\245\347\272\246\345\256\232.md" "b/docs/\346\216\245\345\205\245\347\272\246\345\256\232.md" index 301abbf..c22ccc8 100644 --- "a/docs/\346\216\245\345\205\245\347\272\246\345\256\232.md" +++ "b/docs/\346\216\245\345\205\245\347\272\246\345\256\232.md" @@ -2,7 +2,7 @@ 本仓库维护外部 Agent 的连接、安装与使用引导。Quicker 提供动作工具、实时知识、草稿存储和审批。各平台适配遵循本文,平台交付要求见[新增平台](新增平台.md)。 -Codex 插件 0.1.1 已通过原生安装及真实 Quicker Debug 草稿编写、保存和预览验收,Cursor、Claude Code 与通用 MCP 安装器现由同一共享源码生成。使用前确认 Quicker 的设置 → Agent 中有「启用 MCP」入口,并使用包含默认技能包发现修复的新构建;不能仅凭 `2.3.0` 版本号判断兼容。 +Codex 插件 0.1.1 已通过原生安装及真实 Quicker Debug 草稿编写、保存和预览验收,Cursor、Claude Code、DeepSeek Harness 与通用 MCP 安装器现由同一共享源码生成。使用前确认 Quicker 的设置 → Agent 中有「启用 MCP」入口,并使用包含默认技能包发现修复的新构建;不能仅凭 `2.3.0` 版本号判断兼容。 Release 支持已实现,待包含这些变更的正式版发布;已发布旧版没有 MCP 入口时仍不可用。新构建的 Debug / Release 均默认关闭 MCP,由用户在设置中启用;安装插件不会开启服务或写入权限。具体构建、测试与正式发布情况见[兼容性说明](兼容性.md)。 diff --git "a/docs/\346\226\260\345\242\236\345\271\263\345\217\260.md" "b/docs/\346\226\260\345\242\236\345\271\263\345\217\260.md" index 82fa409..ad58b35 100644 --- "a/docs/\346\226\260\345\242\236\345\271\263\345\217\260.md" +++ "b/docs/\346\226\260\345\242\236\345\271\263\345\217\260.md" @@ -28,7 +28,7 @@ ## 共享的时机 -当前 shared/ 保存转接与技能唯一源码,sync-packages.py 复制到 Codex / Cursor / Claude / 通用 MCP 安装单元。平台身份由受限 Client 参数指定;清单与重载方式各自维护。 +当前 shared/ 保存转接与技能唯一源码,sync-packages.py 复制到 Codex / Cursor / Claude / 通用 MCP / DeepSeek Harness 安装单元。平台身份由受限 Client 参数指定;清单与重载方式各自维护。 若提取到 `shared/`,指定唯一源码,通过一个确定的打包步骤复制到各安装单元;验证复制结果与源码一致,并测试最终包。不要手工维护多份转接实现,也不要为尚未开发的平台预建抽象框架。 diff --git a/plugins/quicker-claude/scripts/quicker-mcp.ps1 b/plugins/quicker-claude/scripts/quicker-mcp.ps1 index 3811fe8..a5e57fe 100644 --- a/plugins/quicker-claude/scripts/quicker-mcp.ps1 +++ b/plugins/quicker-claude/scripts/quicker-mcp.ps1 @@ -3,7 +3,7 @@ param( [string]$SettingsPath = '', [ValidateRange(1, 600)] [int]$RequestTimeoutSeconds = 180, - [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini')] + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] [string]$Client = 'codex', [switch]$Check ) diff --git a/plugins/quicker-claude/skills/write-action/references/connection.md b/plugins/quicker-claude/skills/write-action/references/connection.md index 04ed0f4..3a854e0 100644 --- a/plugins/quicker-claude/skills/write-action/references/connection.md +++ b/plugins/quicker-claude/skills/write-action/references/connection.md @@ -13,6 +13,6 @@ The relay reads the current port and token from the local Quicker configuration - Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. - A write timeout can leave an unknown outcome: inspect the slot before retrying. -After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/plugins/quicker-cursor/scripts/quicker-mcp.ps1 b/plugins/quicker-cursor/scripts/quicker-mcp.ps1 index 3811fe8..a5e57fe 100644 --- a/plugins/quicker-cursor/scripts/quicker-mcp.ps1 +++ b/plugins/quicker-cursor/scripts/quicker-mcp.ps1 @@ -3,7 +3,7 @@ param( [string]$SettingsPath = '', [ValidateRange(1, 600)] [int]$RequestTimeoutSeconds = 180, - [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini')] + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] [string]$Client = 'codex', [switch]$Check ) diff --git a/plugins/quicker-cursor/skills/write-action/references/connection.md b/plugins/quicker-cursor/skills/write-action/references/connection.md index 04ed0f4..3a854e0 100644 --- a/plugins/quicker-cursor/skills/write-action/references/connection.md +++ b/plugins/quicker-cursor/skills/write-action/references/connection.md @@ -13,6 +13,6 @@ The relay reads the current port and token from the local Quicker configuration - Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. - A write timeout can leave an unknown outcome: inspect the slot before retrying. -After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/plugins/quicker-dsh/LICENSE b/plugins/quicker-dsh/LICENSE new file mode 100644 index 0000000..b4dfb73 --- /dev/null +++ b/plugins/quicker-dsh/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 QuickerOrg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/quicker-dsh/README.md b/plugins/quicker-dsh/README.md new file mode 100644 index 0000000..ec02ae2 --- /dev/null +++ b/plugins/quicker-dsh/README.md @@ -0,0 +1,53 @@ +# Quicker DeepSeek Harness 插件 + +版本:0.2.0。需要 Windows PowerShell 5.1、已安装的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh` CLI 或桌面端),以及设置 → Agent 中带「启用 MCP」入口的本机 Quicker。此目录是完整 DSH bundle:写动作引导、stdio 转接,以及把 `@deepseek-ai/dsh-mcp-client` 接到本机 Quicker 的入口。运行时不依赖开发检出。 + +## 安装 + +在 Windows PowerShell 中执行(需要 Git): + +```powershell +$quickerInstall = Join-Path $env:TEMP ("quicker-agent-" + [guid]::NewGuid().ToString("N")) +git clone --depth 1 https://github.com/QuickerOrg/quicker-agent-integrations.git $quickerInstall +if ($LASTEXITCODE -eq 0) { + powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $quickerInstall "scripts/install-client.ps1") -Client dsh +} +``` + +安装器把自包含包复制到 `%USERPROFILE%/.quicker/agent-integrations/dsh`。若 PATH 中有 `dsh`,会再执行: + +```powershell +dsh plugin --profile web add link:$env:USERPROFILE\.quicker\agent-integrations\dsh +``` + +没有 CLI 时,把上面的 `add` 命令复制到已初始化的 profile(`web` 或 `desktop`)后执行。开发检出可直接: + +```powershell +dsh plugin --profile web add link:<本仓库>/plugins/quicker-dsh +``` + +启用 Quicker 设置 → Agent 中的 MCP 与允许写入,然后重启 `dsh web` 或 DSH 桌面端。首次连接在 Quicker 中确认 `dsh-quicker-plugin` 客户端。安装本身不授予执行或覆盖权限。 + +可以这样开始: + +> 用 Quicker 写一个动作,显示“来自 DeepSeek Harness”,保存到暂存区并打开预览,不运行。 + +Quicker 工具在 DSH 中的名称带命名空间,例如 `mcp__quicker__skill_load`、`mcp__quicker__quicker_create`。默认保存到暂存区;正式保留、覆盖或运行由用户要求和 Quicker 审批决定。 + +## 更新、卸载和诊断 + +重新下载仓库并再次运行安装器即可更新。卸载: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-client.ps1 -Client dsh -Uninstall +``` + +若曾手动 `dsh plugin add`,卸载时也会尝试 `dsh plugin --profile web remove dsh-plugin-quicker`。 + +```powershell +powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$env:USERPROFILE\.quicker\agent-integrations\dsh\scripts\quicker-mcp.ps1" -Check +``` + +`-Check` 只说明开关、端口和 TCP 可达,不代表已授权或能够写动作。不要修改 Quicker 的 `server.json` / `clients.json` 来授予权限。 + +传输脚本与技能由 shared/ 生成;维护时运行 `python scripts/sync-packages.py`,不手工修改副本。 diff --git a/plugins/quicker-dsh/cordis.patch.yml b/plugins/quicker-dsh/cordis.patch.yml new file mode 100644 index 0000000..ae54bf7 --- /dev/null +++ b/plugins/quicker-dsh/cordis.patch.yml @@ -0,0 +1,5 @@ +# Insert the Quicker bundle. index.js resolves the stdio relay path and +# loads @deepseek-ai/dsh-mcp-client so the token never enters this file. +- insert: + - id: quicker + name: dsh-plugin-quicker diff --git a/plugins/quicker-dsh/index.js b/plugins/quicker-dsh/index.js new file mode 100644 index 0000000..380ec65 --- /dev/null +++ b/plugins/quicker-dsh/index.js @@ -0,0 +1,76 @@ +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url)) +const RELAY_SCRIPT = 'quicker-mcp.ps1' +const SKILL_FILE = join(PACKAGE_ROOT, 'skills', 'write-action', 'SKILL.md') + +export const name = 'quicker' +export const inject = ['tools'] + +export function resolvePackageRoot() { + return PACKAGE_ROOT +} + +export function mcpClientConfig(root = PACKAGE_ROOT) { + return { + serverName: 'quicker', + transport: 'stdio', + command: 'powershell.exe', + args: [ + '-NoLogo', + '-NoProfile', + '-NonInteractive', + '-ExecutionPolicy', + 'Bypass', + '-File', + join(root, 'scripts', RELAY_SCRIPT), + '-Client', + 'dsh', + ], + cwd: root, + toolCallTimeoutMs: 180000, + failOnStartupError: false, + } +} + +function loadWriteActionSkill() { + try { + return readFileSync(SKILL_FILE, 'utf8').trim() + } catch { + return [ + 'Use Quicker MCP tools to create, edit, save and preview actions.', + 'Tool names are prefixed as mcp__quicker__.', + 'Load action-source with skill_load before editing program.source.yaml.', + ].join(' ') + } +} + +function authoringPreamble() { + return [ + 'Quicker MCP tools are registered as mcp__quicker__', + '(for example mcp__quicker__skill_load and mcp__quicker__quicker_create).', + 'They edit Quicker virtual slots, not the local workspace.', + '', + loadWriteActionSkill(), + ].join('\n') +} + +function injectAuthoringGuide(agent) { + try { + agent.inject({ + content: [{ type: 'text', text: authoringPreamble() }], + source: { kind: 'plugin', plugin: 'dsh-plugin-quicker' }, + }) + } catch { + // A disposed agent must not fail plugin activation. + } +} + +export function apply(ctx) { + ctx.plugin('@deepseek-ai/dsh-mcp-client', mcpClientConfig()) + ctx.on('agent/session-start', ({ agent }) => { + injectAuthoringGuide(agent) + }) +} diff --git a/plugins/quicker-dsh/package.json b/plugins/quicker-dsh/package.json new file mode 100644 index 0000000..f05da96 --- /dev/null +++ b/plugins/quicker-dsh/package.json @@ -0,0 +1,41 @@ +{ + "name": "dsh-plugin-quicker", + "version": "0.2.0", + "description": "通过本机 Quicker MCP 编写、保存和预览自动化动作。", + "type": "module", + "main": "index.js", + "exports": { + ".": "./index.js" + }, + "files": [ + "index.js", + "cordis.patch.yml", + "scripts/", + "skills/", + "README.md", + "LICENSE" + ], + "keywords": [ + "dsh", + "dsh-plugin", + "dsh-bundle", + "quicker", + "mcp" + ], + "author": "QuickerOrg", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/QuickerOrg/quicker-agent-integrations.git", + "directory": "plugins/quicker-dsh" + }, + "homepage": "https://github.com/QuickerOrg/quicker-agent-integrations", + "engines": { + "node": ">=22" + }, + "dsh": { + "bundle": { + "patch": "./cordis.patch.yml" + } + } +} diff --git a/plugins/quicker-dsh/scripts/quicker-mcp.ps1 b/plugins/quicker-dsh/scripts/quicker-mcp.ps1 new file mode 100644 index 0000000..a5e57fe --- /dev/null +++ b/plugins/quicker-dsh/scripts/quicker-mcp.ps1 @@ -0,0 +1,285 @@ +[CmdletBinding()] +param( + [string]$SettingsPath = '', + [ValidateRange(1, 600)] + [int]$RequestTimeoutSeconds = 180, + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] + [string]$Client = 'codex', + [switch]$Check +) + +# A transport adapter only: Quicker owns tools, authoring state and approval policy. +# Keep JSON payloads as text so Windows PowerShell never rewrites schemas or numbers. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +$utf8 = New-Object System.Text.UTF8Encoding($false) +[Console]::InputEncoding = $utf8 +[Console]::OutputEncoding = $utf8 +$OutputEncoding = $utf8 +Add-Type -AssemblyName System.Web.Extensions +$jsonReader = New-Object System.Web.Script.Serialization.JavaScriptSerializer +$jsonReader.MaxJsonLength = [int]::MaxValue +$jsonReader.RecursionLimit = 256 +if ([string]::IsNullOrWhiteSpace($SettingsPath)) { + $SettingsPath = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.quicker\mcp\server.json' +} + +function Write-ProtocolLine([string]$Text) { + [Console]::Out.WriteLine($Text) + [Console]::Out.Flush() +} + +function Compress-JsonText([string]$Text) { + $builder = New-Object System.Text.StringBuilder + $inString = $false + $escaped = $false + foreach ($character in $Text.ToCharArray()) { + if ($inString) { + [void]$builder.Append($character) + if ($escaped) { $escaped = $false } + elseif ($character -eq '\') { $escaped = $true } + elseif ($character -eq '"') { $inString = $false } + } + elseif ($character -eq '"') { + $inString = $true + [void]$builder.Append($character) + } + elseif (-not [char]::IsWhiteSpace($character)) { [void]$builder.Append($character) } + } + return $builder.ToString() +} + +function Get-RpcIdText([string]$Text) { + # Read only the top-level id token, preserving a numeric id's exact spelling. + $depth = 0 + for ($index = 0; $index -lt $Text.Length; $index++) { + $character = $Text[$index] + if ($character -eq '{' -or $character -eq '[') { $depth++; continue } + if ($character -eq '}' -or $character -eq ']') { $depth--; continue } + if ($character -ne '"') { continue } + $start = $index + $index++ + for (; $index -lt $Text.Length; $index++) { + if ($Text[$index] -eq '\') { $index++; continue } + if ($Text[$index] -eq '"') { break } + } + if ($depth -ne 1) { continue } + $after = $index + 1 + while ($after -lt $Text.Length -and [char]::IsWhiteSpace($Text[$after])) { $after++ } + if ($after -ge $Text.Length -or $Text[$after] -ne ':') { continue } + $keyToken = $Text.Substring($start, $index - $start + 1) + $key = $jsonReader.DeserializeObject('{"key":' + $keyToken + '}') + if ($key.key -cne 'id') { continue } + $valueStart = $after + 1 + while ($valueStart -lt $Text.Length -and [char]::IsWhiteSpace($Text[$valueStart])) { $valueStart++ } + $valueEnd = $valueStart + if ($Text[$valueStart] -eq '"') { + $valueEnd++ + for (; $valueEnd -lt $Text.Length; $valueEnd++) { + if ($Text[$valueEnd] -eq '\') { $valueEnd++; continue } + if ($Text[$valueEnd] -eq '"') { $valueEnd++; break } + } + } + else { + while ($valueEnd -lt $Text.Length -and $Text[$valueEnd] -ne ',' -and $Text[$valueEnd] -ne '}') { $valueEnd++ } + } + return $Text.Substring($valueStart, $valueEnd - $valueStart).Trim() + } + return $null +} + +function Write-RpcFailure { + param([string]$IdText, [int]$Code, [string]$FailureCode, [string]$Message, + [bool]$StateUnknown = $false, [int]$HttpStatus = 0) + if ([string]::IsNullOrEmpty($IdText)) { + # Notifications must not receive JSON-RPC replies. Diagnostics never contain input data. + [Console]::Error.WriteLine('Quicker MCP: ' + $FailureCode + '. ' + $Message) + return + } + $data = [ordered]@{ code = $FailureCode; stateUnknown = $StateUnknown } + if ($HttpStatus -ne 0) { $data.httpStatus = $HttpStatus } + $errorBody = [ordered]@{ code = $Code; message = $Message; data = $data } | ConvertTo-Json -Depth 8 -Compress + Write-ProtocolLine ('{"jsonrpc":"2.0","id":' + $IdText + ',"error":' + $errorBody + '}') +} + +function Read-McpSettings { + if (-not [System.IO.File]::Exists($SettingsPath)) { + return @{ Code = 'settings_missing'; Message = 'Enable MCP Server in a Debug build of Quicker first.' } + } + try { + $settings = $jsonReader.DeserializeObject([System.IO.File]::ReadAllText($SettingsPath, $utf8)) + if ($null -eq $settings -or $settings -is [array]) { throw 'invalid' } + $port = 0 + if (-not [int]::TryParse([string]$settings.Port, [ref]$port) -or $port -lt 1024 -or $port -gt 65535) { + throw 'invalid' + } + $tokenPresent = $settings.Token -is [string] -and -not [string]::IsNullOrWhiteSpace($settings.Token) + if ($tokenPresent -and $settings.Token -match '\s') { throw 'invalid' } + $enabled = $settings.Enabled -is [bool] -and $settings.Enabled + $allowWrites = $settings.AllowWrites -is [bool] -and $settings.AllowWrites + return @{ Enabled = $enabled; AllowWrites = $allowWrites; Port = $port; + TokenPresent = $tokenPresent; Token = $settings.Token } + } + catch { + return @{ Code = 'settings_invalid'; Message = 'Quicker MCP settings could not be read. Check them in Quicker.' } + } +} + +if ($Check) { + $settings = Read-McpSettings + if ($settings.Code) { + Write-ProtocolLine (([ordered]@{ ok = $false; code = $settings.Code; message = $settings.Message }) | ConvertTo-Json -Compress) + exit 1 + } + $reachable = $false + $tcpClient = New-Object System.Net.Sockets.TcpClient + try { + $pending = $tcpClient.BeginConnect('127.0.0.1', $settings.Port, $null, $null) + if ($pending.AsyncWaitHandle.WaitOne(2000)) { + $tcpClient.EndConnect($pending) + $reachable = $tcpClient.Connected + } + $pending.AsyncWaitHandle.Close() + } + catch { $reachable = $false } + finally { $tcpClient.Close() } + $ready = $settings.Enabled -and $settings.TokenPresent -and $reachable + Write-ProtocolLine (([ordered]@{ ok = [bool]$ready; enabled = [bool]$settings.Enabled; + allowWrites = [bool]$settings.AllowWrites; port = $settings.Port; + tokenPresent = [bool]$settings.TokenPresent; portReachable = $reachable }) | ConvertTo-Json -Compress) + if ($ready) { exit 0 } + exit 1 +} + +$protocolVersion = $null +while ($null -ne ($line = [Console]::In.ReadLine())) { + if ([string]::IsNullOrWhiteSpace($line)) { continue } + $idText = $null + try { + $rpc = $jsonReader.DeserializeObject($line) + $idText = Get-RpcIdText $line + if ($null -eq $rpc -or $rpc -is [array] -or $rpc.jsonrpc -cne '2.0' -or + $rpc.method -isnot [string] -or [string]::IsNullOrWhiteSpace($rpc.method)) { + throw 'invalid request' + } + if ($null -ne $rpc.id -and ($rpc.id -is [bool] -or ($rpc.id -isnot [string] -and $rpc.id -isnot [ValueType]))) { throw 'invalid id' } + } + catch { + Write-RpcFailure -IdText 'null' -Code -32600 -FailureCode 'invalid_request' -Message 'Expected one JSON-RPC 2.0 request or notification per line.' + continue + } + + $settings = Read-McpSettings + if ($settings.Code) { + Write-RpcFailure -IdText $idText -Code -32001 -FailureCode $settings.Code -Message $settings.Message + continue + } + if (-not $settings.Enabled) { + Write-RpcFailure -IdText $idText -Code -32001 -FailureCode 'server_disabled' -Message 'Enable MCP Server in Quicker settings.' + continue + } + if (-not $settings.TokenPresent) { + Write-RpcFailure -IdText $idText -Code -32001 -FailureCode 'token_missing' -Message 'Quicker MCP has no token. Check MCP Server settings in Quicker.' + continue + } + + $request = $null + $response = $null + $mayHaveSent = $false + $forwardedResponse = $false + try { + # Only the persisted port is configurable. Never send the token to another host, + # an HTTP proxy, or a redirect target. + $request = [System.Net.HttpWebRequest]::Create('http://127.0.0.1:' + $settings.Port + '/mcp') + $request.Method = 'POST' + $request.Proxy = $null + $request.AllowAutoRedirect = $false + # Avoid automatic replay when a reused keep-alive socket is closed by the server. + $request.KeepAlive = $false + $request.Timeout = $RequestTimeoutSeconds * 1000 + $request.ReadWriteTimeout = $RequestTimeoutSeconds * 1000 + $request.ContentType = 'application/json; charset=utf-8' + $request.Accept = 'application/json, text/event-stream' + $request.Headers['Authorization'] = 'Bearer ' + $settings.Token + $request.Headers['Mcp-Client-Info'] = ($Client + '-quicker-plugin') + if ($protocolVersion) { $request.Headers['MCP-Protocol-Version'] = $protocolVersion } + $request.ServicePoint.Expect100Continue = $false + $bytes = $utf8.GetBytes($line) + $request.ContentLength = $bytes.Length + $mayHaveSent = $true + $stream = $request.GetRequestStream() + try { $stream.Write($bytes, 0, $bytes.Length) } + finally { $stream.Dispose() } + try { $response = $request.GetResponse() } + catch [System.Net.WebException] { + if ($null -eq $_.Exception.Response) { throw } + $response = $_.Exception.Response + } + $status = [int]$response.StatusCode + if ($status -lt 200 -or $status -ge 300) { + $failureCode = 'http_error' + $message = 'Quicker MCP rejected the HTTP request. Check Quicker before retrying.' + $stateUnknown = $status -ge 500 + if ($status -eq 401) { $failureCode = 'unauthorized'; $message = 'Quicker rejected the MCP token. Check MCP settings in Quicker.' } + elseif ($status -eq 403) { $failureCode = 'client_not_approved'; $message = 'Approve the connecting MCP client in Quicker, then reconnect.' } + elseif ($status -ge 300 -and $status -lt 400) { $failureCode = 'redirect_refused'; $message = 'Quicker MCP returned a redirect. Redirects are refused; check the local endpoint.' } + Write-RpcFailure -IdText $idText -Code -32003 -FailureCode $failureCode -Message $message -StateUnknown $stateUnknown -HttpStatus $status + continue + } + if ($status -eq 202) { + if ($idText) { throw 'missing response' } + continue + } + $reader = New-Object System.IO.StreamReader($response.GetResponseStream(), $utf8) + try { $body = $reader.ReadToEnd() } + finally { $reader.Dispose() } + if ([string]::IsNullOrWhiteSpace($body)) { + if ($idText) { throw 'missing response' } + continue + } + $payloads = New-Object 'System.Collections.Generic.List[string]' + if ($response.ContentType -match '^text/event-stream(?:;|$)') { + $eventData = New-Object 'System.Collections.Generic.List[string]' + foreach ($eventLine in ($body -split '\r\n|\n|\r')) { + if ($eventLine.Length -eq 0) { + if ($eventData.Count -gt 0) { $payloads.Add(($eventData -join "`n")); $eventData.Clear() } + } + elseif ($eventLine.StartsWith('data:')) { + $value = $eventLine.Substring(5) + if ($value.StartsWith(' ')) { $value = $value.Substring(1) } + $eventData.Add($value) + } + } + if ($eventData.Count -gt 0) { $payloads.Add(($eventData -join "`n")) } + } + elseif ($response.ContentType -match '^application/json(?:;|$)') { $payloads.Add($body) } + else { throw 'unsupported response' } + + foreach ($payload in $payloads) { + $parsed = $jsonReader.DeserializeObject($payload) + if ($null -eq $parsed -or $parsed -is [array] -or $parsed.jsonrpc -cne '2.0') { throw 'invalid response' } + $responseId = Get-RpcIdText $payload + if ($null -ne $responseId) { + if (-not $idText -or $forwardedResponse -or $parsed.id -cne $rpc.id) { throw 'unexpected response id' } + if ($rpc.method -ceq 'initialize' -and $parsed.result.protocolVersion -is [string]) { + $version = $parsed.result.protocolVersion + if ($version -match '^\d{4}-\d{2}-\d{2}$') { $protocolVersion = $version } + } + $forwardedResponse = $true + } + elseif ($parsed.method -isnot [string]) { throw 'invalid notification' } + Write-ProtocolLine (Compress-JsonText $payload) + } + if ($idText -and -not $forwardedResponse) { throw 'missing response' } + } + catch { + if (-not $forwardedResponse) { + Write-RpcFailure -IdText $idText -Code -32002 -FailureCode 'transport_failed' -StateUnknown $mayHaveSent -Message 'The Quicker MCP request did not produce a complete response. Its outcome may be unknown; inspect Quicker and reopen or reread the action before retrying a write.' + } + else { [Console]::Error.WriteLine('Quicker MCP: invalid data after the completed response.') } + } + finally { + if ($null -ne $response) { $response.Dispose() } + if ($null -ne $request) { $request.Abort() } + } +} diff --git a/plugins/quicker-dsh/skills/write-action/SKILL.md b/plugins/quicker-dsh/skills/write-action/SKILL.md new file mode 100644 index 0000000..6900424 --- /dev/null +++ b/plugins/quicker-dsh/skills/write-action/SKILL.md @@ -0,0 +1,28 @@ +--- +name: write-action +description: Create, edit, save and preview Windows automation actions in a running Quicker through its MCP tools. Use when the user asks to write or modify a Quicker action, or consult Quicker step knowledge. Does not apply to developing the Quicker application's source code. +--- + +# Write a Quicker action + +Use the Quicker plugin's MCP tools, whose names may have a client namespace prefix. These edit Quicker's virtual workspace, not the agent repository. In particular, use Quicker's read_file, write_file, edit_file and grep for slots and @knowledge; ordinary local filesystem tools cannot resolve them. + +The live server's initialize instructions and returned capability rules describe the current authoring protocol. Read all content blocks, including attachedRules. Tool availability and current knowledge are authoritative; this plugin does not carry a duplicate module catalog. + +## Authoring workflow + +1. Load the runtime grammar using skill_load with `{ "id": "action-source" }` before editing. For an unfamiliar step, follow its steps skill: grep one keyword under @knowledge/catalog, then read the matching module YAML. Use quicker_eval with steps.resolve only for the needed branch or referenced docs. Do not invent step names or input/output keys. Host JS uses JavaScript; action `$=` expressions use Quicker's C# expression syntax. +2. For a new action use quicker_create with a concise title. To edit an existing one, use quicker_search once, identify the intended result, then quicker_open with its actionId. Opening a formal action creates an editing draft; it does not overwrite the original. +3. Keep the returned slot and actionId. Always address files explicitly as `/program.source.yaml`, `/info.yaml` or `/files/...`, and save with quicker_save `{ "path": "" }`. Several tasks can share an agent backend process and Quicker's current location; do not rely on the implicit working directory or concurrently edit the same slot. +4. Write the program in program.source.yaml; use info.yaml for title, description and appearance. data.json and info.json are Host internals. quicker_save validates and stores the draft in Quicker's 暂存区. Inspect its result and correct reported errors before reporting completion. +5. Use quicker_preview `{ "target": "" }` when showing the result helps the user. It opens Quicker's designer. Only open a browser URL actually returned by the tool; do not construct one from a designer route. + +A request to write an action normally ends with a saved draft. Promote a new draft only when the user has specified an unambiguous existing destination scene; otherwise explain the 暂存区 → 保留到… flow. Overwrite a formal action or run an action only within the user's requested scope, using quicker_overwrite or quicker_run and Quicker's approval flow. State whether the action was only saved, also previewed, or actually run. + +## Failure handling + +Check JSON-RPC errors, MCP isError, `{ok:false,code,message}`, and quicker_eval's `{error:...}` envelope. Do not infer success from receiving text. + +When a mutation times out, the connection drops, or stateUnknown is returned, inspect the relevant slot/action before retrying. If create reports programCreated with an actionId, reopen that action rather than creating another. For catalog_stale or catalog_freshness_unknown, inspect the current action and preserve pending changes; do not blindly overwrite or re-open over dirty work. + +If tools are unavailable, consult [connection.md](references/connection.md). A missing write tool usually means Quicker's 允许 MCP 写入 switch is off. The plugin must not edit server.json or clients.json to enable access, grant consent, or change approval settings. diff --git a/plugins/quicker-dsh/skills/write-action/references/connection.md b/plugins/quicker-dsh/skills/write-action/references/connection.md new file mode 100644 index 0000000..3a854e0 --- /dev/null +++ b/plugins/quicker-dsh/skills/write-action/references/connection.md @@ -0,0 +1,18 @@ +# Connection troubleshooting + +Requires Windows PowerShell 5.1 and a Quicker build with Settings → Agent → Enable MCP. Both supported Debug and Release builds work; older releases without that setting do not. + +Run powershell.exe -NoProfile -ExecutionPolicy Bypass -File "/scripts/quicker-mcp.ps1" -Check for a redacted configuration/TCP check. This does not prove client consent or write access. + +The relay reads the current port and token from the local Quicker configuration for each request. Keep the token out of commands, client configuration, logs and conversation. It uses only loopback and does not follow proxies or redirects. + +- Missing/disabled MCP: open Quicker Settings → Agent and enable MCP. +- Connection refused: start the supported Quicker build and check the configured port. +- HTTP 401: check the running instance; token rotation is picked up on the next read. +- HTTP 403 or a first connection waiting: complete Quicker's consent UI for the actual connecting client. The relay never grants itself access. +- Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. +- A write timeout can leave an unknown outcome: inspect the slot before retrying. + +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. + +Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/plugins/quicker-mcp/scripts/quicker-mcp.ps1 b/plugins/quicker-mcp/scripts/quicker-mcp.ps1 index 3811fe8..a5e57fe 100644 --- a/plugins/quicker-mcp/scripts/quicker-mcp.ps1 +++ b/plugins/quicker-mcp/scripts/quicker-mcp.ps1 @@ -3,7 +3,7 @@ param( [string]$SettingsPath = '', [ValidateRange(1, 600)] [int]$RequestTimeoutSeconds = 180, - [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini')] + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] [string]$Client = 'codex', [switch]$Check ) diff --git a/plugins/quicker-mcp/skills/write-action/references/connection.md b/plugins/quicker-mcp/skills/write-action/references/connection.md index 04ed0f4..3a854e0 100644 --- a/plugins/quicker-mcp/skills/write-action/references/connection.md +++ b/plugins/quicker-mcp/skills/write-action/references/connection.md @@ -13,6 +13,6 @@ The relay reads the current port and token from the local Quicker configuration - Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. - A write timeout can leave an unknown outcome: inspect the slot before retrying. -After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/plugins/quicker/scripts/quicker-mcp.ps1 b/plugins/quicker/scripts/quicker-mcp.ps1 index 3811fe8..a5e57fe 100644 --- a/plugins/quicker/scripts/quicker-mcp.ps1 +++ b/plugins/quicker/scripts/quicker-mcp.ps1 @@ -3,7 +3,7 @@ param( [string]$SettingsPath = '', [ValidateRange(1, 600)] [int]$RequestTimeoutSeconds = 180, - [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini')] + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] [string]$Client = 'codex', [switch]$Check ) diff --git a/plugins/quicker/skills/write-action/references/connection.md b/plugins/quicker/skills/write-action/references/connection.md index 04ed0f4..3a854e0 100644 --- a/plugins/quicker/skills/write-action/references/connection.md +++ b/plugins/quicker/skills/write-action/references/connection.md @@ -13,6 +13,6 @@ The relay reads the current port and token from the local Quicker configuration - Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. - A write timeout can leave an unknown outcome: inspect the slot before retrying. -After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/scripts/install-client.ps1 b/scripts/install-client.ps1 index d28ac20..3d143c8 100644 --- a/scripts/install-client.ps1 +++ b/scripts/install-client.ps1 @@ -1,7 +1,7 @@ [CmdletBinding()] param( [Parameter(Mandatory = $true)] - [ValidateSet('cursor', 'vscode', 'gemini')] + [ValidateSet('cursor', 'vscode', 'gemini', 'dsh')] [string]$Client, [string]$UserRoot = [Environment]::GetFolderPath('UserProfile'), [switch]$Uninstall @@ -57,7 +57,39 @@ function Write-Json([string]$Path, $Value) { } else { [IO.File]::Move($temporary, $Path) } } -$packageName = if ($Client -eq 'cursor') { 'quicker-cursor' } else { 'quicker-mcp' } +function Get-DshCommand { + $command = Get-Command dsh -ErrorAction SilentlyContinue + if ($command) { return $command.Source } + return $null +} + +function Invoke-DshPlugin([string]$Action, [string]$PackagePath) { + $dsh = Get-DshCommand + if (-not $dsh) { + $link = "dsh plugin --profile web add link:$PackagePath" + if ($Action -eq 'remove') { $link = 'dsh plugin --profile web remove dsh-plugin-quicker' } + Write-Output "dsh CLI was not found. Run this in the target profile when ready: $link" + return + } + $pluginArgs = if ($Action -eq 'remove') { + @('plugin', '--profile', 'web', 'remove', 'dsh-plugin-quicker') + } else { + @('plugin', '--profile', 'web', 'add', ('link:' + $PackagePath)) + } + $result = & $dsh @pluginArgs + if ($LASTEXITCODE -ne 0) { + Write-Output ($result | Out-String) + Write-Output "dsh plugin $Action did not finish. Package files are at $PackagePath." + return + } + if ($result) { Write-Output ($result | Out-String).TrimEnd() } +} + +$packageName = switch ($Client) { + 'cursor' { 'quicker-cursor' } + 'dsh' { 'quicker-dsh' } + default { 'quicker-mcp' } +} $relativeTarget = if ($Client -eq 'cursor') { '.cursor/plugins/local/quicker' } else { '.quicker/agent-integrations/' + $Client } $target = Assert-UserPath (Join-Path $userPath $relativeTarget) $markerPath = Join-Path $target '.quicker-managed.json' @@ -84,7 +116,7 @@ $configPath = $null $config = $null $section = 'mcpServers' $server = $null -if ($Client -ne 'cursor') { +if ($Client -in @('vscode', 'gemini')) { $relativeConfig = if ($Client -eq 'vscode') { 'AppData/Roaming/Code/User/mcp.json' } else { '.gemini/settings.json' } $configPath = Assert-UserPath (Join-Path $userPath $relativeConfig) if ($Client -eq 'vscode') { $section = 'servers' } @@ -106,6 +138,7 @@ if ($Client -ne 'cursor') { if ($Uninstall) { if (-not $marker) { Write-Output 'Nothing installed by this installer.'; exit 0 } + if ($Client -eq 'dsh') { Invoke-DshPlugin -Action remove -PackagePath $target } if ($configPath) { [void]$config[$section].Remove('quicker') Write-Json $configPath $config @@ -155,6 +188,12 @@ try { throw } Write-Output "Installed Quicker for $Client at $target" -if ($Client -eq 'cursor') { Write-Output 'Cursor IDE: Developer: Reload Window. Cursor CLI: start a new task; use --plugin-dir with the installed directory if local plugins are not discovered.' } -else { Write-Output 'Restart the MCP server or start a new client task.' } +if ($Client -eq 'cursor') { + Write-Output 'Cursor IDE: Developer: Reload Window. Cursor CLI: start a new task; use --plugin-dir with the installed directory if local plugins are not discovered.' +} elseif ($Client -eq 'dsh') { + Invoke-DshPlugin -Action add -PackagePath $target + Write-Output 'Restart dsh web or the DeepSeek Harness desktop app so the bundle layer loads.' +} else { + Write-Output 'Restart the MCP server or start a new client task.' +} Write-Output 'Enable MCP and allow writes in Quicker Settings > Agent; complete Quicker client consent when prompted.' diff --git a/scripts/release-packages.json b/scripts/release-packages.json index a0ff8cf..49cd15a 100644 --- a/scripts/release-packages.json +++ b/scripts/release-packages.json @@ -31,6 +31,11 @@ "id": "mcp", "path": "plugins/quicker-mcp", "manifest": "package.json" + }, + { + "id": "dsh", + "path": "plugins/quicker-dsh", + "manifest": "package.json" } ] } diff --git a/shared/scripts/quicker-mcp.ps1 b/shared/scripts/quicker-mcp.ps1 index 3811fe8..a5e57fe 100644 --- a/shared/scripts/quicker-mcp.ps1 +++ b/shared/scripts/quicker-mcp.ps1 @@ -3,7 +3,7 @@ param( [string]$SettingsPath = '', [ValidateRange(1, 600)] [int]$RequestTimeoutSeconds = 180, - [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini')] + [ValidateSet('codex', 'cursor', 'claude', 'vscode', 'gemini', 'dsh')] [string]$Client = 'codex', [switch]$Check ) diff --git a/shared/skills/write-action/references/connection.md b/shared/skills/write-action/references/connection.md index 04ed0f4..3a854e0 100644 --- a/shared/skills/write-action/references/connection.md +++ b/shared/skills/write-action/references/connection.md @@ -13,6 +13,6 @@ The relay reads the current port and token from the local Quicker configuration - Write tools absent: enable 允许 MCP 写入 in Quicker, keep the existing approval mode, then reconnect or start a new agent task. - A write timeout can leave an unknown outcome: inspect the slot before retrying. -After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. +After installation/update, reload the client and create a new task. Cursor IDE: Developer: Reload Window; Cursor CLI: restart agent. Claude Code: /reload-plugins or restart. VS Code: MCP: List Servers → Quicker → Restart. Gemini CLI: restart. DeepSeek Harness: restart `dsh web` or the desktop app after `dsh plugin add`. Plugins ship their own relay. Cursor and Claude expand their respective plugin-root variables; Codex uses package-relative cwd. Generic MCP configuration points at the installed copy, never the development checkout. Do not edit server.json or clients.json to grant access. diff --git a/tests/test_install_clients.py b/tests/test_install_clients.py index 9f55a62..7fa23f8 100644 --- a/tests/test_install_clients.py +++ b/tests/test_install_clients.py @@ -20,6 +20,16 @@ def test_shared_sources_and_manifests(self): self.assertTrue((package / 'skills/write-action/SKILL.md').is_file()) market = json.loads((ROOT / ('.' + client + '-plugin') / 'marketplace.json').read_text(encoding='utf-8')) self.assertEqual((ROOT / market['plugins'][0]['source']).resolve(), package.resolve()) + dsh = json.loads((ROOT / 'plugins/quicker-dsh/package.json').read_text(encoding='utf-8')) + self.assertEqual(dsh['name'], 'dsh-plugin-quicker') + self.assertEqual(dsh['dsh']['bundle']['patch'], './cordis.patch.yml') + self.assertTrue((ROOT / 'plugins/quicker-dsh/index.js').is_file()) + self.assertTrue((ROOT / 'plugins/quicker-dsh/cordis.patch.yml').is_file()) + self.assertTrue((ROOT / 'plugins/quicker-dsh/skills/write-action/SKILL.md').is_file()) + index = (ROOT / 'plugins/quicker-dsh/index.js').read_text(encoding='utf-8') + self.assertIn("@deepseek-ai/dsh-mcp-client", index) + self.assertIn("'dsh'", index) + self.assertIn('agent/session-start', index) @unittest.skipUnless(POWERSHELL, 'Windows PowerShell required') @@ -98,7 +108,7 @@ def test_foreign_local_plugin_is_not_replaced(self): self.assertEqual((target / 'keep').read_text(), 'foreign') def test_each_plugin_launches_from_unrelated_directory(self): - for client, variable in [('cursor', 'CURSOR_PLUGIN_ROOT'), ('claude', 'CLAUDE_PLUGIN_ROOT')]: + for client, variable in [('cursor', 'CURSOR_PLUGIN_ROOT'), ('claude', 'CLAUDE_PLUGIN_ROOT'), ('dsh', None)]: with self.subTest(client=client): host = MockHost() try: @@ -106,8 +116,12 @@ def test_each_plugin_launches_from_unrelated_directory(self): settings.write_bytes(encoded({'Enabled': True, 'Port': host.port, 'Token': 'test-only-token'})) package = self.profile / '插件 缓存' / ('quicker-' + client) shutil.copytree(ROOT / 'plugins' / ('quicker-' + client), package) - config = json.loads((package / ('mcp.json' if client == 'cursor' else '.mcp.json')).read_text(encoding='utf-8'))['mcpServers']['quicker'] - args = [a.replace('${' + variable + '}', str(package)) for a in config['args']] + if variable: + config = json.loads((package / ('mcp.json' if client == 'cursor' else '.mcp.json')).read_text(encoding='utf-8'))['mcpServers']['quicker'] + args = [a.replace('${' + variable + '}', str(package)) for a in config['args']] + else: + args = ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', + '-File', str(package / 'scripts/quicker-mcp.ps1'), '-Client', client] host.respond(reply(1, {'tools': [], 'extra': '中文保真'})) result = subprocess.run([str(POWERSHELL), *args, '-SettingsPath', str(settings)], input=encoded(request()) + b'\n', capture_output=True, cwd=self.profile, timeout=35) @@ -118,3 +132,18 @@ def test_each_plugin_launches_from_unrelated_directory(self): self.assertNotIn(b'test-only-token', result.stdout + result.stderr) finally: host.close() + + def test_dsh_package_install_update_and_local_edits(self): + result = self.install('dsh') + target = self.profile / '.quicker/agent-integrations/dsh' + self.assertTrue((target / 'package.json').is_file()) + self.assertTrue((target / 'index.js').is_file()) + self.assertTrue((target / 'cordis.patch.yml').is_file()) + self.assertTrue((target / 'scripts/quicker-mcp.ps1').is_file()) + if shutil.which('dsh') is None: + self.assertIn(b'dsh CLI was not found', result.stdout) + self.install('dsh') + (target / 'custom.txt').write_text('keep', encoding='utf-8') + self.install('dsh', success=False) + self.install('dsh', '-Uninstall', success=False) + self.assertEqual((target / 'custom.txt').read_text(), 'keep')