Files
zzAllenn 97e3ab2fde feat: add DeepSeek Harness and Kimi MCP routing (#96)
## What changed

- teach the one-line macOS/Linux and Windows installers to detect DSH,
install the AgentKey skill globally, and invoke the DSH device-code MCP
writer without passing `dsh` to `skills add -a`
- remove only top-level AgentKey managed blocks during uninstall while
preserving marker-prefix collisions, YAML block scalars, malformed
blocks, and unrelated patches
- add a DSH-specific Skill setup branch explaining why DSH rc.7 must use
device-code Bearer authentication instead of generic MCP OAuth
- document the home-level patch, safe legacy migration, symlink-profile
behavior, HMR limits, tool-policy limits, and actual readiness checks in
English and Chinese
- route the Kimi plugin through the client-attributed
`https://api.agentkey.app/kimi/v1/mcp` alias while keeping its native
OAuth flow and the same AgentKey MCP surface
- add macOS/Linux Bats coverage, an attributed endpoint contract, and a
Windows PowerShell marker regression gate

## Why

DSH users need one global AgentKey integration across current and future
profiles without selecting an AgentKey preset. DSH 0.1.0-rc.7 cannot
complete the generic header-free MCP OAuth flow, so the public setup and
self-repair guidance must consistently route DSH through `@agentkey/cli
--auth-login --only dsh`.

Kimi uses a client-attributed MCP path so Server telemetry and routing
can identify the originating client without changing Kimi's OAuth or
tool behavior.

## User impact

After the Server and CLI dependency are released, DSH users can run the
normal one-line installer or the documented two-step flow. They verify
success by checking the `agentkey` Loader row and then calling
`find_tools`, `describe_tool`, and `execute_tool`; Mounted/active alone
is not treated as a successful MCP connection.

Kimi plugin users continue to authenticate with the existing native
OAuth command; only the remote path changes to the attributed alias.

## Validation

- `bats tests/` (41/41)
- `bash -n scripts/install.sh`
- `bash -n scripts/uninstall.sh`
- Windows marker regression is configured on `windows-latest`
- `git diff --check`

## Release dependency

- Server/CLI implementation:
https://github.com/chainbase-labs/AgentKey-Server/pull/241
- deploy and verify the Server routes first
- publish and canary `@agentkey/cli@1.0.4` second
- release these installers, Skill changes, Kimi route, and public
documentation last

This is a CLI-managed DSH MCP integration, not a native installable DSH
plugin.

---------

Co-authored-by: Allen <0xfatdog@gmail.com>
2026-08-23 04:04:57 +08:00

501 lines
21 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# AgentKey uninstaller for macOS and Linux
# Usage: curl -fsSL https://agentkey.app/uninstall.sh | bash
# curl -fsSL https://agentkey.app/uninstall.sh | bash -s -- --keep-marketplace
# curl -fsSL https://agentkey.app/uninstall.sh | bash -s -- --force-in-repo
#
# Cleans up everything install.sh (and the legacy two-command flow) ever wrote:
# 1. Skill files in every agent (via `skills remove` — fans across 40+ agents)
# 2. MCP server entries (Claude Code / Claude Desktop / Cursor configs)
# 3. Claude Code plugin + marketplace registrations (legacy plugin install path)
# 4. Plugin / marketplace / npx caches
# 5. Shell RC exports + CLAUDE.md sections (legacy)
# 6. MCP stdio log
set -euo pipefail
KEEP_MARKETPLACE=false
FORCE_IN_REPO=false
SKIP_SKILL_REMOVE=false
for arg in "$@"; do
case "$arg" in
--keep-marketplace) KEEP_MARKETPLACE=true ;;
--force-in-repo) FORCE_IN_REPO=true ;;
--skip-skill-remove) SKIP_SKILL_REMOVE=true ;;
-h|--help)
cat <<EOF
AgentKey uninstaller (macOS / Linux)
Usage:
curl -fsSL https://agentkey.app/uninstall.sh | bash [-s -- OPTIONS]
Options:
--keep-marketplace Keep the Claude Code plugin marketplace registration
--force-in-repo Allow running inside the AgentKey-Skill source repo
--skip-skill-remove Skip 'npx skills remove' (only clean configs/caches)
-h, --help Show this help
EOF
exit 0 ;;
esac
done
# ── Colors ────────────────────────────────────────────────────────────────
# Use $'...' so variables hold real ESC bytes (works in both printf and heredoc).
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
BOLD=$'\033[1m'; SUCCESS=$'\033[38;2;0;220;150m'; INFO=$'\033[38;2;136;146;176m'
WARN=$'\033[38;2;255;176;32m'; ERROR=$'\033[38;2;230;57;70m'
MUTED=$'\033[38;2;110;118;132m'; NC=$'\033[0m'
else
BOLD=''; SUCCESS=''; INFO=''; WARN=''; ERROR=''; MUTED=''; NC=''
fi
info() { printf " ${INFO}${NC} %s\n" "$*"; }
ok() { printf " ${SUCCESS}${NC} %s\n" "$*"; }
warn() { printf " ${WARN}!${NC} %s\n" "$*"; }
skipped() { printf " ${MUTED}-${NC} %s\n" "$*"; }
step() { printf "\n ${BOLD}%s${NC}\n" "$*"; }
# ── Safety rail ──────────────────────────────────────────────────────────
if [ -f ".claude-plugin/plugin.json" ] \
&& grep -q '"name"[[:space:]]*:[[:space:]]*"agentkey"' .claude-plugin/plugin.json 2>/dev/null \
&& ! $FORCE_IN_REPO; then
printf "\n ${BOLD}AgentKey — Uninstall${NC}\n\n"
printf " ${ERROR}Refusing to run inside the AgentKey-Skill source repo.${NC}\n"
printf " Running here would wipe this repo's own .mcp.json and CLAUDE.md.\n"
printf " Re-run with ${BOLD}--force-in-repo${NC} if you really mean it.\n\n"
exit 2
fi
printf "\n ${BOLD}AgentKey — Uninstall${NC}\n"
printf " ${MUTED}https://agentkey.app${NC}\n"
# ── 1. Remove the skill via skills CLI ────────────────────────────────────
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 agentkey -g)"
else
# `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' produced unexpected output — some agents may still have skill files"
warn "Check manually: npx skills list -g"
fi
fi
# ── 2. MCP config cleanup ────────────────────────────────────────────────
step "2. MCP server entries"
OS="$(uname -s)"
# All known JSON MCP config paths across the 16 auto-supported agents. The
# scrub logic is intentionally schema-agnostic — it walks the JSON tree and
# drops any key named "agentkey" / "agentkey.app AgentKey" wherever it
# finds it, so the same scrubber handles:
# - mcpServers.agentkey (Claude Code, Desktop, Cursor, Gemini, Windsurf, Warp, Qwen, iFlow, Kimi, Kiro)
# - projects.*.mcpServers (Claude Code's per-project shape)
# - mcp.agentkey (OpenCode, Crush)
# - amp.mcpServers.agentkey (Amp's flat dotted key)
# Keep this list in sync with AGENT_REGISTRY in
# AgentKey-Server/cli/src/lib/mcp-clients.ts.
MCP_JSON_CONFIGS=(
"$HOME/.claude.json" # Claude Code
"$HOME/.cursor/mcp.json" # Cursor
"$HOME/.gemini/settings.json" # Gemini CLI
"$HOME/.qwen/settings.json" # Qwen Code
"$HOME/.iflow/settings.json" # iFlow CLI
"$HOME/.kimi/mcp.json" # Kimi CLI
"$HOME/.kiro/settings/mcp.json" # Kiro CLI
"$HOME/.codeium/windsurf/mcp_config.json" # Windsurf
"$HOME/.warp/.mcp.json" # Warp
"$HOME/.config/opencode/opencode.json" # OpenCode (mcp.<name>)
"$HOME/.config/amp/settings.json" # Amp (amp.mcpServers.<name>)
"$HOME/.config/crush/crush.json" # Crush (mcp.<name>)
)
if [ "$OS" = "Darwin" ]; then
MCP_JSON_CONFIGS+=("$HOME/Library/Application Support/Claude/claude_desktop_config.json")
else
MCP_JSON_CONFIGS+=("$HOME/.config/Claude/claude_desktop_config.json")
fi
# TOML configs — handled separately because we can't parse TOML without a
# library. We splice out `[mcp_servers.agentkey]` blocks via line-scan.
MCP_TOML_CONFIGS=(
"$HOME/.codex/config.toml" # Codex CLI
)
have_python() { command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1; }
py() { if command -v python3 >/dev/null 2>&1; then python3 "$@"; else python "$@"; fi; }
if ! have_python; then
warn "python not found — skipping JSON cleanup; edit these files manually:"
for f in "${MCP_JSON_CONFIGS[@]}"; do [ -f "$f" ] && echo " $f"; done
else
for cfg in "${MCP_JSON_CONFIGS[@]}"; do
if [ ! -f "$cfg" ]; then
skipped "$(basename "$cfg") not found"
continue
fi
RESULT=$(py - "$cfg" <<'EOF'
import json, sys
path = sys.argv[1]
try:
with open(path) as f: d = json.load(f)
except Exception as e:
print(f"ERROR: {e}"); sys.exit(0)
# Schema-agnostic recursive scrub: drop any dict key whose name exactly
# matches one of our known MCP server names (current + legacy). Covers every
# dialect we write: mcpServers.agentkey, mcp.agentkey,
# amp.mcpServers.agentkey, projects.X.mcpServers.agentkey, etc.
# Exact-match (not substring) so we don't nuke unrelated user keys like
# "agentkey-helper" or "my-agentkey-config".
NAMES = {'agentkey', 'agentkey.app agentkey'}
def scrub(obj):
removed = 0
if isinstance(obj, dict):
for k in list(obj.keys()):
if k.lower() in NAMES:
del obj[k]; removed += 1
else:
removed += scrub(obj[k])
elif isinstance(obj, list):
for item in obj:
removed += scrub(item)
return removed
removed = scrub(d)
if removed:
with open(path, 'w') as f:
json.dump(d, f, indent=2)
print(removed)
EOF
)
if [ "$RESULT" = "0" ]; then
skipped "No agentkey entry in $cfg"
elif [[ "$RESULT" =~ ^[0-9]+$ ]]; then
ok "Removed $RESULT entry/entries from $cfg"
else
warn "Failed to update $cfg: $RESULT"
fi
done
fi
# Codex TOML: splice out [mcp_servers.agentkey] blocks (bare + legacy quoted).
# Bash-only — no Python dependency — because we never parse, only line-edit.
for cfg in "${MCP_TOML_CONFIGS[@]}"; do
if [ ! -f "$cfg" ]; then
skipped "$(basename "$cfg") not found"
continue
fi
if ! grep -qE '^\s*\[\s*mcp_servers\s*\.\s*(agentkey|"agentkey\.app AgentKey")\s*\]' "$cfg" 2>/dev/null; then
skipped "No agentkey block in $cfg"
continue
fi
# Line-scan: drop from our header line to the next [section] header or EOF.
awk '
BEGIN { skip = 0 }
/^[[:space:]]*\[[[:space:]]*mcp_servers[[:space:]]*\.[[:space:]]*(agentkey|"agentkey\.app AgentKey")[[:space:]]*\][[:space:]]*$/ { skip = 1; next }
/^[[:space:]]*\[[^]]+\][[:space:]]*$/ { skip = 0 }
!skip { print }
' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg"
ok "Removed agentkey block from $cfg"
done
# DSH cleanup is marker-scoped so unrelated home/profile patches survive.
DSH_ROOT="${DSH_HOME:-}"; [[ -n "$DSH_ROOT" && "$DSH_ROOT" == *[![:space:]]* ]] || DSH_ROOT="$HOME/.dsh"
case "$DSH_ROOT" in "~"|"~/"*) DSH_ROOT="$HOME${DSH_ROOT#"~"}" ;; /*) ;; *) DSH_ROOT="$PWD/$DSH_ROOT" ;; esac
DSH_CLEANED=false
for cfg in "$DSH_ROOT/cordis.patch.yml" "$DSH_ROOT"/profiles/*/cordis.patch.yml; do
[ -f "$cfg" ] || continue; grep -qE '^# agentkey:start([[:space:]]|$)' "$cfg" 2>/dev/null || { skipped "No AgentKey DSH block in $cfg"; continue; }; tmp="${cfg}.agentkey-uninstall.$$"
if awk 'BEGIN{s=0;i=0} /^# agentkey:start([[:space:]]|$)/{if(s)i=1;s=1;next} /^# agentkey:end([[:space:]]|$)/{if(!s)i=1;else{s=0;next}} !s{print} END{if(s||i)exit 42}' "$cfg" > "$tmp"; then
grep -q '^[[:space:]]*[^#[:space:]]' "$tmp" || printf '\n[]\n' >> "$tmp"
chmod 600 "$tmp"; mv "$tmp" "$cfg"; DSH_CLEANED=true; ok "Removed AgentKey DSH block from $cfg"
else
rm -f "$tmp"; warn "Malformed AgentKey markers in $cfg — left unchanged"
fi
done
LEGACY_DSH_PRESET="$DSH_ROOT/.agent-presets/agentkey"
if [ -e "$LEGACY_DSH_PRESET" ] || [ -L "$LEGACY_DSH_PRESET" ]; then
DSH_PRESET_BACKUP="${LEGACY_DSH_PRESET}.backup-$(date -u +%Y%m%dT%H%M%SZ)"; while [ -e "$DSH_PRESET_BACKUP" ]; do DSH_PRESET_BACKUP="${DSH_PRESET_BACKUP}-1"; done; mv "$LEGACY_DSH_PRESET" "$DSH_PRESET_BACKUP"; ok "Archived legacy DSH preset at $DSH_PRESET_BACKUP"; fi
DSH_SETTINGS="$DSH_ROOT/settings.yaml"
if [ -f "$DSH_SETTINGS" ]; then
tmp="${DSH_SETTINGS}.agentkey-uninstall.$$"
if awk 'BEGIN{p=0;r=0} /^[[:space:]]*agent-presets\.default:[[:space:]]*["\047]?agentkey["\047]?[[:space:]]*(#.*)?$/{r=1;next} /^[^[:space:]#][^:]*:[[:space:]]*/{p=($0~/^agent-presets:[[:space:]]*(#.*)?$/);print;next} p&&/^[[:space:]]+default:[[:space:]]*["\047]?agentkey["\047]?[[:space:]]*(#.*)?$/{r=1;next} {print} END{if(r)exit 10}' "$DSH_SETTINGS" > "$tmp"; then status=0; else status=$?; fi
case "$status" in 0) rm -f "$tmp" ;; 10) mv "$tmp" "$DSH_SETTINGS"; ok "Removed legacy agent-presets default from $DSH_SETTINGS" ;; *) rm -f "$tmp"; warn "Could not clean legacy DSH settings in $DSH_SETTINGS" ;; esac
fi
# ── 2b. CLI-registered agents (droid / openclaw) ─────────────────────────
# These two agents have no documented file-edit path; we registered them via
# their own CLIs (`droid mcp add`, `openclaw mcp set`), so we have to use the
# CLI to unregister. Best-effort — silently skip if the CLI isn't on PATH or
# the entry was never created.
step "2b. CLI-registered agents (droid / openclaw)"
if command -v droid >/dev/null 2>&1; then
if droid mcp remove agentkey >/dev/null 2>&1; then
ok "Removed agentkey from droid (\`droid mcp remove\`)"
else
skipped "No agentkey entry in droid (or already removed)"
fi
else
skipped "droid CLI not on PATH"
fi
if command -v openclaw >/dev/null 2>&1; then
if openclaw mcp unset agentkey >/dev/null 2>&1; then
ok "Removed agentkey from openclaw (\`openclaw mcp unset\`)"
else
skipped "No agentkey entry in openclaw (or already removed)"
fi
else
skipped "openclaw CLI not on PATH"
fi
# ── 3. Claude Code plugin registrations (legacy) ─────────────────────────
step "3. Claude Code plugin registrations (legacy)"
if ! command -v claude >/dev/null 2>&1; then
skipped "Claude Code CLI not on PATH — nothing to do here"
else
PLUGIN_LIST=$(claude plugin list 2>/dev/null || true)
MARKETS=$(echo "$PLUGIN_LIST" | grep -oE 'agentkey@[a-zA-Z0-9_-]+' || true)
if [ -z "$MARKETS" ]; then
skipped "No agentkey plugin registered"
else
while IFS= read -r entry; do
name="${entry%%@*}"
info "Uninstalling $entry ..."
if claude plugin uninstall "$name" --scope user 2>/dev/null; then
ok "Removed $entry"
else
warn "Could not remove $entry — try: claude plugin uninstall $name --scope user"
fi
done <<< "$MARKETS"
fi
# Legacy MCP registration via `claude mcp`
if claude mcp list 2>/dev/null | grep -q "^agentkey"; then
info "Removing MCP server 'agentkey' via claude CLI ..."
if claude mcp remove agentkey 2>/dev/null; then
ok "MCP server removed"
else
warn "Could not remove MCP server via claude CLI"
fi
else
skipped "No 'agentkey' MCP via claude CLI"
fi
# Marketplace entry
if $KEEP_MARKETPLACE; then
skipped "Marketplace removal skipped (--keep-marketplace)"
else
AGENTKEY_MARKETS=$(claude plugin marketplace list 2>/dev/null \
| grep -B1 -A1 -E "(AgentKey-Skill|chainbase-labs/AgentKey-Skill|agentkey-skill|chainbase-labs/agentkey)" \
| grep -oE '^ [a-zA-Z0-9_-]+' | awk '{print $2}' || true)
if [ -z "$AGENTKEY_MARKETS" ]; then
skipped "No AgentKey marketplace entry"
else
while IFS= read -r mkt; do
info "Removing marketplace '$mkt' ..."
if claude plugin marketplace remove "$mkt" 2>/dev/null; then
ok "Removed marketplace '$mkt'"
else
warn "Could not remove marketplace '$mkt'"
fi
done <<< "$AGENTKEY_MARKETS"
fi
fi
fi
# ── 4. Plugin + marketplace caches ────────────────────────────────────────
step "4. Plugin / marketplace caches"
CACHE_HITS=()
for d in "$HOME/.claude/plugins/cache"/agentkey \
"$HOME/.claude/plugins/cache"/agentkey-skill \
"$HOME/.claude/plugins/cache"/agentkey-*; do
[ -d "$d" ] && CACHE_HITS+=("$d")
done
if [ -d "$HOME/.claude/plugins/marketplaces" ]; then
for d in "$HOME/.claude/plugins/marketplaces"/*agentkey*; do
[ -d "$d" ] && CACHE_HITS+=("$d")
done
fi
if [ ${#CACHE_HITS[@]} -eq 0 ]; then
skipped "No cache found"
else
for d in "${CACHE_HITS[@]}"; do
rm -rf "$d" && ok "Removed $d"
done
fi
# ── 5. Shell RC environment exports (legacy) ──────────────────────────────
step "5. Shell environment exports (legacy)"
changed=false
for RC in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do
[ -f "$RC" ] || continue
if grep -q "agentkey-env-start" "$RC" 2>/dev/null; then
if have_python; then
py - "$RC" <<'EOF'
import re, sys
p = sys.argv[1]
c = open(p).read()
n = re.sub(r'\n# agentkey-env-start.*?# agentkey-env-end', '', c, flags=re.DOTALL)
if n != c: open(p, 'w').write(n)
EOF
ok "Cleaned agentkey env block from $RC"
changed=true
else
warn "python not found — edit $RC manually (look for 'agentkey-env-start')"
fi
fi
done
$changed || skipped "No agentkey env block in any shell RC"
# ── 6. CLAUDE.md cleanup (legacy) ─────────────────────────────────────────
step "6. CLAUDE.md sections (legacy)"
md_changed=false
for CLAUDE_MD in "$HOME/.claude/CLAUDE.md" ".claude/CLAUDE.md" "CLAUDE.md"; do
[ -f "$CLAUDE_MD" ] || continue
grep -q "AgentKey\|agentkey\|AGENTKEY" "$CLAUDE_MD" 2>/dev/null || continue
if have_python; then
OUT=$(py - "$CLAUDE_MD" <<'EOF'
import re, sys
p = sys.argv[1]
c = open(p).read()
c2 = re.sub(r'\n# AgentKey\n.*?(?=\n# |\Z)', '', c, flags=re.DOTALL)
c2 = re.sub(r'\n[^\n]*(\.agentkey|agentkey.*activation\.md|agentkey.*SKILL\.md)[^\n]*', '', c2, flags=re.IGNORECASE)
if c2 != c:
open(p, 'w').write(c2)
print("CHANGED")
else:
print("NO_MATCH")
EOF
)
if [ "$OUT" = "CHANGED" ]; then
ok "Removed AgentKey section from $CLAUDE_MD"
md_changed=true
fi
else
warn "python not found — edit $CLAUDE_MD manually"
fi
done
$md_changed || skipped "No removable AgentKey section in CLAUDE.md"
# ── 7. npm / npx caches ───────────────────────────────────────────────────
step "7. npm / npx caches"
if command -v npm >/dev/null 2>&1; then
_global_list="$(npm list -g --depth=0 2>/dev/null || true)"
_removed_any=false
# Sweep both the current package name and the legacy v0.x name so
# users who installed before the @agentkey/mcp → @agentkey/cli rename
# get a clean uninstall.
for _pkg in "@agentkey/cli" "@agentkey/mcp"; do
if echo "$_global_list" | grep -q "$_pkg"; then
info "Uninstalling global $_pkg ..."
if npm uninstall -g "$_pkg" >/dev/null 2>&1; then
ok "Removed $_pkg"
_removed_any=true
else
warn "Could not remove $_pkg — try: npm uninstall -g $_pkg"
fi
fi
done
if ! $_removed_any; then
skipped "No global @agentkey/cli or @agentkey/mcp installed"
fi
else
skipped "npm not on PATH"
fi
if [ -d "$HOME/.npm/_npx" ]; then
NPX_HITS=$(find "$HOME/.npm/_npx" -maxdepth 3 -type d -iname "*agentkey*" 2>/dev/null || true)
if [ -n "$NPX_HITS" ]; then
echo "$NPX_HITS" | xargs rm -rf
ok "Cleared agentkey entries from npx cache"
else
skipped "No agentkey entries in npx cache"
fi
else
skipped "No ~/.npm/_npx directory"
fi
# ── 7b. AgentKey config dir (snooze/disable/telemetry state) ─────────────
step "7b. AgentKey config directory"
AGENTKEY_CFG="$HOME/.config/agentkey"
if [ -d "$AGENTKEY_CFG" ]; then
rm -rf "$AGENTKEY_CFG" && ok "Removed $AGENTKEY_CFG"
else
skipped "No $AGENTKEY_CFG directory"
fi
# ── 8. Residual artifacts ─────────────────────────────────────────────────
step "8. Residual artifacts"
# 8a. MCP stdio log (macOS only; Claude Desktop path)
MCP_LOG_MAC="$HOME/Library/Logs/Claude/mcp-server-agentkey.log"
if [ -f "$MCP_LOG_MAC" ]; then
rm -f "$MCP_LOG_MAC" && ok "Removed $MCP_LOG_MAC"
fi
# 8b. Plugin registry JSONs — strip any agentkey-keyed entries
if have_python; then
for REG in "$HOME/.claude/plugins/installed_plugins.json" \
"$HOME/.claude/plugins/known_marketplaces.json" \
"$HOME/.claude/mcp-needs-auth-cache.json"; do
[ -f "$REG" ] || continue
RESULT=$(py - "$REG" <<'EOF'
import json, sys
p = sys.argv[1]
try:
with open(p) as f: d = json.load(f)
except Exception: sys.exit(0)
def scrub(obj):
removed = 0
if isinstance(obj, dict):
for k in list(obj.keys()):
if 'agentkey' in k.lower(): del obj[k]; removed += 1
else: removed += scrub(obj[k])
elif isinstance(obj, list):
kept = []
for item in obj:
s = json.dumps(item).lower() if not isinstance(item, str) else item.lower()
if 'agentkey' in s: removed += 1
else: removed += scrub(item); kept.append(item)
obj[:] = kept
return removed
n = scrub(d)
if n:
with open(p, 'w') as f: json.dump(d, f, indent=2)
print(n)
EOF
)
if [[ "$RESULT" =~ ^[0-9]+$ ]] && [ "$RESULT" -gt 0 ]; then
ok "Cleaned $RESULT entry/entries from $REG"
fi
done
fi
# ── Done ──────────────────────────────────────────────────────────────────
if $DSH_CLEANED; then
printf "\n ${BOLD}✓ Uninstall complete.${NC} Running DSH profiles watch removal; close legacy sessions or restart once if needed.\n\n"
else
printf "\n ${BOLD}✓ Uninstall complete.${NC} Restart your agent to apply changes.\n\n"
fi