--- name: awesome-skills-purge description: "Removes installed agent skills from every AI agent on the machine (Claude Code, Codex, Gemini CLI, Cursor...) behind a keep list, an archive and a confirmation gate. Use when asked to uninstall installed skills." license: MIT metadata: author: Khasky tags: ["agent-skills", "uninstall", "cleanup", "cross-platform", "safety"] documentation: "https://github.com/khasky/awesome-agent-skills/tree/main/skills/awesome-skills-purge" --- # Agent Skills Purge Delete installed skills across every agent on the machine, keeping only what the user names. One agent's skills directory is easy to clear by hand; the problem this solves is that a dozen agents each read their own path, half of those paths hold links into a shared store or a git clone, and the same skill exists under four names at four locations. Why the ceremony: deleting through a link, not the link itself, is how a purge eats a git clone that was never in scope. And a skill directory is not always what it looks like — `~/.claude/skills/foo` can be a real folder, a symlink into `~/.agents/skills`, or a junction into a repository under active development. Each needs a different removal, and telling them apart is the whole job. ## Core principle NOTHING IS DELETED UNTIL THREE THINGS HOLD: the full inventory has been shown to the user, the keep list is agreed, and the user has explicitly confirmed. Everything before the gate is read-only. Three invariants hold throughout: - Delete a link as a link, never recursively. Whether a recursive delete follows a symlink or a junction depends on the tool, the shell, and its version. Remove the link itself and the question never comes up. - A link's target is out of scope unless it is independently in scope. Removing `~/.claude/skills/foo` never authorizes touching whatever `foo` points at. - Never delete a directory that is inside a git work tree. That is somebody's clone — the source of a symlinked install, not an installed copy. Report it, delete the links to it, leave it alone. ## Invocation ``` /awesome-skills-purge [--keep collection|self|none|[,…]] [--scope global|project |all] [--no-backup] ``` - `--keep` — defaults to `self`, so the skill survives its own run and can be used again. - `collection` — every skill belonging to the awesome-agent-skills set (resolved by evidence, see Phase 2). - `self` — `awesome-skills-purge`, every copy of it, at every location. - `none` — everything goes. Requires the user to say the word; never inferred from "delete all". - `,` — exact folder names, matched at every location. - `--scope` — `global` (per-user agent directories, the default), `project ` (skill directories inside one repository), or `all`. - `--no-backup` — skip the archive. Only when the user says so; the default is to archive first. Invoked with no arguments: run Phase 1, show the inventory, and ask for the keep list before anything else. --- ## Phase 0 — Detect the platform (never assume it) Read the platform from the environment before writing a single command, and take every command below from that detection. A skill that hardcodes one OS is broken on the other two. | | POSIX (Linux, macOS, BSD) | Windows | |---|---|---| | Detect | `uname -s` | `$IsWindows` (PowerShell 6+), or `$env:OS` = `Windows_NT` | | Home | `$HOME` | `$env:USERPROFILE` | | Link kinds | symlink | symlink and junction (`LinkType` tells you which) | | Remove a link | `rm -- ` | `[System.IO.Directory]::Delete('', $false)` | | Remove a real directory | `rm -rf -- ` | `Remove-Item '' -Recurse -Force` | Two traps worth naming: - Git Bash on Windows is Windows. A POSIX-looking shell there still faces junctions, `USERPROFILE`, and backslash paths. Detect the OS, not the shell. - A wildcard path can be refused. Some agent harnesses block a delete whose path ends in `\*` or `/*` as a protected-path pattern. Iterate over entries and delete each by its full path — which is what this skill does anyway, because entries need per-entry classification. Prove the link handling before the destructive run (optional, ten seconds, and the one check that stands between a mistake and a deleted repository): Make a throwaway directory holding a file, point a symbolic link at it, delete the link, and read the file back: it has to still be there. On Windows the same proof uses a junction and the directory-delete call that removes the junction itself rather than its contents. Ten seconds, and it is the one check standing between a mistake and a deleted repository. --- ## Phase 1 — Inventory (read-only) Find the skill roots. Sweep the home directory rather than working from a fixed list — agents keep appearing, and a hardcoded table silently misses the one the user actually installed into. ```bash find "$HOME" -maxdepth 3 -type d -name skills 2>/dev/null ``` ```powershell Get-ChildItem $env:USERPROFILE -Directory -Force -Recurse -Depth 2 -Filter skills -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName ``` Depth 3 (POSIX) / depth 2 (PowerShell, which counts from the search root) is the same reach: it covers `~/.claude/skills`, `~/.agents/skills`, and the nested forms — `~/.config/opencode/skills`, `~/.codeium/windsurf/skills`, `~/.gemini/antigravity/skills`. The commonly documented roots are below; the sweep is authoritative, this table is only for reading the results back to the user in terms they recognize. | Agent | Global root | |---|---| | Claude Code | `~/.claude/skills/` | | OpenAI Codex, Amp, Cursor, Copilot, Gemini CLI (shared) | `~/.agents/skills/` | | Gemini CLI | `~/.gemini/skills/` | | Cursor | `~/.cursor/skills/` | | GitHub Copilot | `~/.copilot/skills/` | | opencode | `~/.config/opencode/skills/` | | Windsurf | `~/.codeium/windsurf/skills/` | For `--scope project `, search that path instead: `/.claude/skills`, `/.agents/skills`, `/.cursor/skills`, `/.github/skills`, `/.opencode/skills`, `/.gemini/skills`. Classify every entry — this is the part that decides how it gets deleted: Per root, list every entry including hidden ones and print one line each: whether it is a link or a real directory, its full path, and — for a link — what it points at. Then, for every real directory and every link target, check whether it sits inside a git work tree: ``` git -C rev-parse --show-toplevel ``` Exit 0 → it is a clone. Record it as source, not installed — it is protected by invariant three, whatever the keep list says. Report the inventory as a table before asking anything: root, entry count, how many are links, how many are real, and which targets are clones. A user who installed with `npx skills add` (symlinks by default) will see almost all links and one clone; a user who installed with `--copy` will see real directories everywhere. --- ## Phase 2 — Resolve the keep list Turn `--keep` into an explicit set of folder names, and show it. A keep list the user has not read is not a keep list. - `collection` — resolve by evidence, not by guessing at names. For each candidate, read `SKILL.md` frontmatter and keep it when `metadata.documentation` points at the collection's repository. Where frontmatter carries no such field, fall back to the folder-name prefix (`awesome-*`) and say that you did — the prefix over-keeps any unrelated skill that happens to share it. If a clone of the collection is on disk (Phase 1 found it as a link target), the names under its `skills/` directory are the exact set; prefer that. - `self` — `awesome-skills-purge`, matched at every root. - `` — exact folder names, case-sensitive on POSIX, case-insensitive on Windows. Keeping is by name at every location: keeping `foo` keeps `~/.claude/skills/foo` *and* `~/.agents/skills/foo`, link or real. Keeping a link whose target you are about to delete produces a dangling link — so when a kept entry is a link, its target is kept too. Add it to the set and say so. Then read the deletion list back: how many entries at how many roots, and the total on disk of the real ones. This is the number the confirmation gate quotes. --- ## Phase 3 — Backup and confirmation gate Archive the real directories (links cost nothing to recreate, and archiving through them would copy the target): ```bash tar -czf "$HOME/agent-skills-backup-$(date +%Y%m%d-%H%M%S).tgz" -C / ``` On Windows, archive the same real directories into a zip beside it, with the same timestamp in the name. Report the archive's absolute path and its size. Nothing here deletes it. If the user passed `--no-backup`, state plainly in the gate that there is no rollback path. The gate. State exactly what will happen, then get an explicit yes: > This will delete `` skills across `` agent directories (`` links, `` real folders, `` on disk). Kept: ``. Not touched: ``. Backup: `` | none — this cannot be undone. Proceed? No explicit confirmation → stop. Everything so far was read-only. --- ## Phase 4 — Delete Links first, then real directories. Links first is not cosmetic: it means that at every moment a link either points at live content or is already gone, so nothing ever recurses into a half-deleted store. Two passes over the same entry list, per root, skipping anything on the keep list. First pass: every entry that is a link is removed as a link — the link itself, never its target, which is the whole reason for the proof above. Second pass: every remaining entry, now a real directory, is removed with its contents. On Windows the link pass uses the delete that removes a junction without recursing into it; the real pass is an ordinary recursive remove. - `rm --` and `Get-ChildItem -Force` handle the two things that break naive loops: an entry whose name starts with `-`, and dot-prefixed entries an unforced listing skips. - A real directory inside a git work tree is skipped and reported, never deleted, even when the keep list does not name it. - Count what each pass removed. A pass that deletes zero where the inventory listed entries means the loop is not seeing what the inventory saw — stop and re-inventory rather than escalating force. - Leave the `skills` root directories in place, empty. Agents create them anyway, and removing them buys nothing. Remove a root only if the user asks. --- ## Phase 5 — Verify Re-run the Phase 1 inventory. It is the proof, not the deletion's own exit codes: - Every remaining entry is in the keep list. Anything else means a root was missed. - Every kept link still resolves: `[ -e "$link" ]` (POSIX) / `Test-Path $e.Target` (Windows). A dangling kept link means Phase 2 failed to pull in a target. - Every clone recorded in Phase 1 still exists, and `git -C status --porcelain` is unchanged from before. - The archive exists and opens: `tar -tzf | head` / `Get-ChildItem` on the expanded zip listing. --- ## Phase 6 — Report ```text Platform: / Roots: skill directories under Deleted: links, real folders ( reclaimed) Kept: (at ) Protected: — links removed, working trees untouched Backup: () | none (--no-backup) Verified: re-inventory = keep list only; kept links resolve; clones intact ``` Manual residuals (a file delete cannot reach these): - Plugin-provided skills. Skills that arrive with a plugin live under the plugin's own directory (Claude Code: `~/.claude/plugins/`) and reappear on the next plugin load. Remove the plugin through the agent's plugin manager (`/plugin` in Claude Code), not by deleting files. - Hosted skills. Claude.ai's uploaded skills live in the account, not on disk — Settings → Skills in the web UI. - Project-level directories when the scope was `global`, and vice versa. Name what was left out of scope. - Restart the agents. A running session keeps its loaded skill list; a deleted skill can still appear in an open session until it reloads. - The install source. If skills were symlinked from a clone, the clone is still on disk and one `skills add` re-installs everything. Say where it is; deleting it is the user's separate decision. ## Guardrails - Read-only until the Phase 3 gate. Inventory, classification, and keep-list resolution change nothing. - Links are removed as links; a recursive delete is never pointed at one. - A directory inside a git work tree is never deleted by this skill — not as an installed skill, not as a link target, not on a keep-list miss. - The keep list is shown and confirmed before use. `--keep none` requires the user to say it; "delete all skills" alone still keeps this skill, and the report says so. - The backup is never deleted here, and its path is always reported. - Every command is chosen from the Phase 0 detection. Commands from the other platform's column are not written, not suggested, and not run.