- Shell 100%
makleen #
Find what is eating disk space on a developer's Mac, pick what to clean, and reclaim it safely.
makleen scans your Mac for space it can reclaim: data left behind by apps you uninstalled, tool caches, build folders in projects you haven't touched in a while, and a few large optional items. It shows everything in a checklist, you tick what should go, and it cleans only that. Or ask an AI agent such as Claude Code or Codex to do it through the bundled skill.
It is about 65 KB of zsh with nothing to install or compile, so you can read every line before it runs.
makleen ██████████████░░░░░░ 316.9 GB used · 124.8 GB free
Selected 48.2 GB of 70.6 GB found
[~] Tool caches deleted, rebuild themselves 19.7 GB of 24.3 GB
[x] 4.1 GB Docker build cache
[ ] 3.6 GB npm cache
[-] 993 MB VS Code caches in use
[~] Optional extras deleted, large optional items 28.5 GB of 46.3 GB
❯ [x] 20.5 GB iOS simulators + 2 runtime(s)
[ ] 8.1 GB Android emulators: Medium_Phone, inovus_test optional
────────────────────────────────────────────────────────────────────────────────────
Xcode re-downloads runtimes on demand (Settings → Components) · id: ios-simulators
runs: xcrun simctl shutdown all; xcrun simctl delete all; …
↑↓ move · space select · a section · enter continue · q quit
Contents #
- Requirements
- Quick start
- Use it from an AI agent
- What it finds
- Safety
- The checklist
- Commands and options
- Quarantine
- Logs
- Weekly report
- Configuration
- Compared with other cleaners
- Limitations
- Project layout
- Uninstall
Requirements #
- macOS. makleen relies on macOS tools such as
lsregister,plutil,codesign,mdlsandlaunchctl. It has been tested on macOS 15 (Sequoia) on Apple Silicon. - zsh, which macOS ships as
/bin/zsh. Nothing else needs installing. - The tools whose caches it cleans are optional; anything not installed is skipped.
Quick start #
brew install decoded-cipher/tap/makleen
makleen --dry-run # scan, pick items, see the plan; nothing changes
makleen # scan, pick items, confirm, clean
makleen scan # just the report
A full scan takes about a minute.
Or run it from a clone. To run it from anywhere, link it into a folder on your PATH, and keep the repo
where it is: makleen follows the link to find its lib/ folder.
git clone https://github.com/decoded-cipher/makleen && cd makleen
ln -s "$PWD/makleen" ~/.local/bin/makleen
Use it from an AI agent #
makleen ships an Agent Skill, so you can ask Claude Code, Codex or another agent
that reads SKILL.md skills to free up space. The agent scans, explains what it found, investigates the
check first items, and cleans only what you approve after showing you the dry-run plan.
Link the skill into your agent's skills folder. With Homebrew it's in $(brew --prefix)/share/makleen:
ln -s "$(brew --prefix)/share/makleen/skills/makleen" ~/.claude/skills/makleen # Claude Code
ln -s "$(brew --prefix)/share/makleen/skills/makleen" ~/.agents/skills/makleen # Codex, and other agents that read ~/.agents/skills
From a clone, link "$PWD/skills/makleen" instead. Keep it a link rather than a copy: when makleen isn't
on your PATH, the skill finds it through the link.
The skill drives two options that are also useful in your own scripts: makleen scan --json lists every
item with an ID, and makleen clean --id ID,... picks exactly those items.
What it finds #
Findings are grouped into four sections. Each section is cleaned differently.
| Section | Cleaning does | Selected by default |
|---|---|---|
| Leftovers from uninstalled apps | moves them to the quarantine, so you can restore them | items whose app is confirmed gone |
| Tool caches | deletes them; they rebuild on their own | all |
| Build folders in idle projects | deletes them; reinstall or rebuild to get them back | all |
| Optional extras | deletes them | only the ones listed in EXTRAS |
Leftovers from uninstalled apps #
When you drag an app to the Trash, its data in ~/Library stays behind. makleen looks in
Application Support (including its Caches folder), Caches, Containers, Group Containers and Logs. For apps
that macOS remembers as once installed, it also looks in Preferences, Saved Application State,
HTTPStorages, WebKit, Application Scripts and Cookies.
A folder is reported only when no installed app owns it. makleen compares each folder with the bundle ID, name, executable and vendor of every installed app, matches group containers by the app's team ID, and treats folders named after an installed command-line tool as that tool's data. Apple's own folders are always skipped: a folder is Apple's when macOS ships a framework, app, daemon or launch job of that name, or when it is on a short built-in list. Folders the catalog covers are reported as caches instead. Folders are grouped per app, and apps with less than 1 MB of leftovers are folded into one line.
macOS won't let other apps move folders in ~/Library/Containers unless your terminal has Full Disk
Access. Without it, leftovers there are listed separately as (app container) and can't be selected.
Each leftover is either confirmed or marked check first:
- Confirmed: macOS remembers the app, and none of the places it was installed still exists. Folders with a browser or Electron profile inside, and bundle IDs that no installed app uses, also count.
- Check first: the owner is unclear, for example a folder that may belong to a command-line tool, or one from a vendor that still has another app installed. These start unticked.
Tool caches #
The caches makleen knows are listed in catalog.zsh, one line each. Only the ones that exist on your Mac, and whose tool is installed where that matters, show up.
| Area | Caches |
|---|---|
| JavaScript | npm, npx, Bun, pnpm (unused packages only), Yarn, Deno, Puppeteer, Playwright, Cypress, Electron, node-gyp, Prisma, Expo, Wrangler logs |
| Go, Rust | Go module and build caches, Cargo downloaded crates |
| Python | uv, pip, Poetry, Conda packages |
| JVM | Gradle caches and wrapper distributions, Maven repository (starts unticked: it also holds what you mvn installed) |
| Ruby, PHP, Dart | RubyGems, Bundler, Composer, Dart and Flutter packages |
| Apple and mobile | CocoaPods, Xcode DerivedData and device support files, simulator caches, Swift Package Manager, Android build caches, unavailable simulators |
| Browsers | Chrome, Brave, Edge and Firefox caches only; history, logins and settings stay |
| Editors | VS Code, Cursor, VSCodium and Windsurf caches and superseded extension versions; JetBrains caches of old IDE versions, keeping the newest of each IDE |
| Apps | caches of installed apps in ~/Library/Caches over 50 MB |
| Others | Homebrew old versions and downloads, Docker build cache, Arduino download staging |
Where a tool has its own clean command (npm cache clean, go clean -modcache, brew cleanup,
docker builder prune, …), makleen uses it; other caches are deleted. Items that belong to an app,
such as browser, editor and app caches, can't be selected while that app runs. Cache folders under
10 MB are not listed.
Add your own. Put lines in the same format in ~/.config/makleen/catalog.zsh; they're loaded after
the built-in catalog:
cache "My tool's cache" ~/.mytool/cache
cache "Other tool cache" ~/Library/Caches/othertool run="othertool cache clear" needs=othertool
extra big-models "Downloaded models" ~/ml/models note="downloaded again when needed"
run= cleans with the tool's own command instead of deleting, needs= skips the line when that command
isn't installed, while= locks the item while a matching process runs, note= explains it, and off makes
it start unticked. An extra starts unticked unless its ID is listed in EXTRAS. A path in single quotes
is expanded only when the line is used: '$(go env GOMODCACHE)' runs go only if it's installed.
Folders must be at least two levels inside your home folder (~/.tool/cache, not ~/.tool), and must
not match NEVER_TOUCH; others are never offered.
Build folders in idle projects #
makleen looks for projects in PROJECT_ROOTS. When that is empty, it uses every folder in your home
folder that contains at least two git repositories within three levels. Desktop, Documents and Downloads
are skipped, because reading them makes macOS ask for permission; list them in PROJECT_ROOTS if your
projects live there.
A folder counts as a build artifact only when the file that produces it sits next to it:
| Folder | Must sit next to | Restore with |
|---|---|---|
node_modules |
package.json |
npm install, or pnpm/yarn/bun install, detected from the lock file |
.next .nuxt .svelte-kit .turbo .parcel-cache .angular .expo |
package.json |
the next build or dev run |
.venv venv env |
must contain pyvenv.cfg |
uv sync, poetry install, or recreate it and pip install |
Pods |
Podfile |
pod install |
build |
build.gradle(.kts), pubspec.yaml, or an ios/ folder with a Podfile |
the next build |
.cxx .gradle |
a Gradle build or settings file | the next build |
target |
Cargo.toml or pom.xml |
cargo build or mvn package |
.dart_tool |
pubspec.yaml |
flutter pub get |
.terraform |
a .tf file |
terraform init |
A folder containing any file tracked by git is never touched, whatever its name.
A project is idle when the newest of these is older than INACTIVE_DAYS (60 by default):
- its last commit
- the last change to git's HEAD, which moves on commit, checkout, pull and rebase
- the build folder's own modification time, which moves on install or build
- for folders without git history, the newest file within three levels
Each idle project is one line in the checklist, covering all of its build folders.
Optional extras #
| ID | What it removes | Notes |
|---|---|---|
ios-simulators |
every simulator device and downloaded iOS runtime | Xcode downloads runtimes again when needed (Settings → Components) |
chrome-ai-model |
Chrome's on-device AI model (OptGuideOnDeviceModel) |
also sets the Chrome policy GenAILocalFoundationalModelSettings to 1 so it isn't downloaded again |
android-emulators |
the emulators in ~/.android/avd |
recreate them in Android Studio → Device Manager |
ollama-models |
every pulled Ollama model, with ollama rm |
Ollama must be running; ollama pull brings a model back |
old-node-versions |
Node versions from nvm, fnm, Volta, asdf or mise that nothing needs | keeps each manager's default and newest version, the node on your PATH, and versions pinned by .nvmrc, .node-version or .tool-versions in your projects or home folder; an exact pin keeps that version, a major-only pin the newest of that major |
docker-unused |
stopped containers and images no container uses | data in a stopped container that isn't in a volume is lost |
ml-models |
Hugging Face and PyTorch model downloads (~/.cache/huggingface/hub, ~/.cache/torch) |
downloaded again the next time a model is used |
xcode-archives |
Xcode archives of builds you made | you need them to symbolicate crash reports from builds you shipped |
claude-desktop-vm |
Claude desktop's VM bundles, while Claude.app is installed | downloaded again if you use its agent features |
Extras listed in EXTRAS start ticked; the others are listed unticked. The ones that are plain folders
(ml-models, xcode-archives) are catalog lines, so you can add more of your own the same way.
Apps you don't use #
The full report also lists apps in /Applications not opened in UNUSED_APP_DAYS (90 by default), with
their size. makleen never uninstalls apps.
Safety #
What makleen never does:
- Use
sudo. Everything it deletes or moves itself has to be at least two folders deep inside your home folder, and macOS protects its own system files anyway. - Touch protected paths. Anything matching a
NEVER_TOUCHpattern you set is skipped by every scan and refused by the delete and move steps. Nothing is protected this way by default; a pattern matches anywhere in the path, so keep it specific, for example'*/.claude/*'. - Look in your personal data. Photos, Mail, Messages and the keychain are never scanned or cleaned.
Desktop, Documents and Downloads are only searched for projects if you add them to
PROJECT_ROOTS. - Delete without asking. Nothing happens until you confirm the plan, unless you pass
--yes. - Clean anything in use. Items that belong to a running app, such as a browser, an editor, the iOS Simulator or an Android emulator, can't be selected.
What cleaning costs:
- Caches and build folders come back, but the next install or build re-downloads and takes longer.
- Projects without a lock file can get newer package versions on reinstall, which occasionally breaks an old project.
- iOS simulator runtimes are several GB each to download again.
- Some MCP servers start through
npx. If one is running while the npx cache is cleared, restart it.
Run makleen --dry-run first to see exactly what would happen.
The checklist #
| Key | Action |
|---|---|
↑ ↓ or j k |
move |
PgUp PgDn |
move a page |
g G or Home End |
jump to the top or bottom |
space |
tick or untick the item; on a section header, the whole section |
a |
tick or untick the whole section the cursor is in |
enter |
continue to the plan and confirmation |
q or Esc |
quit; nothing changes |
| Mark | Meaning |
|---|---|
[x] [ ] |
selected, not selected |
[~] |
part of the section is selected |
[-] |
can't be selected: its app is running |
check first |
the owner of this leftover is unclear |
optional |
an extra that isn't listed in EXTRAS |
The panel at the bottom explains the highlighted item and shows its first path or the command it runs. The total at the top updates as you tick items.
Commands and options #
makleen scan, then pick what to clean from the checklist
makleen scan [CATEGORY...] print a report; nothing changes
makleen scan --json the same scan as JSON, every item with its ID
makleen clean [CATEGORY...] the checklist for some categories only
makleen quarantine list quarantined batches
makleen restore BATCH move a batch back where it came from
makleen purge permanently delete batches older than QUARANTINE_DAYS
makleen schedule install|remove set up or remove the weekly report
makleen help | --version
Categories are leftovers, caches, projects and extras. Plain makleen without a terminal, for
example in a pipe, prints the report instead of opening the checklist, and --dry-run without a terminal
shows the plan for the preselected items.
| Option | Effect |
|---|---|
--dry-run |
show the plan and stop |
--only TEXT |
preselect only items whose name or path contains TEXT, including check first and optional ones |
--id ID,... |
clean only the items with these IDs, from scan --json, without the checklist; scans only their categories |
--json |
scan: print every item as JSON |
--yes |
skip the checklist and the confirmation, and clean the preselected items |
--days N |
count projects as idle after N days |
--all |
scan: list every item instead of the largest ten per section; purge: delete every batch |
--older-than N |
purge: delete batches older than N days |
Clean one thing without the checklist:
makleen clean leftovers --only cursor --dry-run # check what matches
makleen clean leftovers --only cursor --yes
makleen clean --id caches/npm-cache,extras/ios-simulators --yes
An item's ID is its category and a slug of its name, such as caches/npm-cache; extras use their own ID,
such as extras/ios-simulators.
Quarantine #
Leftovers are moved, not deleted, to ~/.makleen/quarantine/<date>-<time>/, keeping their original
paths and a MANIFEST of what came from where. They still use disk space until you purge them.
makleen quarantine # list batches with size and contents
makleen restore 20261025-032640 # put a batch back
makleen purge # delete batches older than QUARANTINE_DAYS (14 by default)
makleen purge --all # delete every batch now
Restore never overwrites: if something exists at the original path again, that item stays in quarantine.
Logs #
Every run that changes something writes one log file, named by when it started. Logs go to logs/ in
the repo when you run makleen from a clone, and to ~/.makleen/logs/ otherwise, as with Homebrew:
logs/2026-10-25_03-26-40_clean.log
logs/2026-10-25_03-31-02_restore.log
logs/2026-10-25_03-40-15_purge.log
logs/2026-11-01_10-00-00_report.log
Each item gets a line with its result (done, partial, failed or skipped), followed by the paths it
deleted or quarantined, or the command it ran with that command's output and exit status. Every log
starts with your free space; clean and purge logs also end with it.
03:26:45 done 4.1 GB [caches] Docker build cache
ran: docker builder prune -af
│ Total reclaimed space: 4.091GB
03:27:41 done 9.2 GB [projects] work/mobile-app
deleted ~/Developer/work/mobile-app/node_modules
scan and --dry-run change nothing and write no log. In a clone the logs/ folder is git-ignored because
logs contain your own paths. Set LOG_DIR to keep them elsewhere.
Weekly report #
makleen schedule install # Sundays at 10:00
makleen schedule remove
This adds a launchd agent (dev.makleen.weekly) that runs a full scan, writes the report to
<date>_<time>_report.log in the log folder, and shows a notification with how much can be
reclaimed. It never deletes anything. The agent records the script's location and your PATH when you
install it, so run schedule install again after moving the repo. Homebrew upgrades don't need that.
Configuration #
Copy config.example.zsh to ~/.config/makleen/config.zsh and edit it. It is plain
zsh.
| Setting | Default | Meaning |
|---|---|---|
INACTIVE_DAYS |
60 |
days without use before a project counts as idle |
PROJECT_ROOTS |
auto-detected | folders to search for projects |
PROTECT |
none | globs of projects, or folders of projects, whose build folders are never touched |
EXTRAS |
none | extras that start ticked, by ID |
LEFTOVER_IGNORE |
none | globs of ~/Library entry names never reported as leftovers |
NEVER_TOUCH |
none | globs of paths never reported, moved or deleted, along with everything inside them; a pattern matches anywhere in the path |
UNUSED_APP_DAYS |
90 |
days without opening before an app is listed in the report |
QUARANTINE_DAYS |
14 |
age at which makleen purge deletes a batch |
LOG_DIR |
logs/ in a clone, else ~/.makleen/logs |
where run logs go |
Patterns ignore case, and in PROTECT and NEVER_TOUCH a leading ~ is your home folder. A bare name
matches a folder of that name anywhere: NEVER_TOUCH=(mkcert) protects every mkcert folder and what's in it.
Your own caches and extras go in ~/.config/makleen/catalog.zsh; see Tool caches.
Environment variables: MAKLEEN_CONFIG and MAKLEEN_CATALOG point at a different config or catalog
file, and MAKLEEN_HOME moves the state folder (default ~/.makleen, which holds the quarantine).
Compared with other cleaners #
Sizes are download sizes; sizes and prices were checked in October 2026.
| Tool | What it is | Size | Price | Focus |
|---|---|---|---|---|
| makleen | zsh script | 65 KB | free, MIT | dev caches, idle projects' build folders, leftovers of deleted apps |
| CleanMyMac | app, plus a CLI preview | 346 MB | about $40 a year | general cleanup, uninstaller, malware scan; the CLI adds dev caches and old build folders |
| Mole | CLI, shell and Go | about 8 MB | free, GPL-3.0 | caches, build folders, uninstaller, disk map, system tweaks |
| Pearcleaner | app | 11 MB | free, source-available | uninstaller with an orphaned-file search |
| AppCleaner | app | 3–4 MB | free | uninstaller |
| DaisyDisk | app | 3 MB | $9.99 | map of what uses space; you decide what to delete |
| DevCleaner for Xcode | app with a CLI | 6 MB | free | Xcode's caches only |
| npkill | Node CLI | 135 KB, plus Node | free, MIT | node_modules only |
| kondo | Rust CLI | 1 MB | free, MIT | build folders in 20+ project types |
Where makleen differs:
- Leftovers can be undone. They go to a quarantine you can restore from. Mole's
cleanandpurgedelete permanently. - Idle means unused for a while. A project counts as idle after 60 days without a commit, checkout or build. CleanMyMac's and Mole's build-folder cleaners preselect anything without activity in the last 7 days.
- Nothing to trust blindly. There is no binary and no installer, only shell code, and every command it runs is shown in the checklist and the log.
What it doesn't do: uninstall apps, map disk usage, empty the Trash, clean anything that needs sudo, or
offer a GUI. For those, use one of the tools above.
Limitations #
- Leftover detection is a judgement made from installed apps and what macOS remembers. It has only been tested on one Mac, which is why leftovers are quarantined rather than deleted.
- Without Full Disk Access for your terminal, Mail, Messages, Photos, Safari and the Trash can't be measured, and leftovers in app containers can't be moved. makleen doesn't clean those personal folders anyway, but the report says so. Moving app containers with Full Disk Access hasn't been tested.
- System-wide leftovers outside your home folder, such as files in
/etcor/Libraryleft by an uninstaller, are out of scope because makleen never usessudo. - Docker's build cache and unused images are measured only while Docker is running.
- Catalog lines for tools that weren't on the test Mac (Cargo, Maven, Conda, Composer, Dart and others) were checked against test folders, not real installs of those tools.
Project layout #
| File | What it holds |
|---|---|
makleen |
usage text, settings, config loading and the command dispatcher |
skills/makleen/SKILL.md |
the Agent Skill for Claude Code, Codex and other agents |
catalog.zsh |
the caches and simple extras makleen knows, one line each |
lib/common.zsh |
the item table, sizes, logging, catalog loading, and the safety checks (NEVER_TOUCH, safe_path) |
lib/leftovers.zsh |
detecting data of uninstalled apps |
lib/caches.zsh |
the cache and editor catalog entries, and caches that need more than a path: JetBrains, installed apps, Homebrew, Docker |
lib/projects.zsh |
idle projects and their build folders |
lib/extras.zsh |
optional extras |
lib/scan.zsh |
running the scanners and the default selection |
lib/report.zsh |
the makleen scan report and its JSON form |
lib/checklist.zsh |
the interactive checklist |
lib/clean.zsh |
cleaning what was picked |
lib/quarantine.zsh |
quarantine, restore and purge |
lib/schedule.zsh |
the weekly report |
To add a cache everyone should get, add a line to catalog.zsh. Only caches that need logic, like
keeping the newest version of something, belong in lib/caches.zsh.
Uninstall #
makleen schedule remove # if you installed the weekly report
makleen purge --all # or restore anything you still want first
rm -rf ~/.makleen ~/.config/makleen
Then brew uninstall makleen, or for a clone remove ~/.local/bin/makleen and delete the repo folder,
including logs/ if you don't want the history. Remove the skill links from your agents' skills folders.
License #
The name is mac clean, said quickly.