Mole replaces five Mac utilities with one shell script

December 27, 2025

|repo-review

by Florian Narr

Mole replaces five Mac utilities with one shell script

What it does

Mole is a macOS system maintenance CLI that consolidates cache cleaning, app uninstalling, disk analysis, system optimization, and live monitoring into a single mo command. 45k+ stars. One binary, no Electron, no GUI.

Why I starred it

I've bounced between CleanMyMac, DaisyDisk, and ncdu for years without committing to any of them. Mole caught my eye because it replaces all three (plus iStat Menus and AppCleaner) and ships as a Homebrew formula. brew install mole and you're done. The fact that it's mostly shell scripts means you can actually read what it's about to delete before you run it.

How it works

The architecture splits into two layers: shell scripts handle cleanup, uninstall, and optimization; Go handles the performance-sensitive disk analyzer and status dashboard.

The shell layer

The entry point is mole — a Bash script that routes subcommands through lib/core/commands.sh. Every destructive operation sources lib/core/file_ops.sh, which validates paths before deletion. The validation in validate_path_for_deletion() is thorough — it rejects empty paths, relative paths, path traversal (..), dangerous characters, and even resolves symlink targets to check if they point to protected system directories like /System or /usr/bin.

The cleaning modules live in lib/clean/ — separate files for brew caches, app caches, dev tools, system logs, and user data. Each module knows its domain. lib/clean/dev.sh handles Xcode derived data, node_modules, and simulator volumes. lib/clean/apps.sh scans for orphaned application support directories.

What's notable is the protection system in lib/core/app_protection.sh. There are two tiers: SYSTEM_CRITICAL_BUNDLES_FAST uses wildcard patterns like com.apple.* for quick matching during cleanup, while SYSTEM_CRITICAL_BUNDLES is a detailed list for uninstall protection that explicitly enumerates system apps. This distinction matters — it lets you uninstall user-installed Apple apps like Xcode or Final Cut Pro while still protecting Finder, Dock, and Safari.

The whitelist in lib/core/base.sh is equally deliberate. It protects Hugging Face model caches, JetBrains caches, Playwright browsers, Poetry virtualenvs, and Spotlight indexes by default. The comment above the Spotlight entry says it all: "CRITICAL: Removing these will cause system search and UI issues."

The Go layer

The disk analyzer (cmd/analyze/) is where the engineering gets interesting. scanner.go (764 lines) implements a concurrent directory scanner using worker pools sized to CPU count. It uses golang.org/x/sync/singleflight to deduplicate concurrent scans of the same path — if two goroutines try to scan the same directory simultaneously, only one does the work.

var scanGroup singleflight.Group

The scanner maintains two min-heaps (cmd/analyze/heap.go) — one for the top N largest directory entries, one for large files. As it walks the filesystem, it pushes entries onto the heap and evicts the smallest when full. This keeps memory bounded regardless of how many files exist on disk.

The health score calculation in cmd/status/metrics_health.go uses weighted penalties across five dimensions:

healthCPUWeight     = 30.0
healthMemWeight     = 25.0
healthDiskWeight    = 20.0
healthThermalWeight = 15.0
healthIOWeight      = 10.0

It starts at 100 and subtracts penalties based on thresholds. CPU above 70% gets a full penalty; memory above 80% triggers escalation. Battery cycle counts above 500 trigger warnings, above 900 gets flagged as danger. The TUI itself uses Bubble Tea and Lip Gloss for rendering.

Dependencies

The Go side pulls in charmbracelet/bubbletea for the TUI, shirou/gopsutil for system metrics, and cespare/xxhash for hashing. The shell side has zero dependencies beyond standard macOS utilities, with an optional fd recommendation for faster project artifact scanning.

Using it

brew install mole

# Preview what would be cleaned without deleting anything
mo clean --dry-run

# Interactive menu
mo

# Deep clean with debug logging
mo clean --dry-run --debug

# Analyze disk usage (skips external drives by default)
mo analyze

# JSON output for scripting
mo status --json | jq '.health_score'

The --dry-run flag works across all destructive commands. mo analyze moves files to Trash through Finder instead of rm, which is a nice safety net. Vim keybindings (h/j/k/l) work in every interactive view.

Rough edges

The project is shell-heavy — roughly 30+ Bash files with a Go module bolted on for the performance-critical paths. That makes it harder to contribute to than a pure Go or Rust CLI would be. Shell scripts are readable, but the error handling patterns vary between modules.

Localization is baked into lib/core/base.sh via get_brand_name() with hardcoded Chinese app name mappings. It works, but it's brittle — adding a new language means editing a case statement.

There's no --undo or backup mechanism beyond mo analyze using Finder's Trash. If mo clean deletes something you needed, your only recourse is Time Machine.

The test suite uses BATS (38 test files), which is solid coverage for a shell project. But the Go code's test coverage is lighter — scanner_test.go and heap_test.go exist but the status metrics tests are thin.

Bottom line

If you're on macOS and tired of paying subscriptions for cleanup utilities that do less than a shell script can, Mole is worth the brew install. Especially useful for developers drowning in Xcode derived data, node_modules, and simulator caches.

tw93/Mole on GitHub
tw93/Mole