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>
423 lines
18 KiB
PowerShell
423 lines
18 KiB
PowerShell
#Requires -Version 5.1
|
||
<#
|
||
.SYNOPSIS
|
||
AgentKey uninstaller for Windows
|
||
.DESCRIPTION
|
||
Usage:
|
||
irm https://agentkey.app/uninstall.ps1 | iex
|
||
& ([scriptblock]::Create((irm https://agentkey.app/uninstall.ps1))) -KeepMarketplace
|
||
& ([scriptblock]::Create((irm https://agentkey.app/uninstall.ps1))) -ForceInRepo
|
||
|
||
Cleans up everything install.ps1 (and the legacy two-command flow) ever wrote:
|
||
1. Skill files in every agent (via `skills remove`)
|
||
2. MCP server entries (Claude Code / Claude Desktop / Cursor)
|
||
3. Plugin + marketplace caches
|
||
4. CLAUDE.md sections + npm/npx caches (legacy)
|
||
#>
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch]$KeepMarketplace,
|
||
[switch]$ForceInRepo,
|
||
[switch]$SkipSkillRemove,
|
||
[switch]$Help
|
||
)
|
||
|
||
$ErrorActionPreference = 'Continue'
|
||
|
||
if ($Help) {
|
||
@'
|
||
AgentKey uninstaller (Windows)
|
||
|
||
Usage:
|
||
irm https://agentkey.app/uninstall.ps1 | iex
|
||
|
||
Parameters:
|
||
-KeepMarketplace Keep the Claude Code plugin marketplace registration
|
||
-ForceInRepo Allow running inside the AgentKey-Skill source repo
|
||
-SkipSkillRemove Skip 'npx skills remove' (only clean configs/caches)
|
||
-Help Show this help
|
||
'@
|
||
exit 0
|
||
}
|
||
|
||
# ── UI helpers ────────────────────────────────────────────────────────────
|
||
function Write-Step ($t) { Write-Host ''; Write-Host " $t" -ForegroundColor White }
|
||
function Write-Info ($t) { Write-Host " › $t" -ForegroundColor Gray }
|
||
function Write-Ok ($t) { Write-Host " ✓ $t" -ForegroundColor Green }
|
||
function Write-Warn2($t) { Write-Host " ! $t" -ForegroundColor Yellow }
|
||
function Write-Skip ($t) { Write-Host " - $t" -ForegroundColor DarkGray }
|
||
function Write-Err ($t) { Write-Host " ✗ $t" -ForegroundColor Red }
|
||
|
||
# ── Safety rail ──────────────────────────────────────────────────────────
|
||
if ((Test-Path '.claude-plugin/plugin.json') -and -not $ForceInRepo) {
|
||
$content = Get-Content '.claude-plugin/plugin.json' -Raw -ErrorAction SilentlyContinue
|
||
if ($content -match '"name"\s*:\s*"agentkey"') {
|
||
Write-Host ''
|
||
Write-Host ' AgentKey — Uninstall' -ForegroundColor White
|
||
Write-Host ''
|
||
Write-Err 'Refusing to run inside the AgentKey-Skill source repo.'
|
||
Write-Host " Running here would wipe this repo's own .mcp.json and CLAUDE.md." -ForegroundColor DarkGray
|
||
Write-Host ' Re-run with -ForceInRepo if you really mean it.' -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
exit 2
|
||
}
|
||
}
|
||
|
||
Write-Host ''
|
||
Write-Host ' AgentKey — Uninstall' -ForegroundColor White
|
||
Write-Host ' https://agentkey.app' -ForegroundColor DarkGray
|
||
|
||
# ── 1. Skill removal via skills CLI ──────────────────────────────────────
|
||
Write-Step '1. Skill files'
|
||
|
||
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 agentkey -g' -ForegroundColor DarkGray
|
||
} 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.
|
||
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' produced unexpected output — some agents may still have skill files"
|
||
Write-Host ' Check manually: npx skills list -g' -ForegroundColor DarkGray
|
||
}
|
||
}
|
||
|
||
# ── 2. MCP config cleanup ────────────────────────────────────────────────
|
||
Write-Step '2. MCP server entries'
|
||
|
||
$home2 = [Environment]::GetFolderPath('UserProfile')
|
||
|
||
# All known JSON MCP config paths across the 16 auto-supported agents. The
|
||
# scrub logic is schema-agnostic — it walks the JSON tree and drops any
|
||
# dict key whose name exactly matches our server name (current + legacy),
|
||
# so the same scrubber handles every dialect: mcpServers, mcp,
|
||
# amp.mcpServers, projects.X.mcpServers, etc. Keep this list aligned with
|
||
# AGENT_REGISTRY in AgentKey-Server/cli/src/lib/mcp-clients.ts.
|
||
$mcpJsonConfigs = @(
|
||
(Join-Path $home2 '.claude.json'), # Claude Code
|
||
(Join-Path $home2 '.cursor\mcp.json'), # Cursor
|
||
(Join-Path $env:APPDATA 'Claude\claude_desktop_config.json'), # Claude Desktop
|
||
(Join-Path $home2 '.gemini\settings.json'), # Gemini CLI
|
||
(Join-Path $home2 '.qwen\settings.json'), # Qwen Code
|
||
(Join-Path $home2 '.iflow\settings.json'), # iFlow CLI
|
||
(Join-Path $home2 '.kimi\mcp.json'), # Kimi CLI
|
||
(Join-Path $home2 '.kiro\settings\mcp.json'), # Kiro CLI
|
||
(Join-Path $home2 '.codeium\windsurf\mcp_config.json'), # Windsurf
|
||
(Join-Path $home2 '.warp\.mcp.json'), # Warp
|
||
(Join-Path $env:APPDATA 'opencode\opencode.json'), # OpenCode (mcp.<name>)
|
||
(Join-Path $env:APPDATA 'amp\settings.json'), # Amp (amp.mcpServers.<name>)
|
||
(Join-Path $env:APPDATA 'crush\crush.json') # Crush (mcp.<name>)
|
||
)
|
||
|
||
# TOML config — handled separately (no built-in TOML parser pre-PS7.4).
|
||
$mcpTomlConfigs = @(
|
||
(Join-Path $home2 '.codex\config.toml') # Codex CLI
|
||
)
|
||
|
||
$ServerNames = @('agentkey', 'agentkey.app agentkey')
|
||
|
||
# Schema-agnostic recursive scrub: drop any dict key whose lowercased name
|
||
# is in $ServerNames. Covers mcpServers / mcp / amp.mcpServers / projects.*
|
||
# in one pass. Exact match (not substring) so we don't nuke unrelated user
|
||
# keys like "my-agentkey-helper".
|
||
function Scrub-Mcp($node) {
|
||
$removed = 0
|
||
if ($node -is [System.Management.Automation.PSCustomObject]) {
|
||
$keys = @($node.PSObject.Properties.Name)
|
||
foreach ($k in $keys) {
|
||
if ($ServerNames -contains $k.ToLower()) {
|
||
$node.PSObject.Properties.Remove($k)
|
||
$removed++
|
||
} else {
|
||
$removed += Scrub-Mcp $node.$k
|
||
}
|
||
}
|
||
} elseif ($node -is [System.Collections.IList]) {
|
||
foreach ($item in $node) {
|
||
$removed += Scrub-Mcp $item
|
||
}
|
||
}
|
||
return $removed
|
||
}
|
||
|
||
function Clean-McpJsonConfig($path) {
|
||
if (-not (Test-Path $path)) {
|
||
Write-Skip "$([System.IO.Path]::GetFileName($path)) not found"
|
||
return
|
||
}
|
||
try {
|
||
$raw = Get-Content $path -Raw
|
||
$obj = $raw | ConvertFrom-Json
|
||
} catch {
|
||
Write-Warn2 "Could not parse $path — skipping"
|
||
return
|
||
}
|
||
$removed = Scrub-Mcp $obj
|
||
if ($removed -gt 0) {
|
||
($obj | ConvertTo-Json -Depth 100) | Set-Content -Path $path -Encoding UTF8
|
||
Write-Ok "Removed $removed entry/entries from $path"
|
||
} else {
|
||
Write-Skip "No agentkey entry in $path"
|
||
}
|
||
}
|
||
|
||
foreach ($cfg in $mcpJsonConfigs) { Clean-McpJsonConfig $cfg }
|
||
|
||
# Codex TOML — line-scan to splice out our [mcp_servers.agentkey] block
|
||
# (bare + legacy quoted name). Done in pure PowerShell — no TOML lib needed
|
||
# because we only ever delete, never re-emit unrelated tables.
|
||
function Clean-McpTomlConfig($path) {
|
||
if (-not (Test-Path $path)) {
|
||
Write-Skip "$([System.IO.Path]::GetFileName($path)) not found"
|
||
return
|
||
}
|
||
$lines = Get-Content $path
|
||
$headerPattern = '^\s*\[\s*mcp_servers\s*\.\s*(agentkey|"agentkey\.app AgentKey")\s*\]\s*$'
|
||
$anyHeader = '^\s*\[[^\]]+\]\s*$'
|
||
if (-not ($lines -match $headerPattern)) {
|
||
Write-Skip "No agentkey block in $path"
|
||
return
|
||
}
|
||
$out = New-Object System.Collections.Generic.List[string]
|
||
$skip = $false
|
||
foreach ($line in $lines) {
|
||
if ($line -match $headerPattern) { $skip = $true; continue }
|
||
if ($skip -and $line -match $anyHeader) { $skip = $false }
|
||
if (-not $skip) { $out.Add($line) }
|
||
}
|
||
Set-Content -Path $path -Value $out -Encoding UTF8
|
||
Write-Ok "Removed agentkey block from $path"
|
||
}
|
||
|
||
foreach ($cfg in $mcpTomlConfigs) { Clean-McpTomlConfig $cfg }
|
||
|
||
# ── 2b. CLI-registered agents (droid / openclaw) ─────────────────────────
|
||
# These two have no documented file-edit path; we registered them via their
|
||
# CLIs so we have to unregister the same way. Best-effort — silently skip
|
||
# if the CLI isn't on PATH or the entry was never created.
|
||
Write-Step '2b. CLI-registered agents (droid / openclaw)'
|
||
|
||
if (Get-Command droid -ErrorAction SilentlyContinue) {
|
||
& droid mcp remove agentkey 2>$null | Out-Null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok 'Removed agentkey from droid (`droid mcp remove`)' }
|
||
else { Write-Skip 'No agentkey entry in droid (or already removed)' }
|
||
} else {
|
||
Write-Skip 'droid CLI not on PATH'
|
||
}
|
||
|
||
if (Get-Command openclaw -ErrorAction SilentlyContinue) {
|
||
& openclaw mcp unset agentkey 2>$null | Out-Null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok 'Removed agentkey from openclaw (`openclaw mcp unset`)' }
|
||
else { Write-Skip 'No agentkey entry in openclaw (or already removed)' }
|
||
} else {
|
||
Write-Skip 'openclaw CLI not on PATH'
|
||
}
|
||
|
||
# ── 3. Claude Code plugin registrations (legacy) ─────────────────────────
|
||
Write-Step '3. Claude Code plugin registrations (legacy)'
|
||
|
||
if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {
|
||
Write-Skip 'Claude Code CLI not on PATH — nothing to do here'
|
||
} else {
|
||
$pluginList = & claude plugin list 2>$null
|
||
$markets = @()
|
||
if ($pluginList) {
|
||
$markets = $pluginList | Select-String -Pattern 'agentkey@[a-zA-Z0-9_-]+' -AllMatches |
|
||
ForEach-Object { $_.Matches.Value } | Sort-Object -Unique
|
||
}
|
||
if ($markets.Count -eq 0) {
|
||
Write-Skip 'No agentkey plugin registered'
|
||
} else {
|
||
foreach ($entry in $markets) {
|
||
$name = $entry.Split('@')[0]
|
||
Write-Info "Uninstalling $entry ..."
|
||
& claude plugin uninstall $name --scope user 2>$null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok "Removed $entry" }
|
||
else { Write-Warn2 "Could not remove $entry" }
|
||
}
|
||
}
|
||
|
||
$mcpList = & claude mcp list 2>$null
|
||
if ($mcpList -and ($mcpList -match '^agentkey')) {
|
||
Write-Info "Removing MCP server 'agentkey' via claude CLI ..."
|
||
& claude mcp remove agentkey 2>$null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok 'MCP server removed' }
|
||
else { Write-Warn2 'Could not remove MCP server via claude CLI' }
|
||
} else {
|
||
Write-Skip "No 'agentkey' MCP via claude CLI"
|
||
}
|
||
|
||
if ($KeepMarketplace) {
|
||
Write-Skip 'Marketplace removal skipped (-KeepMarketplace)'
|
||
} else {
|
||
$mktList = & claude plugin marketplace list 2>$null
|
||
$agentkeyMkts = @()
|
||
if ($mktList) {
|
||
$joined = $mktList -join "`n"
|
||
if ($joined -match '(AgentKey-Skill|chainbase-labs/AgentKey-Skill|agentkey-skill|chainbase-labs/agentkey)') {
|
||
$agentkeyMkts = $mktList | Select-String -Pattern '^\s*❯\s+([a-zA-Z0-9_-]+)' |
|
||
ForEach-Object { $_.Matches[0].Groups[1].Value }
|
||
}
|
||
}
|
||
if ($agentkeyMkts.Count -eq 0) {
|
||
Write-Skip 'No AgentKey marketplace entry'
|
||
} else {
|
||
foreach ($mkt in $agentkeyMkts) {
|
||
Write-Info "Removing marketplace '$mkt' ..."
|
||
& claude plugin marketplace remove $mkt 2>$null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok "Removed marketplace '$mkt'" }
|
||
else { Write-Warn2 "Could not remove marketplace '$mkt'" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# ── 4. Plugin + marketplace caches ────────────────────────────────────────
|
||
Write-Step '4. Plugin / marketplace caches'
|
||
|
||
$cacheHits = @()
|
||
$pluginCache = Join-Path $home2 '.claude\plugins\cache'
|
||
if (Test-Path $pluginCache) {
|
||
$cacheHits += Get-ChildItem -Path $pluginCache -Directory -Filter 'agentkey*' -ErrorAction SilentlyContinue
|
||
}
|
||
$mktCache = Join-Path $home2 '.claude\plugins\marketplaces'
|
||
if (Test-Path $mktCache) {
|
||
$cacheHits += Get-ChildItem -Path $mktCache -Directory -Filter '*agentkey*' -ErrorAction SilentlyContinue
|
||
}
|
||
if ($cacheHits.Count -eq 0) {
|
||
Write-Skip 'No cache found'
|
||
} else {
|
||
foreach ($d in $cacheHits) {
|
||
Remove-Item -Recurse -Force $d.FullName -ErrorAction SilentlyContinue
|
||
Write-Ok "Removed $($d.FullName)"
|
||
}
|
||
}
|
||
|
||
# ── 5. CLAUDE.md cleanup (legacy) ─────────────────────────────────────────
|
||
Write-Step '5. CLAUDE.md sections (legacy)'
|
||
|
||
$mdChanged = $false
|
||
$candidates = @(
|
||
(Join-Path $home2 '.claude\CLAUDE.md'),
|
||
'.claude\CLAUDE.md',
|
||
'CLAUDE.md'
|
||
)
|
||
foreach ($md in $candidates) {
|
||
if (-not (Test-Path $md)) { continue }
|
||
$c = Get-Content $md -Raw
|
||
if ($c -notmatch '(AgentKey|agentkey|AGENTKEY)') { continue }
|
||
$c2 = [regex]::Replace($c, '\n# AgentKey\n.*?(?=\n# |\z)', '', 'Singleline')
|
||
$c2 = [regex]::Replace($c2, '\n[^\n]*(\.agentkey|agentkey.*activation\.md|agentkey.*SKILL\.md)[^\n]*', '', 'IgnoreCase')
|
||
if ($c2 -ne $c) {
|
||
Set-Content -Path $md -Value $c2 -Encoding UTF8
|
||
Write-Ok "Removed AgentKey section from $md"
|
||
$mdChanged = $true
|
||
}
|
||
}
|
||
if (-not $mdChanged) { Write-Skip 'No removable AgentKey section in CLAUDE.md' }
|
||
|
||
# ── 6. npm / npx caches ───────────────────────────────────────────────────
|
||
Write-Step '6. npm / npx caches'
|
||
|
||
if (Get-Command npm -ErrorAction SilentlyContinue) {
|
||
$globalList = & npm list -g --depth=0 2>$null
|
||
$removedAny = $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.
|
||
foreach ($pkg in @('@agentkey/cli', '@agentkey/mcp')) {
|
||
if ($globalList -and ($globalList -match [regex]::Escape($pkg))) {
|
||
Write-Info "Uninstalling global $pkg ..."
|
||
& npm uninstall -g $pkg 2>$null | Out-Null
|
||
if ($LASTEXITCODE -eq 0) { Write-Ok "Removed $pkg"; $removedAny = $true }
|
||
else { Write-Warn2 "Could not remove $pkg" }
|
||
}
|
||
}
|
||
if (-not $removedAny) {
|
||
Write-Skip 'No global @agentkey/cli or @agentkey/mcp installed'
|
||
}
|
||
} else {
|
||
Write-Skip 'npm not on PATH'
|
||
}
|
||
|
||
$npxCache = Join-Path $home2 '.npm\_npx'
|
||
if (Test-Path $npxCache) {
|
||
$hits = Get-ChildItem -Path $npxCache -Directory -Recurse -Depth 2 -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.Name -match 'agentkey' }
|
||
if ($hits) {
|
||
$hits | ForEach-Object { Remove-Item -Recurse -Force $_.FullName -ErrorAction SilentlyContinue }
|
||
Write-Ok 'Cleared agentkey entries from npx cache'
|
||
} else {
|
||
Write-Skip 'No agentkey entries in npx cache'
|
||
}
|
||
} else {
|
||
Write-Skip 'No npx cache directory'
|
||
}
|
||
|
||
# ── 7. Residual plugin registries ─────────────────────────────────────────
|
||
Write-Step '7. Residual plugin registries'
|
||
|
||
$regs = @(
|
||
(Join-Path $home2 '.claude\plugins\installed_plugins.json'),
|
||
(Join-Path $home2 '.claude\plugins\known_marketplaces.json'),
|
||
(Join-Path $home2 '.claude\mcp-needs-auth-cache.json')
|
||
)
|
||
|
||
function Scrub-Agentkey($node) {
|
||
$removed = 0
|
||
if ($node -is [System.Management.Automation.PSCustomObject]) {
|
||
$keys = @($node.PSObject.Properties.Name)
|
||
foreach ($k in $keys) {
|
||
if ($k -match 'agentkey') {
|
||
$node.PSObject.Properties.Remove($k)
|
||
$removed++
|
||
} else {
|
||
$removed += Scrub-Agentkey $node.$k
|
||
}
|
||
}
|
||
} elseif ($node -is [System.Collections.IList]) {
|
||
# Rebuild list without agentkey items
|
||
$kept = New-Object System.Collections.ArrayList
|
||
foreach ($item in $node) {
|
||
$s = ($item | ConvertTo-Json -Depth 10 -Compress).ToLower()
|
||
if ($s -match 'agentkey') {
|
||
$removed++
|
||
} else {
|
||
$removed += Scrub-Agentkey $item
|
||
[void]$kept.Add($item)
|
||
}
|
||
}
|
||
return @{ removed = $removed; list = $kept }
|
||
}
|
||
return $removed
|
||
}
|
||
|
||
foreach ($reg in $regs) {
|
||
if (-not (Test-Path $reg)) { continue }
|
||
try {
|
||
$obj = Get-Content $reg -Raw | ConvertFrom-Json
|
||
} catch { continue }
|
||
|
||
$res = Scrub-Agentkey $obj
|
||
$n = if ($res -is [hashtable]) { $res.removed } else { $res }
|
||
if ($n -gt 0) {
|
||
($obj | ConvertTo-Json -Depth 100) | Set-Content -Path $reg -Encoding UTF8
|
||
Write-Ok "Cleaned $n entry/entries from $reg"
|
||
}
|
||
}
|
||
|
||
# ── Done ──────────────────────────────────────────────────────────────────
|
||
Write-Host ''
|
||
Write-Host ' ✓ Uninstall complete.' -ForegroundColor Green -NoNewline
|
||
Write-Host ' Restart your agent to apply changes.' -ForegroundColor White
|
||
Write-Host ''
|