crazydisk
v1.0.2
Published
Terminal disk usage analyzer
Readme
crazydisk
A terminal disk-usage analyzer inspired by DaisyDisk: scan a directory, see its contents as proportional bars, drill down, and delete straight from the UI (safely, to Trash).
See idea.md for the full spec and v1 scope.
Requirements
- macOS
- Node.js 18+ (developed against Node 23)
Install
npm installUsage
npm start [path]path defaults to the current directory. Example:
npm start ~/DownloadsKeybindings
| Key | Action |
| --------------- | ---------------------------------------- |
| ↑ / ↓ | Move selection |
| → / Enter | Open the selected folder |
| ← / Backspace| Go up one level |
| / | Filter entries by name (Enter/Esc to exit) |
| d / x | Delete the selected entry (with confirm) |
| y / n | Confirm / cancel a pending delete |
| u | Rescan the current tree (picks up changes made outside the CLI) |
| q / Ctrl+C | Quit |
Deleted files are moved to ~/.Trash, not permanently removed.
Long directory listings are capped at 15 entries at a time; select the
…N more… row and press →/Enter to reveal the next 15.
Development
npm test # run the BDD (Gherkin) and UI test suites
npm run typecheck
npm run build # compile to dist/, producing the crazydisk binTests live under tests/:
tests/features/*.feature+tests/steps/*.steps.ts— Gherkin specs for the scanner, cache, sort/filter, and navigation logic (via jest-cucumber).tests/scan-cache-integration.test.ts— proves the cache actually skips filesystem work: noreaddircalls on an unchanged re-scan or a deep content-only change, and exactly onereaddirfor the directory whose entries actually changed.tests/app-smoke.test.tsx— renders the real Ink UI (viaink-testing-library) against a temp directory and drives the golden path: scan → sort → drill down → filter → delete.
How the cache works
scanDirectory(path, { useCache: true }) (used by the CLI) persists the
scanned tree to ~/.cache/crazydisk/ (overridable via CRAZYDISK_CACHE_DIR),
keyed by path with each entry's mtime attached. On a re-scan, a directory
whose mtime still matches its cached value is known — per POSIX/APFS
semantics — to have the same set of immediate entries, so readdir is
skipped for it and the cached child list is reused. Cached file entries
under such a directory are trusted outright (no lstat at all); cached
directories are still recursed into, so structural changes anywhere
deeper in the tree are always detected correctly.
Trade-off: a directory's mtime only changes when an entry is added,
removed, or renamed — not when an existing file's content changes in place.
So editing a file without touching its siblings won't have its new size
picked up until something else invalidates that directory (e.g. a sibling
file being added/removed, or the cache being cleared). This favors speed
on large, mostly-static trees (e.g. node_modules, ~/Projects) over
catching every possible in-place edit.
The cache file itself (a full-tree JSON blob) is only rewritten when
something in the tree actually changed, so a no-op re-scan skips the
readdir calls, the per-file lstat calls, and the cache write.
Known limitations / not yet implemented
- Scanning is single-threaded. The original spec called for worker
threads to parallelize the walk; the current
scanDirectorywalks recursively on the main thread. - Whole-disk scanning, a treemap/sunburst view, and cross-platform support remain out of scope for v1 (see idea.md).
