mcpbeat

Disk Cleanup

glebis/disk-cleanup

Scan and clean macOS caches, package-manager data, crash dumps, and app caches to reclaim disk space. Deterministic — a config registry (targets.json) plus two scripts (survey.py read-only, clean.py executor) do all the measuring and deleting; the agent only relays a compressed summary and makes the few human-judgment calls. IMPORTANT — use this skill whenever the user's request on macOS involves: freeing disk space, cleaning/clearing caches, "disk is full", "clean up my Mac", "free up space", "what's eating my disk", "running low on disk", needing space for an install, or any low-storage complaint. Covers the whole workflow survey → choose → clean → empty Trash.

12k tokens
context cost
the whole folder, loaded on every use
10
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
337
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/glebis/claude-skills --skill disk-cleanup

What comes with it

40 505 bytes besides the instruction
.claude-plugin/plugin.json
.gitignore
config.json
config.local.example.json
scripts/clean.py
scripts/lib.py
scripts/survey.py
scripts/tests/test_safety.py
targets.json

The instruction itself

7 sections, as written by the author

Disk Cleanup

Deterministic by design. All target knowledge lives in targets.json; all measuring and

deleting lives in scripts/survey.py (read-only) and scripts/clean.py (executor, **dry-run by

default). They run headless with zero dependencies** (stdlib only) — a user can run them in a

terminal without any agent. The agent's job is small: run the scripts, relay the compressed

output, and decide the handful of things that need human judgment.

The two scripts

python3 scripts/survey.py            # read-only: sizes, risk, flags, uncategorized. Touches nothing.
python3 scripts/survey.py --json     # same, machine-readable (preferred for the agent)

python3 scripts/clean.py --preset safe              # DRY-RUN plan (default — nothing deleted)
python3 scripts/clean.py --preset safe --go         # execute (safe risk only)
python3 scripts/clean.py --preset full --allow-medium --go --empty-trash   # safe+medium, then empty Trash
python3 scripts/clean.py --ids cargo-registry-cache,go-mod-cache --go       # specific targets
python3 scripts/clean.py --preset safe --skip ollama-models --go            # exclude one

trash is used for all file removal (never rm); freed space sits in Trash until emptied

(--empty-trash, or the user empties it). Sizes are du estimates — approximate on APFS.

Safety model (enforced in code, not prose)

  • Risk gating: safe runs automatically; medium needs --allow-medium; never is

refused even if named by id. advisory targets only print guidance, never execute.

  • Preflight on every trashed path: canonical realpath → must resolve under an

allowed_roots entry → must not be a symlink → never $HOME or /. Anything failing is

skipped and reported, not deleted.

  • Dry-run by default: clean.py prints the plan and touches nothing unless --go.

Agent workflow

  • Run python3 scripts/survey.py --json. Relay the compressed summary: disk free,

safe/medium recoverable totals, any flags (e.g. crash-loop), and the top targets.

Do not dump the whole JSON.

  • Auto-path: for a plain "clean up safe stuff", show the safe total and run

clean.py --preset safe --go (offer --empty-trash). Safe targets are regenerable.

  • Escalate to the user ONLY for (these are genuine judgment calls the scripts deliberately

refuse to auto-decide):

  • medium targets (ML models, device support, project node_modules) — confirm before --allow-medium.

ML-model targets (ollama-models, huggingface-models) carry a last_used_days field

(newest file atime under the target, aggregate across all models in that store — not

per-model) as a "how stale is this" signal; surface it before suggesting deletion.

  • uncategorized discoveries — unknown dirs >100 MB; ask or investigate before adding.
  • advisory notes — surface them (Telegram cache, simulators via simctl, uv/tools,

Chrome whole-dir, Xcode Archives, mo clean deep-clean); never act on them automatically.

For mole-deep-clean: suggest the user run mo clean themselves (interactive TUI, permanent

deletes, sudo for system caches) — never invoke it from the agent. **Never shell out to mo

at all** (not even --dry-run): it's TUI-only and blocks waiting for a real terminal even in

dry-run mode — confirmed hanging under a piped subprocess, stdin=DEVNULL, and even a

script(1)-allocated pty. A mole flag in survey.py's output only reads the mtime of

mole's own leftover ~/.config/mole/clean-list.txt (last-run recency), never invokes it.

  • surgical Docker / simulator decisions (see below).
  • Run clean.py with the resolved selection. Relay the result (freed_human, disk before→after).

Maintaining the registry

Add or correct targets by editing targets.json — no code change needed. Each target:

{id, category, risk, method, paths|find, regenerates, priority, note}. Methods:

  • trash — trash literal paths (globs allowed).
  • find-trash — exact-name dir sweep with a min_mb floor (crash dumps, project node_modules).
  • command — run a CLI (npm cache clean…); set scope_path so freed bytes can be measured.
  • simctlxcrun simctl delete unavailable (removes only sims for uninstalled runtimes; safe).
  • downloads-scan — config-driven (config.jsondownloads_scan): files older than age_days

whose name doesn't match exclude_patterns. The dry-run lists every file by name for review.

  • advisory — never executes; only prints guidance.

Keep installed software at risk: never (learned the hard way: uv/tools, uv/python,

~/.rustup/toolchains, ~/.bun, ~/.deno are NOT caches). Every non-advisory target's paths

must resolve under allowed_roots or preflight will (correctly) refuse them.

Customization & setup (per-machine, never committed)

config.json ships generic, public-safe defaults. Anything personal — names, family

names, a non-English tax/legal/financial vocabulary — or machine-specific goes in

config.local.json (gitignored). load_config() deep-merges it over config.json:

lists are unioned (local terms only *add* protection to the Downloads exclude list), scalars

override. See config.local.example.json for the shape.

Setup mode — when the user first uses the skill, asks to personalize it, or has sensitive

files in ~/Downloads, offer to build config.local.json by asking (one short batch):

  • Names/keywords in Downloads filenames that must never be swept (own name, family names).
  • Their language's tax/legal/financial terms (e.g. German steuer, rechnung, vertrag).
  • Their projects directory (for the node_modules sweep) and any extra app caches.

Then write config.local.json (copy config.local.example.json and fill it in). Confirm what

was saved. Never commit it.

Per-machine paths in targets.json (allowed_roots, the node-modules-projects find root

~/ai_projects) are examples — adjust them to the user's layout. Targets whose paths don't

exist on this machine simply measure 0 and are skipped.

Still agent-driven (only what genuinely can't be deterministic)

  • Docker only — surgical and stateful: survey with docker images / docker ps -as /

docker system df -v, let the user pick per-name (docker rm/rmi/volume rm/builder prune),

or blunt docker system prune -a -f. A named volume removed = data gone; confirm by name.

(Everything else — simulators via the simctl method, Downloads via downloads-scan,

crash dumps, all caches — now runs through the scripts.)

How to use it

Copy the folder

Take glebis/disk-cleanup from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.