Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <ids>` 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 <ids>` 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
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/scripts-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <ids>` 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 <ids>` 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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
2 changes: 1 addition & 1 deletion docs/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
10 changes: 6 additions & 4 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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'
}
Expand Down
6 changes: 4 additions & 2 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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: <agent-id>|<marker>[,<marker>...]
# marker types: cmd:foo — `command -v foo`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion skills/agentkey/references/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
49 changes: 49 additions & 0 deletions tests/dsh-install-target.ps1
Original file line number Diff line number Diff line change
@@ -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'
33 changes: 31 additions & 2 deletions tests/dsh-plugin.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
Loading