fix(installer): avoid PromptScript failure for DSH (#97)

## Summary

- install the AgentKey Skill through the Skills CLI universal target for
DSH-only installs
- avoid passing either dsh or the unsupported global PromptScript target
while preserving explicit mixed-agent targets
- update Bash, PowerShell, English/Chinese setup guidance, retry
instructions, and release-asset source docs
- add Bash mixed-target coverage and a Windows PowerShell invocation
regression

## Root cause

skills@1.5.23 -g -y adds PromptScript to its universal target set even
though PromptScript has no global install directory. The canonical
AgentKey Skill still installs, but the command prints Failed to install
1. Explicitly targeting universal writes the same
~/.agents/skills/agentkey copy without selecting PromptScript.

## Validation

- bats tests/ — 42/42
- bash -n scripts/install.sh
- bash -n scripts/uninstall.sh
- bash -n scripts/build-release-assets.sh
- scripts/build-release-assets.sh with an isolated output directory
- workflow YAML parse
- git diff --check
- Windows behavior regression added to the windows-latest Scripts tests
job

## Deployment follow-up

Merging/releasing this PR does not automatically update
agentkey.app/install.sh. After release, publish the current
Bash/PowerShell install and uninstall scripts to the website origin and
purge the CDN; the live endpoints were serving stale pre-DSH content
during verification.

Co-authored-by: Allen <0xfatdog@gmail.com>
This commit is contained in:
zzAllenn
2026-08-25 00:55:26 +08:00
committed by GitHub
parent def77066ef
commit 8e93c67a33
10 changed files with 101 additions and 16 deletions
+2 -2
View File
@@ -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 <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
+4 -1
View File
@@ -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
+2 -2
View File
@@ -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 <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)
+1 -1
View File
@@ -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
```
+1 -1
View File
@@ -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
```
+6 -4
View File
@@ -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'
}
+4 -2
View File
@@ -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`
@@ -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
+1 -1
View File
@@ -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
```
+49
View File
@@ -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'
+31 -2
View File
@@ -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",