MacLeanup โ a project by Ahsan Mahmood
A safe-by-default macOS cleanup CLI covering 28 cleanup sections with a real dry-run, per-section confirmations, and zero-install delivery via npx.
A cleanup tool is a delete tool with a friendlier name.
That is the whole design problem, and everything else follows from it: how much the tool asks, what it refuses to do unattended, whether an action can be taken back.
MacLeanup covers 28 sections.
Xcode and DerivedData. Gradle. Package-manager caches, a Docker prune, browser caches. Stale node_modules, large dormant files, plus an audit of what has installed itself as a LaunchAgent.
The interesting part is the dry run.
It is a real one. The --dry-run flag routes every destructive call through a no-op helper rather than printing a description of what the tool intends to do, so the code path you rehearse is the code path that runs. A dry run written as a separate set of print statements is a second implementation, and the day the two disagree is the day you find out which one was lying.
Deletion decisions use two clocks.
A file has to be old by access time and by modification time before it becomes a candidate. Either measure on its own will condemn something that is still quietly in use, and the cost of that mistake is not symmetrical with the cost of leaving a stale folder alone.
Removing an application moves it to the Trash rather than calling rm, so the usual recovery is dragging it back out.
System sections are behind sudo. Irreversible sections are excluded from the all-sections flag unless you opt into them by name, which makes the convenient option also the conservative one.
Beyond that sit named profiles: dev, minimal, cache-only, deep, audit. Per-section threshold flags cover anyone who wants to disagree with a default.
The engine is one Bash file of roughly 3,600 lines with zero runtime dependencies. It is published to npm. It runs with npx macleanup, on a machine where nothing has been installed yet.
Being one file is the point.
A tool that deletes things should be short enough to read before you run it, and a dependency tree is a list of other people you have also decided to trust. Inspectable is a claim, and a single script is the only version of that claim a person can actually check in an afternoon.
Nothing is reported anywhere.
There is no telemetry and there are no network calls; logs and JSON run summaries stay in a folder in your home directory, where a run from three months ago is still there to compare against.
There is a window, and it is the part I would point at first.
A native macOS application built with Tauri wraps the same script through its JSON output mode. It reimplements nothing. Two implementations of the same destructive behaviour is how a graphical tool and a command-line tool end up disagreeing about what one flag means, and only one of those is the one anybody tested.
What it cost is the licence and the shape.
MacLeanup is source-available rather than OSI open source, which is a real limit and belongs on the page rather than in a file nobody opens. The single-file engine has its own price: every new section is a destructive path that also has to exist as a no-op path, which is roughly twice the work per feature.
Twice the work, deliberately.
That is why the dry run stays honest. The source is at github.com/aoneahsan/macleanup, and the dry-run flag is the right way to meet it, because everything written above is a claim until you have watched the tool decide to do nothing.
What it does, and what that costs to build
- 28 targeted cleanup sections spanning developer caches, Docker, browser caches, and system maintenance
- Real --dry-run that routes every destructive call through no-op helpers
- Per-section confirmation prompts with deep sections gated behind an explicit opt-in flag
- Two-condition delete gate using both atime and mtime to protect files still in use
- Named profiles (dev, minimal, cache-only, deep, audit) plus per-section threshold flags
- Persistent logs and reports written to ~/.mac-cleanup with JSON run summaries
- Zero telemetry and zero network calls from the cleanup engine
- Native macOS GUI (Tauri) wrapping the same script via its --json output
Built with
- Bash
- Node.js
- npm
- npx
- macOS
- Tauri
- Rust
- React
- Vite
- TypeScript
- Firebase
Worth knowing
- macos
- cleanup
- cli
- bash
- npm
- developer-tools
- disk-space
- maintenance
Who built MacLeanup?
Ahsan Mahmood wrote all of it, engine and interface both. The cleanup engine is one Bash file of roughly 3,600 lines with zero runtime dependencies, published to npm so that it runs with npx macleanup, and the source is at github.com/aoneahsan/macleanup. The native macOS window lives in the same repository and calls that same script. It is source-available rather than OSI open source, which is a licence limit worth knowing before you plan anything on top of it.
What does the dry run actually do?
It runs the real code path with every destructive call routed through a no-op helper. That is the difference between a dry run and a description of one: a mode that prints what a tool intends to do is a second implementation of the tool, and the two can disagree without either of them telling you. Here the sequence you rehearse is the sequence that executes, minus the deletion. Running with --dry-run first is the recommended way to meet the 28 sections, because the output is the same and nothing has happened.
How does it decide a file is dormant?
By two clocks rather than one. A file has to be old by access time and by modification time before it becomes a candidate, because either measure alone will condemn something that is quietly still in use: a file nobody has edited for a year may be read every morning, and a file touched last week may have been written once by an installer and never opened. Thresholds are adjustable per section, and named profiles cover the common shapes: dev, minimal, cache-only, deep and audit.
Is there a graphical version?
Yes, a native macOS application built with Tauri, and it lives in the same repository. It wraps the same Bash script through that script's --json output and reimplements no cleanup logic whatsoever, which is the whole point of it. Two implementations of the same destructive behaviour is how a window and a terminal end up disagreeing about what a flag means, and only one of them is the version anybody tested. The window is a front end. The script is still the product.
Does it report anything back to me or anyone else?
Only to you, on your own machine. There is no telemetry and there are no network calls in the cleanup engine, so nothing about your disk, your applications or your run leaves the laptop. Logs and JSON run summaries are written under a folder in your home directory, which means a run from three months ago is still readable and comparable. The cleanup engine makes no network calls, which is the claim the row itself scopes to the engine. The graphical version reimplements none of that logic; it is the same script with a window in front of it.
https://aoneahsan.com/projects/com.aoneahsan.macleanup