A native macOS cache cleaner built in Flutter — four cleanup modes, a live disk tree-map, and a Trash-first deletion policy so you never lose anything you didn't mean to.
Get it now: the latest release ships a
.dmg(drag-to-Applications) and a.zipof theSweep.appbundle. macOS 10.15+, Apple Silicon and Intel.
Cleaner — Light Scrub mode. Risk-tagged caches, one-click reclaim, live total.
Additional screenshots (Tree Map, Splash, Permission prompt) and demo gifs go under
docs/screenshots/anddocs/gifs/. Seedocs/screenshots/CAPTURE.mdfor the exact filenames + sizes the README will pick up — the showcase block expands automatically once they're committed.
macOS quietly hoards gigabytes in caches, derived data, simulator runtimes, and old archives. The built-in tools don't show where the bytes went, and most third-party cleaners either delete too aggressively or hide what they're touching.
Sweep's design rules:
- Show the path, the size, and the risk before anything is touched.
- Trash first, delete second — moves go to Finder's Trash by default; permanent removal is opt-in.
- Privilege only when needed — system caches require admin; everything else runs as your user.
- No network. No telemetry. Disk only.
| Mode | Icon | Targets | Typical reclaim | Risk |
|---|---|---|---|---|
| Light Scrub ✨ | ✨ | ~/Library/Caches, ~/Library/Logs |
100 MB – 1 GB | Safe for daily use |
| Boilwash 🔥 | 🔥 | + Xcode DerivedData, iOS sim caches, /Library/Caches |
1 – 10 GB | Generally safe |
| Sandblast 💥 | 💥 | + /Library/Updates, /var/log, Xcode archives, iOS device backups |
5 – 30+ GB | Advanced — admin required |
| Development 🛠️ | 🛠️ | Homebrew, npm, pip, Gradle, Cargo, Maven, Docker VMs, AVDs, Xcode | 2 – 50+ GB | Mixed — re-downloads on demand |
Exact paths live in lib/data/cleaning_targets.dart. Each entry carries a RiskLevel (safe, moderate, higher) that drives the chip colour and the deletion path.
Boilwash is named for the laundry cycle — hot enough to clean, not hot enough to ruin the fabric. Sandblast is the "I know what I'm doing" mode.
Cache removal funnels through two services:
TrashService— default path. Calls Finder viaosascriptto move the entry to the Trash. Recoverable.CacheRemover— used forRiskLevel.higheritems when the user has granted admin. Runsfind … -mindepth 1 -deleteunderosascript ... with administrator privilegesso the cache parent directory survives (apps that re-create their own cache get unhappy when the parent vanishes).
Sizes come from CacheScanner: privileged scans use du -sk; unprivileged scans walk the tree in Dart and skip what they can't read. Either way, scans are async and the UI stays interactive.
lib/
├── main.dart # Entry — runs SweepApp
├── app.dart # Theme + first-launch gate (splash vs. shell)
├── data/
│ └── cleaning_targets.dart # Hard-coded paths per cleaning mode
├── models/ # CacheTarget, CacheEntry, CleaningLevel, NavSelection, …
├── screens/
│ ├── splash_screen.dart # Aurora intro shown once per machine
│ ├── home_shell.dart # Sidebar + app bar shell, holds NavSelection
│ ├── cleaner_screen.dart # Per-mode cache list + reclaim flow
│ └── tree_map_screen.dart # Live disk drill-down view
├── services/
│ ├── permission_service.dart # osascript-based admin escalation
│ ├── path_resolver.dart # `~`, `$USER`, absolute path expansion
│ ├── cache_scanner.dart # du -sk / fallback walk
│ ├── cache_remover.dart # Privileged empty-in-place
│ ├── trash_service.dart # Default: send to Finder Trash
│ ├── disk_scanner.dart # Tree-map streaming scan
│ ├── disk_stats_service.dart # Total / free space lookup
│ └── first_launch_service.dart # Splash gate marker
├── theme/ # AppTheme + Aurora colour tokens + risk palette
├── utils/ # byte_formatter, splash animation curves
└── widgets/ # Aurora sidebar/app-bar, donut, scan ring, splash bits
brew tap will-march/tap
brew install --cask sweepThe cask pulls the .dmg from the latest GitHub release, drags Sweep.app into /Applications, and prints the Gatekeeper override one-liner. Updates land via brew upgrade --cask sweep. Clean uninstall with brew uninstall --cask --zap sweep — the --zap flag also wipes ~/Library/Application Support/Sweep and the launchd agent plist.
Head to releases/latest for the latest .dmg (drag-to-Applications, ~20 MB) or .zip (raw .app, ~50 MB). Drag Sweep.app to /Applications.
First launch — macOS Gatekeeper will refuse to open it because the build isn't signed with an Apple Developer ID yet. Right-click Sweep.app → Open → Open to override. Subsequent launches work normally.
Sweep will request administrator privileges so it can read system caches under /Library and /var. Decline if you only want user-scope cleaning — everything else still works.
The .app ships a full CLI surface — every GUI feature is also reachable from a terminal, launchd, cron, or ssh. The binary inside the bundle does double duty: with no flags it opens the GUI, with --headless it runs a subcommand and exits.
Three ways, in order of friction:
- Homebrew install (recommended) — the cask drops a wrapper at
$HOMEBREW_PREFIX/bin/sweepautomatically. Just typesweep helpafterbrew install --cask sweep. - DMG install — open Sweep, click the menu bar icon (sparkle in the system bar), pick Install Command-Line Tool…. The action probes
/opt/homebrew/binfirst (no admin needed on Apple Silicon Homebrew); falls back to/usr/local/binwith an admin prompt otherwise. The same menu item flips to "Uninstall" once installed. - Manual — paste this if you'd rather skip the GUI step:
sudo tee /usr/local/bin/sweep >/dev/null <<'SH' #!/bin/sh for app in "/Applications/Sweep.app" "$HOME/Applications/Sweep.app"; do [ -x "$app/Contents/MacOS/Sweep" ] && exec "$app/Contents/MacOS/Sweep" --headless "$@" done echo "Sweep.app not found" >&2; exit 127 SH sudo chmod +x /usr/local/bin/sweep
After install, sweep help should print the full subcommand reference. (/usr/bin is SIP-protected on macOS; we install to /usr/local/bin or /opt/homebrew/bin, both of which are on the default $PATH.)
| Subcommand | What it does |
|---|---|
help |
Print the full CLI help (the same content as below). |
light-scrub |
Empty user caches and logs. Safe for cron / scheduled runs. |
boilwash |
Light Scrub + Xcode DerivedData + iOS simulator caches + system app caches. |
sandblast [--admin] |
Deep clean — adds /Library/Updates, /var/log, Xcode archives, iOS device backups. Add --admin for /Library + /var. |
development |
Build-tool caches: npm, gradle, cargo, Maven, pip, Homebrew, Xcode DerivedData, Docker VMs, AVDs. |
update-defs |
Pull the latest threat signatures from abuse.ch MalwareBazaar. |
scan-threats |
Hash-match /Applications, ~/Downloads, ~/Library/LaunchAgents against local definitions; hits land in History. |
tree-map [path] |
Print the top-20 entries by size at [path] (defaults to $HOME). |
list-apps |
List every .app under /Applications + ~/Applications with bundle ID and total disk impact (bundle + leftovers). |
uninstall <id|name|path> |
Archive the bundle plus every leftover (Caches, Application Support, Containers, etc.) to ~/.Trash and record a restore id. |
exclusions list / add <path> / remove <path> |
Manage the prefix-match exclusion list. Honoured by both cleaner and tree-map. |
schedule status |
Print current schedule + last-run timestamp. |
schedule set <off|daily|weekly|monthly> [--light-scrub] [--threat-scan] [--update-defs] |
Configure the scheduled job. Negate any task with the --no-… form. |
scheduled-job |
Run the configured schedule once — what launchd invokes. |
agent status |
Whether the launchd agent plist is installed. |
agent install |
Write ~/Library/LaunchAgents/dev.willmarch.sweep.scheduler.plist and bootstrap it via launchctl bootstrap gui/$UID. The plist runs Sweep --headless scheduled-job at 03:30 local time on the configured cadence. |
agent uninstall |
bootout + delete the plist. |
launch-items list / remove <plist|label> |
Inspect / disable launchd agents in ~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons. Removed plists are archived to Trash so they're restorable. |
history [limit] |
Print the most recent History entries (default 20). |
restore |
List restorable archive entries from the local restore log. |
restore <id> |
Move every item inside that archive back to its original path. Refuses to overwrite. |
reset-onboarding |
Clear splash / tour / walkthrough markers — next GUI launch replays the full intro. |
Reclaim user caches every night (no GUI needed):
sweep schedule set daily --light-scrub --no-threat-scan --no-update-defs
sweep agent installHeavier weekly job with threat scan and definitions refresh:
sweep schedule set weekly --light-scrub --threat-scan --update-defs
sweep agent installRun the configured job once, right now:
sweep scheduled-jobInspect what's about to happen before scheduling: sweep schedule status, sweep agent status.
Remove an app + every leftover with one command:
sweep uninstall com.tinyspeck.slackmacgap
# Note the restore id printed at the end. To put it back:
sweep restore <id>Audit launch items for adware:
sweep launch-items list # `!` prefix flags suspicious entries
sweep launch-items remove com.adware.example.agentEvery headless / launchd-driven run appends to ~/Library/Application Support/Sweep/logs/headless.log and (when launched by the agent) scheduler.out.log / scheduler.err.log in the same dir. The menu bar's Reveal Logs in Finder opens that folder.
- macOS 10.15+
- Flutter SDK 3.0 or newer (
flutter --version) - Xcode + Command Line Tools (CocoaPods picks these up for the macOS shell)
- Optional: an admin password handy for the Sandblast / system-cache flows
git clone https://github.com/will-march/sweep.git
cd sweep
flutter pub get
flutter run -d macosflutter clean
rm -rf macos/Pods macos/Flutter/ephemeral build .dart_tool
flutter pub get
flutter run -d macosflutter build macos --release
open build/macos/Build/Products/Release/Sweep.appThe signed .app lands in build/macos/Build/Products/Release/. Drag it to /Applications to install.
flutter testUnit tests live under test/. The cache/trash services are deliberately thin wrappers over osascript so they're easy to fake in tests.
On launch, PermissionService fires a single osascript … with administrator privileges call. The macOS auth ticket lasts ~5 minutes, so subsequent privileged operations don't re-prompt during a normal session.
| Lock state | Meaning | What you can clean |
|---|---|---|
| 🔓 Open | Admin granted | Everything in the four modes |
| 🔒 Closed | Standard user | User-scope caches only — no /Library, no /var |
Decline the auth dialog and the app stays useful — just a smaller blast radius.
- Light / Boilwash / Sandblast / Development modes
- Trash-first deletion with admin opt-in
- Live tree-map drill-down
- First-launch splash with Aurora theme
- Scheduled / unattended cleans
- Per-app exclusion lists
- Cleaning history + before/after report
- Localisation
PRs are welcome. Two house rules:
- Don't add a path to
cleaning_targets.dartwithout aRiskLevel. The UI fans out from that field. - Privileged operations must go through
osascriptwith quoted-form escaping — seeCacheRemover._privilegedEmptyfor the pattern. Don't shell out raw paths.
Run flutter analyze and flutter test before opening a PR.
Sweep doesn't talk to the network. Ever. There's no analytics, no crash reporting, no auto-updater. The entire surface area is local disk + Finder + osascript.
MIT. See LICENSE once added — until then, treat the source as MIT-licensed (see commit history for authorship).
Sweep — keep your Mac clean, keep your data.