mirror of
https://github.com/chainbase-labs/Agentkey.git
synced 2026-09-20 14:20:23 +08:00
feat: server-beacon skill-update path for non-Bash clients (#39)
## Summary
Fixes the silent-update-failure mode where Claude Desktop (and any MCP
client without a Bash tool) gets stuck on whatever skill version shipped
at first install. On this developer's Desktop the skill had been frozen
at `0.1.2` since April — no upgrade ever fired.
Root cause is structural: SKILL.md Step 0's update check uses an inline
` ```bash ``` ` block. Claude Code executes it; Desktop reads it as
documentation. So the entire upgrade flow is dead code on Desktop. This
PR routes the version check through the MCP server instead (always-on,
available to every client), and tightens a couple of correctness bugs in
the existing install/uninstall path while we're here.
Companion PR: chainbase-labs/AgentKey-Server (server-side
`agentkey_skill_meta` tool).
## What's in here
1. **Protocol** (`protocol/skill-meta-v1.md` +
`skill-meta-v1.schema.json` + 4 fixtures) — versioned,
additive-evolution wire format for an MCP meta tool that returns
`{skill_version_latest, client_detected, update_command, update_doc_url,
…}`. Spec lives in this repo (single source of truth); server vendors a
copy and CI on both sides diffs them.
2. **SKILL.md** — Step 0 now has 0.A (beacon, cross-client) → 0.B
(inline bash, Code-only compat) → 0.C (MCP tool sanity check). Step B
branches every persistence option on whether Bash is available, with
explicit no-Bash fallback text that tells the user what didn't get saved
and the exact terminal command to persist it manually. Step C points the
non-shell fallback at GitHub Releases (we don't have a docs site).
3. **install/uninstall scripts** — `npx skills remove
chainbase-labs/agentkey` was the wrong invocation: the CLI takes the
skill name (`agentkey`), exits 0 on no-match, and made the uninstaller
falsely report success. Same class of silent-success bug in `install.sh`
when `git clone` fails mid-run. Both fixed; added post-install
filesystem verification.
4. **README / README_zh** — accurate per-client update story, including
a one-time bootstrap command for users currently stuck on a pre-1.4.0
skill on Desktop.
5. **CI** (`protocol-validate.yml`) — every fixture validates against
the schema, schema rejects 4 known-bad payloads (regression guard), spec
doc references every fixture (forces docs ↔ artifact sync).
6. **`docs/SERVER-IMPLEMENTATION.md`** — handoff doc for the server PR.
## How verified
- 4/4 fixtures pass schema; 4/4 bad payloads correctly rejected
- All cross-references in spec doc resolve
- `verify-version-sync` awk still extracts `1.3.0` from SKILL.md
frontmatter
- Companion server PR exercises the actual MCP handshake (initialize +
tools/list + tools/call); response is valid v1 JSON
- Real GitHub Releases fetch + ETag caching works on the server side
## Test plan
- [ ] CI green (`protocol-validate.yml` and `verify-version-sync.yml`
both pass)
- [ ] Companion server PR merged + new `@agentkey/mcp` published
- [ ] Release-please cuts `v1.4.0` from this branch
- [ ] On Claude Code: existing inline-bash Step 0 still fires for users
on `v1.3.x`; they get prompted to update normally
- [ ] On Claude Desktop with a pre-1.4.0 skill: user runs the README
bootstrap command once to land `v1.4.0`; from that point on, every
subsequent version is auto-discovered via the meta tool
- [ ] On Cursor / Codex: meta tool returns the `npx skills update -g
agentkey` recipe; user upgrades via shell
## Notes for the reviewer
- This is **additive**: Claude Code's existing inline-bash path is
unchanged, so no regression risk there. The protocol's
`protocol_version: 1` + immortal `update_doc_url` fallback make future
v2 servers safely degradable for v1 skills.
- Claude Desktop deliberately has no `update_command` recipe yet —
Desktop installs skills into a sandboxed `~/Library/Application
Support/Claude/local-agent-mode-sessions/skills-plugin/<UUID>/...` path
that no external CLI can reach, and we don't have a first-party
installer script. The skill rule's "no command → point at GitHub
Releases" fallback handles this until one exists. Adding a Desktop
recipe later is a non-breaking change (one row in the server's `RECIPES`
map).
This commit is contained in:
@@ -295,6 +295,33 @@ if (-not $SkipSkill) {
|
||||
|
||||
& npx @skillsArgs
|
||||
if ($LASTEXITCODE -ne 0) { Die "Failed to install skill via 'skills' CLI" }
|
||||
# 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.
|
||||
$userHome = [Environment]::GetFolderPath('UserProfile')
|
||||
$candidatePaths = @(
|
||||
'.agents\skills\agentkey',
|
||||
'.claude\skills\agentkey',
|
||||
'.cursor\skills\agentkey',
|
||||
'.codex\skills\agentkey',
|
||||
'.gemini\skills\agentkey',
|
||||
'.opencode\skills\agentkey',
|
||||
'.openclaw\skills\agentkey',
|
||||
'.qwen\skills\agentkey',
|
||||
'.iflow\skills\agentkey',
|
||||
'.windsurf\skills\agentkey',
|
||||
'.warp\skills\agentkey'
|
||||
)
|
||||
$agentkeyFound = $false
|
||||
foreach ($rel in $candidatePaths) {
|
||||
if (Test-Path (Join-Path $userHome (Join-Path $rel 'SKILL.md'))) {
|
||||
$agentkeyFound = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
Write-Ok 'Skill installed'
|
||||
} else {
|
||||
Write-Step '2. Install the AgentKey skill'
|
||||
|
||||
@@ -411,6 +411,27 @@ 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" \
|
||||
"$HOME/.claude/skills/agentkey" \
|
||||
"$HOME/.cursor/skills/agentkey" \
|
||||
"$HOME/.codex/skills/agentkey" \
|
||||
"$HOME/.gemini/skills/agentkey" \
|
||||
"$HOME/.opencode/skills/agentkey" \
|
||||
"$HOME/.openclaw/skills/agentkey" \
|
||||
"$HOME/.qwen/skills/agentkey" \
|
||||
"$HOME/.iflow/skills/agentkey" \
|
||||
"$HOME/.windsurf/skills/agentkey" \
|
||||
"$HOME/.warp/skills/agentkey"; do
|
||||
[ -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"
|
||||
fi
|
||||
ui_ok "Skill installed"
|
||||
else
|
||||
ui_step "2. Install the AgentKey skill"
|
||||
|
||||
+10
-5
@@ -75,14 +75,19 @@ if ($SkipSkillRemove) {
|
||||
Write-Skip 'Skipped (-SkipSkillRemove)'
|
||||
} elseif (-not (Get-Command npx -ErrorAction SilentlyContinue)) {
|
||||
Write-Warn2 "npx not found — skipping 'skills remove'"
|
||||
Write-Host ' Manual: npx skills remove chainbase-labs/agentkey -g' -ForegroundColor DarkGray
|
||||
Write-Host ' Manual: npx skills remove agentkey -g' -ForegroundColor DarkGray
|
||||
} else {
|
||||
Write-Info 'Running: npx -y skills remove chainbase-labs/agentkey -g -y'
|
||||
& npx -y skills remove chainbase-labs/agentkey -g -y 2>$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
# `skills remove` takes the **skill name** (`agentkey`), not the repo path.
|
||||
# The CLI also exits 0 when nothing matches, so we inspect stdout instead.
|
||||
Write-Info 'Running: npx -y skills remove agentkey -g -y'
|
||||
$removeOutput = (& npx -y skills remove agentkey -g -y 2>&1) -join "`n"
|
||||
if ($removeOutput -match 'Successfully removed') {
|
||||
Write-Ok 'Skill removed from detected agents'
|
||||
} elseif ($removeOutput -match 'No matching skills found') {
|
||||
Write-Skip "Not registered with 'skills' CLI (already removed or installed via plugin marketplace)"
|
||||
} else {
|
||||
Write-Warn2 "'skills remove' exited non-zero — some agents may still have skill files"
|
||||
Write-Warn2 "'skills remove' produced unexpected output — some agents may still have skill files"
|
||||
Write-Host ' Check manually: npx skills list -g' -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -76,14 +76,19 @@ step "1. Skill files"
|
||||
if $SKIP_SKILL_REMOVE; then
|
||||
skipped "Skipped (--skip-skill-remove)"
|
||||
elif ! command -v npx >/dev/null 2>&1; then
|
||||
warn "npx not found — skipping 'skills remove' (manual: npx skills remove chainbase-labs/agentkey -g)"
|
||||
warn "npx not found — skipping 'skills remove' (manual: npx skills remove agentkey -g)"
|
||||
else
|
||||
info "Running: npx -y skills remove chainbase-labs/agentkey -g -y"
|
||||
if npx -y skills remove chainbase-labs/agentkey -g -y 2>/dev/null; then
|
||||
# `skills remove` takes the **skill name** (`agentkey`), not the repo path.
|
||||
# The CLI also exits 0 when nothing matches, so we inspect stdout instead.
|
||||
info "Running: npx -y skills remove agentkey -g -y"
|
||||
REMOVE_OUTPUT="$(npx -y skills remove agentkey -g -y 2>&1 || true)"
|
||||
if printf '%s\n' "$REMOVE_OUTPUT" | grep -q "Successfully removed"; then
|
||||
ok "Skill removed from detected agents"
|
||||
elif printf '%s\n' "$REMOVE_OUTPUT" | grep -q "No matching skills found"; then
|
||||
skipped "Not registered with 'skills' CLI (already removed or installed via plugin marketplace)"
|
||||
else
|
||||
warn "'skills remove' exited non-zero — some agents may still have skill files"
|
||||
warn "Check manually: npx skills list"
|
||||
warn "'skills remove' produced unexpected output — some agents may still have skill files"
|
||||
warn "Check manually: npx skills list -g"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user