cruftkill (cft)
██████╗███████╗████████╗
██╔════╝██╔════╝╚══██╔══╝
██║ █████╗ ██║
██║ ██╔══╝ ██║
╚██████╗██║ ██║
╚═════╝╚═╝ ╚═╝
Polyglot dev-cache reaper. Find and delete node_modules, .venv,
target, DerivedData, __pycache__, obj, .gradle, .next,
.turbo and the rest of your build cruft from a fast terminal UI.
Inspired by voidcosmos/npkill — but rewritten in Rust with a parallel async scanner and extended to 17 ecosystems.
⚠️
cftdeletes recursively without a recycle bin. Always review the list before pressingd. Run with--dry-runfirst if you want to preview.
Features (v0.2)
- 🌐 17 hardcoded ecosystem profiles — node, python, rust, java, swift,
dotnet, ruby, elixir, haskell, scala, cpp, unity, unreal, godot, infra,
data-science (or pass
--profile all) - 🔍 Parallel async directory scanner with
CancellationToken - 📏 True on-disk size per folder (Unix
blocks × 512) - 🛡️ Risk analyzer — flags paths inside
~/.config, AppData,.appbundles - 🗑️ Safe delete with two-layer guard (basename + canonicalize containment)
- 🖥️ Two UX modes:
- Interactive TUI (ratatui): navigate, sort by size/name/last-used, delete with confirm, rescan
--no-tuimode: streams NDJSON for scripting / CI pipelines
Install
From crates.io:
From source:
Requires Rust 1.85+ (edition 2024).
Usage
Interactive TUI
Keybinds:
| Key | Action |
|---|---|
↑ / k |
move cursor up |
↓ / j |
move cursor down |
d / Space / Enter |
open delete confirm |
y / Y |
confirm delete |
n / N / Esc |
cancel modal |
s |
toggle sort by size (desc default) |
n |
toggle sort by name (asc default) |
m |
toggle sort by last-used (desc default) |
r / F5 |
rescan |
q, Ctrl-C |
quit |
Scriptable JSON output
|
Each line is a JSON object:
When stdout is not a TTY (e.g. piped or in CI), cft auto-falls-back to
--no-tui mode.
Profiles
Run cft --help or read src/core/profiles.rs for
the full target list. Highlights:
| Profile | Matches |
|---|---|
node (default) |
node_modules, .npm, .pnpm-store, .next, .nuxt, .turbo, .cache, coverage, … |
python |
__pycache__, .pytest_cache, .venv, .tox, .mypy_cache, … |
rust |
target |
java |
target, .gradle, out |
swift |
DerivedData, .swiftpm |
dotnet |
obj, TestResults, .vs |
cpp |
CMakeFiles, cmake-build-debug, cmake-build-release |
unity |
Library, Temp, Obj |
unreal |
Intermediate, DerivedDataCache, Binaries |
godot |
.import, .godot |
data-science |
.ipynb_checkpoints, .dvc, .mlruns, … |
infra |
.serverless, .vercel, .netlify, .terraform, … |
all |
union of every profile |
Add ad-hoc targets with -t:
Safety model
Two layered guards run before any FS mutation:
- Basename guard — the path's basename must appear in the resolved target list. Catches the "wrong path" mistake.
- Containment guard — both the scan root and the target path are
canonicalized (symlinks resolved). The canonical target must
starts_withthe canonical root. Catches symlink escape attacks even if the link is named like a target.
std::fs::remove_dir_all is hardened against symlink-traversal
(CVE-2022-21658)
— Rust does not follow symlinks when removing a directory.
Architecture
cft binary (src/main.rs)
│
├── TUI mode ──► src/tui/ (ratatui + crossterm + tokio::select!)
│ │
└── --no-tui ──► src/main.rs::run_no_tui → NDJSON
│
▼
src/core/
├── scanner (parallel tokio worker pool, unbounded dispatch)
├── size (refcounted async sum, 60s timeout)
├── risk (pure path classifier)
├── safe_delete (basename guard)
├── delete (canonicalize + remove_dir_all)
├── profiles (17 profiles, resolve_targets)
├── sort (path / size / age comparators)
├── filter (case-insensitive substring)
├── ignore (GLOBAL_IGNORE set)
├── types (ScanOptions, FolderResult, …)
└── error (CruftError)
Development
History
This crate was originally published as
nodemoduleskiller v0.1.0
(binary nmk). It was renamed to cruftkill (binary cft) in v0.2.0
because the scope had outgrown "node_modules only" — the tool now wipes 17
different language ecosystems' build cruft from a single command. The
GitHub repository was renamed too; the old URL xuanphamdev/nodemoduleskiller
auto-redirects to xuanphamdev/cruftkill.
Attribution
This project was inspired by — and ported from — voidcosmos/npkill (© voidcosmos, MIT license). Many design decisions (target detection rules, risk-analysis heuristics, profile definitions, behavioural invariants) are preserved verbatim.
License
MIT — see LICENSE.