Overview
A full-width storage bar like Apple's, split into real developer categories. One row each: what removing it frees, how much is safe, how much needs a look, and your biggest safe wins.
enter open · space select · s all safe · c plan
Open source · MIT · v0.2.0
decruft is the Storage screen developers never got. One scan shows everything your machine has piled up: Xcode simulators, Go and Homebrew caches, Docker, the Android SDK, node_modules, Cargo targets, Flutter builds and forgotten agent worktrees. Then it removes only what you pick, through each tool's own command, and measures what it actually freed.
brew install matthew-eucaristo/tap/decruft
Then run decruft. Nervous? decruft --dry-run changes nothing. Other ways to install →
A live replica of the TUI, with the numbers from the recorded demo machine. Nothing here touches your computer.
The tour
A full-width storage bar like Apple's, split into real developer categories. One row each: what removing it frees, how much is safe, how much needs a look, and your biggest safe wins.
enter open · space select · s all safe · c plan
Every item with its size, safety level and the exact command or action. A pane says what the item is, what will run, how it comes back, and why it got its level.
● safe ▲ review ■ protected ◉ live
A squarified treemap in truecolor blocks, corrected for the shape of terminal cells. Zoom into a category, select tiles. Cleaned tiles stay put, hatched, so the map never jumps.
t treemap · enter zoom · esc out
The bars spring to their new sizes and free space is measured from the disk, before and after. Trash, freed-now and delegated are kept apart, because they mean different things.
Under-delivering commands are flagged claimed, not freed
demo/setup.sh, so nothing in it is from a real machine.What it covers
Built-in modules only offer what a tool can remove with its own documented command. No command means the item is shown with the owner's way to remove it, and never acted on. Every source is listed in docs/modules.md.
folder → Trash (while Xcode is closed)● safexcrun simctl delete unavailable● safexcrun simctl runtime delete <id>▲ reviewXcode › Organizer■ protectedgo clean -cache● safego clean -modcache● safegolangci-lint cache clean● safebrew cleanup --prune=all● safebrew autoremove▲ reviewpod cache clean --all● safeuv cache prune● safepip cache purge● safedocker system prune --force▲ reviewsdkmanager --uninstall <pkg>▲ reviewavdmanager delete avd -n <name>▲ reviewshown only■ protectednode_modules, framework caches, package-manager caches, Node versionsnodekill apply --plan -↗ delegatedflutterkill apply --plan -↗ delegatedtarget folders, registry and git caches, sccache, cargo install binaries, rustup toolchainsrustkill apply --plan -↗ delegatedfell apply --plan -↗ delegatedsudo apt-get clean■ shown, never sudoflatpak uninstall --user --unused▲ reviewEvery byte is offered once. When two sources report the same folder, or one inside another, the built-in module wins, then the outer item. So fell's worktree hides nodekill's node_modules inside it, and a Tauri app's src-tauri/target is rustkill's, not nodekill's.
What macOS does not show you
Settings lumps developer data into “Developer” and “System Data”, and counts purgeable space as free. decruft opens the box.
statfs). Every before/after number uses it.Illustration. Your own bar is drawn from your own disk.
Safety model
Cleaners earn their reputation by what they refuse to do. decruft fails closed, everywhere.
Caches go through their own tool's command, folders without one go to the Trash, family items go back to their own tool. sudo commands and apps that manage their own data are shown, never run.
Every item carries a fingerprint of its state: path, folder identity, level, command. Right before acting, decruft re-reads it, refuses on any change, and acts on the fresh copy only.
DerivedData while Xcode runs, a booted simulator or its runtime, the SDK while Android Studio runs: shown as live, never selectable.
Folders go to the Trash, where Put Back restores them. --permanent deletes instead. Official commands delete by design, so the plan and the receipt keep them apart.
An unknown safety level, unreadable tool output or another contract version: the item is protected or the source is skipped, with a warning. Never a guess.
Absolute paths, inside your home folder, at least three levels deep, never through a symlink. Commands run by absolute path with fixed arguments, stdin closed, never through a shell.
--list, --json and --dry-run never change anything. --yes takes safe items only, never review ones.
decruft writes no config, cache or state and never contacts the network itself. Uninstall it and nothing is left behind.
decruft · done in 2s ✓ Moved to Trash 608.2 MB 3 item(s) freed when you empty the Trash; Put Back restores ✓ Freed now 1.8 GB 7 item(s) xcrun simctl delete unavailable · go clean -cache · … ✓ Delegated 1.1 GB 7 item(s) flutterkill ×2 · nodekill ×2 · rustkill ×3 (each tool's own rules) Free space 1.8 GB → 3.6 GB +1.8 GB measured · about 608.2 MB more when you empty the Trash ↺ Build caches (Go, Homebrew, DerivedData) come back as you work; that is normal.
Three numbers that mean three different things, never summed into one flattering total. Free space is measured, not estimated.
The family
decruft is the front door to four focused cleaners. brew install brings them along; decruft runs them in parallel through a versioned JSON contract and hands items back by {id, fingerprint} only, so each tool re-checks with its own rules. Each one also works on its own.
capabilities --json · scan --json · apply --plan -Fells forgotten git and AI-agent worktrees. Hands back retire, or strip when only that is allowed.
Clears Cargo target folders it recognises by Cargo's own tag, Cargo's caches, and rustup toolchains, through rustup and cargo uninstall.
Kills node_modules, framework and package-manager caches, and Node versions you no longer use.
Clears Flutter and Dart build outputs plus pub, Gradle, FVM and Android Studio caches. Filed by kind: Gradle → Android, Xcode → Xcode.
github.com/Matthew-Eucaristo/flutterkill →The contract is public: fell's contract. Unknown schema means the source is skipped with a warning. decruft never passes --force or --unlock.
Use
Plain table when output is not a terminal, JSON for your own tooling (home paths written as ~/…), headless cleaning of safe items for the brave. Fits 40 columns, and NO_COLOR switches to glyphs only.
# interactive
decruft
# interactive, but nothing changes
decruft --dry-run
# plain table, read-only
decruft --list
# machine-readable, read-only
decruft --json | jq '.items[0]'
# headless: clean every safe item (never review items)
decruft --yes
# roots for fell, rustkill, nodekill, flutterkill (default ~/dev)
decruft ~/work ~/side
# built-in modules only
decruft --no-family
Install
brew install matthew-eucaristo/tap/decruftRecommended on macOS and Linux. Homebrew installs fell, rustkill, nodekill and flutterkill as dependencies, and decruft runs them for their areas.
go install github.com/Matthew-Eucaristo/decruft@latestNeeds Go 1.26+. Then install the family tools you want; decruft finds them on PATH and skips the ones that are missing.
Download from GitHub Releases →
Archives for macOS, Linux and Windows on amd64 and arm64, with checksums.txt. Windows runs the cross-platform modules.
brew uninstall decruft && brew autoremove -n # lists what autoremove would take, including the family tools
brew autoremove # then run it if nothing on the list is something you use
brew untap matthew-eucaristo/tap # if you no longer use the tap
decruft writes nothing to your disk, so there is nothing else to remove. With go install: rm "$(go env GOPATH)/bin/decruft".
Questions
Only what you select, and only through the owning tool's documented command or a move to the Trash. Anything that needs judgement is marked ▲ review and is never taken by s or --yes. Archives, Gradle and anything behind sudo are shown with instructions and never acted on.
rm -rf ~/Library/Developer?Because tools keep state about their own files. Go's module cache is read-only, so plain rm fails half-way; uv's cache goes inconsistent if you delete it by hand; simulator runtimes are registered with CoreSimulator. The official command is the one that leaves the tool in a correct state.
Because decruft measures free space before and after with statfs. If a command frees far less than it promised (APFS clones, snapshots holding blocks), the receipt says so instead of repeating the promise.
No. decruft makes no network requests and keeps no state. Commands it runs get a quiet environment (HOMEBREW_NO_AUTO_UPDATE, NO_COLOR, PIP_NO_INPUT…) so nothing updates itself either. This page loads nothing from third parties, too.
No. Homebrew installs them for you, but decruft skips any that are missing, and --no-family uses built-in modules only.
It is a developer tool, not a system tuner: it knows what Xcode, Go, Homebrew and Docker are, uses their commands, explains every item, and proves the result with a measured receipt. Open source, MIT, a single binary you can audit.
That is a security issue. Please report it privately through GitHub Security Advisories, not a public issue.
brew install matthew-eucaristo/tap/decruftStart with decruft --dry-run. It only looks.