Expand description
§cruftkill — 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 — rewritten in Rust with a parallel async scanner and extended to 17 ecosystems. Single binary, no runtime dependencies.
⚠️
cftdeletes recursively without a recycle bin. Always review the list before pressingd. Run with--dry-runfirst if you want a preview.
§Quick start
cargo install cruftkill
cft # scan current dir with `node` profile
cft ~/Projects # scan a specific directory
cft -p rust ~/code # use the `rust` profile (matches `target/`)
cft -p node -p python ~/code # combine profiles
cft -p all ~/ # every ecosystem at once
cft --dry-run ~/Projects # preview without deleting
cft --no-tui ~/Projects | jq # scriptable NDJSON output§What gets deleted (17 ecosystems)
| Profile | Matches |
|---|---|
node (default) | node_modules, .npm, .pnpm-store, .next, .nuxt, .angular, .svelte-kit, .vite, .nx, .turbo, .parcel-cache, .cache, coverage, .jest, … |
python | __pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .tox, .venv, venv, … |
rust | target |
java | target, .gradle, out |
swift | DerivedData, .swiftpm |
dotnet | obj, TestResults, .vs |
cpp | CMakeFiles, cmake-build-debug, cmake-build-release |
android | .cxx, externalNativeBuild |
ruby | .bundle |
elixir | _build, deps, cover |
haskell | dist-newstyle, .stack-work |
scala | .bloop, .metals, target |
unity | Library, Temp, Obj |
unreal | Intermediate, DerivedDataCache, Binaries |
godot | .import, .godot |
data-science | .ipynb_checkpoints, .dvc, .mlruns, outputs, … |
infra | .serverless, .vercel, .netlify, .terraform, .sass-cache, … |
all | union of every profile above |
§Interactive TUI
| Key | Action |
|---|---|
↑ / k · ↓ / j | move cursor |
d / Space / Enter | open delete confirm |
y / n / Esc | confirm / 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 |
The header surfaces live progress (dirs scanned, releasable space,
reclaimed space) and the modal shows risk reasons for paths that
shouldn’t normally be deleted (~/.config, AppData, .app bundles, …).
§Safety
Two layered guards run before any filesystem mutation:
- Basename guard — the path’s basename must appear in the resolved target list. Catches “I typed the wrong path” mistakes.
- Containment guard — both the scan root and the target path are
canonicalised (symlinks resolved). The canonical target must
starts_withthe canonical root. Catches symlink-escape attacks even if the link is named like a legitimate target.
Underneath, std::fs::remove_dir_all is hardened against symlink
traversal (CVE-2022-21658).
§Library usage
cruftkill is also a library — the cft binary is just a TUI on top.
Import the core module to embed scanning, sizing, risk analysis,
and safe deletion in your own tool.
use cruftkill::core::{scanner, types::ScanOptions};
use cruftkill::core::profiles::resolve_targets;
let targets = resolve_targets(&["node", "rust"]);
let opts = ScanOptions {
targets,
exclude: vec![],
sort_by: None,
perform_risk_analysis: true,
};
let mut handle = scanner::start_scan("/home/me/Projects".into(), opts);
while let Some(found) = handle.results.recv().await {
println!("found: {} (sensitive: {:?})",
found.path.display(),
found.risk_analysis.as_ref().map(|r| r.is_sensitive));
}§Architecture
cft binary (src/main.rs)
│
├── TUI mode ──► src/tui/ (ratatui + crossterm + tokio::select!)
│ │
└── --no-tui ──► run_no_tui → NDJSON
│
▼
src/core/
├── scanner parallel tokio worker pool
├── size refcounted async sum, 60s timeout
├── risk pure path classifier
├── safe_delete basename guard
├── delete canonicalize + remove_dir_all
├── profiles 17 ecosystem profiles
├── sort path / size / age comparators
├── filter case-insensitive substring
├── ignore GLOBAL_IGNORE set (skip-on-walk)
├── types ScanOptions, FolderResult, …
└── error CruftError (thiserror)§Links
- GitHub: https://github.com/xuanphamdev/cruftkill
- crates.io: https://crates.io/crates/cruftkill
- Upstream inspiration: https://github.com/voidcosmos/npkill
§License
MIT.
Re-exports§
pub use crate::core::error::CruftError;pub use crate::core::types::DeleteResult;pub use crate::core::types::FolderResult;pub use crate::core::types::RiskAnalysis;pub use crate::core::types::ScanFoundFolder;pub use crate::core::types::ScanOptions;pub use crate::core::types::SortBy;pub use crate::core::types::SortDirection;