dsopt

Rust · ratatui · MIT

Free your disk from
build artifacts

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
single binary no runtime deps interactive TUI scriptable
–8threads
1filesystem walk
0runtime dependencies
✓confirm before delete

What it finds

Two detectors, chosen because they are the biggest and the most disposable.

⬢

JavaScript dependencies

Any directory named node_modules — reinstallable in one command, so the safest gigabytes on the machine to take back.

detector: node_modules · typed as node_modules

⚙

Rust build output

Directories named target — compiled artifacts that cargo build will regenerate on demand.

detector: target · typed as rust_target

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.

How to use it

Five steps from install to reclaimed space.

  1. Install

    One command, one binary: cargo install dsopt. See Install if you would rather grab a prebuilt archive.

  2. Open it

    Run dsopt with no arguments to scan the whole filesystem, or narrow the walk to the directories that actually grow: dsopt ~/code ~/work.

  3. Watch the scan

    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.

  4. Pick what to drop

    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.

  5. Reclaim

    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.

shell
# the whole loop
cargo install dsopt      # install
dsopt ~/code             # scan the places that grow
# space to select · D to remove · Y to confirm

Options

The complete command-line surface. dsopt --help prints the same list.

OptionDefaultWhat it does
ROOTS.../Roots to scan. Pass one or more; each is walked in turn.
--threads <N>4Scanner threads, 1–8. Invalid values exit with code 2.
--listfalsePrint candidates as an aligned table instead of opening the TUI.
--jsonfalseWith --list, emit one JSON object per line.
--updatefalseAsk crates.io for the newest release and reinstall if it is newer.
--forcefalseWith --update, reinstall even when already current.
-h, --helpPrint help and exit.
-V, --versionPrint the version and exit.

Cookbook

shell
# 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'

Keyboard shortcuts

Spaceselect or unselect the highlighted directory
Aselect all
Nselect none
Dremove the selected directories
Rrescan the filesystem
Tscanner thread settings — type a digit, Enter to close
↑↓move · PgUpPgDnHomeEnd also work
?help overlay
Qquit (also Ctrl+C)
Yconfirm removal in the dialog · N or Esc cancels

Docs

Files and environment

Cache file ~/.cache/dsopt/last_scan.json

The last completed scan. Reopening dsopt loads it instantly; press R for a fresh walk.

Environment XDG_CACHE_HOME

Respected when locating the cache; set it to move last_scan.json elsewhere.

Binary ~/.cargo/bin/dsopt

Where cargo install puts it. No config files, no daemons, nothing else written.

Exit codes 0 ok · 1 failure · 2 usage

Scan, terminal, or network failures exit 1; bad flags such as --threads 9 exit 2.

JSON Lines schema

--list --json writes one object per candidate, so the output streams and pipes cleanly.

jsonl
{"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}
FieldTypeMeaning
kindstringnode_modules or rust_target.
pathstringAbsolute path of the candidate directory.
size_bytesnumberRecursive size of the whole subtree, in bytes.

How it works

Restoring what you removed

Delete freely — both detectors are regenerable.

RemovedBring it back with
node_modulesnpm install · pnpm install · yarn · bun install
target/cargo build · cargo check (slower the first time)

Troubleshooting

The scan looks frozen

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.

Nothing was found

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.

A directory refused to delete

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.

I want to start over

Delete ~/.cache/dsopt/last_scan.json. dsopt starts with a fresh scan next time.

Do I need Rust installed?

Only to build from source or to run --update, which shells out to cargo install. Prebuilt archives need nothing.

Safety

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:

These checks are re-evaluated per directory right before deletion, not only when the scan is collected.

Install

Requirements

RouteNeeds
Prebuilt archivemacOS 11+ · Linux (glibc) · Windows 10+. Nothing else.
cargo installRust 1.95 or newer, from rustup.rs.
From sourceRust 1.95+, a C toolchain, and git.

Cargo

The shortest path, and the one --update keeps fresh.

cargo install dsopt

Prebuilt binaries

macOS (Apple Silicon and Intel), Linux x64/ARM64, and Windows x64 archives, with SHA256SUMS, attached to every release.

Browse releases ↗

From source

Clone and install into your cargo bin, or build a debug binary and keep developing.

git clone https://github.com/handyutils/dsopt

Install from source, step by step

shell
git 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

Updating

shell
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