diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 452c2fc..48b4080 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -25,7 +25,7 @@ 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. +It also has a **CLI-managed DeepSeek Harness integration**. The installers detect `${DSH_HOME:-~/.dsh}` / `dsh`, install the skill globally through the Skills CLI's `universal` target (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 @@ -140,7 +140,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl ## 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 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. +- `@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 the global `skills add -g -a universal` 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 diff --git a/.github/workflows/scripts-test.yml b/.github/workflows/scripts-test.yml index e574978..4b46d20 100644 --- a/.github/workflows/scripts-test.yml +++ b/.github/workflows/scripts-test.yml @@ -50,10 +50,13 @@ jobs: fi - run: bats tests/ - dsh-powershell-marker: + dsh-powershell: runs-on: windows-latest steps: - uses: actions/checkout@v4 + - name: Use the universal target for DSH-only global installs + shell: pwsh + run: ./tests/dsh-install-target.ps1 - name: Preserve indented DSH block-scalar markers shell: pwsh run: ./tests/dsh-uninstall-marker.ps1 diff --git a/AGENTS.md b/AGENTS.md index ea298e1..29c2c7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +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. +- a **CLI-managed DeepSeek Harness integration**. The installers detect `${DSH_HOME:-~/.dsh}` / `dsh`, install the skill globally through the Skills CLI's `universal` target (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 @@ -136,7 +136,7 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl ## 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 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. +- `@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 the global `skills add -g -a universal` 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) diff --git a/README.md b/README.md index 236d224..f37e7e9 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ DSH 0.1.0-rc.7 does not provide an OAuth `authProvider` to its MCP SDK client. A For a DSH-only manual install, run exactly these two steps: ```bash -npx skills add chainbase-labs/agentkey -g -y +npx -y skills add chainbase-labs/agentkey -g -a universal -s agentkey -y npx -y @agentkey/cli --auth-login --only dsh ``` diff --git a/docs/README_zh.md b/docs/README_zh.md index 0b0146a..51080f5 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -108,7 +108,7 @@ DSH 0.1.0-rc.7 没有向 MCP SDK client 提供 OAuth `authProvider`。不带 hea 如果只想手动安装到 DSH,严格执行下面两步: ```bash -npx skills add chainbase-labs/agentkey -g -y +npx -y skills add chainbase-labs/agentkey -g -a universal -s agentkey -y npx -y @agentkey/cli --auth-login --only dsh ``` diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 0fd09a0..fc28007 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -43,7 +43,7 @@ $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` and `dsh` are exceptions. Neither is passed to -# `skills add -a`; DSH reads the global skill installed by `skills add -g`. +# `skills add -a`; DSH reads the global skill installed for `universal`. $DshHome = if ([string]::IsNullOrWhiteSpace($env:DSH_HOME)) { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.dsh' } else { $env:DSH_HOME } if ($DshHome -eq '~') { $DshHome = [Environment]::GetFolderPath('UserProfile') @@ -75,7 +75,7 @@ $AgentMarkers = @( ) # 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. +# skill path, while DSH consumes the global `universal` copy. $SkillsAgentExclusions = @('claude-desktop', 'dsh') # Agent ids whose MCP registration the installer can drive automatically. @@ -290,7 +290,7 @@ if ($SkipSkill) { Write-Muted 'Skipped (-SkipSkill)' } 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`. + # `skills add -g -a universal` 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 { @@ -300,6 +300,8 @@ if ($SkipSkill) { if ($SkillTargets.Count -gt 0) { $skillsArgs += '-a' $skillsArgs += $SkillTargets + } elseif ($DshSelected) { + $skillsArgs += @('-a', 'universal', '-s', 'agentkey') } # Always pass -y in noninteractive mode AND when we already resolved # an explicit target list — there's nothing left to ask the user. @@ -344,7 +346,7 @@ if ($SkipSkill) { } } if (-not $agentkeyFound) { - Die "Skill install reported success but no agentkey SKILL.md was created — likely a network or git clone failure. Retry: npx -y skills add $SkillRepo -g -y" + Die "Skill install reported success but no agentkey SKILL.md was created — likely a network or git clone failure. Retry: npx -y skills add $SkillRepo -g -a universal -s agentkey -y" } Write-Ok 'Skill installed' } diff --git a/scripts/install.sh b/scripts/install.sh index cbc507a..63c9afd 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -17,7 +17,7 @@ case "$DSH_DETECT_HOME" in "~"|"~/"*) DSH_DETECT_HOME="$HOME${DSH_DETECT_HOME#"~ # ── Agent markers ───────────────────────────────────────────────────────── # `claude-desktop` and `dsh` are exceptions. Neither may be passed to -# `skills add -a`; DSH reads the global skill installed by `skills add -g`. +# `skills add -a`; DSH reads the global skill installed for `universal`. # # Format: |[,...] # marker types: cmd:foo — `command -v foo` @@ -387,6 +387,8 @@ main() { local AGENT_LIST=() IFS=',' read -ra AGENT_LIST <<<"$SKILL_TARGETS" SKILLS_ARGS+=(-a "${AGENT_LIST[@]}") + elif $DSH_SELECTED; then + SKILLS_ARGS+=(-a universal -s agentkey) fi if [ "$MODE" = noninteractive ] || [ -n "$ALL_TARGETS" ]; then SKILLS_ARGS+=(-y) @@ -421,7 +423,7 @@ main() { [ -f "$_dir/SKILL.md" ] && { _agentkey_found=true; break; } done if ! $_agentkey_found; then - die "Skill install reported success but no agentkey SKILL.md was created — likely a network or git clone failure. Retry: npx -y skills add $SKILL_REPO -g -y" + die "Skill install reported success but no agentkey SKILL.md was created — likely a network or git clone failure. Retry: npx -y skills add $SKILL_REPO -g -a universal -s agentkey -y" fi ui_ok "Skill installed" fi diff --git a/skills/agentkey/references/setup.md b/skills/agentkey/references/setup.md index 4caff00..a371cd1 100644 --- a/skills/agentkey/references/setup.md +++ b/skills/agentkey/references/setup.md @@ -16,7 +16,7 @@ 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 skills add chainbase-labs/agentkey -g -a universal -s agentkey -y npx -y @agentkey/cli --auth-login --only dsh ``` diff --git a/tests/dsh-install-target.ps1 b/tests/dsh-install-target.ps1 new file mode 100644 index 0000000..bf16c0a --- /dev/null +++ b/tests/dsh-install-target.ps1 @@ -0,0 +1,49 @@ +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path $PSScriptRoot -Parent +$installerPath = Join-Path $repoRoot 'scripts/install.ps1' +$tempRoot = if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { [System.IO.Path]::GetTempPath() } else { $env:RUNNER_TEMP } +$testRoot = Join-Path $tempRoot "agentkey-dsh-install-target-$([guid]::NewGuid())" +$fakeBin = Join-Path $testRoot 'bin' +$fakeAppData = Join-Path $testRoot 'appdata' +$logPath = Join-Path $testRoot 'npx.log' + +New-Item -ItemType Directory -Force -Path $fakeBin, $fakeAppData | Out-Null +Remove-Item -Force -ErrorAction SilentlyContinue $logPath + +$fakeNpx = @" +@echo off +echo %*>>"$logPath" +mkdir "%APPDATA%\amp\skills\agentkey" 2>nul +echo # fake skill>"%APPDATA%\amp\skills\agentkey\SKILL.md" +exit /b 0 +"@ +Set-Content -LiteralPath (Join-Path $fakeBin 'npx.cmd') -Value $fakeNpx -Encoding Ascii + +$originalPath = $env:PATH +$originalAppData = $env:APPDATA +$originalDshHome = $env:DSH_HOME +try { + $env:PATH = "$fakeBin;$originalPath" + $env:APPDATA = $fakeAppData + $env:DSH_HOME = Join-Path $testRoot 'dsh-home' + + & $installerPath -Yes -Only dsh -SkipMcp + if ($LASTEXITCODE -ne 0) { + throw "PowerShell installer exited with $LASTEXITCODE" + } +} finally { + $env:PATH = $originalPath + $env:APPDATA = $originalAppData + $env:DSH_HOME = $originalDshHome +} + +$log = [System.IO.File]::ReadAllText($logPath) +if ($log -notmatch '(?m)-y skills add chainbase-labs/agentkey -g -a universal -s agentkey -y') { + throw "DSH-only install did not use the universal global target. npx log: $log" +} +if ($log -match '(?m)skills add .* -a .*dsh') { + throw "DSH was incorrectly passed to skills add -a. npx log: $log" +} + +Write-Host 'PowerShell DSH universal-target regression: PASS' diff --git a/tests/dsh-plugin.bats b/tests/dsh-plugin.bats index bcf990d..eecebd2 100644 --- a/tests/dsh-plugin.bats +++ b/tests/dsh-plugin.bats @@ -15,9 +15,11 @@ 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 'SKILLS_ARGS+=(-a universal -s agentkey)' in bash assert "@{ Id = 'dsh'" in ps assert "$SkillsAgentExclusions = @('claude-desktop', 'dsh')" in ps assert "'openclaw', 'dsh'" in ps +assert "$skillsArgs += @('-a', 'universal', '-s', 'agentkey')" in ps PY } @@ -42,11 +44,38 @@ SH 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 -F 'skills add chainbase-labs/agentkey -g -a universal -s agentkey -y' "$log" ! grep -E 'skills add .* -a .*dsh|skills add .* -a dsh' "$log" grep -F '@agentkey/cli --auth-login --only dsh' "$log" } +@test "--only dsh,codex keeps the explicit codex skill target" { + home="$BATS_TEST_TMPDIR/mixed-home" + work="$BATS_TEST_TMPDIR/mixed-work" + fakebin="$BATS_TEST_TMPDIR/mixed-bin" + mkdir -p "$home" "$work" "$fakebin" + log="$BATS_TEST_TMPDIR/mixed-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,codex --no-telemetry' bash "$work" "$REPO_ROOT/scripts/install.sh" + + [ "$status" -eq 0 ] + grep -F 'skills add chainbase-labs/agentkey -g -a codex -y' "$log" + ! grep -F 'skills add chainbase-labs/agentkey -g -a universal' "$log" + ! grep -E 'skills add .* -a .*dsh|skills add .* -a dsh' "$log" + grep -F '@agentkey/cli --auth-login --only dsh,codex' "$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" @@ -277,7 +306,7 @@ 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 skills add chainbase-labs/agentkey -g -a universal -s agentkey -y", "npx -y @agentkey/cli --auth-login --only dsh", "cordis.patch.yml", "@deepseek-ai/dsh-mcp-client",