JavaScript dependencies
Any directory named node_modules — reinstallable in one command, so the safest
gigabytes on the machine to take back.
Rust · ratatui · MIT
Every project leaves behind a node_modules or a Rust target/.
dsopt walks your filesystem once, shows you exactly what those directories
cost, and reclaims the gigabytes you forgot you were storing — after asking nicely.
cargo install dsopt
Two detectors, chosen because they are the biggest and the most disposable.
Any directory named node_modules — reinstallable in one command, so the safest
gigabytes on the machine to take back.
Directories named target — compiled artifacts that cargo build
will regenerate on demand.
Nested detectors collapse into their outermost parent, so one removal reclaims the whole subtree
instead of double-counting node_modules buried inside another project's dependencies.
Candidates are deduplicated by (device, inode), which stops macOS volume aliases from
being counted twice.
Five steps from install to reclaimed space.
One command, one binary: cargo install dsopt. See Install
if you would rather grab a prebuilt archive.
Run dsopt with no arguments to scan the whole filesystem, or narrow the walk to
the directories that actually grow: dsopt ~/code ~/work.
The status line streams live folder, file, and byte counters while the parallel walk runs.
Scanning / on a busy machine takes a minute or two — it is not frozen.
Candidates are sorted largest first. Move with ↑ ↓ and mark directories with Space; A selects everything, N clears. The selection bar updates the reclaimable total as you go.
Press D, read the confirmation, and confirm with Y. Deletions run in the
background with per-directory progress. Restoration is on you: npm install or
cargo build rebuilds anything you removed.
# the whole loop
cargo install dsopt # install
dsopt ~/code # scan the places that grow
# space to select · D to remove · Y to confirm
The complete command-line surface. dsopt --help prints the same list.
| Option | Default | What it does |
|---|---|---|
ROOTS... | / | Roots to scan. Pass one or more; each is walked in turn. |
--threads <N> | 4 | Scanner threads, 1–8. Invalid values exit with code 2. |
--list | false | Print candidates as an aligned table instead of opening the TUI. |
--json | false | With --list, emit one JSON object per line. |
--update | false | Ask crates.io for the newest release and reinstall if it is newer. |
--force | false | With --update, reinstall even when already current. |
-h, --help | Print help and exit. | |
-V, --version | Print the version and exit. |
# scan only home projects with more threads
dsopt ~/code --threads 8
# biggest offenders first, no UI — pipe into head
dsopt --list ~/code | head -20
# JSON Lines for a script or a cron job
dsopt --list --json ~/code | jq -r 'select(.size_bytes > 1073741824) | .path'
# total reclaimable across the whole disk
dsopt --list --json | jq -s 'map(.size_bytes) | add / 1e9'
~/.cache/dsopt/last_scan.json
The last completed scan. Reopening dsopt loads it instantly; press R for a fresh walk.
XDG_CACHE_HOME
Respected when locating the cache; set it to move last_scan.json elsewhere.
~/.cargo/bin/dsopt
Where cargo install puts it. No config files, no daemons, nothing else written.
0 ok · 1 failure · 2 usage
Scan, terminal, or network failures exit 1; bad flags such as --threads 9 exit 2.
--list --json writes one object per candidate, so the output streams
and pipes cleanly.
{"kind":"node_modules","path":"/Users/you/code/app/node_modules","size_bytes":412304128}
{"kind":"rust_target","path":"/Users/you/code/api/target","size_bytes":2463961088}
| Field | Type | Meaning |
|---|---|---|
kind | string | node_modules or rust_target. |
path | string | Absolute path of the candidate directory. |
size_bytes | number | Recursive size of the whole subtree, in bytes. |
parallel-disk-usage builds a size tree over each root on a
rayon thread pool; a second pass collects, deduplicates, and sorts candidates. Live counters
travel through atomics to the status line.Delete freely — both detectors are regenerable.
| Removed | Bring it back with |
|---|---|
node_modules | npm install · pnpm install · yarn · bun install |
target/ | cargo build · cargo check (slower the first time) |
It is not. The status line streams folder, file, and byte counters plus the path currently being walked. Raise the thread count with T if the disk is fast and the CPU is idle.
Either you already cleaned up, or you scanned a root without projects under it. Try
dsopt ~/code, and remember the cache may be showing an older scan — press
R to rescan.
The safety re-check runs immediately before every delete. Paths that resolve outside the scan root, the protected system directories, virtual filesystems, and macOS volume aliases are rejected by design. Permissions are the other usual cause.
Delete ~/.cache/dsopt/last_scan.json. dsopt starts with a fresh scan next time.
Only to build from source or to run --update, which shells out to
cargo install. Prebuilt archives need nothing.
Removal is permanent — there is no Trash and no undo, and dsopt says so before it does anything. A directory is only deleted when every one of these holds:
/, /System, /Users, or /Applications/dev, /proc, /sys, or /System/Volumes/DataThese checks are re-evaluated per directory right before deletion, not only when the scan is collected.
| Route | Needs |
|---|---|
| Prebuilt archive | macOS 11+ · Linux (glibc) · Windows 10+. Nothing else. |
cargo install | Rust 1.95 or newer, from rustup.rs. |
| From source | Rust 1.95+, a C toolchain, and git. |
The shortest path, and the one --update keeps fresh.
cargo install dsoptmacOS (Apple Silicon and Intel), Linux x64/ARM64, and Windows x64 archives, with SHA256SUMS, attached to every release.
Browse releases ↗Clone and install into your cargo bin, or build a debug binary and keep developing.
git clone https://github.com/handyutils/dsoptgit clone https://github.com/handyutils/dsopt
cd dsopt
cargo build # debug binary at target/debug/dsopt
cargo test # unit + integration tests
cargo install --path . # install this working copy into ~/.cargo/bin
dsopt --update # check crates.io, reinstall when newer
dsopt --update --force # reinstall even when already current
# or, without Rust on PATH
cargo install dsopt --force