From f45c3c49a27b912032b1910cda7f54fb773505ef Mon Sep 17 00:00:00 2001 From: Allen <0xfatdog@gmail.com> Date: Tue, 18 Aug 2026 23:54:49 +0800 Subject: [PATCH 1/4] feat: add DeepSeek Harness setup --- .claude/CLAUDE.md | 7 +- .github/workflows/scripts-test.yml | 8 + AGENTS.md | 6 +- README.md | 55 +++++- docs/README_zh.md | 55 +++++- scripts/install.ps1 | 42 ++-- scripts/install.sh | 127 ++---------- scripts/uninstall.ps1 | 84 +++++++- scripts/uninstall.sh | 29 ++- skills/agentkey/SKILL.md | 10 +- skills/agentkey/references/setup.md | 38 +++- tests/dsh-plugin.bats | 294 ++++++++++++++++++++++++++++ tests/dsh-uninstall-marker.ps1 | 43 ++++ 13 files changed, 662 insertions(+), 136 deletions(-) create mode 100644 tests/dsh-plugin.bats create mode 100644 tests/dsh-uninstall-marker.ps1 diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index e83638c..2d16a5d 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -25,6 +25,8 @@ It also works as a **Gemini CLI extension** (root `gemini-extension.json` + `ski It also works as an **Antigravity 2.0 and Antigravity CLI plugin** (root `plugin.json` + `mcp_config.json` + `skills/`). Both runtimes use the same package, require `serverUrl` for remote MCP, and authenticate through automatic OAuth discovery. +It also has a **CLI-managed DeepSeek Harness integration**. The installers detect `${DSH_HOME:-~/.dsh}` / `dsh`, install the skill globally (never `skills add -a dsh`), and let `@agentkey/cli --auth-login --only dsh` maintain one marked `@deepseek-ai/dsh-mcp-client` entry in `$DSH_HOME/cordis.patch.yml`. DSH composes that home layer over current and future profiles; running processes watch it through HMR. DSH rc.7 has no MCP OAuth `authProvider`, so this path requires the CLI-written Bearer key. Tool policy may still hide tools. + ## Directory Structure ``` @@ -132,11 +134,14 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Update both `README.md` and `docs/README_zh.md` together — they mirror each other - The canonical install is always the two-command sequence (`npx skills add …` + `npx -y @agentkey/cli --auth-login`). Don't imply either command does both. - Do **not** re-add OpenClaw / per-agent installers without a new design — historical context is in git history (removed in chore/remove-archive-directory) +- Describe DSH as a CLI-managed MCP integration, not a native installable DSH plugin. Do not reintroduce the removed README-only `.dsh-plugin/agentkey/` placeholder without a real package and install contract. +- Never commit a real DSH Authorization key. Tests and examples use obviously fake values; production keys live only in the user's local `cordis.patch.yml`. ## Architecture Constraints - Setup mode in SKILL.md runs `! npx -y @agentkey/cli --auth-login` to authenticate via browser — same command as step 2 of the public install -- `@agentkey/cli --auth-login` auto-writes MCP configs for 16 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`): Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, OpenCode, Qwen Code, iFlow CLI, Kimi CLI, Kiro CLI, Windsurf, Warp, Amp, Crush, droid, openclaw. The `--only ` flag (used by install.sh's `MCP_TARGETS` and install.ps1's `$McpTargets`) filters this list — its id values MUST match `npx skills add -a` ids, with `claude-desktop` as the one documented MCP-only exception. Goose / kode / kilo still need a manual JSON paste (see SKILL.md's "Fallback" section); when adding more agents server-side, keep `MCP_AUTO_AGENTS` in both install scripts and the cleanup list in both uninstall scripts in sync. +- `@agentkey/cli --auth-login` auto-writes MCP configs for 18 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`), including Hermes and DeepSeek Harness. The `--only ` flag filters this list. Most ids match `npx skills add -a`; `claude-desktop` has no skill path, `hermes` is a local CLI exception, and `dsh` deliberately uses only the global `skills add -g` path. Goose / kode / kilo still need manual MCP setup. Keep the Bash/PowerShell installer target subsets and uninstall cleanup behavior synchronized with their intended registry entries. +- DSH automatic config is `${DSH_HOME:-~/.dsh}/cordis.patch.yml`, with exactly one `# agentkey:start` / `# agentkey:end` block in the home patch. The Loader entry id and `serverName` are both `agentkey`. Existing per-profile managed blocks are migration inputs only; recognize markers at column 1 and never inside indented YAML block scalars. Structurally detected unmarked legacy Loader rows must stop migration for manual removal, never trigger guessed text deletion. Symlinked profile patches are read-only migration inputs: allow clean ones, but stop before all writes when either legacy form is present. A profile is not required before installation. Archive a legacy `.agent-presets/agentkey` directory instead of deleting it. `Mounted` is not connection proof; readiness requires the three core MCP tools to be visible and callable in the intended tool policy. - `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow - `.kimi-plugin/plugin.json` registers the same endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. diff --git a/.github/workflows/scripts-test.yml b/.github/workflows/scripts-test.yml index 32a0d37..e574978 100644 --- a/.github/workflows/scripts-test.yml +++ b/.github/workflows/scripts-test.yml @@ -49,3 +49,11 @@ jobs: sudo apt-get update && sudo apt-get install -y bats fi - run: bats tests/ + + dsh-powershell-marker: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Preserve indented DSH block-scalar markers + shell: pwsh + run: ./tests/dsh-uninstall-marker.ps1 diff --git a/AGENTS.md b/AGENTS.md index bbdcff0..8d215e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,7 @@ The same repo also works as: - a **Kimi Code plugin** (`.kimi-plugin/plugin.json`). Kimi requires `mcpServers` to be an inline object in the manifest. The remote AgentKey endpoint uses Kimi's native MCP OAuth flow; after install Kimi shows the standard `/reload` hint, then the user signs in with `/mcp-config login plugin-agentkey:agentkey` when Kimi reports that OAuth is required. - a **Gemini CLI extension** (root `gemini-extension.json` + `skills/`). Gemini requires the manifest at the extension root, discovers bundled agent skills automatically, and connects to AgentKey with `httpUrl` plus native MCP OAuth discovery. `oauth.enabled` requests the browser flow automatically; `/mcp auth agentkey` is the manual fallback. Either substitutes for step 2. - an **Antigravity 2.0 and Antigravity CLI plugin** (root `plugin.json` + `mcp_config.json` + `skills/`). Both runtimes use the same package, require `serverUrl` for remote MCP, and authenticate through automatic OAuth discovery. +- a **CLI-managed DeepSeek Harness integration**. The installers detect `${DSH_HOME:-~/.dsh}` / `dsh`, install the skill globally (never `skills add -a dsh`), and let `@agentkey/cli --auth-login --only dsh` maintain one marked `@deepseek-ai/dsh-mcp-client` entry in `$DSH_HOME/cordis.patch.yml`. DSH composes that home layer over current and future profiles; running processes watch it through HMR. DSH rc.7 has no MCP OAuth `authProvider`, so this path requires the CLI-written Bearer key. Tool policy may still hide tools. ## Directory Structure @@ -129,11 +130,14 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Update both `README.md` and `docs/README_zh.md` together — they mirror each other - The canonical install is always the two-command sequence (`npx skills add …` + `npx -y @agentkey/cli --auth-login`). Don't imply either command does both. - Do **not** re-add OpenClaw / per-agent installers without a new design — historical context is in git history (removed in chore/remove-archive-directory) +- Describe DSH as a CLI-managed MCP integration, not a native installable DSH plugin. Do not reintroduce the removed README-only `.dsh-plugin/agentkey/` placeholder without a real package and install contract. +- Never commit a real DSH Authorization key. Tests and examples use obviously fake values; production keys live only in the user's local `cordis.patch.yml`. ## Architecture Constraints - Setup mode in SKILL.md runs `! npx -y @agentkey/cli --auth-login` to authenticate via browser — same command as step 2 of the public install -- `@agentkey/cli --auth-login` auto-writes MCP configs for 16 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`): Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, OpenCode, Qwen Code, iFlow CLI, Kimi CLI, Kiro CLI, Windsurf, Warp, Amp, Crush, droid, openclaw. The `--only ` flag (used by install.sh's `MCP_TARGETS` and install.ps1's `$McpTargets`) filters this list — its id values MUST match `npx skills add -a` ids, with `claude-desktop` as the one documented MCP-only exception. Goose / kode / kilo still need a manual JSON paste (see SKILL.md's "Fallback" section); when adding more agents server-side, keep `MCP_AUTO_AGENTS` in both install scripts and the cleanup list in both uninstall scripts in sync. +- `@agentkey/cli --auth-login` auto-writes MCP configs for 18 agents (canonical list lives in `AGENT_REGISTRY` in `../AgentKey-Server/cli/src/lib/mcp-clients.ts`), including Hermes and DeepSeek Harness. The `--only ` flag filters this list. Most ids match `npx skills add -a`; `claude-desktop` has no skill path, `hermes` is a local CLI exception, and `dsh` deliberately uses only the global `skills add -g` path. Goose / kode / kilo still need manual MCP setup. Keep the Bash/PowerShell installer target subsets and uninstall cleanup behavior synchronized with their intended registry entries. +- DSH automatic config is `${DSH_HOME:-~/.dsh}/cordis.patch.yml`, with exactly one `# agentkey:start` / `# agentkey:end` block in the home patch. The Loader entry id and `serverName` are both `agentkey`. Existing per-profile managed blocks are migration inputs only; recognize markers at column 1 and never inside indented YAML block scalars. Structurally detected unmarked legacy Loader rows must stop migration for manual removal, never trigger guessed text deletion. Symlinked profile patches are read-only migration inputs: allow clean ones, but stop before all writes when either legacy form is present. A profile is not required before installation. Archive a legacy `.agent-presets/agentkey` directory instead of deleting it. `Mounted` is not connection proof; readiness requires the three core MCP tools to be visible and callable in the intended tool policy. - Root `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.codex-plugin/mcp.json` registers the same endpoint in Codex plugin mode, authenticated via MCP OAuth (RFC 9728 discovery; no `oauth_resource` — see checklist above) - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow diff --git a/README.md b/README.md index bf40f83..6f86f85 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ curl -fsSL https://agentkey.app/install.sh | bash irm https://agentkey.app/install.ps1 | iex ``` -Restart your agent, then ask it something that needs the internet: +Restart your agent, then ask it something that needs the internet. A running DeepSeek Harness profile watches the home patch through HMR; stopped profiles load it on their next start. > *"What has Musk been tweeting about lately?"* @@ -93,6 +93,57 @@ That's it. No API key to copy, no JSON to edit. --- +### DeepSeek Harness (DSH) + +The one-line installers above detect `${DSH_HOME:-~/.dsh}` or the `dsh` command automatically. They install the AgentKey skill globally at `~/.agents/skills/agentkey`, authenticate with AgentKey, and add one managed Loader block to DSH's home-level patch: + +```text +${DSH_HOME:-~/.dsh}/cordis.patch.yml +``` + +This is a **CLI-managed DSH MCP integration**, not a native installable DSH plugin. The home layer uses Loader id `agentkey`, module `@deepseek-ai/dsh-mcp-client`, and MCP `serverName: agentkey`; it is composed over current and future profiles. Tool allow/deny policy still controls whether a preset, session, or subagent can see the tools. + +DSH 0.1.0-rc.7 does not provide an OAuth `authProvider` to its MCP SDK client. A header-free server entry cannot complete 401/RFC 9728 discovery or open a browser. DSH must use the device-code command below so the CLI writes a local Bearer key. + +For a DSH-only manual install, run exactly these two steps: + +```bash +npx skills add chainbase-labs/agentkey -g -y +npx -y @agentkey/cli --auth-login --only dsh +``` + +The CLI stores the real API key only in the single local home patch; no key belongs in Git. Re-running the command rotates the key and replaces the managed block. No existing profile is required. During migration it removes only top-level, column-1 AgentKey managed blocks from per-profile patches and renames a legacy `.agent-presets/agentkey` directory to a timestamped backup; the CLI prints that backup path. If it structurally detects an older unmarked AgentKey Loader row, it stops without changing any patch and asks you to remove that top-level `insert` child manually. Symlinked profile patches are inspected read-only: a clean symlink profile is allowed, while either legacy AgentKey form stops installation and reports the path for manual removal. + +Only currently running profile processes observe the home-file change immediately through HMR. Stopped and future profiles load it when they start. If an old preset was already active in a session, close that session or restart DSH once after migration. + +#### How to confirm DSH installation + +1. Open **DSH → Settings → Plugins → Plugin list**, search for configured id `agentkey`, and expand the `mcp-client` row. DSH 0.1 currently renders: + - Loader path: `include:agentkey` (the stable configured entry id is `agentkey`) + - module title: `@deepseek-ai/dsh-mcp-client` (the card shortens it to `mcp-client`) + - Cordis status: `Mounted` (the underlying fiber phase is `active`) + + `Mounted`/`active` proves only that Cordis loaded the row. Because `failOnStartupError: false`, it does **not** prove that MCP authenticated or connected. + +2. In the intended preset/session, confirm the three core tools are visible: + - `mcp__agentkey__find_tools` + - `mcp__agentkey__describe_tool` + - `mcp__agentkey__execute_tool` + + `list_tools` is deprecated and is not a readiness requirement. If a core tool is hidden only in one context, inspect that preset/session/subagent's tool policy. + +3. Ask: **“Please use AgentKey to search for today's latest AI news.”** Success requires an actual `find_tools` → `describe_tool` → `execute_tool` call, not merely a Mounted card. + +Troubleshooting: + +- **No `agentkey` row:** the home patch was not written or `DSH_HOME` points elsewhere. Re-run the DSH-only CLI command and inspect its reported path. +- **Row exists but is not Mounted/active:** inspect the Loader error and verify `@deepseek-ai/dsh-mcp-client` exists in the DSH installation. +- **Mounted but tools are missing:** inspect MCP connection logs, the local Authorization key, and the active tool policy. In `tools/list`, each tool's root `inputSchema.additionalProperties` must be absent or `true`, never `false`. +- **401:** the local key is missing or invalid; rerun auth-login to rotate it. +- **`serverName already in use`:** rerun the current CLI so it archives the legacy preset, then close sessions that still hold the old preset or restart DSH once. + +--- + ## What your agent can now do AgentKey maintains cloud-side integrations across the open web — no extra accounts, no extra keys. @@ -301,7 +352,7 @@ npx -y @agentkey/cli --auth-login
My agent isn't on the auto-configured list — how do I set it up manually? -MCP auto-configuration covers **Claude Code**, **Claude Desktop**, and **Cursor**. For **Codex / OpenCode / Gemini CLI / Hermes / Manus** (or Linux Claude Desktop), the skill still installs automatically — but you'll need to paste this MCP snippet into the agent's own config (path varies per agent): +The CLI supports the clients shown in its current **Supported agents** list, and the one-line installer targets the supported clients it detects — including **DeepSeek Harness**. If your client is not detected or supported for automatic configuration, the skill can still be installed globally, but you must paste this MCP snippet into that client's own config (path varies per client): ```json { diff --git a/docs/README_zh.md b/docs/README_zh.md index 396484b..c30da18 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -83,7 +83,7 @@ curl -fsSL https://agentkey.app/install.sh | bash irm https://agentkey.app/install.ps1 | iex ``` -重启 Agent,然后问它一些需要联网的问题: +重启 Agent,然后问它一些需要联网的问题。正在运行的 DeepSeek Harness profile 会通过 HMR 监听 home patch;未运行的 profile 会在下次启动时加载。 > *"马斯克最近在推特上在说什么?"* @@ -93,6 +93,57 @@ irm https://agentkey.app/install.ps1 | iex --- +### DeepSeek Harness(DSH) + +上面的一键安装会自动检测 `${DSH_HOME:-~/.dsh}` 或 `dsh` 命令,把 AgentKey Skill 全局安装到 `~/.agents/skills/agentkey`,完成 AgentKey 授权,并把一个受管 Loader 块写入 DSH home-level patch: + +```text +${DSH_HOME:-~/.dsh}/cordis.patch.yml +``` + +这是 **CLI 管理的 DSH MCP 集成**,不是可通过 `dsh plugin` 安装的原生 DSH plugin。home layer 使用 Loader id `agentkey`、模块 `@deepseek-ai/dsh-mcp-client` 和 MCP `serverName: agentkey`,会叠加到当前及未来的 profile;但 preset、session 或 subagent 的 tool policy 仍可隐藏这些工具。 + +DSH 0.1.0-rc.7 没有向 MCP SDK client 提供 OAuth `authProvider`。不带 header 的 server entry 无法完成 401/RFC 9728 自动发现,也不会弹出浏览器。因此 DSH 必须执行下面的 device-code 命令,由 CLI 写入本地 Bearer Key。 + +如果只想手动安装到 DSH,严格执行下面两步: + +```bash +npx skills add chainbase-labs/agentkey -g -y +npx -y @agentkey/cli --auth-login --only dsh +``` + +CLI 只会把真实 API Key 写入单一的本地 home patch,API Key 不得进入 Git。重复运行会轮换 Key 并替换唯一的受管块,不会重复追加,也不要求已经存在 profile。迁移时只清除 per-profile patch 中位于 YAML 顶层、marker 从第 1 列开始的 AgentKey 受管块,并把旧 `.agent-presets/agentkey` 目录改名为带时间戳的备份;CLI 会输出备份位置。如果结构化检测到更早版本写入的未标记 AgentKey Loader 行,CLI 会在不修改任何 patch 的前提下停止,并提示手工删除对应的顶层 `insert` child。符号链接 profile patch 只做只读检查:不含旧 AgentKey 配置时允许安装;存在任一 legacy 形式时停止安装,输出路径并要求手工清理 symlink target。 + +只有正在运行的 profile 进程会通过 HMR 立即观察到 home patch 变化;已停止和未来创建的 profile 会在启动时加载。如果旧 preset 已在某个 session 中激活,迁移后关闭该 session,或重启一次 DSH。 + +#### 如何确认 DSH 安装成功 + +1. 打开 **DSH → Settings → Plugins → Plugin list**,搜索配置 id `agentkey`,展开 `mcp-client` 行。DSH 0.1 当前显示为: + - Loader path:`include:agentkey`(稳定的配置条目 id 仍是 `agentkey`) + - module title:`@deepseek-ai/dsh-mcp-client`(卡片会缩写为 `mcp-client`) + - Cordis status:`Mounted`(底层 fiber phase 是 `active`) + + `Mounted`/`active` 只证明 Cordis 已加载该配置行。由于 `failOnStartupError: false`,它**不能**证明 MCP 已认证或连接。 + +2. 在目标 preset/session 中确认三个核心工具可见: + - `mcp__agentkey__find_tools` + - `mcp__agentkey__describe_tool` + - `mcp__agentkey__execute_tool` + + `list_tools` 已弃用,不是 readiness 条件。如果只有某个上下文看不到核心工具,检查该 preset/session/subagent 的 tool policy。 + +3. 提问:**“请使用 AgentKey 搜索今天最新的 AI 新闻”**。成功标准是实际完成 `find_tools` → `describe_tool` → `execute_tool` 调用,而不是只看到 Mounted 卡片。 + +故障诊断: + +- **没有 `agentkey` 行:** home patch 未写入,或 `DSH_HOME` 指错位置。重跑 DSH-only CLI 命令,并检查它输出的路径。 +- **有配置行但不是 Mounted/active:** 查看 Loader 错误,并确认 DSH 安装中存在 `@deepseek-ai/dsh-mcp-client`。 +- **Mounted 但没有工具:** 检查 MCP 连接日志、本地 Authorization Key 和当前 tool policy;`tools/list` 中每个工具根 `inputSchema.additionalProperties` 必须缺省或为 `true`,不能为 `false`。 +- **401:** 本地 Key 缺失或无效;重跑 auth-login 轮换 Key。 +- **`serverName already in use`:** 重跑当前 CLI 归档旧 preset,再关闭仍持有旧 preset 的 session,或重启一次 DSH。 + +--- + ## 装好之后能干什么 AgentKey 在云端维护与开放互联网各类平台的对接 —— 你不需要额外开账号,也不用再填 Key。 @@ -301,7 +352,7 @@ npx -y @agentkey/cli --auth-login
我的 Agent 没被自动配置,怎么手动设置? -MCP 自动配置覆盖 **Claude Code**、**Claude Desktop**、**Cursor**。如果你用的是 **Codex / OpenCode / Gemini CLI / Hermes / Manus**(或 Linux 版 Claude Desktop),Skill 会正常装上,但你需要把下面这段 MCP 片段手动贴到该 Agent 的配置里(路径因 Agent 而异): +CLI 支持其当前 **Supported agents** 列表中的客户端,一键安装器会自动配置检测到的受支持客户端,其中包括 **DeepSeek Harness**。如果客户端未被检测到,或不支持自动配置,仍可全局安装 Skill,但需要把下面这段 MCP 片段手动贴到该客户端自己的配置里(路径因客户端而异): ```json { diff --git a/scripts/install.ps1 b/scripts/install.ps1 index e913594..0fd09a0 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -42,13 +42,21 @@ $NodeMinMajor = 18 # # IMPORTANT: ids here MUST match the `--only` ids accepted by both # `npx skills add -a` and `npx -y @agentkey/cli --auth-login --only`. -# `claude-desktop` is the documented exception (no skill install path, but -# MCP config is writable) — it's listed below and used only for MCP --only. +# `claude-desktop` and `dsh` are exceptions. Neither is passed to +# `skills add -a`; DSH reads the global skill installed by `skills add -g`. +$DshHome = if ([string]::IsNullOrWhiteSpace($env:DSH_HOME)) { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.dsh' } else { $env:DSH_HOME } +if ($DshHome -eq '~') { + $DshHome = [Environment]::GetFolderPath('UserProfile') +} elseif ($DshHome -match '^~[\\/]') { + $DshHome = Join-Path ([Environment]::GetFolderPath('UserProfile')) $DshHome.Substring(2) +} +$DshHome = [System.IO.Path]::GetFullPath($DshHome) $AgentMarkers = @( @{ Id = 'claude-code'; Markers = @("path:$env:USERPROFILE\.claude.json", 'cmd:claude') } @{ Id = 'claude-desktop'; Markers = @("path:$env:LOCALAPPDATA\AnthropicClaude", "path:$env:APPDATA\Claude\claude_desktop_config.json", "path:$env:APPDATA\Claude") } @{ Id = 'cursor'; Markers = @("path:$env:USERPROFILE\.cursor", 'cmd:cursor', "path:$env:LOCALAPPDATA\Programs\cursor") } @{ Id = 'codex'; Markers = @("path:$env:USERPROFILE\.codex", 'cmd:codex') } + @{ Id = 'dsh'; Markers = @("path:$DshHome", 'cmd:dsh') } @{ Id = 'gemini-cli'; Markers = @("path:$env:USERPROFILE\.gemini", 'cmd:gemini') } @{ Id = 'opencode'; Markers = @("path:$env:APPDATA\opencode", "path:$env:USERPROFILE\.opencode", 'cmd:opencode') } @{ Id = 'openclaw'; Markers = @("path:$env:USERPROFILE\.openclaw", 'cmd:openclaw') } @@ -66,9 +74,9 @@ $AgentMarkers = @( @{ Id = 'kiro-cli'; Markers = @("path:$env:USERPROFILE\.kiro", 'cmd:kiro') } ) -# Agent ids that are MCP-only (no skill install path). Never passed to -# `npx skills add -a`, only to `--auth-login --only`. -$McpOnlyAgents = @('claude-desktop') +# Agent ids excluded from per-agent `skills add -a`: Claude Desktop has no +# skill path, while DSH intentionally consumes the global `skills add -g` copy. +$SkillsAgentExclusions = @('claude-desktop', 'dsh') # Agent ids whose MCP registration the installer can drive automatically. # Mirror of MCP_AUTO_AGENTS in install.sh and AGENT_REGISTRY in @@ -76,7 +84,7 @@ $McpOnlyAgents = @('claude-desktop') $McpAutoAgents = @( 'claude-code', 'claude-desktop', 'cursor', 'codex', 'gemini-cli', 'opencode', 'qwen-code', 'iflow-cli', 'kimi-cli', 'kiro-cli', - 'windsurf', 'warp', 'amp', 'crush', 'droid', 'openclaw' + 'windsurf', 'warp', 'amp', 'crush', 'droid', 'openclaw', 'dsh' ) # ── UI helpers ──────────────────────────────────────────────────────────── @@ -252,8 +260,8 @@ if (-not (Get-Command npx -ErrorAction SilentlyContinue)) { } # Resolve target agent list — shared between the skill step and the MCP step. -# $AllTargets — every detected agent, including MCP-only ones (claude-desktop) -# $SkillTargets — $AllTargets minus MCP-only ids (those would fail `skills add`) +# $AllTargets — every detected agent, including local exceptions +# $SkillTargets — $AllTargets minus ids excluded from `skills add -a` # $McpTargets — $AllTargets filtered to ids the MCP CLI knows how to write $AllTargets = @() if ($Only) { @@ -271,17 +279,18 @@ if ($Only) { } } -$SkillTargets = @($AllTargets | Where-Object { $_ -notin $McpOnlyAgents }) +$SkillTargets = @($AllTargets | Where-Object { $_ -notin $SkillsAgentExclusions }) $McpTargets = @($AllTargets | Where-Object { $_ -in $McpAutoAgents }) +$DshSelected = $AllTargets -contains 'dsh' +$DshConfigured = $false # ── 2. Install the AgentKey skill ───────────────────────────────────────── if ($SkipSkill) { Write-Step '2. Install the AgentKey skill' Write-Muted 'Skipped (-SkipSkill)' -} elseif ($AllTargets.Count -gt 0 -and $SkillTargets.Count -eq 0) { - # User explicitly selected only MCP-only ids (e.g. `-Only claude-desktop`). - # There's nothing for `skills add` to do — skip the step entirely rather - # than fall through to "install for every detected agent." +} elseif ($AllTargets.Count -gt 0 -and $SkillTargets.Count -eq 0 -and -not $DshSelected) { + # DSH never enters this branch: `-Only dsh` must still run the global + # `skills add -g` path, without passing dsh to `-a`. Write-Step '2. Install the AgentKey skill' Write-Muted "Skipped — selected targets ($($AllTargets -join ',')) are MCP-only (no skill install path)." } else { @@ -409,6 +418,7 @@ if ($SkipMcp) { Write-Muted "Retry manually: npx -y $CliPackage $($authArgs -join ' ')" exit 1 } + $DshConfigured = $McpTargets -contains 'dsh' Write-Ok 'MCP server registered' } @@ -416,7 +426,11 @@ if ($SkipMcp) { Write-Step '✨ Installation complete' Write-Host '' Write-Host ' Next steps' -ForegroundColor White -Write-Muted '1. Restart your agent (Claude Code / Cursor / etc.)' +if ($DshConfigured) { + Write-Muted '1. Running DSH profiles hot-apply the home patch; start stopped DSH profiles and restart other clients.' +} else { + Write-Muted '1. Restart your agent (Claude Code / Cursor / etc.)' +} Write-Muted '2. Ask it something that needs the internet:' Write-Host ' "What has Musk been tweeting about lately?"' -ForegroundColor Cyan Write-Host '' diff --git a/scripts/install.sh b/scripts/install.sh index 4827743..cbc507a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,17 +1,10 @@ #!/usr/bin/env bash -# # AgentKey installer for macOS and Linux # Usage: curl -fsSL https://agentkey.app/install.sh | bash # curl -fsSL https://agentkey.app/install.sh | bash -s -- --yes # curl -fsSL https://agentkey.app/install.sh | bash -s -- --interactive # curl -fsSL https://agentkey.app/install.sh | bash -s -- --only claude-code,cursor # curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-mcp -# -# The whole procedural body is wrapped in `main()` so that under `curl | bash` -# bash reads the entire script into memory (as a function definition) before -# executing any of it. Without this wrapper, `exec < /dev/tty` would clobber -# bash's own script-source fd and the shell would hang trying to read the rest -# of itself from the terminal. set -euo pipefail @@ -19,22 +12,12 @@ set -euo pipefail SKILL_REPO="chainbase-labs/agentkey" CLI_PACKAGE="@agentkey/cli" NODE_MIN_MAJOR=18 +DSH_DETECT_HOME="${DSH_HOME:-}"; [[ -n "$DSH_DETECT_HOME" && "$DSH_DETECT_HOME" == *[![:space:]]* ]] || DSH_DETECT_HOME="$HOME/.dsh" +case "$DSH_DETECT_HOME" in "~"|"~/"*) DSH_DETECT_HOME="$HOME${DSH_DETECT_HOME#"~"}" ;; /*) ;; *) DSH_DETECT_HOME="$PWD/$DSH_DETECT_HOME" ;; esac # ── Agent markers ───────────────────────────────────────────────────────── -# Subset of vercel-labs/skills' 45 supported agent IDs that have reliable -# on-disk markers (config dirs / binaries on PATH). Agents we can't probe -# cleanly (mostly VS Code extensions like cline/continue/roo) just don't get -# pre-detected — the user can pass --all-agents or --only to include them. -# Sync source: https://github.com/vercel-labs/skills (Supported Agents table). -# -# IMPORTANT: ids here MUST match the `--only` ids accepted by both -# `npx skills add -a` and `npx -y @agentkey/cli --auth-login --only`. -# That alignment is what lets the installer drive both halves with one list. -# -# `claude-desktop` is the documented exception — it isn't in the skills CLI -# (Desktop installs skills into a sandbox path the CLI can't write), but -# Desktop's MCP config IS auto-writable, so we list it separately and pass -# it ONLY to the MCP --only filter (see SKILL_TARGETS / MCP_TARGETS below). +# `claude-desktop` and `dsh` are exceptions. Neither may be passed to +# `skills add -a`; DSH reads the global skill installed by `skills add -g`. # # Format: |[,...] # marker types: cmd:foo — `command -v foo` @@ -44,6 +27,7 @@ AGENT_MARKERS=( "claude-desktop|path:/Applications/Claude.app,path:~/Applications/Claude.app,path:~/Library/Application Support/Claude/claude_desktop_config.json,path:~/Library/Application Support/Claude,path:~/.config/Claude/claude_desktop_config.json,path:~/.config/Claude" "cursor|path:~/.cursor,cmd:cursor" "codex|path:~/.codex,cmd:codex" + "dsh|path:$DSH_DETECT_HOME,cmd:dsh" "gemini-cli|path:~/.gemini,cmd:gemini" "opencode|path:~/.config/opencode,path:~/.opencode,cmd:opencode" "openclaw|path:~/.openclaw,cmd:openclaw" @@ -61,23 +45,15 @@ AGENT_MARKERS=( "kiro-cli|path:~/.kiro,cmd:kiro" ) -# Agent ids that are MCP-only (no skill install path). These get passed to -# `--auth-login --only` but NEVER to `npx skills add -a`. -MCP_ONLY_AGENTS=(claude-desktop) +SKILLS_AGENT_EXCLUSIONS=(claude-desktop dsh) -# Agent ids whose MCP registration the installer can drive automatically. -# Skipped agents (goose / kode / kilo) still get the skill, but the user -# must register MCP manually for them. Keep this in sync with -# AGENT_REGISTRY in AgentKey-Server/cli/src/lib/mcp-clients.ts. MCP_AUTO_AGENTS=( claude-code claude-desktop cursor codex gemini-cli opencode qwen-code iflow-cli kimi-cli kiro-cli windsurf warp - amp crush droid openclaw + amp crush droid openclaw dsh ) # ── Colors (only if stdout is a TTY) ───────────────────────────────────── -# Use $'...' so variables hold real ESC bytes — otherwise heredoc output prints -# the literal string "\033[1m" instead of applying the SGR code. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then BOLD=$'\033[1m' ACCENT=$'\033[38;2;0;200;180m' # AgentKey teal @@ -145,7 +121,6 @@ EOF # ── Helpers: agent detection ────────────────────────────────────────────── -# Expand a leading "~" to \$HOME (no glob expansion, no eval). _expand_path() { local p="$1" case "$p" in @@ -154,7 +129,6 @@ _expand_path() { esac } -# Probe a single marker: cmd:NAME (binary on PATH) or path:PATH (file/dir). _probe_marker() { local m="$1" case "$m" in @@ -164,13 +138,11 @@ _probe_marker() { esac } -# Print detected agent IDs as a comma-separated list (empty if none). detect_agents() { local entry id markers marker hits=() for entry in "${AGENT_MARKERS[@]}"; do id="${entry%%|*}" markers="${entry#*|}" - # Any marker hit ⇒ agent detected. IFS=',' read -ra marker_list <<<"$markers" for marker in "${marker_list[@]}"; do if _probe_marker "$marker"; then @@ -184,7 +156,6 @@ detect_agents() { fi } -# Membership helper: is "$1" in the rest of the argument list? _in_list() { local needle="$1"; shift local item @@ -194,9 +165,6 @@ _in_list() { return 1 } -# Filter a comma-separated id list, keeping only ids that are passed in the -# remaining arguments. Output is comma-separated. Short-circuits on empty -# input so callers don't have to guard. _filter_csv() { local csv="$1"; shift [ -z "$csv" ] && return 0 @@ -223,7 +191,6 @@ install_node() { die "Homebrew not found. Install Node.js v$NODE_MIN_MAJOR+ manually: https://nodejs.org/" fi else - # Linux: NodeSource for apt/dnf/yum; apk for Alpine; otherwise manual if command -v apt-get >/dev/null 2>&1; then curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - >/dev/null 2>&1 \ && sudo apt-get install -y nodejs >/dev/null 2>&1 || die "apt install nodejs failed" @@ -242,9 +209,6 @@ install_node() { ui_ok "Node.js installed" } -# Compute a stable per-device fingerprint for install_completed dedup. -# spec §6.3: sha256(hostname+platform+username)[:16]. Falls back to a random -# value if neither sha256sum nor shasum is available (extremely rare). compute_device_fingerprint() { local platform="$1" local hn user input hash @@ -256,16 +220,11 @@ compute_device_fingerprint() { elif command -v shasum >/dev/null 2>&1; then hash="$(printf '%s' "$input" | shasum -a 256 | cut -c1-16)" else - # Last resort: use $RANDOM. Won't dedup across runs but won't crash. hash="rnd$(printf '%04x%04x%04x' "$RANDOM" "$RANDOM" "$RANDOM")" fi printf '%s' "$hash" } -# ────────────────────────────────────────────────────────────────────────── -# main — wraps the entire procedural body so that under `curl | bash` -# bash finishes reading the script before any fd-rebinding happens. -# ────────────────────────────────────────────────────────────────────────── main() { local MODE="" local ONLY_AGENTS="" @@ -276,8 +235,6 @@ main() { local ALL_AGENTS=false local NO_TELEMETRY=false - # Snapshot original args before the parse loop shifts them away — needed - # later for AGENTKEY_INSTALLER_FLAGS env passthrough. local _orig_args=("$@") while [ $# -gt 0 ]; do @@ -323,16 +280,6 @@ main() { esac ui_ok "Platform: $PLATFORM" - # Resolve stdin. `curl | bash` eats stdin — but /dev/tty is usually still - # reachable. Test by *actually opening* /dev/tty in a subshell; `[ -r ]` - # returns true even when the process has lost its controlling terminal - # (e.g. backgrounded, daemonized). - # - # IMPORTANT: we do NOT `exec < /dev/tty` globally. Under `curl | bash` - # bash is reading the script from its own stdin (the pipe); a global - # rebind would hijack bash's script reader and hang after `main` returns - # (bash would try to read the next byte from /dev/tty instead of EOF). - # Instead we redirect stdin *per interactive command* below. local TTY_AVAILABLE=false if ( : < /dev/tty ) >/dev/null 2>&1; then TTY_AVAILABLE=true @@ -350,8 +297,6 @@ main() { fi ui_ok "Mode: $MODE" - # Resolve telemetry intent: --no-telemetry overrides everything; existing - # ~/.config/agentkey/telemetry-disabled file means already-opted-out. local TELEMETRY_OPT_OUT_FILE="$HOME/.config/agentkey/telemetry-disabled" if $NO_TELEMETRY; then mkdir -p "$(dirname "$TELEMETRY_OPT_OUT_FILE")" 2>/dev/null || true @@ -363,7 +308,6 @@ main() { ui_info "Telemetry: anonymous usage stats enabled (re-run with --no-telemetry to opt out)" fi - # Node check local NODE_OK=false NODE_VERSION NODE_MAJOR if command -v node >/dev/null 2>&1; then NODE_VERSION="$(node --version 2>/dev/null | sed 's/^v//')" @@ -381,7 +325,6 @@ main() { printf "\n ${BOLD}Node.js v%s+ is required but not found.${NC}\n" "$NODE_MIN_MAJOR" printf " Install it now? [Y/n] " local REPLY="" - # Read directly from the terminal, not from bash's stdin (the pipe) read -r REPLY < /dev/tty || REPLY="" case "$REPLY" in n|N|no|No) die "Node.js required. Aborting." ;; @@ -393,13 +336,6 @@ main() { command -v npx >/dev/null 2>&1 || die "npx not found after Node install — please reinstall Node.js" # ── Resolve target agent list ───────────────────────────────────────── - # Used by step 2 (skill) and step 3 (MCP). Computed once here so both - # halves see the same source of truth — that's the invariant the unified - # install+register design depends on. Two derived lists: - # - # ALL_TARGETS — every detected agent, including MCP-only ones (claude-desktop) - # SKILL_TARGETS — ALL_TARGETS minus MCP-only ids (those would error in `skills add`) - # MCP_TARGETS — ALL_TARGETS filtered to ids the MCP CLI knows how to write local ALL_TARGETS="" if [ -n "$ONLY_AGENTS" ]; then ALL_TARGETS="$ONLY_AGENTS" @@ -418,20 +354,21 @@ main() { local SKILL_TARGETS="" local MCP_TARGETS="" + local DSH_SELECTED=false + local DSH_MCP_CONFIGURED=false if [ -n "$ALL_TARGETS" ]; then - # SKILL_TARGETS: drop MCP-only ids (would fail in `skills add -a`). local _id local -a _id_list=() _kept=() IFS=',' read -ra _id_list <<<"$ALL_TARGETS" for _id in "${_id_list[@]}"; do - if ! _in_list "$_id" "${MCP_ONLY_AGENTS[@]}"; then + [ "$_id" = dsh ] && DSH_SELECTED=true + if ! _in_list "$_id" "${SKILLS_AGENT_EXCLUSIONS[@]}"; then _kept+=("$_id") fi done if [ ${#_kept[@]} -gt 0 ]; then SKILL_TARGETS="$(printf '%s\n' "${_kept[@]}" | paste -sd, -)" fi - # MCP_TARGETS: keep only ids the MCP CLI knows how to register. MCP_TARGETS="$(_filter_csv "$ALL_TARGETS" "${MCP_AUTO_AGENTS[@]}")" fi @@ -439,10 +376,7 @@ main() { if $SKIP_SKILL; then ui_step "2. Install the AgentKey skill" ui_muted "Skipped (--skip-skill)" - elif [ -n "$ALL_TARGETS" ] && [ -z "$SKILL_TARGETS" ]; then - # User explicitly selected only MCP-only ids (e.g. `--only claude-desktop`). - # There's nothing for `skills add` to do — skip the step entirely - # rather than fall through to "install for every detected agent." + elif [ -n "$ALL_TARGETS" ] && [ -z "$SKILL_TARGETS" ] && ! $DSH_SELECTED; then ui_step "2. Install the AgentKey skill" ui_muted "Skipped — selected targets ($ALL_TARGETS) are MCP-only (no skill install path)." else @@ -450,22 +384,14 @@ main() { local SKILLS_ARGS=(-y skills add "$SKILL_REPO" -g) if [ -n "$SKILL_TARGETS" ]; then - # `skills` CLI accepts -a as either repeated or comma-separated. - # We pass each ID individually for maximum compatibility. local AGENT_LIST=() IFS=',' read -ra AGENT_LIST <<<"$SKILL_TARGETS" SKILLS_ARGS+=(-a "${AGENT_LIST[@]}") fi - # Always pass -y in noninteractive mode AND when we already resolved - # an explicit target list — there's nothing left to ask the user. if [ "$MODE" = noninteractive ] || [ -n "$ALL_TARGETS" ]; then SKILLS_ARGS+=(-y) fi - # Route npx's stdin to the terminal so its interactive multi-select can - # prompt the user — otherwise it inherits bash's piped stdin and breaks. - # When non-interactive (no TTY), stdin stays as /dev/null via < /dev/null - # to guarantee npx never blocks waiting for input. local npx_stdin="/dev/null" if [ "$MODE" = interactive ] && $TTY_AVAILABLE; then npx_stdin="/dev/tty" @@ -473,9 +399,6 @@ main() { if ! npx "${SKILLS_ARGS[@]}" < "$npx_stdin"; then die "Failed to install skill via 'skills' CLI" fi - # The skills CLI sometimes prints "Installation failed" and still - # exits 0 (e.g. network error during git clone). Verify the skill - # actually landed on disk before declaring success. local _agentkey_found=false _dir for _dir in \ "$HOME/.agents/skills/agentkey" \ @@ -504,26 +427,13 @@ main() { fi # ── 3. MCP authentication ──────────────────────────────────────────── - # Always run auth-login. The CLI itself decides whether the existing - # token can be reused or a fresh device-code flow is needed — the - # installer no longer second-guesses by sniffing config files (which - # produced false positives across the stdio → HTTP schema change). if $SKIP_MCP; then ui_step "3. Register the MCP server" ui_muted "Skipped (--skip-mcp)" elif [ -n "$ALL_TARGETS" ] && [ -z "$MCP_TARGETS" ]; then - # User selected ONLY MCP-incompatible agents (goose / kode / kilo - # via --only). Running auth-login without --only would silently - # register MCP in every detected agent — overriding the user's - # explicit scope. Skip rather than over-register. See PR #41 B1. ui_step "3. Register the MCP server" ui_muted "Skipped — selected agents ($ALL_TARGETS) need manual MCP setup (see SKILL.md Fallback section)." else - # Pin MCP registration to the same agent list the skill step - # targeted. When MCP_TARGETS is empty (auto-detect found nothing), - # let `@agentkey/cli` do its own detection — same fallback we use - # for skill install. Older CLI versions silently ignore --only, - # so this is forward-compatible. local AUTH_ARGS=(--auth-login) if [ -n "$MCP_TARGETS" ]; then AUTH_ARGS+=(--only "$MCP_TARGETS") @@ -538,12 +448,6 @@ main() { fi echo - # Telemetry context for `install_completed`. Opt-out is honored at - # the SOURCE: when AGENTKEY_TELEMETRY=0, no other context env vars - # are exported — hostname-derived fingerprint, agent lists, and - # installer flags are never computed nor passed to the child - # `npx @agentkey/cli` process. The server treats AGENTKEY_TELEMETRY=0 - # as a hard skip. if $NO_TELEMETRY || [ -f "$TELEMETRY_OPT_OUT_FILE" ]; then export AGENTKEY_TELEMETRY=0 else @@ -564,15 +468,20 @@ main() { ui_muted "Retry manually: npx -y $CLI_PACKAGE ${AUTH_ARGS[*]}" exit 1 fi + case ",$MCP_TARGETS," in *,dsh,*) DSH_MCP_CONFIGURED=true ;; esac ui_ok "MCP server registered" fi # ── 4. Summary ─────────────────────────────────────────────────────── ui_step "✨ Installation complete" + local APPLY_HINT="Restart your agent (Claude Code / Cursor / etc.)" + if $DSH_MCP_CONFIGURED; then + APPLY_HINT="Running DSH profiles hot-apply the home patch; start stopped DSH profiles and restart other clients" + fi cat < "$cfg.tmp" && mv "$cfg.tmp" "$cfg" ok "Removed agentkey block from $cfg" done - +# DSH cleanup is marker-scoped so unrelated home/profile patches survive. +DSH_ROOT="${DSH_HOME:-}"; [[ -n "$DSH_ROOT" && "$DSH_ROOT" == *[![:space:]]* ]] || DSH_ROOT="$HOME/.dsh" +case "$DSH_ROOT" in "~"|"~/"*) DSH_ROOT="$HOME${DSH_ROOT#"~"}" ;; /*) ;; *) DSH_ROOT="$PWD/$DSH_ROOT" ;; esac +DSH_CLEANED=false +for cfg in "$DSH_ROOT/cordis.patch.yml" "$DSH_ROOT"/profiles/*/cordis.patch.yml; do + [ -f "$cfg" ] || continue; grep -qE '^# agentkey:start([[:space:]]|$)' "$cfg" 2>/dev/null || { skipped "No AgentKey DSH block in $cfg"; continue; }; tmp="${cfg}.agentkey-uninstall.$$" + if awk 'BEGIN{s=0;i=0} /^# agentkey:start([[:space:]]|$)/{if(s)i=1;s=1;next} /^# agentkey:end([[:space:]]|$)/{if(!s)i=1;else{s=0;next}} !s{print} END{if(s||i)exit 42}' "$cfg" > "$tmp"; then + grep -q '^[[:space:]]*[^#[:space:]]' "$tmp" || printf '\n[]\n' >> "$tmp" + chmod 600 "$tmp"; mv "$tmp" "$cfg"; DSH_CLEANED=true; ok "Removed AgentKey DSH block from $cfg" + else + rm -f "$tmp"; warn "Malformed AgentKey markers in $cfg — left unchanged" + fi +done +LEGACY_DSH_PRESET="$DSH_ROOT/.agent-presets/agentkey" +if [ -e "$LEGACY_DSH_PRESET" ] || [ -L "$LEGACY_DSH_PRESET" ]; then + DSH_PRESET_BACKUP="${LEGACY_DSH_PRESET}.backup-$(date -u +%Y%m%dT%H%M%SZ)"; while [ -e "$DSH_PRESET_BACKUP" ]; do DSH_PRESET_BACKUP="${DSH_PRESET_BACKUP}-1"; done; mv "$LEGACY_DSH_PRESET" "$DSH_PRESET_BACKUP"; ok "Archived legacy DSH preset at $DSH_PRESET_BACKUP"; fi +DSH_SETTINGS="$DSH_ROOT/settings.yaml" +if [ -f "$DSH_SETTINGS" ]; then + tmp="${DSH_SETTINGS}.agentkey-uninstall.$$" + if awk 'BEGIN{p=0;r=0} /^[[:space:]]*agent-presets\.default:[[:space:]]*["\047]?agentkey["\047]?[[:space:]]*(#.*)?$/{r=1;next} /^[^[:space:]#][^:]*:[[:space:]]*/{p=($0~/^agent-presets:[[:space:]]*(#.*)?$/);print;next} p&&/^[[:space:]]+default:[[:space:]]*["\047]?agentkey["\047]?[[:space:]]*(#.*)?$/{r=1;next} {print} END{if(r)exit 10}' "$DSH_SETTINGS" > "$tmp"; then status=0; else status=$?; fi + case "$status" in 0) rm -f "$tmp" ;; 10) mv "$tmp" "$DSH_SETTINGS"; ok "Removed legacy agent-presets default from $DSH_SETTINGS" ;; *) rm -f "$tmp"; warn "Could not clean legacy DSH settings in $DSH_SETTINGS" ;; esac +fi # ── 2b. CLI-registered agents (droid / openclaw) ───────────────────────── # These two agents have no documented file-edit path; we registered them via # their own CLIs (`droid mcp add`, `openclaw mcp set`), so we have to use the @@ -472,4 +493,8 @@ EOF fi # ── Done ────────────────────────────────────────────────────────────────── -printf "\n ${BOLD}✓ Uninstall complete.${NC} Restart your agent to apply changes.\n\n" +if $DSH_CLEANED; then + printf "\n ${BOLD}✓ Uninstall complete.${NC} Running DSH profiles watch removal; close legacy sessions or restart once if needed.\n\n" +else + printf "\n ${BOLD}✓ Uninstall complete.${NC} Restart your agent to apply changes.\n\n" +fi diff --git a/skills/agentkey/SKILL.md b/skills/agentkey/SKILL.md index 3acb94e..0a37b6c 100644 --- a/skills/agentkey/SKILL.md +++ b/skills/agentkey/SKILL.md @@ -87,7 +87,15 @@ Never expose raw error details to the user. ## Setup -The skill is useless without the AgentKey MCP server registered with the user's agent. Two ways to connect — **try OAuth first**; fall back to an API key only if OAuth isn't available. +The skill is useless without the AgentKey MCP server registered with the user's agent. For clients other than the DSH exception below, **try OAuth first** and fall back to an API key only if OAuth isn't available. + +**DeepSeek Harness (DSH) is an exception:** DSH 0.1.0-rc.7's MCP client does not provide an OAuth `authProvider` to the MCP SDK. A header-free entry fails after the server's 401 and cannot open a browser flow. Do not use the generic OAuth or JSON instructions below in DSH. Run: + +```bash +npx -y @agentkey/cli --auth-login --only dsh +``` + +This device-code flow writes a Bearer key to the single `$DSH_HOME/cordis.patch.yml` home layer. Running profiles watch that layer through HMR; stopped and future profiles load it when they start. Stop after setup and retry the original request only after `find_tools`, `describe_tool`, and `execute_tool` are visible. Tool allow/deny policy can still hide them. Before adding anything, check whether an `agentkey` MCP server is already present but disconnected or waiting for authentication. Plugin and extension installs bundle that server entry. **Authenticate the bundled entry; do not register a duplicate server and do not run the standalone AgentKey CLI for that client.** diff --git a/skills/agentkey/references/setup.md b/skills/agentkey/references/setup.md index 89bacc5..4caff00 100644 --- a/skills/agentkey/references/setup.md +++ b/skills/agentkey/references/setup.md @@ -1,5 +1,41 @@ # AgentKey — Setup details +## DeepSeek Harness (DSH) + +DSH 0.1.0-rc.7 does not pass an OAuth `authProvider` into its MCP SDK client. +It cannot follow a 401/RFC 9728 challenge or launch AgentKey's browser OAuth. +Do not add a header-free MCP entry and do not use the generic JSON below. + +If this Skill is already running in DSH, authenticate and write the home-level +Bearer configuration with: + +```bash +npx -y @agentkey/cli --auth-login --only dsh +``` + +For a completely fresh install, install the global Skill first: + +```bash +npx skills add chainbase-labs/agentkey -g -y +npx -y @agentkey/cli --auth-login --only dsh +``` + +The CLI writes one managed loader block to `$DSH_HOME/cordis.patch.yml`, which +applies to current and future profiles. Running profiles watch the home patch +through HMR; stopped profiles load it on next start. A Loader status of +`Mounted` only proves the row loaded: verify `find_tools`, `describe_tool`, and +`execute_tool` are visible and callable. A preset/session/subagent tool policy +may intentionally hide them. Close an old session or restart DSH once if a +legacy preset was active during migration. + +Migration removes only AgentKey managed blocks whose markers start in column 1. +Indented marker text inside a YAML block scalar is user data and stays untouched. +If the CLI detects an older unmarked AgentKey Loader as a real top-level `insert` +child, it stops without changing patches; remove that child manually and retry. +Symlinked profile patches are inspected read-only. Clean symlink profiles do not +block installation; a managed or unmarked legacy Loader in one must be removed +manually from the reported target before retrying. + Two ways to connect the hosted MCP server (`https://api.agentkey.app/v1/mcp`). **Prefer OAuth.** Use the API-key fallback only when the client can't do MCP OAuth, or the OAuth flow fails. @@ -46,7 +82,7 @@ credential-free and authenticate the bundled `serverUrl` entry: Do not put OAuth client secrets, access tokens, or an `Authorization` header in the plugin package. -### Other clients +### Other clients (not DSH) If no AgentKey server entry exists, add the server with **no API key** and let the client run its own browser OAuth. The exact step depends on the client; diff --git a/tests/dsh-plugin.bats b/tests/dsh-plugin.bats new file mode 100644 index 0000000..bcf990d --- /dev/null +++ b/tests/dsh-plugin.bats @@ -0,0 +1,294 @@ +#!/usr/bin/env bats + +setup() { + REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" +} + +@test "installers detect DSH, auto-configure MCP, and exclude it from skills add -a" { + python3 - "$REPO_ROOT/scripts/install.sh" "$REPO_ROOT/scripts/install.ps1" <<'PY' +import sys + +bash = open(sys.argv[1], encoding="utf-8").read() +ps = open(sys.argv[2], encoding="utf-8").read() + +assert '"dsh|path:$DSH_DETECT_HOME,cmd:dsh"' in bash +assert 'DSH_DETECT_HOME="${DSH_HOME:-}"' in bash +assert 'SKILLS_AGENT_EXCLUSIONS=(claude-desktop dsh)' in bash +assert 'amp crush droid openclaw dsh' in bash +assert "@{ Id = 'dsh'" in ps +assert "$SkillsAgentExclusions = @('claude-desktop', 'dsh')" in ps +assert "'openclaw', 'dsh'" in ps +PY +} + +@test "--only dsh performs global skill install without passing dsh to -a" { + home="$BATS_TEST_TMPDIR/install-home" + work="$BATS_TEST_TMPDIR/install-work" + fakebin="$BATS_TEST_TMPDIR/bin" + mkdir -p "$home" "$work" "$fakebin" + log="$BATS_TEST_TMPDIR/npx.log" + cat > "$fakebin/npx" <<'SH' +#!/usr/bin/env bash +printf '%s\n' "$*" >> "$AGENTKEY_NPX_LOG" +if [[ " $* " == *" skills add "* ]]; then + mkdir -p "$HOME/.agents/skills/agentkey" + printf '%s\n' '# fake skill' > "$HOME/.agents/skills/agentkey/SKILL.md" +fi +exit 0 +SH + chmod +x "$fakebin/npx" + + run env HOME="$home" DSH_HOME="$home/.dsh" AGENTKEY_NPX_LOG="$log" PATH="$fakebin:$PATH" \ + bash -c 'cd "$1" && "$2" --yes --only dsh --no-telemetry' bash "$work" "$REPO_ROOT/scripts/install.sh" + + [ "$status" -eq 0 ] + grep -F 'skills add chainbase-labs/agentkey -g' "$log" + ! grep -E 'skills add .* -a .*dsh|skills add .* -a dsh' "$log" + grep -F '@agentkey/cli --auth-login --only dsh' "$log" +} + +@test "--skip-mcp --only dsh does not claim that DSH hot-applied MCP" { + home="$BATS_TEST_TMPDIR/skip-home" + work="$BATS_TEST_TMPDIR/skip-work" + fakebin="$BATS_TEST_TMPDIR/skip-bin" + mkdir -p "$home" "$work" "$fakebin" + cat > "$fakebin/npx" <<'SH' +#!/usr/bin/env bash +if [[ " $* " == *" skills add "* ]]; then + mkdir -p "$HOME/.agents/skills/agentkey" + printf '%s\n' '# fake skill' > "$HOME/.agents/skills/agentkey/SKILL.md" +fi +exit 0 +SH + chmod +x "$fakebin/npx" + + run env HOME="$home" DSH_HOME="$home/.dsh" PATH="$fakebin:$PATH" \ + bash -c 'cd "$1" && "$2" --yes --only dsh --skip-mcp --no-telemetry' bash "$work" "$REPO_ROOT/scripts/install.sh" + + [ "$status" -eq 0 ] + [[ "$output" != *"hot-appl"* ]] + [[ "$output" == *"Restart your agent"* ]] +} + +@test "bash uninstaller removes only managed DSH state and legacy defaults" { + home="$BATS_TEST_TMPDIR/uninstall-home" + dsh="$BATS_TEST_TMPDIR/custom-dsh" + work="$BATS_TEST_TMPDIR/uninstall-work" + patch="$dsh/cordis.patch.yml" + mkdir -p "$home" "$work" "$dsh" "$dsh/.agent-presets/agentkey" + cat > "$patch" <<'YAML' +# user comment +- id: preserve-before + enabled: true +# agentkey:start (managed by `@agentkey/cli --auth-login`) +- insert: + - id: agentkey + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: agentkey + headers: + Authorization: 'Bearer fake-test-key' +# agentkey:end +- id: preserve-after + enabled: true +YAML + cat > "$dsh/settings.yaml" <<'YAML' +agent-presets: + default: agentkey + sort: recent +theme: dark +YAML + printf '%s\n' user-custom > "$dsh/.agent-presets/agentkey/custom.yml" + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + ! grep -q '# agentkey:start' "$patch" + ! grep -q 'fake-test-key' "$patch" + grep -q 'id: preserve-before' "$patch" + grep -q 'id: preserve-after' "$patch" + [ ! -e "$dsh/.agent-presets/agentkey" ] + backup="$(find "$dsh/.agent-presets" -maxdepth 1 -name 'agentkey.backup-*' -print -quit)" + [ -n "$backup" ] + grep -q user-custom "$backup/custom.yml" + ! grep -Eq '^\s*default:\s*agentkey' "$dsh/settings.yaml" + grep -q 'sort: recent' "$dsh/settings.yaml" + grep -q 'theme: dark' "$dsh/settings.yaml" +} + +@test "bash uninstaller ignores marker-prefix collisions" { + home="$BATS_TEST_TMPDIR/collision-home" + dsh="$BATS_TEST_TMPDIR/collision-dsh" + work="$BATS_TEST_TMPDIR/collision-work" + patch="$dsh/cordis.patch.yml" + mkdir -p "$home" "$work" "$dsh" + printf '%s\n' '# agentkey:starter' '- id: user-plugin' '# agentkey:ending' > "$patch" + before="$(cat "$patch")" + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + [ "$(cat "$patch")" = "$before" ] + [[ "$output" == *"No AgentKey DSH block"* ]] +} + +@test "bash uninstaller preserves indented DSH block-scalar markers byte-for-byte" { + home="$BATS_TEST_TMPDIR/scalar-home" + dsh="$BATS_TEST_TMPDIR/scalar-dsh" + work="$BATS_TEST_TMPDIR/scalar-work" + patch="$dsh/cordis.patch.yml" + before="$BATS_TEST_TMPDIR/scalar-before.yml" + mkdir -p "$home" "$work" "$dsh" + cat > "$patch" <<'YAML' +- insert: + - id: user-plugin + name: '@example/plugin' + config: + instructions: | + # agentkey:start + This is ordinary user prompt text. + # agentkey:end + legacyExample: | + - id: agentkey + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: example +YAML + cp "$patch" "$before" + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + cmp -s "$patch" "$before" + [[ "$output" == *"No AgentKey DSH block"* ]] +} + +@test "bash uninstaller leaves malformed DSH marker blocks unchanged" { + home="$BATS_TEST_TMPDIR/malformed-home" + dsh="$BATS_TEST_TMPDIR/malformed-dsh" + work="$BATS_TEST_TMPDIR/malformed-work" + patch="$dsh/cordis.patch.yml" + mkdir -p "$home" "$work" "$dsh" + printf '%s\n' '# agentkey:start (missing end)' 'keep: true' > "$patch" + before="$(cat "$patch")" + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + [ "$(cat "$patch")" = "$before" ] + [[ "$output" == *"left unchanged"* ]] +} + +@test "bash uninstaller does not rewrite a profile without AgentKey markers" { + home="$BATS_TEST_TMPDIR/unrelated-home" + dsh="$BATS_TEST_TMPDIR/unrelated-dsh" + work="$BATS_TEST_TMPDIR/unrelated-work" + patch="$dsh/profiles/web/cordis.patch.yml" + mkdir -p "$home" "$work" "$(dirname "$patch")" + printf '%s\n' '# user-only profile' '- id: preserve-me' ' enabled: true' > "$patch" + before="$(cat "$patch")" + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + [ "$(cat "$patch")" = "$before" ] + [[ "$output" == *"No AgentKey DSH block"* ]] +} + +@test "bash uninstaller restores DSH's empty patch array" { + home="$BATS_TEST_TMPDIR/empty-home" + dsh="$BATS_TEST_TMPDIR/empty-dsh" + work="$BATS_TEST_TMPDIR/empty-work" + patch="$dsh/profiles/web/cordis.patch.yml" + mkdir -p "$home" "$work" "$(dirname "$patch")" + cat > "$patch" <<'YAML' +# Created by DSH +# agentkey:start (managed by `@agentkey/cli --auth-login`) +- insert: + - id: agentkey + name: '@deepseek-ai/dsh-mcp-client' +# agentkey:end +YAML + + run env HOME="$home" DSH_HOME="$dsh" bash -c 'cd "$1" && "$2" --skip-skill-remove' bash "$work" "$REPO_ROOT/scripts/uninstall.sh" + + [ "$status" -eq 0 ] + grep -q '^# Created by DSH$' "$patch" + [ "$(grep -c '^\[\]$' "$patch")" -eq 1 ] + ! grep -q '# agentkey:start' "$patch" +} + +@test "PowerShell uninstaller mirrors DSH patch, preset, and settings cleanup" { + python3 - "$REPO_ROOT/scripts/uninstall.ps1" <<'PY' +import sys + +script = open(sys.argv[1], encoding="utf-8").read() +for expected in ( + "$env:DSH_HOME", + "Join-Path $DshHome 'cordis.patch.yml'", + "profiles", + "cordis.patch.yml", + "# agentkey:start", + "# agentkey:end", + ".agent-presets\\agentkey", + "Move-Item $legacyDshPreset $presetBackup", + "settings.yaml", + "agent-presets\\.default", + "default:", +): + assert expected in script, expected +PY +} + +@test "PowerShell uninstaller preserves indented DSH block-scalar markers byte-for-byte" { + python3 - "$REPO_ROOT/scripts/uninstall.ps1" <<'PY' +import re +import sys + +script = open(sys.argv[1], encoding="utf-8").read() +match = re.search(r"\$managedPattern = '([^']+)'", script) +assert match, "managedPattern assignment missing" +pattern = match.group(1) +assert "^[ \\t]*# agentkey:start" not in pattern +assert "^# agentkey:start" in pattern +assert "^# agentkey:end" in pattern +assert "-match '^# agentkey:start(?:[ \\t]|$)'" in script +assert "-match '^# agentkey:end(?:[ \\t]|$)'" in script + +source = """- insert: + - id: user-plugin + config: + instructions: | + # agentkey:start + This is ordinary user prompt text. + # agentkey:end + legacyExample: | + - id: agentkey + name: '@deepseek-ai/dsh-mcp-client' +""" +assert re.sub(pattern, "", source) == source +PY +} + +@test "English and Chinese docs describe the same DSH verification contract" { + python3 - "$REPO_ROOT/README.md" "$REPO_ROOT/docs/README_zh.md" <<'PY' +import sys + +for path in sys.argv[1:]: + text = open(path, encoding="utf-8").read() + for expected in ( + "npx skills add chainbase-labs/agentkey -g -y", + "npx -y @agentkey/cli --auth-login --only dsh", + "cordis.patch.yml", + "@deepseek-ai/dsh-mcp-client", + "mcp__agentkey__find_tools", + "mcp__agentkey__describe_tool", + "mcp__agentkey__execute_tool", + "OAuth", + "tool policy", + "additionalProperties", + "symlink", + ): + assert expected in text, f"{path}: {expected}" +PY +} diff --git a/tests/dsh-uninstall-marker.ps1 b/tests/dsh-uninstall-marker.ps1 new file mode 100644 index 0000000..c71d06b --- /dev/null +++ b/tests/dsh-uninstall-marker.ps1 @@ -0,0 +1,43 @@ +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path $PSScriptRoot -Parent +$uninstallerPath = Join-Path $repoRoot 'scripts/uninstall.ps1' +$uninstaller = [System.IO.File]::ReadAllText($uninstallerPath) +$assignment = [regex]::Match( + $uninstaller, + '(?m)^\$managedPattern = ''([^'']+)''$' +) + +if (-not $assignment.Success) { + throw 'Could not find the PowerShell DSH managed marker pattern' +} + +$pattern = $assignment.Groups[1].Value +if ($pattern.Contains('^[ \t]*# agentkey:start')) { + throw 'PowerShell DSH start marker still accepts indentation' +} +if (-not $pattern.Contains('^# agentkey:start') -or -not $pattern.Contains('^# agentkey:end')) { + throw 'PowerShell DSH marker pattern is not anchored at column 1' +} + +$fixture = @' +- insert: + - id: user-plugin + name: '@example/plugin' + config: + instructions: | + # agentkey:start + This is ordinary user prompt text. + # agentkey:end + legacyExample: | + - id: agentkey + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: example +'@ +$updated = [regex]::Replace($fixture, $pattern, '') +if ($updated -cne $fixture) { + throw 'PowerShell DSH cleanup changed an indented YAML block scalar' +} + +Write-Host 'PowerShell DSH block-scalar marker regression: PASS' From 7ab3d608560a0df6f13a3123ecdedcb9dbc58d15 Mon Sep 17 00:00:00 2001 From: Allen <0xfatdog@gmail.com> Date: Wed, 19 Aug 2026 00:00:38 +0800 Subject: [PATCH 2/4] test: support CRLF in DSH PowerShell regression --- tests/dsh-uninstall-marker.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dsh-uninstall-marker.ps1 b/tests/dsh-uninstall-marker.ps1 index c71d06b..c2d9535 100644 --- a/tests/dsh-uninstall-marker.ps1 +++ b/tests/dsh-uninstall-marker.ps1 @@ -5,7 +5,7 @@ $uninstallerPath = Join-Path $repoRoot 'scripts/uninstall.ps1' $uninstaller = [System.IO.File]::ReadAllText($uninstallerPath) $assignment = [regex]::Match( $uninstaller, - '(?m)^\$managedPattern = ''([^'']+)''$' + '(?m)^\$managedPattern = ''([^'']+)''\r?$' ) if (-not $assignment.Success) { From d31c49534031bd0d7113f712a2963ed8f3c9612c Mon Sep 17 00:00:00 2001 From: Allen <0xfatdog@gmail.com> Date: Wed, 19 Aug 2026 15:05:36 +0800 Subject: [PATCH 3/4] add kimi mcp url --- .kimi-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kimi-plugin/plugin.json b/.kimi-plugin/plugin.json index 03548ca..79b3d60 100644 --- a/.kimi-plugin/plugin.json +++ b/.kimi-plugin/plugin.json @@ -25,7 +25,7 @@ "skills": "./skills/", "mcpServers": { "agentkey": { - "url": "https://api.agentkey.app/v1/mcp" + "url": "https://api.agentkey.app/kimi/v1/mcp" } }, "interface": { From a299024c020f0bc2d08783c829b765ef3870454b Mon Sep 17 00:00:00 2001 From: Allen <0xfatdog@gmail.com> Date: Wed, 19 Aug 2026 15:12:51 +0800 Subject: [PATCH 4/4] test: align Kimi attributed MCP route contract --- .claude/CLAUDE.md | 14 +++++++------- AGENTS.md | 14 +++++++------- README.md | 2 +- docs/README_zh.md | 2 +- tests/antigravity-plugin.bats | 11 +++++++++-- tests/kimi-plugin.bats | 2 +- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 2d16a5d..452c2fc 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -95,28 +95,28 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl **Changes to `.codex-plugin/mcp.json`:** - Codex plugin MCP config does NOT support `${user_config.*}` interpolation — a literal `${…}` would be sent as the Authorization header. Auth is MCP OAuth via RFC 9728 discovery: the server's 401 advertises `resource_metadata`, and the rmcp client automatically appends `resource=` to the authorization request. - Do NOT set `oauth_resource`: rmcp already sends `resource` on its own, and Codex appends `oauth_resource` as a *second* `resource` query param without deduplication (`codex-rs/rmcp-client/src/perform_oauth_login.rs`). Clerk enforces RFC 6749 (no repeated params) and rejects the request with `invalid_request: The request includes the parameter 'resource' more than once`. The official Notion/Figma plugins get away with it only because their authorization servers tolerate duplicates. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.cursor-plugin/plugin.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep each plugin endpoint aligned with the Server routing contract. Kimi uses the attributed `/kimi/v1/mcp` alias; the other plugin manifests currently use `/v1/mcp`. Do not require every client path to be byte-identical. **Changes to `.cursor-plugin/plugin.json`:** - The manifest MUST stay at `.cursor-plugin/plugin.json`; component paths resolve from the plugin root. - Use only fields documented by the Cursor plugin reference. Do not copy Codex/Kimi-only metadata such as `interface` into this manifest. - Keep `skills` pointed at `./skills/` and `mcpServers` as the minimal inline `{"agentkey":{"url":"https://api.agentkey.app/v1/mcp"}}` entry. Do not add static credentials or `${user_config.*}` interpolation; Cursor handles MCP OAuth itself. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.codex-plugin/mcp.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Cursor endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. - This repository is a single Cursor plugin, so `.cursor-plugin/marketplace.json` is not required. Submit the public repository URL through Cursor's marketplace publisher. **Changes to `.kimi-plugin/plugin.json`:** - `mcpServers` MUST be an inline object. Kimi does not accept a path such as `"./mcp.json"` for this field. -- Keep the HTTP entry minimal: `{"agentkey":{"url":"https://api.agentkey.app/v1/mcp"}}`. Kimi infers the transport from `url`. +- Keep the HTTP entry minimal: `{"agentkey":{"url":"https://api.agentkey.app/kimi/v1/mcp"}}`. Kimi infers the transport from `url`; the path preserves Kimi attribution while reaching the same MCP surface. - Do not add `userConfig`, a static Authorization header, or `${user_config.*}` interpolation. Kimi discovers and persists MCP OAuth credentials itself. - Kimi displays `Run /new or /reload to apply plugin changes.` after install. Once reloaded, the user completes native MCP OAuth with `/mcp-config login plugin-agentkey:agentkey` when Kimi reports that authentication is required. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Kimi endpoint aligned with the Server's `/kimi/v1/mcp` attributed alias. **Changes to `gemini-extension.json`:** - The manifest MUST remain at the repository root because Gemini installs the repository as the extension root and expects the extension name to match its install directory. - Keep `mcpServers.agentkey` inline and use `httpUrl` for the Streamable HTTP endpoint. Do not use the SSE-only `url` field for `/v1/mcp`. - Keep `oauth` limited to `{"enabled":true}` so Gemini starts its native browser flow after the server's 401 while still discovering all endpoints dynamically. Do not add static credentials, OAuth endpoints/client credentials, `settings`, custom headers, or `trust`. `/mcp auth agentkey` remains the manual fallback. - Do not duplicate `skills/agentkey/` or add an always-loaded `GEMINI.md`; Gemini discovers the existing skill automatically. -- Keep the endpoint URL in sync with `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, and `.kimi-plugin/plugin.json`. +- Keep the Gemini endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. **Changes to GitHub Release assets:** - Keep `agentkey.skill` for Skill consumers, but never publish it as the only generic Release asset. Gemini CLI treats a lone generic asset as an extension archive and only extracts `.tar.gz` or `.zip` files. @@ -128,7 +128,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Keep `plugin.json` limited to the documented `$schema`, `name`, and `description` fields. The Antigravity schema has no `version` field, so release-please must not add one. - Keep `mcpServers.agentkey` inline in `mcp_config.json` and use `serverUrl`; legacy `url` and `httpUrl` fields are unsupported. - Do not add static credentials, headers, or manual OAuth client secrets. AgentKey supports dynamic client registration, so Antigravity performs automatic OAuth discovery. -- Keep the endpoint URL in sync with `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Antigravity endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. **Changes to install/uninstall docs:** - Update both `README.md` and `docs/README_zh.md` together — they mirror each other @@ -144,7 +144,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - DSH automatic config is `${DSH_HOME:-~/.dsh}/cordis.patch.yml`, with exactly one `# agentkey:start` / `# agentkey:end` block in the home patch. The Loader entry id and `serverName` are both `agentkey`. Existing per-profile managed blocks are migration inputs only; recognize markers at column 1 and never inside indented YAML block scalars. Structurally detected unmarked legacy Loader rows must stop migration for manual removal, never trigger guessed text deletion. Symlinked profile patches are read-only migration inputs: allow clean ones, but stop before all writes when either legacy form is present. A profile is not required before installation. Archive a legacy `.agent-presets/agentkey` directory instead of deleting it. `Mounted` is not connection proof; readiness requires the three core MCP tools to be visible and callable in the intended tool policy. - `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow -- `.kimi-plugin/plugin.json` registers the same endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. +- `.kimi-plugin/plugin.json` registers the attributed `https://api.agentkey.app/kimi/v1/mcp` endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. - `gemini-extension.json` registers the same endpoint through `httpUrl` in Gemini CLI extension mode. Gemini discovers the existing `skills/agentkey/` tree; `oauth.enabled` starts native OAuth automatically and `/mcp auth agentkey` retries it manually. - Root `plugin.json` and `mcp_config.json` package the existing skill and the same endpoint for both Antigravity 2.0 and Antigravity CLI; remote MCP uses `serverUrl` and automatic OAuth discovery. - `README.md` / `docs/README_zh.md` are the public-facing docs; keep them in sync with any structural changes diff --git a/AGENTS.md b/AGENTS.md index 8d215e5..ea298e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,28 +91,28 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl **Changes to `.codex-plugin/mcp.json` (Codex plugin path):** - Codex plugin MCP config does NOT support `${user_config.*}` interpolation — a literal `${…}` would be sent as the Authorization header. Auth is MCP OAuth via RFC 9728 discovery: the server's 401 advertises `resource_metadata`, and the rmcp client automatically appends `resource=` to the authorization request. - Do NOT set `oauth_resource`: rmcp already sends `resource` on its own, and Codex appends `oauth_resource` as a *second* `resource` query param without deduplication (`codex-rs/rmcp-client/src/perform_oauth_login.rs`). Clerk enforces RFC 6749 (no repeated params) and rejects the request with `invalid_request: The request includes the parameter 'resource' more than once`. The official Notion/Figma plugins get away with it only because their authorization servers tolerate duplicates. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.cursor-plugin/plugin.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep each plugin endpoint aligned with the Server routing contract. Kimi uses the attributed `/kimi/v1/mcp` alias; the other plugin manifests currently use `/v1/mcp`. Do not require every client path to be byte-identical. **Changes to `.cursor-plugin/plugin.json` (Cursor plugin path):** - The manifest MUST stay at `.cursor-plugin/plugin.json`; component paths resolve from the plugin root. - Use only fields documented by the Cursor plugin reference. Do not copy Codex/Kimi-only metadata such as `interface` into this manifest. - Keep `skills` pointed at `./skills/` and `mcpServers` as the minimal inline `{"agentkey":{"url":"https://api.agentkey.app/v1/mcp"}}` entry. Do not add static credentials or `${user_config.*}` interpolation; Cursor handles MCP OAuth itself. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.codex-plugin/mcp.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Cursor endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. - This repository is a single Cursor plugin, so `.cursor-plugin/marketplace.json` is not required. Submit the public repository URL through Cursor's marketplace publisher. **Changes to `.kimi-plugin/plugin.json` (Kimi Code plugin path):** - `mcpServers` MUST be an inline object. Kimi does not accept a path such as `"./mcp.json"` for this field. -- Keep the HTTP entry minimal: `{"agentkey":{"url":"https://api.agentkey.app/v1/mcp"}}`. Kimi infers the transport from `url`. +- Keep the HTTP entry minimal: `{"agentkey":{"url":"https://api.agentkey.app/kimi/v1/mcp"}}`. Kimi infers the transport from `url`; the path preserves Kimi attribution while reaching the same MCP surface. - Do not add `userConfig`, a static Authorization header, or `${user_config.*}` interpolation. Kimi discovers and persists MCP OAuth credentials itself. - Kimi displays `Run /new or /reload to apply plugin changes.` after install. Once reloaded, the user completes native MCP OAuth with `/mcp-config login plugin-agentkey:agentkey` when Kimi reports that authentication is required. -- Keep the endpoint URL in sync with the root `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Kimi endpoint aligned with the Server's `/kimi/v1/mcp` attributed alias. **Changes to `gemini-extension.json` (Gemini CLI extension path):** - The manifest MUST remain at the repository root because Gemini installs the repository as the extension root and expects the extension name to match its install directory. - Keep `mcpServers.agentkey` inline and use `httpUrl` for the Streamable HTTP endpoint. Do not use the SSE-only `url` field for `/v1/mcp`. - Keep `oauth` limited to `{"enabled":true}` so Gemini starts its native browser flow after the server's 401 while still discovering all endpoints dynamically. Do not add static credentials, OAuth endpoints/client credentials, `settings`, custom headers, or `trust`. `/mcp auth agentkey` remains the manual fallback. - Do not duplicate `skills/agentkey/` or add an always-loaded `GEMINI.md`; Gemini discovers the existing skill automatically. -- Keep the endpoint URL in sync with `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, and `.kimi-plugin/plugin.json`. +- Keep the Gemini endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. **Changes to GitHub Release assets:** - Keep `agentkey.skill` for Skill consumers, but never publish it as the only generic Release asset. Gemini CLI treats a lone generic asset as an extension archive and only extracts `.tar.gz` or `.zip` files. @@ -124,7 +124,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Keep `plugin.json` limited to the documented `$schema`, `name`, and `description` fields. The Antigravity schema has no `version` field, so release-please must not add one. - Keep `mcpServers.agentkey` inline in `mcp_config.json` and use `serverUrl`; legacy `url` and `httpUrl` fields are unsupported. - Do not add static credentials, headers, or manual OAuth client secrets. AgentKey supports dynamic client registration, so Antigravity performs automatic OAuth discovery. -- Keep the endpoint URL in sync with `.mcp.json`, `.codex-plugin/mcp.json`, `.cursor-plugin/plugin.json`, `.kimi-plugin/plugin.json`, and `gemini-extension.json`. +- Keep the Antigravity endpoint at `/v1/mcp` until the Server routing contract assigns it a client-specific path. **Changes to install/uninstall docs:** - Update both `README.md` and `docs/README_zh.md` together — they mirror each other @@ -141,7 +141,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl - Root `.mcp.json` registers the remote-HTTP MCP endpoint (`https://api.agentkey.app/v1/mcp`) in Claude Code plugin mode with no static header or `userConfig`; Claude Code performs native MCP OAuth discovery after the server's 401 response. - `.codex-plugin/mcp.json` registers the same endpoint in Codex plugin mode, authenticated via MCP OAuth (RFC 9728 discovery; no `oauth_resource` — see checklist above) - `.cursor-plugin/plugin.json` registers the same endpoint inline in Cursor plugin mode, authenticated through Cursor's native MCP OAuth flow -- `.kimi-plugin/plugin.json` registers the same endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. +- `.kimi-plugin/plugin.json` registers the attributed `https://api.agentkey.app/kimi/v1/mcp` endpoint inline in Kimi Code plugin mode. After reloading, the user starts Kimi's native MCP OAuth flow with `/mcp-config login plugin-agentkey:agentkey`. - `gemini-extension.json` registers the same endpoint through `httpUrl` in Gemini CLI extension mode. Gemini discovers the existing `skills/agentkey/` tree; `oauth.enabled` starts native OAuth automatically and `/mcp auth agentkey` retries it manually. - Root `plugin.json` and `mcp_config.json` package the existing skill and the same endpoint for both Antigravity 2.0 and Antigravity CLI; remote MCP uses `serverUrl` and automatic OAuth discovery. - `README.md` / `docs/README_zh.md` are the public-facing docs; keep them in sync with any structural changes diff --git a/README.md b/README.md index 6f86f85..236d224 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,7 @@ codex plugin marketplace add chainbase-labs/agentkey The plugin manifest lives in `.codex-plugin/plugin.json`; it bundles the same skill plus a remote-HTTP MCP entry (`.codex-plugin/mcp.json`) that authenticates against `https://api.agentkey.app/v1/mcp` via MCP OAuth (RFC 9728 discovery). Sign in with your AgentKey account when Codex prompts you. -**Kimi Code plugin mode** — install the repo directly from Kimi Code. The manifest bundles the skill and an inline remote-HTTP MCP entry, so there is **no API key to paste and no second `@agentkey/cli` step**: +**Kimi Code plugin mode** — install the repo directly from Kimi Code. The manifest bundles the skill and an inline remote-HTTP MCP entry at the client-attributed `https://api.agentkey.app/kimi/v1/mcp` route, so there is **no API key to paste and no second `@agentkey/cli` step**: ```text # Public install diff --git a/docs/README_zh.md b/docs/README_zh.md index c30da18..0b0146a 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -415,7 +415,7 @@ codex plugin marketplace add chainbase-labs/agentkey 插件清单在 `.codex-plugin/plugin.json`;它捆绑了同一个 Skill,外加一条远程 HTTP MCP 配置(`.codex-plugin/mcp.json`),通过 MCP OAuth(RFC 9728 自动发现)对 `https://api.agentkey.app/v1/mcp` 做认证。Codex 提示时用你的 AgentKey 账号登录即可。 -**Kimi Code 插件模式** —— 直接在 Kimi Code 中安装本仓库。插件清单同时捆绑 Skill 和内联的远程 HTTP MCP 配置,**不用粘贴 API Key,也不需要再单独跑 `@agentkey/cli`**: +**Kimi Code 插件模式** —— 直接在 Kimi Code 中安装本仓库。插件清单同时捆绑 Skill,并通过客户端归因路由 `https://api.agentkey.app/kimi/v1/mcp` 提供内联的远程 HTTP MCP 配置,**不用粘贴 API Key,也不需要再单独跑 `@agentkey/cli`**: ```text # 公开安装 diff --git a/tests/antigravity-plugin.bats b/tests/antigravity-plugin.bats index 32e23ca..36e5065 100644 --- a/tests/antigravity-plugin.bats +++ b/tests/antigravity-plugin.bats @@ -73,7 +73,7 @@ assert "\nname: agentkey\n" in f"\n{frontmatter}\n" PY } -@test "Antigravity endpoint stays synchronized with every plugin client" { +@test "Plugin endpoints follow the client-attributed route contract" { python3 - "$REPO_ROOT" <<'PY' import json import os @@ -93,6 +93,13 @@ for client, (relative_path, field) in configs.items(): with open(os.path.join(root, relative_path), encoding="utf-8") as handle: endpoints[client] = json.load(handle)["mcpServers"]["agentkey"][field] -assert set(endpoints.values()) == {"https://api.agentkey.app/v1/mcp"}, endpoints +assert endpoints == { + "claude": "https://api.agentkey.app/v1/mcp", + "codex": "https://api.agentkey.app/v1/mcp", + "cursor": "https://api.agentkey.app/v1/mcp", + "kimi": "https://api.agentkey.app/kimi/v1/mcp", + "gemini": "https://api.agentkey.app/v1/mcp", + "antigravity": "https://api.agentkey.app/v1/mcp", +}, endpoints PY } diff --git a/tests/kimi-plugin.bats b/tests/kimi-plugin.bats index 6dc2ab0..1878fbb 100644 --- a/tests/kimi-plugin.bats +++ b/tests/kimi-plugin.bats @@ -17,7 +17,7 @@ servers = manifest.get("mcpServers") assert isinstance(servers, dict), "mcpServers must be an inline object" assert servers == { "agentkey": { - "url": "https://api.agentkey.app/v1/mcp", + "url": "https://api.agentkey.app/kimi/v1/mcp", } } PY