mirror of
https://github.com/chainbase-labs/Agentkey.git
synced 2026-09-20 14:20:23 +08:00
83363014c4
## Summary - Drive both `npx skills add -a` and `@agentkey/mcp --auth-login --only` from a single detected-agent list. MCP registration now follows the same per-host auto-detection that skill install already does, expanding MCP auto-registration from 3 clients to **16**. - Fix the longstanding `claude-code` marker bug: it included Claude Desktop's config dir, causing skills CLI to target a nonexistent Claude Code on Desktop-only machines. `claude-desktop` is now its own id (MCP-only) — passed to `--auth-login --only` but never to `skills add`. - Detect Claude Desktop via `/Applications/Claude.app` / `%LOCALAPPDATA%\AnthropicClaude` so "installed but never launched" still registers (Linux still requires the config dir). - New `scripts/dev-smoke.sh` — sandboxed 4-phase regression suite, 42 assertions, ~10s, never touches real `$HOME`. Run before any PR touching install/uninstall scripts. ## Depends on [chainbase-labs/AgentKey-Server#9](https://github.com/chainbase-labs/AgentKey-Server/pull/9) — adds `--only <ids>` to `@agentkey/mcp --auth-login`. Older CLI versions silently ignore the flag, so this PR is forward-compatible either way. ## Uninstaller (the bigger gap before this) The previous uninstaller only cleaned 3 config paths and only knew the `mcpServers.<name>` JSON shape. With 13 new agents using 4 different schema dialects, that left AgentKey configured everywhere after uninstall. - Expanded MCP cleanup to **14 JSON paths + codex TOML**, covering all 16 auto-registered agents - Schema-agnostic JSON scrub: walks the tree and drops dict keys whose name EXACTLY matches our server names. Handles `mcpServers.<name>` / `mcp.<name>` / `amp.mcpServers.<name>` / `projects.X.mcpServers.<name>` in one pass - Codex TOML splice via awk / PowerShell (no parser dep) — drops `[mcp_servers.agentkey]` + legacy quoted block, preserves sibling sections - `droid mcp remove` + `openclaw mcp unset` for CLI-registered agents - **Exact-match** server names (not substring) so user keys like `agentkey-helper` are preserved (regression test included in dev-smoke) ## Bugs fixed during review | Where | Bug | |---|---| | install.sh:487 | Unbound `$TARGETS` variable (renamed during refactor) — `set -u` would have made this fatal | | install.ps1 | `$SkillTargets.Count` used where `$AllTargets.Count` was meant — diverged from install.sh behavior | | install.sh | `--only claude-desktop` ran `skills add -a` with no filter, defeating the user's `--only` intent. Now correctly skips the skill step | | install.sh helpers | Leaked-scope loop vars (`_ids`, `_id`) — declared `local -a` | ## Test plan - [x] `scripts/dev-smoke.sh` — 42 passing / 0 failing (Phase 1 unit tests + Phase 2 installer + Phase 3 writer schemas + Phase 4 uninstaller w/ false-positive guard) - [x] `bash -n` clean for install.sh + uninstall.sh - [x] `--list-agents` correctly lists `claude-desktop` as a separate id - [x] `--only claude-desktop --skip-mcp --yes` walks the new "MCP-only, skip skill" branch - [x] Auto-detect path tested via `bash -x` trace under `set -u` (no unbound-variable explosion) - [x] Uninstaller decoy fixtures: `agentkey-helper`, `other-svr`, `[mcp_servers.other]`, `[unrelated_section]` all preserved after scrub - [ ] Windows: `install.ps1` / `uninstall.ps1` syntax-checked but not runtime-tested (no Windows box handy — happy to test if reviewer has one) --------- Co-authored-by: Bruce <bruce@checkabc.me>
486 lines
20 KiB
Bash
Executable File
486 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# dev-smoke.sh — sandboxed regression suite for installer + uninstaller + MCP
|
||
# writer strategies. Nothing in this script touches your real $HOME — every
|
||
# phase creates its own mktemp -d sandbox and tears it down on exit.
|
||
#
|
||
# Run before opening a PR that changes:
|
||
# - scripts/install.sh / install.ps1
|
||
# - scripts/uninstall.sh / uninstall.ps1
|
||
# - AgentKey-Server/cli/src/lib/mcp-clients.ts
|
||
# - The AGENT_REGISTRY contract in general
|
||
#
|
||
# Usage:
|
||
# scripts/dev-smoke.sh # run all phases
|
||
# scripts/dev-smoke.sh 1 # run only phase 1
|
||
# scripts/dev-smoke.sh 2 4 # run phases 2 and 4
|
||
# AGENTKEY_CLI_SRC=/path/to/cli scripts/dev-smoke.sh # override
|
||
#
|
||
# Phases:
|
||
# 1. Unit tests — npm test in cli/ (registry + skill-meta)
|
||
# 2. Installer sandbox — --list-agents, --only edge cases (no real npx work)
|
||
# 3. Writers sandbox — call every AGENT_REGISTRY writer; assert schemas
|
||
# 4. Uninstaller sandbox — scrub fakes; assert decoys preserved
|
||
|
||
# Keep `-e` off so one failing assertion does not stop the smoke run before the
|
||
# remaining phases report their own failures.
|
||
set -uo pipefail
|
||
|
||
# ── Locate repos ──────────────────────────────────────────────────────────
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
SKILL_REPO="$(dirname "$SCRIPT_DIR")"
|
||
|
||
# Find the sibling AgentKey-Server/cli repo (the npm-published @agentkey/cli
|
||
# source). Try in order:
|
||
# 1. $AGENTKEY_CLI_SRC env var (manual override)
|
||
# 2. Direct sibling of skill repo (main worktree case)
|
||
# 3. Climb up from a git worktree (3 levels up from .claude/worktrees/<name>)
|
||
# 4. Fall back to a generic sibling path so the error message is actionable
|
||
_find_cli_src() {
|
||
local cand
|
||
for cand in \
|
||
"${AGENTKEY_CLI_SRC:-}" \
|
||
"$SKILL_REPO/../AgentKey-Server/cli" \
|
||
"$SKILL_REPO/../../../../AgentKey-Server/cli"
|
||
do
|
||
[ -z "$cand" ] && continue
|
||
if [ -d "$cand" ] && [ -f "$cand/package.json" ]; then
|
||
(cd "$cand" && pwd); return
|
||
fi
|
||
done
|
||
# Nothing found — return the first candidate so the error message
|
||
# tells the user what we looked for.
|
||
printf '%s\n' "${AGENTKEY_CLI_SRC:-$SKILL_REPO/../AgentKey-Server/cli}"
|
||
}
|
||
CLI_SRC="$(_find_cli_src)"
|
||
|
||
# ── UI ────────────────────────────────────────────────────────────────────
|
||
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
||
GREEN=$'\033[38;2;0;220;150m'; RED=$'\033[38;2;230;57;70m'
|
||
YELLOW=$'\033[38;2;255;176;32m'; CYAN=$'\033[38;2;0;200;180m'
|
||
DIM=$'\033[38;2;110;118;132m'; BOLD=$'\033[1m'; NC=$'\033[0m'
|
||
else
|
||
GREEN=''; RED=''; YELLOW=''; CYAN=''; DIM=''; BOLD=''; NC=''
|
||
fi
|
||
|
||
PASS=0; FAIL=0; SKIP=0
|
||
FAIL_REASONS=()
|
||
|
||
ok() { PASS=$((PASS+1)); printf " ${GREEN}✓${NC} %s\n" "$*"; }
|
||
fail() { FAIL=$((FAIL+1)); FAIL_REASONS+=("$*"); printf " ${RED}✗${NC} %s\n" "$*"; }
|
||
skip() { SKIP=$((SKIP+1)); printf " ${DIM}-${NC} %s\n" "$*"; }
|
||
info() { printf " ${DIM}›${NC} %s\n" "$*"; }
|
||
phase() { printf "\n${CYAN}${BOLD}── %s ──${NC}\n" "$*"; }
|
||
|
||
assert_contains() {
|
||
local name="$1" needle="$2" haystack="$3"
|
||
if printf '%s' "$haystack" | grep -qF -- "$needle"; then
|
||
ok "$name"
|
||
else
|
||
fail "$name (expected to contain '$needle')"
|
||
fi
|
||
}
|
||
|
||
assert_not_contains() {
|
||
local name="$1" needle="$2" haystack="$3"
|
||
if printf '%s' "$haystack" | grep -qF -- "$needle"; then
|
||
fail "$name (must NOT contain '$needle')"
|
||
else
|
||
ok "$name"
|
||
fi
|
||
}
|
||
|
||
assert_file_has() {
|
||
local name="$1" path="$2" needle="$3"
|
||
if [ -f "$path" ] && grep -qF -- "$needle" "$path"; then
|
||
ok "$name"
|
||
else
|
||
fail "$name (file $path missing or doesn't contain '$needle')"
|
||
fi
|
||
}
|
||
|
||
# Sandbox helper: each phase calls `sandbox_init` to get a fresh tmpdir and
|
||
# `sandbox_clean` to tear it down. Trapped so Ctrl-C still cleans up.
|
||
SANDBOX=""
|
||
sandbox_init() {
|
||
SANDBOX="$(mktemp -d -t agentkey-smoke.XXXXXX)"
|
||
info "sandbox: $SANDBOX"
|
||
}
|
||
sandbox_clean() {
|
||
[ -n "$SANDBOX" ] && [ -d "$SANDBOX" ] && rm -rf "$SANDBOX"
|
||
SANDBOX=""
|
||
}
|
||
trap 'sandbox_clean' EXIT INT TERM
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
# Phase 1: Unit tests
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
phase_1() {
|
||
phase "Phase 1: Unit tests (cli)"
|
||
if [ ! -d "$CLI_SRC" ]; then
|
||
skip "@agentkey/cli source not found at $CLI_SRC — set AGENTKEY_CLI_SRC to override"
|
||
return
|
||
fi
|
||
|
||
# Subshells under nvm often fall back to an older default Node, even when
|
||
# the parent shell has a current one. `node:test` (used by tsx --test)
|
||
# needs Node 18+. Detect explicitly and skip with a hint rather than
|
||
# surfacing a cryptic "bad option: --test" error.
|
||
local node_major
|
||
node_major="$(cd "$CLI_SRC" && node --version 2>/dev/null | sed -nE 's/^v([0-9]+).*/\1/p')"
|
||
if [ -z "$node_major" ]; then
|
||
skip "node not found in cli subshell"
|
||
return
|
||
fi
|
||
if [ "$node_major" -lt 18 ] 2>/dev/null; then
|
||
skip "subshell Node is v$node_major (need >=18). Try: \`nvm alias default 20\` or \`nvm use 20\` then re-run."
|
||
return
|
||
fi
|
||
|
||
info "running: cd $CLI_SRC && npm test (node v$node_major)"
|
||
local out
|
||
if out="$(cd "$CLI_SRC" && npm test 2>&1)"; then
|
||
local count
|
||
count="$(printf '%s' "$out" | sed -nE 's/.*tests ([0-9]+).*/\1/p' | head -1)"
|
||
if printf '%s' "$out" | grep -qE 'fail (0|0$)'; then
|
||
ok "all $count tests pass"
|
||
else
|
||
fail "npm test reported failures"
|
||
printf '%s\n' "$out" | tail -10
|
||
fi
|
||
else
|
||
fail "npm test exited non-zero"
|
||
if printf '%s' "$out" | grep -q 'bad option: --test'; then
|
||
info "hint: a child process picked up an older Node. Check \`which node\` inside $CLI_SRC."
|
||
fi
|
||
printf '%s\n' "$out" | tail -15
|
||
fi
|
||
}
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
# Phase 2: Installer sandbox — exercise install.sh logic without network
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
phase_2() {
|
||
phase "Phase 2: Installer sandbox"
|
||
sandbox_init
|
||
|
||
# Seed enough markers so detect_agents() finds a varied set.
|
||
mkdir -p "$SANDBOX/.cursor" \
|
||
"$SANDBOX/.codex" \
|
||
"$SANDBOX/.gemini" \
|
||
"$SANDBOX/.qwen" \
|
||
"$SANDBOX/Library/Application Support/Claude"
|
||
touch "$SANDBOX/.claude.json"
|
||
|
||
# Test 1: --list-agents output shape
|
||
info "test: --list-agents"
|
||
local listed
|
||
listed="$(HOME="$SANDBOX" bash "$SKILL_REPO/scripts/install.sh" --list-agents 2>&1)"
|
||
assert_contains "lists claude-code" "claude-code" "$listed"
|
||
assert_contains "lists claude-desktop" "claude-desktop" "$listed"
|
||
assert_contains "lists cursor" "cursor" "$listed"
|
||
assert_contains "lists codex" "codex" "$listed"
|
||
assert_contains "lists gemini-cli" "gemini-cli" "$listed"
|
||
assert_contains "lists qwen-code" "qwen-code" "$listed"
|
||
|
||
# Test 2: --only claude-desktop must skip the skill step (MCP-only edge case)
|
||
info "test: --only claude-desktop --skip-mcp --yes (edge case)"
|
||
local out
|
||
out="$(HOME="$SANDBOX" bash "$SKILL_REPO/scripts/install.sh" \
|
||
--only claude-desktop --skip-mcp --yes 2>&1)"
|
||
assert_contains "skill step skipped for MCP-only ids" \
|
||
"MCP-only (no skill install path)" "$out"
|
||
assert_contains "MCP step skipped via --skip-mcp" "Skipped (--skip-mcp)" "$out"
|
||
assert_not_contains "no skills add invocation" "Running: npx" "$out"
|
||
|
||
# Test 3: auto-detect path doesn't crash under set -u (the $TARGETS bug repro)
|
||
info "test: auto-detect with --skip-skill --skip-mcp --yes"
|
||
out="$(HOME="$SANDBOX" bash "$SKILL_REPO/scripts/install.sh" \
|
||
--skip-skill --skip-mcp --yes 2>&1)"
|
||
assert_contains "auto-detect runs" "Detected agents on this host" "$out"
|
||
assert_contains "claude-desktop detected" "claude-desktop" "$out"
|
||
assert_not_contains "no unbound var error" "unbound variable" "$out"
|
||
|
||
sandbox_clean
|
||
}
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
# Phase 3: Writers sandbox — exercise every AGENT_REGISTRY writer
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
phase_3() {
|
||
phase "Phase 3: MCP writer schemas"
|
||
if [ ! -d "$CLI_SRC" ]; then
|
||
skip "MCP server not at $CLI_SRC — skipping writer tests"
|
||
return
|
||
fi
|
||
|
||
# Ensure dist/ is fresh.
|
||
info "rebuilding cli dist/..."
|
||
if ! (cd "$CLI_SRC" && npm run build >/dev/null 2>&1); then
|
||
fail "npm run build failed"
|
||
return
|
||
fi
|
||
|
||
sandbox_init
|
||
|
||
# Run every file-write strategy (skip CLI-only ones since those would
|
||
# need real `droid` / `openclaw` binaries on PATH).
|
||
info "running writers for every JSON / TOML agent..."
|
||
local runner="$SANDBOX/runner.mjs"
|
||
cat > "$runner" <<'EOF'
|
||
import { pathToFileURL } from "node:url";
|
||
|
||
const cliModule = process.env.AGENTKEY_CLI_MODULE;
|
||
if (!cliModule) {
|
||
throw new Error("AGENTKEY_CLI_MODULE is required");
|
||
}
|
||
const { AGENT_REGISTRY, writeAgentConfig } = await import(pathToFileURL(cliModule).href);
|
||
|
||
const SKIP_IDS = new Set(["claude-code", "droid", "openclaw"]);
|
||
const ctx = { apiKey: "ak_test_smoke", baseUrl: "https://api.agentkey.app" };
|
||
|
||
let written = 0, failed = 0;
|
||
for (const spec of AGENT_REGISTRY) {
|
||
if (SKIP_IDS.has(spec.id)) { console.log("SKIP " + spec.id); continue; }
|
||
const r = await writeAgentConfig(spec.id, ctx);
|
||
if (r.ok) { written++; console.log("OK " + spec.id + " -> " + r.detail); }
|
||
else { failed++; console.log("ERR " + spec.id + " -> " + r.error); }
|
||
}
|
||
console.log("---");
|
||
console.log("written=" + written + " failed=" + failed);
|
||
EOF
|
||
local writer_out
|
||
writer_out="$(AGENTKEY_CLI_MODULE="$CLI_SRC/dist/lib/mcp-clients.js" HOME="$SANDBOX" node "$runner" 2>&1)"
|
||
if printf '%s' "$writer_out" | grep -q '^ERR '; then
|
||
fail "some writers reported errors:"
|
||
printf '%s\n' "$writer_out" | grep '^ERR '
|
||
else
|
||
ok "every writer reported success"
|
||
fi
|
||
|
||
# Schema assertions on the files that landed.
|
||
info "asserting per-agent schemas..."
|
||
|
||
# Claude Desktop / Cursor / Gemini / Windsurf / Warp / Qwen / iFlow /
|
||
# Kimi / Kiro all use the same {mcpServers: {agentkey: {command, args, env}}}
|
||
# shape — spot-check a couple.
|
||
assert_file_has "claude-desktop mcpServers.agentkey" \
|
||
"$SANDBOX/Library/Application Support/Claude/claude_desktop_config.json" \
|
||
'"agentkey"'
|
||
assert_file_has "cursor command=npx" \
|
||
"$SANDBOX/.cursor/mcp.json" '"command": "npx"'
|
||
assert_file_has "gemini-cli env API_KEY" \
|
||
"$SANDBOX/.gemini/settings.json" '"AGENTKEY_API_KEY": "ak_test_smoke"'
|
||
|
||
# OpenCode — the schema differs: array command + 'environment' (not 'env'),
|
||
# under top-level 'mcp' (not 'mcpServers').
|
||
local oc="$SANDBOX/.config/opencode/opencode.json"
|
||
assert_file_has "opencode uses 'mcp' key" "$oc" '"mcp":'
|
||
assert_file_has "opencode command is array" "$oc" '"command": ['
|
||
assert_file_has "opencode uses 'environment' key" "$oc" '"environment":'
|
||
if grep -q '"mcpServers"' "$oc" 2>/dev/null; then
|
||
fail "opencode must NOT use mcpServers key"
|
||
else
|
||
ok "opencode does not leak mcpServers"
|
||
fi
|
||
|
||
# Amp — flat dotted key, not nested.
|
||
local amp="$SANDBOX/.config/amp/settings.json"
|
||
assert_file_has "amp uses flat 'amp.mcpServers' key" "$amp" '"amp.mcpServers":'
|
||
|
||
# Crush — mcp.<name> with type:stdio.
|
||
local crush="$SANDBOX/.config/crush/crush.json"
|
||
assert_file_has "crush uses 'mcp' key" "$crush" '"mcp":'
|
||
assert_file_has "crush has type:stdio" "$crush" '"type": "stdio"'
|
||
|
||
# Codex — TOML.
|
||
local codex="$SANDBOX/.codex/config.toml"
|
||
assert_file_has "codex header [mcp_servers.agentkey]" "$codex" '[mcp_servers.agentkey]'
|
||
assert_file_has "codex env table" "$codex" 'AGENTKEY_API_KEY = "ak_test_smoke"'
|
||
|
||
# Idempotency check — re-run all writers and make sure nothing duplicates.
|
||
info "idempotency: re-running every writer..."
|
||
AGENTKEY_CLI_MODULE="$CLI_SRC/dist/lib/mcp-clients.js" HOME="$SANDBOX" node "$runner" >/dev/null 2>&1
|
||
local agentkey_count
|
||
agentkey_count="$(grep -c '\[mcp_servers\.agentkey\]' "$codex" 2>/dev/null)"
|
||
if [ "$agentkey_count" = "1" ]; then
|
||
ok "codex stanza appears exactly once after double-write"
|
||
else
|
||
fail "codex agentkey stanza count after double-write: $agentkey_count (expected 1)"
|
||
fi
|
||
|
||
sandbox_clean
|
||
}
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
# Phase 4: Uninstaller sandbox — assert decoys are preserved
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
phase_4() {
|
||
phase "Phase 4: Uninstaller sandbox"
|
||
sandbox_init
|
||
|
||
# ── Decoy fixtures ────────────────────────────────────────────────────
|
||
# Each fixture mixes a legit agentkey entry with one we MUST NOT touch.
|
||
mkdir -p "$SANDBOX/.cursor" \
|
||
"$SANDBOX/.config/opencode" \
|
||
"$SANDBOX/.config/amp" \
|
||
"$SANDBOX/.config/crush" \
|
||
"$SANDBOX/.codex" \
|
||
"$SANDBOX/.gemini"
|
||
|
||
# Cursor: standard mcpServers + a decoy user key containing "agentkey".
|
||
cat > "$SANDBOX/.cursor/mcp.json" <<'EOF'
|
||
{
|
||
"mcpServers": {
|
||
"agentkey": { "command": "npx", "args": ["-y", "@agentkey/cli"] },
|
||
"agentkey-helper": { "command": "x" },
|
||
"other-svr": { "command": "y" }
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# OpenCode: mcp.<name> dialect.
|
||
cat > "$SANDBOX/.config/opencode/opencode.json" <<'EOF'
|
||
{
|
||
"$schema": "https://opencode.ai/config.json",
|
||
"mcp": {
|
||
"agentkey": { "type": "local", "command": ["npx", "-y", "@agentkey/cli"] },
|
||
"user-svr": { "type": "local", "command": ["x"] }
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# Amp: flat dotted key.
|
||
cat > "$SANDBOX/.config/amp/settings.json" <<'EOF'
|
||
{
|
||
"amp.mcpServers": {
|
||
"agentkey": { "command": "npx" },
|
||
"my-svr": { "command": "y" }
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# Crush: mcp.<name> + type:stdio.
|
||
cat > "$SANDBOX/.config/crush/crush.json" <<'EOF'
|
||
{
|
||
"mcp": {
|
||
"agentkey": { "type": "stdio", "command": "npx" },
|
||
"keep": { "type": "stdio", "command": "z" }
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# Codex TOML: agentkey block + legacy quoted block + unrelated sections.
|
||
cat > "$SANDBOX/.codex/config.toml" <<'EOF'
|
||
model = "gpt-5.1"
|
||
|
||
[mcp_servers.other]
|
||
command = "other"
|
||
args = []
|
||
|
||
[mcp_servers.agentkey]
|
||
command = "npx"
|
||
args = ["-y", "@agentkey/cli"]
|
||
env = { AGENTKEY_API_KEY = "ak_xxx" }
|
||
|
||
[mcp_servers."agentkey.app AgentKey"]
|
||
command = "legacy"
|
||
|
||
[unrelated_section]
|
||
key = "val"
|
||
EOF
|
||
|
||
# Gemini: claude-code per-project shape simulator (nested).
|
||
cat > "$SANDBOX/.claude.json" <<'EOF'
|
||
{
|
||
"mcpServers": { "agentkey": { "command": "npx" } },
|
||
"projects": {
|
||
"/path/a": {
|
||
"mcpServers": {
|
||
"agentkey": { "command": "npx" },
|
||
"user-server": { "command": "keep" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# ── Run the uninstaller in the sandbox ────────────────────────────────
|
||
info "running uninstall.sh --skip-skill-remove --force-in-repo"
|
||
HOME="$SANDBOX" bash "$SKILL_REPO/scripts/uninstall.sh" \
|
||
--skip-skill-remove --force-in-repo >/dev/null 2>&1 || true
|
||
|
||
# ── Assertions: agentkey gone, decoys preserved ───────────────────────
|
||
info "checking: agentkey entries scrubbed"
|
||
assert_not_contains "cursor: agentkey removed" '"agentkey":' "$(cat "$SANDBOX/.cursor/mcp.json")"
|
||
assert_not_contains "opencode: agentkey removed" '"agentkey":' "$(cat "$SANDBOX/.config/opencode/opencode.json")"
|
||
assert_not_contains "amp: agentkey removed" '"agentkey":' "$(cat "$SANDBOX/.config/amp/settings.json")"
|
||
assert_not_contains "crush: agentkey removed" '"agentkey":' "$(cat "$SANDBOX/.config/crush/crush.json")"
|
||
assert_not_contains "codex: agentkey block removed" \
|
||
'[mcp_servers.agentkey]' "$(cat "$SANDBOX/.codex/config.toml")"
|
||
assert_not_contains "codex: legacy block removed" \
|
||
'agentkey.app AgentKey' "$(cat "$SANDBOX/.codex/config.toml")"
|
||
|
||
info "checking: decoy user entries preserved"
|
||
assert_contains "cursor: agentkey-helper kept (false-positive guard)" \
|
||
"agentkey-helper" "$(cat "$SANDBOX/.cursor/mcp.json")"
|
||
assert_contains "cursor: other-svr kept" \
|
||
"other-svr" "$(cat "$SANDBOX/.cursor/mcp.json")"
|
||
assert_contains "opencode: user-svr kept" \
|
||
"user-svr" "$(cat "$SANDBOX/.config/opencode/opencode.json")"
|
||
assert_contains "amp: my-svr kept" \
|
||
"my-svr" "$(cat "$SANDBOX/.config/amp/settings.json")"
|
||
assert_contains "crush: keep kept" \
|
||
'"keep"' "$(cat "$SANDBOX/.config/crush/crush.json")"
|
||
assert_contains "codex: [mcp_servers.other] kept" \
|
||
"[mcp_servers.other]" "$(cat "$SANDBOX/.codex/config.toml")"
|
||
assert_contains "codex: [unrelated_section] kept" \
|
||
"[unrelated_section]" "$(cat "$SANDBOX/.codex/config.toml")"
|
||
assert_contains "codex: top-level model= kept" \
|
||
'model = "gpt-5.1"' "$(cat "$SANDBOX/.codex/config.toml")"
|
||
assert_contains "claude.json: per-project user-server kept" \
|
||
"user-server" "$(cat "$SANDBOX/.claude.json")"
|
||
|
||
sandbox_clean
|
||
}
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
# Driver
|
||
# ──────────────────────────────────────────────────────────────────────────
|
||
main() {
|
||
local selected=("$@")
|
||
if [ ${#selected[@]} -eq 0 ]; then
|
||
selected=(1 2 3 4)
|
||
fi
|
||
|
||
printf "${BOLD}AgentKey dev-smoke${NC}\n"
|
||
printf " ${DIM}skill repo:${NC} %s\n" "$SKILL_REPO"
|
||
printf " ${DIM}cli:${NC} %s\n" "$CLI_SRC"
|
||
|
||
for n in "${selected[@]}"; do
|
||
case "$n" in
|
||
1) phase_1 ;;
|
||
2) phase_2 ;;
|
||
3) phase_3 ;;
|
||
4) phase_4 ;;
|
||
*) echo "Unknown phase: $n (valid: 1-4)" >&2 ;;
|
||
esac
|
||
done
|
||
|
||
# ── Summary ───────────────────────────────────────────────────────────
|
||
printf "\n${BOLD}Summary${NC}\n"
|
||
printf " ${GREEN}pass:${NC} %d ${RED}fail:${NC} %d ${DIM}skip:${NC} %d\n" \
|
||
"$PASS" "$FAIL" "$SKIP"
|
||
if [ "$FAIL" -gt 0 ]; then
|
||
printf "\n${RED}${BOLD}Failures:${NC}\n"
|
||
for r in "${FAIL_REASONS[@]}"; do
|
||
printf " ${RED}-${NC} %s\n" "$r"
|
||
done
|
||
exit 1
|
||
fi
|
||
printf "\n${GREEN}${BOLD}All green.${NC} Ready to PR.\n"
|
||
exit 0
|
||
}
|
||
|
||
main "$@"
|