Skip to main content

CLI reference

Every flag macleanup accepts, what it does, and when to use it. The flag set is identical however you invoke the tool — npx macleanup, the global mac-cleanup binary and a direct ./mac-cleanup.sh all parse the same arguments.

mac-cleanup [options]

With no options, macleanup prints a one-time welcome (first run on a machine) and drops into the interactive menu, where you choose one section at a time with a confirmation before every destructive operation.

Both --flag value and --flag=value forms are accepted (the = form since 4.5.1).

At a glance​

FlagTypeDefaultPurpose
--allswitchoffRun every safe section in batch
--only "L"string—Run only the listed sections
--exclude "L"string—Skip the listed sections
--profile NAMEstring—Run a named bundle
--yes, -yswitchoffAuto-confirm [y/N] prompts
--i-understand-deepswitchoffWith --yes, allow deep sections (6, 10, 11, 14, 21, 24) and section 25 removal in batch mode
--dry-runswitchoffPreview without deleting
--no-sudoswitchoffSkip every sudo section
--quietswitchoffSuppress info chatter
--jsonswitchoffOne-line JSON run summary on stdout (implies --quiet)
--no-colorswitchautoDisable ANSI colour
--notifyswitchoffmacOS notification on completion
--check-updateswitchoffAsk npm whether a newer version exists
--brew-autoremoveswitchoffAllow brew autoremove in section 3
--threshold Nint100Days idle for "unused apps" (section 21)
--cache-age-days Nint100Cache age threshold (sections 1, 2, 3)
--idle-days Nint100Idle threshold — delete gate (12, 23), highlight (16, 17)
--stale-build-days Nint100Stale build artefact age (section 23)
--large-file-days Nint100Large unused file age (section 24)
--large-file-size-gb Nint1Minimum large-file size in GB (section 24)
--scan-roots "P1:P2"stringautoRoots for sections 23 and 24
--exclude-path PATHstring, repeatable—Never touch PATH or its subtree in 23 / 24
--logs-dir PATHstring~/.mac-cleanup/logsPersistent logs directory
--reports-dir PATHstring~/.mac-cleanup/reportsPersistent reports directory
--no-reportsswitchoffSkip writing per-section reports
--cleanup-logs-on-finishswitchoffDelete this run's log on exit
--listswitchoffList sections and exit
--version, -VswitchoffPrint version and exit
--contactswitchoffPrint the author contact card
--feedbackswitchoffOpen mail client with a prefilled message
--report-issue, --report-bugswitchoffOpen a pre-filled GitHub issue
--statsswitchoffShow run history under ~/.mac-cleanup
--prune-history [N]int90Delete logs and reports older than N days
--uninstall-dataswitchoffRemove the entire ~/.mac-cleanup directory
-h, --helpswitchoffShow help and exit

Selection — what runs​

--all​

Run the safe batch without a per-section prompt. The safe batch is exactly:

0 3 5 7 8 9 15 18 22 26

Caches, logs, temp files and read-only reports. Nothing else.

--all never selects the deep sections — not even --all --yes. To run sections 6, 10, 11, 14, 21 or 24 unattended, select them explicitly and pass both --yes and --i-understand-deep.

mac-cleanup --all # safe batch, batch mode
mac-cleanup --all --yes # unattended safe batch
mac-cleanup --all --dry-run # preview it
Section 13 left the safe batch in 4.5.0

It ran sudo periodic unattended, which is privileged system maintenance rather than cache cleanup. Run it from the menu or with --only 13.

--only "L"​

Run only the listed sections. L is a comma-separated list of numbers 0–27. Whitespace is fine; out-of-range numbers are ignored.

mac-cleanup --only 5 # one section
mac-cleanup --only "1,2,3,4" # the dev-cache quartet
mac-cleanup --only 23 --stale-build-days 90 --dry-run

--exclude "L"​

Skip the listed sections. Applied on top of --all, --only or --profile.

mac-cleanup --profile deep --exclude 14 # deep preset, minus the reboot one
mac-cleanup --all --exclude "10,11"

--profile NAME​

Run a named bundle: dev, minimal, cache-only, deep or audit. Combine with --exclude to subtract. Full detail: Profiles.

mac-cleanup --profile dev --dry-run
mac-cleanup --profile deep --exclude 14,17 --yes

Confirmation and behaviour​

--yes, -y​

Auto-confirm [y/N] prompts. Combine with --all for a fully unattended safe cleanup. The typed-yes confirmation in section 14 is not affected — that gate is independent.

--yes alone never enables the deep sections. --only 21 --yes is refused in batch mode; the tool warns and skips.

mac-cleanup --all --yes # unattended safe batch
mac-cleanup --only 21 --yes --dry-run # preview only
mac-cleanup --only 21 --yes --i-understand-deep # actually uninstalls idle apps

--i-understand-deep​

Required in addition to --yes to let the deepest sections run unattended via --only or a profile that includes them. --all never selects them. The gated set:

SectionWhat it does
6System caches wiped as root
10Empty Trash, including external-volume Trash
11Time Machine local snapshots deleted
14/private/var/folders deep wipe
21App uninstall plus companion data
24Large stale files moved to the Trash

Without it those sections are refused in batch mode, so a mistyped section number cannot trigger an irreversible operation. --dry-run previews are always allowed.

Section 25 still runs as a report in batch mode without this flag. To remove its stale launch-item candidates unattended, supply both --yes and --i-understand-deep; that selects every candidate.

mac-cleanup --only 14 --yes --i-understand-deep
mac-cleanup --profile deep --yes --i-understand-deep
danger
--only 21 --yes --i-understand-deep uninstalls without asking

That combination moves every app idle past --threshold to the Trash with no further prompt. Read Section 21 before running it on a machine you depend on.

--dry-run​

Show what would be cleaned, delete nothing. Every destructive helper checks the flag and prints [dry-run] … instead of executing. Combines with every other flag and always wins.

Since 4.6.1 a dry run also estimates the space that would be freed. Since 4.6.0 it never attempts privilege elevation, so previewing a sudo section does not prompt for a password.

mac-cleanup --dry-run --all
mac-cleanup --only 23 --dry-run

See Preview with --dry-run.

--no-sudo​

Skip every section that needs sudo — useful without admin rights, or to verify what the unprivileged sections achieve alone.

Sudo sections: 6, 7 (system logs), 9 (system updates), 11, 13, 14, 20, 25 (system LaunchAgents), 27 (optional system font-cache clear only).

mac-cleanup --all --no-sudo

--quiet​

Suppress info and note lines; errors and warnings still print. Best with --all --yes for cron output.

--json​

Emit a one-line JSON run summary on stdout. All other output — the human summary, logs, warnings — goes to stderr, so stdout carries nothing but the object. Implies --quiet.

{
"tool": "mac-cleanup",
"version": "4.7.1",
"dry_run": false,
"freed_kb": 1048576,
"disk_free_delta_kb": 1310720,
"elapsed_s": 42,
"sections_done": [3, 5, 7],
"reports": ["/Users/you/.mac-cleanup/reports/orphans-2026-06-08.txt"],
"log_file": "/Users/you/.mac-cleanup/logs/mac-cleanup-2026-06-08.log"
}
FieldMeaning
freed_kbTotal tracked KB freed this run (an estimate under --dry-run)
disk_free_delta_kbChange in free space on /, clamped to ≥ 0
sections_doneSection numbers that completed
reportsReport files written by this run (since 4.6.1 it no longer lists older files in the same directory)
mac-cleanup --all --yes --json | jq .freed_kb

--no-color​

Disable ANSI colour. Auto-disabled on non-TTYs, so you mostly need it when piping to less -R or capturing output into a file.

--notify​

Show a macOS notification when the run finishes, via osascript. No-ops quietly if osascript is unavailable.

--check-update​

Query the public npm registry for the latest published version. Prints a tagged line: [ OK ] when current, [WARN] with the upgrade hint when a newer version exists, and stays silent on network failure (4-second connect timeout, 6-second total; the run is never blocked).

This is the only outbound network call the script can make, it is strictly opt-in, and no user data is sent — a single unauthenticated GET to https://registry.npmjs.org/macleanup/latest.

--brew-autoremove​

Run brew autoremove as part of section 3. Off by default since 4.3.1, because it can uninstall formulae installed as dependencies and now considered unused — in practice node, python or openssl — silently breaking every global tool that depended on them. Opt in only with a well-curated brew setup. See Recovery.

Thresholds — what counts as "old"​

Defaults are deliberately conservative at 100 days. All age checks look at atime and mtime, so a file you merely opened recently survives.

--threshold N​

Days an app must be idle to be flagged in section 21. Default 100. Detection uses five signals in priority order — Spotlight kMDItemLastUsedDate, Saved Application State mtime, Container mtime, Preferences plist mtime, Spotlight kMDItemUseCount. Apps with no usable signal are skipped, deliberately under-flagging to avoid false positives.

--cache-age-days N​

Cache-pruning age for sections 1, 2 and 3. Default 100. A file is deleted only when both its atime and mtime are at least N days old. 0 disables the filter — a full wipe.

mac-cleanup --only 3 --cache-age-days 30
mac-cleanup --only 3 --cache-age-days 0 # full wipe

--idle-days N​

The universal idle threshold for non-cache work, with two distinct roles:

  • Delete gate in sections 12 and 23 — an item is deleted only when it is both unused by any installed tool and untouched for ≥ N days. This is the two-condition rule.
  • Highlight only in sections 16 and 17 — items idle ≥ N days are marked [idle ≥Nd], recent ones (recent). There is no delete gate; both sections are interactive and delete whatever you confirm.

Default 100. In the gated sections, 0 disables the second condition.

--stale-build-days N​

Age threshold for stale build artefacts in section 23 — node_modules, vendor, dist, .next, target, Pods and similar. Falls back to --idle-days. Default 100.

--large-file-days N​

Age threshold for unused large files in section 24. Default 100. Both atime and mtime must be at least N days old.

--large-file-size-gb N​

Minimum size in GB for the section 24 scan. Default 1. Smaller values surface more candidates and slow the scan.

--scan-roots "P1:P2:…"​

Override the auto-detected scan roots for sections 23 and 24. Colon-separated absolute paths.

Auto-detection order: ~/Projects, ~/projects, ~/Code, ~/code, ~/Developer, ~/dev, ~/repos, ~/work, ~/Work, ~/Documents, ~/Desktop, ~/Downloads.

If none exist and no --scan-roots is given, section 23 errors out and asks for one. It will not silently fall back to scanning all of $HOME — that was the v4.3.0 bug.

mac-cleanup --only 23 --scan-roots "$HOME/repos:$HOME/code" --dry-run

--exclude-path PATH​

Never touch PATH or anything under it in sections 23 and 24. Repeatable. Enforced as a literal-path plus subtree guard, on top of the always-on CRITICAL_HOME_DIRS protection.

mac-cleanup --only 23 \
--exclude-path "$HOME/Code/active-project" \
--exclude-path "$HOME/Code/client-work" --dry-run

Output and persistence​

--logs-dir PATH​

Persistent logs directory. Default $HOME/.mac-cleanup/logs. Today's log is mac-cleanup-YYYY-MM-DD.log. Env var: MAC_CLEANUP_LOGS_DIR.

--reports-dir PATH​

Persistent reports directory. Default $HOME/.mac-cleanup/reports. Env var: MAC_CLEANUP_REPORTS_DIR. See Reports and logs.

--no-reports​

Skip writing per-section .txt reports. The session log is still written.

--cleanup-logs-on-finish​

Delete this run's log at exit. The default is to keep every log — one per day, roughly 50 KB each.

Informational commands​

Each of these prints and exits without entering the menu.

--list​

Print every section number and label. The catalogue runs 0–27 (28 sections). Section 27 is menu / --only only and is not in the --all safe batch.

--version, -V​

Print the version and exit.

--contact​

Print the author contact card — email, website, LinkedIn, GitHub, repo, npm.

--feedback​

Open your mail client with a prefilled message to the author. Subject auto-fills; the body includes your macOS version. Nothing is auto-sent — you review and press Send. Falls back to printing the address if open is unavailable.

--report-issue​

Alias --report-bug. Opens a pre-filled GitHub issue including the macleanup version, macOS version and chip, RAM, bash and Node versions, and the most recent log path. Environment info is gathered entirely locally; nothing is transmitted until you click Submit on github.com. If pbcopy is available, the last 50 log lines are copied to your clipboard.

--stats​

A read-only summary of ~/.mac-cleanup/: directory location, log count and size, oldest and newest log, report count and size, the 8 most recent reports.

--prune-history [N]​

Delete logs and reports older than N days from both subdirectories of ~/.mac-cleanup. N defaults to 90. Honours --dry-run.

mac-cleanup --prune-history 30 --dry-run
mac-cleanup --prune-history

--uninstall-data​

Remove the entire ~/.mac-cleanup directory — logs, reports and the first-run marker — after a literal-yes confirmation. Honours --dry-run.

-h, --help​

Print the inline help, with the defaults from your runtime config baked in.

Exit codes​

CodeMeaning
0Run completed, or an informational command exited cleanly
1Platform check failed — not macOS, or bash too old
2CLI parsing error — bad flag, missing argument, unknown profile
127The Node launcher could not spawn bash
signalThe same signal it received, re-raised (Ctrl+C, SIGTERM, …)

SIGINT, SIGTERM, SIGHUP and SIGQUIT propagate from the Node launcher to the bash child, so Ctrl+C inside npx cleanly kills the underlying script.

Common compounds​

GoalFlags
Preview the safe batch--dry-run --all
Unattended cron / launchd--all --yes --quiet --notify
Read-only audit--profile audit --dry-run
Developer caches only--profile dev --cache-age-days 60 --yes
Stale node_modules only--only 23 --stale-build-days 90 --dry-run
Big dormant files only--only 24 --large-file-size-gb 5 --large-file-days 180 --dry-run
Skip everything needing sudo--all --no-sudo
Heavy sweep, skip the reboot one--profile deep --exclude 14 --yes

Next​