diskr
A fast, macOS-native terminal file explorer and disk analyzer built in Rust.
Why diskr
Most disk usage tools show you a tree of sizes and leave you to figure out the rest. diskr goes further: it tells you what changed, what's reclaimable, and why your free space doesn't match what Finder says.
Fast, lazy scanning. Directory sizing uses getattrlistbulk(2), a macOS syscall that returns attributes for many entries in a single kernel crossing. Where the typical readdir + stat pattern makes one syscall per file, diskr batches them -- 3-10x faster on directories with thousands of small files like node_modules or ~/Library/Caches. Scans run on a thread pool and are started in small batches around the current selection, so opening / or another broad directory does not immediately walk every child subtree. Press S when you want to fill in every missing or stale visible directory size without refreshing the directory or invalidating known fresh sizes. Cached sizes persist between runs; on relaunch diskr replays macOS FSEvents since the previous session to revalidate only the directories that actually changed, so unchanged trees stay fresh without a rescan.
Allocated vs. apparent size. APFS clones, sparse files, and compressed files mean logical size and on-disk size often diverge. diskr tracks both, sorts by allocated size, and shows apparent size when they differ. This is the number you actually care about when reclaiming space.
Reclaimable space detection. diskr --reclaim scans for developer caches, build artifacts, package manager stores, and other space sinks. Every finding is tagged as safe (pure cache, auto-regenerated), regenerable (costs a rebuild or re-download), or risky (may contain irreplaceable data). diskr reports what it finds but never deletes anything in this mode.
Scan baselines and diffing. Save a snapshot with diskr --save, then run diskr --diff days or weeks later to see exactly which directories grew, shrank, appeared, or disappeared. Baselines are stored under ~/Library/Application Support/diskr/ and diffs don't overwrite them.
APFS space accounting. diskr --space explains the gap between df free space and what's actually available to you by surfacing APFS container info, local Time Machine snapshots, and the user-available vs. free-block distinction. It can also thin local snapshots on request (--thin-snapshots), with a dry-run by default.
Package and dependency inspection. The TUI has a dedicated packages pane showing Homebrew, Cargo, pip, npm, and other package managers with per-package sizes. It also finds project dependency directories (node_modules, target, .venv) under the current path and shows how much space each one takes.
macOS-native integration. Reveal any file in Finder with f, open with the default app with O, and delete to Trash (never permanent removal) with d.
Install
Or from a local checkout:
Usage
Launch the interactive TUI:
CLI reports
All report modes support --json for scripting and automation.
Keys
This table mirrors the in-app ? overlay and diskr --help; an exact-key test
fails the build if the documented and runtime bindings drift. Destructive
keys (d, E, x) are highlighted in red in the TUI.
| Key | Action |
|---|---|
| ? | Show full keyboard help |
| q | Quit |
| Esc, Ctrl+C | Focus the Files pane, or close a modal and clear search/filter |
| Up/Down, j/k | Move selection, or scroll the keyboard-help overlay |
| PageUp/PageDown | Move by a page, including in keyboard help |
| Home/End | Jump to first or last item |
| Enter | Open the selected directory, disk, package, or reclaim finding |
| Backspace | Go to parent directory |
| Left/Right, h/l | Switch pane or package view |
| Tab, Shift+Tab | Cycle panes |
| / | Search files in the current directory or filter packages; Enter keeps, Esc clears |
| i | Show details for the selected file, package, or disk |
| . | Toggle hidden files in the Files pane |
| o | Cycle sort mode in the Files pane |
| r | Refresh the current view and rescan all visible directory sizes |
| S | Scan every missing or stale visible directory size in the Files pane |
| B | Save a history baseline for the current Files directory |
| t | Open the top-files list for the selection |
| c | Rename the selected file |
| n | Create a directory |
| v | Toggle a mark on the selected file |
| a | Mark all visible files |
| d | Move the selected item (or all marks) to Trash |
| f | Reveal selected item in Finder |
| O | Open selected item with the default app |
| y | Copy selected item path to clipboard |
| s | Open selected item location in Terminal |
| p | Open packages pane or switch package view |
| u | Toggle the dependency-leaf filter in the packages pane |
| x | Uninstall the selected package |
| R | Re-scan the Reclaim pane |
| E | Empty Trash from the Reclaim pane, when the report lists a Trash finding |
How it works
diskr uses ratatui, crossterm, libc, anyhow, serde_json, and the
Unicode segmentation/width crates. The size cache and history baselines are
persisted as JSON through serde_json. The core scanner calls
getattrlistbulk(2) with a packed attribute list requesting name, object type,
logical size, and allocated size. The kernel fills a buffer with dozens of
entries per call, and diskr parses the packed binary layout directly without
allocating per entry on the hot path. A custom work-stealing thread pool (no
rayon) fans out across subdirectories so sizing is parallelized across cores.
Sizes count regular-file content only -- symlink targets and directories' own
metadata blocks are not added, so totals run slightly under du/Finder, and
directories or entries that cannot be read are flagged as lower bounds rather
than counted as zero. Human terminal output escapes control characters from
filenames and external tools; JSON retains normal structured escaping.
The reclaimable-space detector runs two passes: a fixed-location pass that checks well-known paths (~/Library/Caches, Xcode DerivedData, Homebrew, Docker, etc.) and a bounded recursive pass that finds repeated build-artifact directories (node_modules, target, .venv, __pycache__, .next, .gradle). Both use the same fast bulk scanner for sizing.
APFS space reporting combines statfs(2) for mount-level stats, a narrow parser for diskutil info -plist output to get container-level free space, and tmutil listlocalsnapshots for snapshot discovery.
Notes
diskr is macOS-only. The minimum supported Rust version is 1.88.0.
Planned work lives in ROADMAP.md, and release history lives in CHANGELOG.md. Private maintenance trackers, audit notes, and agent workflow instructions are intentionally kept out of the public repository.
Release Checks
Releases
Releases are automated through GitHub Actions and appear in the GitHub Releases tab.
One-time setup:
- Add a repository secret named
CARGO_REGISTRY_TOKENwith a crates.io API token that has publish access fordiskr.
Release flow:
- Move the
[Unreleased]section ofCHANGELOG.mdinto a new version section dated today (leave[Unreleased]present and empty). - Update
Cargo.tomlto the new crate version. - Refresh
Cargo.lockif needed and push the version bump tomain. - Create and push a matching tag like
v0.1.14.
CI and release jobs reject private maintenance files if they are accidentally tracked again.
When that tag is pushed, the Release workflow will:
- Verify the tag matches the crate version.
- Verify the tagged commit is reachable from
main. - Run the full release validation set.
- Publish the crate to crates.io if that version is not already published.
- Download the published crate and verify it was built from the exact tag commit.
- Create the matching GitHub release once, using generated notes.