A disk-cleanup planner that treats deletion as a plan you approve β not a button you regret. Scan β categorize by risk β approve in a dashboard β clean caches, or archive real work to an external drive over Tailscale SSH with checksum verification before a single local byte is removed.
zero npm dependencies Β· node β₯ 20 Β· macOS / APFS Β· drives itself from Claude Code
Every "disk cleaner" on the Mac falls into one of two camps: dumb (blindly nukes
~/Library/Caches and prays) or dangerous (one-click "free up 200 GB" that also eats
the project you shipped last week). CleanDrive is neither.
It starts from a hard rule: nothing gets deleted that can't be regenerated or hasn't been verified onto another disk first. Your caches? Delete freely β they rebuild. Your actual projects? They can only leave this machine by being copied to an external drive, checksum-verified, and only then removed locally. And the things you never want touched β your Claude agent history, your coding sessions β are hard-locked and refused even if something tries to force the action.
|
Light |
Dark |
| π§ Risk-tiered, not size-sorted | Every item is classified β regenerable cache, reinstallable node_modules, irreplaceable project, review-only app data. The action space is a function of the risk, so you literally cannot rm a project. |
| π Hard-locked paths | Your Claude Code history (~/.claude), memory (~/.claude-mem), and desktop-app data are flagged π, forced to keep, made non-selectable in the UI, and refused at execution even if the action is forged. Add your own paths in one line of config. |
| π¦ Archive, verify, then delete | Projects don't get deleted β they get rsync'd to a remote drive preserving their path, checksum-verified with a second rsync -c pass, and only removed locally if verification reports zero differences. |
| π Remote over Tailscale SSH | Point it at any device on your tailnet (a Raspberry Pi, an old laptop, a NAS) with an external drive attached. No cloud, no account, no upload β it's your data going to your disk over your network. |
| π A dashboard that explains itself | Capacity bar, per-category breakdown, and a sortable item table β built to a validated, colorblind-safe palette that adapts to light/dark. Approving an action is a click; executing requires typing FREE. |
| π€ Claude Code is a first-class driver | The CLI + JSON state files are a documented connector. Claude reads your scan, proposes a plan in chat, and β only on your explicit approval β approves and executes it, with a full audit log. |
| π Understands APFS | Free space is shared across volumes, so "56% full" from df is a lie. CleanDrive computes real capacity as used / (used + available). |
| π΅οΈ App usage analysis | Correlates every .app bundle's size with its last-used date and its app-support data footprint β so a 700 MB app dragging 9 GB of data, or a 800 MB app you haven't opened in 5 months, surfaces instantly. |
git clone https://github.com/kaiser-data/cleandrive.git
cd cleandrive
cp config.example.json config.json # point projectRoots at your code folders
node bin/cleandrive.js scan # ~1β3 min; writes data/latest.json
node bin/cleandrive.js serve # dashboard β http://localhost:4499Open the dashboard, approve what you want gone, type FREE, watch it work. That's it.
No dependencies to install. CleanDrive is pure Node stdlib + your system's
du,find,rsync, andssh.node_modules/never appears in this repo.
βββββββββββββββββββββββββββββββββββββββββββββββ
scan produces ββββββΆ β every actionable path is an allowlist entry β
the ONLY βββββββββββββββββββββββββββββββββββββββββββββββ
actionable set β
βΌ
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β category β what's allowed β
ββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β pkgcache β official clean cmd (uv/npm/brew/pnpm/bun) β or rm β
β cache β rm (the app rebuilds it) β
β node_modules β rm (reinstall per project) β
β projects β archive / archive+rm / keep β NEVER a bare rm β
β appdata β rm only after explicit "mark reviewed" + approve β
β apps β review only β surfaced with size + last-used β
β models β rm after review β or remove via the owning tool β
ββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Enforced at every layer, not just the UI:
- Allowlist by construction β only paths that came out of the scan are actionable; anything else is refused.
rmis category-gated β permitted only forpkgcache,cache,node_modules. A project can leave solely viaarchive+rm, and only after checksum verification passes.- Protected paths are hard-locked β listed in
config.protected, refused at the executor even if a forged action reaches it. Ships protecting your Claude/agent history. - Protected roots β
~,~/Documents,~/Desktop,~/Downloads,~/Pictures,~/Libraryare refused as direct targets regardless of plan content. - Localhost only β the dashboard binds to
127.0.0.1, and execution requires typing the wordFREE. - Full audit trail β every start/finish lands in
data/actions.logas JSONL.
CleanDrive was built to be driven by an agent. The CLI speaks --json, and the state lives in
flat files, so a Claude Code session can run the whole loop β and never act without your say-so:
node bin/cleandrive.js state --json # items: {id, path, kb, category, suggest, note}
node bin/cleandrive.js approve <idβ¦> --action=archive+rm
node bin/cleandrive.js execute --dry-run # show exactly what would happen
node bin/cleandrive.js execute # do it β logged to data/actions.logA typical session: Claude reads your scan, groups it by category, proposes "here's 33 GB of package caches and 14 GB of
node_modules, all regenerable β clean them?", and only after you confirm the specific items does it approve and execute. The plan is always yours.
| command | what it does |
|---|---|
scan [--json] |
Rescan disk, categorize, write data/latest.json |
state [--json] |
Current scan + plan status, with item ids |
serve |
Dashboard at http://localhost:4499 |
approve <idβ¦> [--action=rm|clean|archive|archive+rm] |
Mark items approved |
skip <idβ¦> |
Mark items skipped |
execute [--dry-run] |
Run all approved actions |
offload-dryrun |
rsync -n approved archive items to the remote |
remote-test |
Check SSH reachability of the configured remote drive |
- Attach an external drive to any machine on your Tailscale network.
- In the dashboard's Remote archive target card (or
config.json), set the device's Tailscale name, the SSH user, and a destination path. - Hit Test connection β it verifies SSH reachability and shows the remote's free space.
- Set a project's action to
archive(copy only) orarchive+rm(copy, verify, then reclaim locally).
Files are rsync'd with -aHR so the path relative to your home folder is preserved on the remote β
your archive mirrors your Mac's layout, making restores obvious.
bin/cleandrive.js CLI + agent connector (every command is scriptable)
lib/scan.js the scanner β categorization, sizing, staleness, app usage
lib/actions.js the executor β validation, rm/clean/archive, verification, audit log
lib/server.js zero-dep HTTP API + static host for the dashboard
lib/util.js shell helpers, hashing, concurrency pool
ui/index.html the single-file dashboard (validated colorblind-safe palette)
config.json your roots, thresholds, protected paths, remote target
data/ latest.json (scan) Β· plan.json (approvals) Β· actions.log (audit)
CleanDrive β the scanner, the safety model, the dashboard, the Playwright capture pipeline that produced the demo above β was designed and implemented in a Claude Code session. Which is exactly why protecting agent history is a first-class feature: the tool knows what it must never delete.
MIT licensed. Clean fearlessly.


