Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧹 CleanDrive

Your Mac is full. Your cleanup tool shouldn't be able to delete your work.

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.


CleanDrive demo


zero npm dependencies Β· node β‰₯ 20 Β· macOS / APFS Β· drives itself from Claude Code


Why this exists

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

Dashboard, light mode

Dark

Dashboard, dark mode


What makes it different

🧠 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.

Quickstart

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:4499

Open 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, and ssh. node_modules/ never appears in this repo.


The safety model (the whole point)

                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   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.
  • rm is category-gated β€” permitted only for pkgcache, cache, node_modules. A project can leave solely via archive+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, ~/Library are refused as direct targets regardless of plan content.
  • Localhost only β€” the dashboard binds to 127.0.0.1, and execution requires typing the word FREE.
  • Full audit trail β€” every start/finish lands in data/actions.log as JSONL.

Claude Code as the connector

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.log

A 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.


CLI reference

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

Remote archive setup

  1. Attach an external drive to any machine on your Tailscale network.
  2. In the dashboard's Remote archive target card (or config.json), set the device's Tailscale name, the SSH user, and a destination path.
  3. Hit Test connection β€” it verifies SSH reachability and shows the remote's free space.
  4. Set a project's action to archive (copy only) or archive+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.


Architecture

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)

Built with Claude Code

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.

About

Risk-tiered Mac disk cleanup: scan, approve in a dashboard, clean caches or archive to a remote drive over Tailscale SSH with checksum verification. Zero deps. Drives itself from Claude Code.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages