Find out which CLI tools are eating your disk — and decide what to reclaim.
English · 简体中文
Cleanup tools like CleanMyMac show how much disk your desktop apps use — but they are blind to CLI tools. A CLI tool's footprint is scattered across hidden dotfiles and XDG directories (~/.claude, ~/.npm, ~/.cache/*), so it gets lumped under "Other" or ignored entirely.
CLI Analyzer scans every CLI tool installed on your machine and attributes its total disk footprint (executable + package dirs + data/cache dirs). Every attributed directory is labeled (cache / config / data / old version / …) — but whether it should be removed is your call: the app never judges "safe to delete" on your behalf. One binary, two interfaces: a terminal CLI and a native dark-mode GUI (Wails v2).
| Light theme | Dark theme |
|---|---|
![]() |
![]() |
- Detection — enumerates
$PATHexecutables, resolves symlinks, and classifies each by install source (versioned installer / brew formula / npm package / pipx / pyenv shim / go / cargo / other). The Node.js runtime family (node / npm / npx / corepack / node-gyp) is merged into onenodejsentry instead of showing every command in the same install dir as a separate tool (common on Windows) - Attribution — total footprint = executable + package dir (
Cellar,node_modules,versions/…) + platform data dirs (~/.cache/<name>,~/.config/<name>,~/.local/share/<name>, macOS~/Library/*, Windows%APPDATA%…) - Labeled attribution, your call — every attributed directory is labeled by kind (cache / config / data / old version / backup / toolchain…). The former SAFE/USER hard gate is gone: nothing is blocked, and nothing is deleted without your explicit choice. Default disposal still goes through the built-in trash (recoverable); permanent deletion is always an explicit
--permanent/confirm choice - Built-in trash — deletions go to the app's own trash first (same filesystem, instant), recoverable for 7 days (configurable in Preferences); expired items move to the OS trash or are deleted permanently
- Restore — restore a trashed item from the GUI trash panel or
cli-analyzer trash restore;clean --permanentskips the built-in trash entirely - Usage trends — every scan appends a snapshot; view total/actionable space over time (SVG chart) and the top actionable growers, with a bell reminder when a tool's actionable space exceeds a configurable limit (click it to list the tools and jump to one)
- Tree drill-down — expand an actionable item to its one-level child dirs (
~/.npm→_cacache10G /_npx764M) and dispose of only the selected children. Sub-path deletion passes the same integrity guards (must be a child of an already-scanned, attributed parent) - Built-in updater — checks GitHub Releases on startup for a new version (toggleable in Preferences, with a 4h rate-limit cache); prompts to download with a progress bar, verifies the SHA256 checksum, then opens the installer for you to complete manually. CLI:
cli-analyzer update check. Note: within 4h of a release, a cached check may not yet report it — the prompt appears once the cache refreshes - Official uninstall — don't know the uninstall command? The tool detects the install source (brew / npm / pipx / cargo…), shows the standard command and can run it for you, then detects leftover config/cache dirs. Residue disposal defaults to the built-in trash (recoverable); permanently deleting residue is an explicit choice. System-critical tools are blocked. CLI:
cli-analyzer uninstall <tool> - Localized UI — Simplified Chinese / Traditional Chinese / English; follows the system language by default, switchable in Preferences → Language (applies instantly; macOS native menu applies after restart)
- Two interfaces — CLI (
scan/clean/cache/trash/trends/update/version) + native GUI - Unattributed data dirs — top-level dirs under the data roots that no tool claims (leftovers of removed or never-on-PATH tools) appear in a collapsible "Unattributed data" section; USER-level, move-to-trash only (recoverable), filtered by the non-CLI exclusion system (GUI apps, their data and command-line companions are out of scope)
- Health probing — tools with an unknown version are probed in the background (
--version/-V/--help, 3s timeout, result cache keyed by binary path/size/mtime, GBK-safe on Windows); failures degrade silently to—
Releases: installers for macOS / Windows / Linux are published on the Releases page. Building from source also works.
Updates: starting from the release that ships this feature, the app checks for new versions automatically on startup (disable in Preferences → Update) and prompts you to download when one is available. Downloads land in
~/Downloadsand are verified against the published SHA256 checksums before the installer is opened — installation itself stays with the system flow. macOS builds are currently unsigned, so Gatekeeper may require right-click → Open on first launch of a downloaded copy.
Requirements: Go ≥ 1.26 and npm.
brew install go
export PATH="/opt/homebrew/opt/go/bin:$HOME/go/bin:$PATH"
go install github.com/wailsapp/wails/v2/cmd/wails@v2.13.0
# CLI only
go build -o bin/cli-analyzer .
# GUI app (the same binary also works as a CLI)
wails build # → build/bin/cli-analyzerCross-platform installers (macOS dmg / Windows installer / Linux deb + AppImage) are covered in docs/packaging.md; one-shot macOS packaging via ./scripts/build-dmg.sh.
Mirror note: This project does not depend on any specific Go mirror — the default proxy works everywhere, no config needed. Users on mainland-China networks who hit download timeouts can run once:
go env -w GOPROXY=https://goproxy.cn,directThat setting lives in your own environment (~/.config/go/env), is not committed, and does not affect other developers; restore withgo env -u GOPROXY.
cli-analyzer scan # scan (cached when nothing changed; auto-rescans when files changed)
cli-analyzer scan --refresh --json # force a rescan, JSON output (includes unattributed + probed versions)
cli-analyzer clean # interactive, per-item disposal (into built-in trash)
cli-analyzer clean --dry-run --all # show the plan only, delete nothing
cli-analyzer clean --yes kimi # dispose of all cleanup-kind items for one tool
cli-analyzer clean --all --include-data # batch also includes config/data/state items
cli-analyzer clean --permanent # delete immediately, skip the built-in trash
cli-analyzer trash list # list built-in trash items
cli-analyzer trash restore <id> # restore an item to its original path
cli-analyzer trash empty # empty the built-in trash (permanent)
cli-analyzer trends [days] # usage trends over the last N days (default 30)
cli-analyzer update check # check for a new version (exit: 0 up-to-date / 2 update / 1 error)
cli-analyzer uninstall <tool> # standard uninstall + residue cleanup (built-in trash by default)
cli-analyzer uninstall <tool> --permanent # permanently delete residue (unrecoverable)
cli-analyzer version # show version and install source (e.g. 0.3.8 (darwin, dmg))
cli-analyzer cache --clear # clear the scan cache
cli-analyzer # open the GUIExample scan output:
Tool Cmd Total Actionable Install Source
nodejs 5 10.5 GB 10.5 GB 89.7 MB nodejs
opencode - 8.2 GB 0 B 8.2 GB other
uv - 7.3 GB 7.2 GB 57.0 MB other
codex - 2.0 GB 288.7 MB 1.8 GB other
pip - 1.1 GB 1.1 GB 0 B pip
go - 1.0 GB 753.7 MB 282.3 MB go
pyenv - 759.8 MB 0 B 759.8 MB pyenv
...
Total - 31.5 GB 19.9 GB 11.7 GB -
(The Node.js runtime family — node / npm / npx / corepack / node-gyp — is merged into a single nodejs entry; its detail panel lists every bundled command and binary path, and the ~/.npm cache stays an actionable cache item.)
Disposal model: the app attributes and labels each directory (cache / config / data / old version / backup / toolchain…); deletion is your call. clean --all batches only cleanup-kind items (cache / old-version / backup / download) by default — config/data/state need an explicit --include-data. Old-version disposal always keeps the current version (e.g. the symlink target for claude); the cleaner's integrity guards still refuse system roots, .. paths, the trash root, and the current version path.
Deferred deletion: disposed items are moved into the app's built-in trash first — same filesystem, instant, and recoverable. They stay there for the retention window (default 7 days, configurable in Preferences) and are then purged: by default into the OS trash, or permanently if configured. The GUI status bar shows the trash occupancy and the earliest expiry, so "disposed" and "space released" stay distinct. Use clean --permanent to bypass the built-in trash.
The app no longer auto-deletes anything, so nothing is "refused". These directories keep their risk labels as guidance for when you pick them yourself:
| Path | What to know before disposing |
|---|---|
~/.cache/opencode, ~/.cache/mimocode |
named "cache" but are actually plugin install dirs of opencode-family tools (MCP servers, language servers, skills, package.json manifests) |
| pyenv / rustup old toolchains (non-current) | pip-installed commands hardcode the interpreter path in their shebang (e.g. 16 commands → ~/.pyenv/versions/3.6.15/bin/python3.6); projects may pin a toolchain via rust-toolchain.toml |
| brew non-current Cellar versions | may be depended on by other formulae (e.g. fontconfig); brew cleanup handles them safely |
~/.cache/codex-runtimes/codex-primary-runtime |
the runtime codex is actively using; only codex-runtime-install-* staging dirs are meant to be cleaned |
- The scan core is pure Go stdlib —
GOOS=linux/windows go build ./...cross-compiles directly - macOS: additionally scans
~/Library/{Caches, Application Support, Preferences} - Linux: XDG dirs; GUI build needs
libwebkit2gtk-4.1,libgtk-3 - Windows:
%APPDATA%/%LOCALAPPDATA%; GUI requires building on a Windows host (WebView2)
- Hard-linked files are counted once per path (sizes slightly inflated); future: inode-based dedup
- Scan results are a snapshot; run
--refresh(or hit "Rescan" in the GUI) after files change - Startup/
scanskip a full rescan when nothing changed (mtime-based fingerprint); the fingerprint covers every measured path plus the PATH discovery dirs, so newly installed tools / new binaries in PATH dirs do trigger a rescan. Only in-place edits inside a data dir (parent dir mtime unchanged) may be missed — "Rescan" orscan --refreshalways forces one
main.go # argv dispatch: scan/clean/cache/trash/trends/version → CLI; otherwise Wails GUI
gui/service.go # Wails bindings (the only file importing wails)
internal/scanner/ # discover → classify → attribute → cleanability (pure core)
internal/rules/ # curated + generic attribution rules
internal/platform/ # per-OS data roots & executable detection (build tags)
internal/disk/ # parallel directory size measurement (no du dependency)
internal/cleaner/ # integrity guards + deferred deletion (built-in trash)
internal/trash/ # built-in trash: defer/restore/sweep + per-OS system trash
internal/config/ # local config (retention, expire action, reminder threshold)
internal/history/ # scan snapshots in SQLite for usage trends
internal/cli/ # scan / clean / cache / trash / trends subcommands
Bug reports and feature ideas are welcome — open an issue. Pull requests are appreciated.
MIT © 2026 kevinjoy89

