CleanDevMac — cdm — 100×40
  • macOS only
  • pure bash
  • MIT
  • zero telemetry

Your Mac is mostly
build cache now.

cdm is short for CleanDevMac — the name you type, and the name of the tool. It is a bash CLI that hunts down developer junk on macOS: DerivedData, node_modules, Go and Cargo caches, Electron blobs, dead app leftovers. It shows you every path and every byte before it touches anything.

Run it — nothing gets installed
curl -sSL https://github.com/cleandevmac/cdm/releases/latest/download/cdm | bash

Dry run — deletes nothing

curl -sSL https://github.com/cleandevmac/cdm/releases/latest/download/cdm | bash -s -- -n

It’s the recommended first run, and the flag most people never stop using.

Read it first — downloads, doesn’t run

curl -fsSL -O https://github.com/cleandevmac/cdm/releases/latest/download/cdm less cdm && bash cdm -n

Piping a stranger’s script into your shell is a fair thing to refuse. So don’t: cdm is one bash file, about 2,000 lines, with no dependencies — you can read the whole thing before you run it, and -n means the first run it does is a rehearsal. Read the source on GitHub ↗

Example scan 17.0 GB selected of 32.9 GB found disk 66 GB free of 228 GB
  • Developer caches & build artifacts 14.2 GB
  • Project junk, grouped per repo 9.4 GB
  • Docker / Podman 6.1 GB
  • Electron, browser & app caches 2.8 GB
  • Orphaned app data 386 MB

Illustrative scan — the sizes above are an example, not a promise. On a working developer’s Mac a dry run usually surfaces anywhere from a few hundred MB to many GB. Run -n to find out what yours holds. Categories are sorted biggest-first, exactly as the real TUI sorts them.

what cdm finds

Five groups. Each one is a set of rules, and each rule knows whether its target is regenerable — which is what earns it a permanent delete — or irreplaceable enough to go to the Trash instead.

Developer caches & build artifacts

Deleted
  • Xcode DerivedData
  • iOS DeviceSupport
  • Go build & module cache
  • npm
  • npx
  • pnpm
  • yarn
  • Turbo
  • Vite
  • webpack
  • Parcel
  • ESLint
  • Gradle
  • Maven
  • sbt / Ivy
  • Cargo
  • pip
  • uv
  • poetry
  • ruff
  • mypy
  • Ruby / Bundler
  • Bun
  • Deno
  • CocoaPods
  • SwiftPM
  • Composer
  • Bazel
  • Zig
  • kubectl
  • AWS
  • gcloud
  • Azure
  • Docker buildx
  • JetBrains
  • Playwright
  • Homebrew download cache
  • sccache
  • Android Studio
  • emulator snapshots
  • unused Android system images

The safe, regenerable ones are pre-selected. The ones that cost real time or bandwidth to rebuild — the Maven repository, Playwright browsers, Android system images — start unchecked. You opt in, not out. An Android image is only listed when no AVD on your Mac boots from it; the ones your emulators use never show up.

Electron, browser & app caches

Deleted
  • Electron disk cache
  • GPU cache
  • code cache
  • VS Code
  • Claude
  • webview partitions
  • Slack
  • Chrome
  • Brave
  • Edge
  • Vivaldi
  • Arc
  • Firefox
  • Sentry
  • Crashlytics
  • Sparkle
  • app-updater payloads
  • Claude Cowork VM image

Chromium and Firefox caches are handled per profile, so a work profile and a personal profile are separate rows. Crash-reporter and telemetry SDK caches are swept out with them. Claude desktop’s ~10 GB Cowork VM image is offered too — unchecked, re-downloaded on next use, with your VM session data kept.

Project junk, per repo

-p
  • node_modules
  • dist
  • build
  • target
  • __pycache__
  • git-ignored files

One git repo, one row. Not a pile of anonymous folder paths — so you clean the three projects you finished in 2023 and leave the one you’re mid-branch on. Off by default behind -p, because scanning $HOME is slow; an interactive run offers it once the cache scan is finished. Build directories are deleted; other git-ignored files go to the Trash.

Docker / Podman

Opt-in
  • stopped containers
  • unused images
  • build cache

Runs system prune -af, and only if you ask for it. Named volumes are never touched — your local Postgres survives.

Orphaned app data

To Trash
  • Application Support
  • Caches
  • Preferences

Leftovers whose owning app is gone. The installed-app list is read from LaunchServices, so a prefPane, a plugin or a driver is never mistaken for an orphan. Everything here goes to the Trash — if cdm guessed wrong, you put it back.

what cdm will never do

Nothing is deleted without an itemized confirmation.

Not a count. Not a category name. The actual paths, the actual sizes, and which of the three fates each one is headed for — printed on your screen, waiting on a y. A cleanup tool is a program you invite to destroy your own data. That deserves an argument, not a feature list.

Three things can happen to a file. You always know which.

Gone for good

Deleted permanently

Get it back?No — that’s the point. They regenerate.

  • Caches — npm, Go, Cargo, pip, Gradle, Homebrew downloads
  • Build output — DerivedData, dist, target, node_modules
  • Docker — stopped containers, unused images, build cache

These are deleted permanently, and that is the entire point: they regenerate. Putting 14 GB of DerivedData in the Trash frees nothing.

Recoverable

Moved to the Trash

Get it back?Yes. Open the Trash, put it back.

  • Orphaned app data — Application Support, Caches, Preferences
  • Git-ignored files — the ones that aren’t named build directories

Anything cdm had to infer was junk goes to the Trash instead of the void. Inference can be wrong. Restore is one ⌘Z away.

Protected

Never touched

Get it back?It never left. Protected regardless of any rule, including your own.

  • ~/Documents
  • ~/Desktop
  • ~/Downloads
  • ~/Pictures
  • ~/.ssh
  • iCloud Drive
  • App sandboxes, and Apple- or system-owned data
  • Docker named volumes — your local Postgres survives

No rule can reach these — not one you wrote, not one you pulled in with --patterns. The list is enforced below the rules, not by them.

  • Zero telemetry. cdm makes no network calls at all, except fetching its own rule JSON. There is no analytics, no phone-home, no opt-out to hunt for, because there is nothing to opt out of. The one thing that reaches outward is b on the prompt after a clean, which hands the donate link to your browser — nothing is ever sent.
  • --dry-run shows what could be freed and deletes nothing. It’s the recommended first run, and the flag most people never stop using.
  • It is not clever. No heuristics guess at your intent. A path is cleaned because a rule you can open and read says so, or it is not cleaned at all.
  • Riskier items start unchecked. Maven repo, Playwright browsers, crash logs, project folders, orphaned data — all off until you say otherwise.
  • Every run is logged to ~/.cleandevmac/clean.log. What ran, what went, what it weighed.

keys, flags, and the log

The interactive run is the whole product: a list you drive with one hand. Categories sorted biggest-first, safe regenerable caches pre-selected, everything else waiting on you. It opens with what your disk looks like — total, used, free — and closes with what it looks like now.

Keys

↑ ↓ · j k
Move the cursor
space
Toggle the category under the cursor
a · s · n
Select all · only the safe ones · none
enter
Show the exact paths and sizes behind a category
c
Clean — prints the itemized plan, confirm with y
q
Quit, having changed nothing

Then, once a clean has run

r
Scan again, and see what is left
b
Open the donate link in your browser — b as in buy me a coffee
q
Quit — the default, and what every other key does

Flags

-n
Dry run — surfaces what could be freed, deletes nothing
--dry-run
The same thing, spelled out
-p
Include project junk — scans $HOME for git repos, so it takes a while
--patterns
Point cdm at your own rules — a directory or a URL
~/.cleandevmac/clean.log 2026-07-15 09:41:02 scan 5 categories · 32.9 GB found 2026-07-15 09:41:44 confirm 2 categories · 17.0 GB · plan accepted 2026-07-15 09:41:44 delete ~/Library/Developer/Xcode/DerivedData · 6.4 GB 2026-07-15 09:41:51 trash ~/Library/Application Support/Postman · 168 MB 2026-07-15 09:42:03 done 17.0 GB reclaimed · 0 errors

Every run, appended. If cdm removed it, there is a line saying so.

ls rules/

There is no hidden list. Every category cdm knows about is described by a JSON rule file. Add a target, drop one you don’t like, or fork the set entirely — no code changes.

  • index.jsonthe manifest
  • dev-caches.jsondev caches
  • app-caches.jsonapp caches
  • containers.jsondocker
  • project-junk.jsonrepos
  • orphans.jsonorphans
dev-caches.json — one rule, unedited{
  "icon":    "🔨",
  "name":    "Xcode derived data & device support",
  "method":  "rm",
  "default": true,
  "desc":    "Xcode build cache, device-support & documentation
             cache — rebuilt on next build.",
  "paths":   [
    "~/Library/Developer/Xcode/DerivedData",
    "~/Library/Developer/Xcode/iOS DeviceSupport",
    …
  ]
}

method decides the fate — rm for things that regenerate, trash for things you might want back. default decides whether it arrives pre-selected, and desc is the why the tool prints next to it. An optional procs list names the apps that rewrite those caches while running, so cdm can tell you to quit them first. One special kind, android-images, takes its globs from images and drops any system image an AVD still uses. Nothing in a rule file can reach the protected paths. Point cdm at your own set with --patterns <dir-or-url> and it will use yours instead.