Sweep your dev machine clean.
A guided terminal UI for reclaiming disk space eaten by the side effects of being a developer โ node_modules, Python venvs, Rust build artifacts, LLM model files, Docker images, build caches, and more.
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ ๐งน broom โ
โ Sweep your dev machine clean โ
โ โ
โ Scans for: โ
โ โข node_modules & Python venvs โ
โ โข Rust target dirs & Cargo cache โ
โ โข Build output (.next, dist, buildโฆ) โ
โ โข LLM model files (HuggingFace, LM Studio, Ollama) โ
โ โข Package caches (npm, uv, pnpm, Homebrewโฆ) โ
โ โข Docker / Podman images & volumes โ
โ โข Xcode DerivedData & simulators โ
โ โข App caches (Puppeteer, Playwrightโฆ) โ
โ โ
โ [ press enter to start scan ] โ
โ q to quit โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
One morning I opened my Mac and it was almost full. Less than 100 GB free on a 926 GB drive. I ran CleanMyMac โ the usual move โ and it clawed back around 50 GB. Still not enough. Still sitting at 83% full with 711 GB used.
Filesystem Size Used Avail Capacity
/dev/disk3s5 926Gi 711Gi 150Gi 83%
That 150 GB free? That was already after CleanMyMac had done its thing.
CleanMyMac is good at what it does โ app caches, mail downloads, language files. But it doesn't know anything about your dev environment. It doesn't know about the buildx_buildkit_default_state Docker volume sitting at 19 GB. It doesn't know about the 15 GB of HuggingFace models you downloaded for an experiment six months ago. It doesn't know about node_modules in projects you last touched in 2022, or Python venvs from abandoned side projects, or Rust target/ directories from a Solana validator you haven't run in months.
So I went in manually. Ran du -sh on everything. Found 44 GB of Docker build cache. Found Bark TTS, MusicGen, NLLB translation โ LLM models I'd long forgotten about. Found node_modules across dozens of dead projects. Python venvs from 2024. The whole archaeology.
By the end I'd freed another 270 GB on top of what CleanMyMac had already done โ going from 150 GB free all the way to 420 GB free. In one session. Things CleanMyMac couldn't touch because it didn't know to look for them.
The whole time I kept thinking: this should be a tool. Not a script that blindly deletes things, but something guided โ something that shows you what it found, how big it is, how old it is, and lets you decide. Something that runs in under a minute and asks the right questions.
That's broom. I built it to scratch my own itch, and then I put it out there because every developer I know has the same problem. We install things, experiment, move on, and forget. The disk quietly fills up. Eventually something breaks or you get that low-storage warning and spend an afternoon doing archaeology.
broom does the archaeology for you.
brew tap okolilemuel/tap
brew trust okolilemuel/tap
brew install broomThe
brew truststep is a Homebrew 6.x security requirement for all third-party taps. You only need to run it once.
curl -fsSL https://raw.githubusercontent.com/okolilemuel/broom/main/install.sh | bashRequires Go 1.22+.
git clone https://github.com/okolilemuel/broom.git
cd broom
make build # builds ./broom
make install # copies to /usr/local/bin/broombroom # interactive scan โ select โ delete
broom --dry-run # scan and preview without deleting anything
broom --older-than 6m # auto-select items older than 6 months
broom --older-than 1y # supports: 6m, 1y, 90d, 180d, or any Go duration
broom --version
broom --help| Key | Action |
|---|---|
โ / โ or j / k |
Navigate list |
space |
Toggle item selection |
tab |
Toggle all items in current category |
a |
Select all visible items |
A |
Deselect all visible items |
/ |
Enter fuzzy filter mode |
esc |
Exit filter mode |
enter |
Proceed to confirm |
q |
Quit |
| Type | Finds |
|---|---|
| Node modules | node_modules/ directories, skips nested ones |
| Python venvs | .venv/, venv/, env/ โ confirmed as real venvs via bin/python check |
| Rust targets | target/ directories next to a Cargo.toml |
| Build output | .next/, dist/, build/, .turbo/, .parcel-cache/, out/, .output/ |
All project artifacts show the last git commit date so you know exactly how stale they are. Items with no git history are flagged as no git.
| Cache | macOS path |
|---|---|
| npm | ~/.npm |
| uv | ~/.cache/uv |
| pnpm store | ~/Library/pnpm/store |
| Cargo registry | ~/.cargo/registry/src + ~/.cargo/git |
| Homebrew | ~/Library/Caches/Homebrew |
| Gradle | ~/.gradle/caches |
| Maven | ~/.m2/repository |
| Source | What's found |
|---|---|
| HuggingFace | Each model under ~/.cache/huggingface/hub/ listed individually by name and size |
| LM Studio | GGUF files under ~/.cache/lm-studio/models/ |
| Ollama | ~/.ollama/models/ blob store |
| Cache | Location |
|---|---|
| Puppeteer | ~/.cache/puppeteer |
| Playwright | ~/Library/Caches/ms-playwright |
| Solana | ~/.cache/solana |
Works with both Docker Desktop and Podman (they share a backend on macOS).
| Item | Clean action |
|---|---|
| Dangling images | docker image prune --force |
| Stopped containers | docker container prune --force |
| Unused volumes | docker volume prune --force |
| BuildKit / buildx cache | docker buildx prune --force |
| Unused named images (>30 days old) | docker rmi <id> per image |
| Item | Clean action |
|---|---|
| DerivedData | rm -rf |
| Build Archives | rm -rf |
| iOS Device Support | rm -rf |
| Unavailable simulators | xcrun simctl delete unavailable |
Always safe to run --dry-run first. broom scans everything, shows you what it found, walks you through the full selection flow, and prints a summary โ without touching a single file.
broom --dry-runThe UI shows a [DRY RUN] badge throughout and the done screen says "Would free X GB" instead of "Freed X GB".
The --older-than flag pre-selects items based on age so you don't have to pick through everything manually:
broom --older-than 6m # pre-select anything last committed >6 months ago
broom --older-than 1y # pre-select anything >1 year oldItems with no git history are always auto-selected when this flag is set โ they're almost always throwaway experiments or demo projects. You can still deselect anything before confirming.
Create ~/.config/broom/ignore to permanently skip paths during scanning:
# broom ignore file โ one path prefix per line, ~ is expanded
~/Projects/active-client
~/Projects/company-monorepo
~/work/do-not-touch
broom runs all scanners concurrently in separate goroutines and streams results into the UI in real time โ you see items appear as they're found rather than waiting for a full scan to complete.
Deduplication uses a shared sync.Map keyed on the resolved filesystem path (filepath.EvalSymlinks), so symlinks and overlapping search roots never produce duplicate entries.
Each item carries its own CleanFunc โ Docker items run targeted prune commands, Xcode simulators run xcrun simctl delete unavailable, everything else is os.RemoveAll. The --dry-run flag skips all clean functions without changing anything else about the flow.
Tag a version and the CI handles everything:
git tag v1.2.0
git push origin v1.2.0The workflow:
- Builds all 4 platform binaries (darwin/linux ร arm64/amd64)
- Creates a GitHub Release with binaries attached
- Downloads the binaries, computes SHA256 checksums, and commits an updated formula to the homebrew-tap repo automatically
brew upgrade broom picks it up for all users within minutes.
| Platform | Status |
|---|---|
| macOS (Apple Silicon) | โ Full support |
| macOS (Intel) | โ Full support |
| Linux (arm64) | โ Supported (Xcode scanner is a no-op) |
| Linux (amd64) | โ Supported (Xcode scanner is a no-op) |
| Windows | ๐ Planned |
Pull requests are welcome. A few areas where contributions would be especially useful:
- Linux paths โ Linux has different cache locations for pnpm, pip, etc. Add them to
internal/platform/linux.go. - Windows support โ
internal/platform/windows.godoesn't exist yet. ThePlatforminterface makes it straightforward to add. - New scanners โ Go module cache, Android SDK, Terraform providers, pip download cache, etc. Each scanner is a small self-contained file in
internal/scan/.
To add a new scanner, implement the scan.Scanner interface and register it in internal/ui/model.go's startScanning():
type Scanner interface {
Name() string
Scan(ctx context.Context, roots []platform.Root, sc ScanContext, out chan<- Item)
}MIT โ see LICENSE.
Built by Lemuel Okoli. If broom saved you disk space, consider giving it a โญ.