@vivero/poda
v0.1.0
Published
Prune stale node_modules directories to reclaim disk space
Downloads
61
Maintainers
Readme
@vivero/poda
Prune stale node_modules directories to reclaim disk space. Built for developers who maintain many Node.js/TypeScript projects.
Poda — Spanish for "pruning."
Quick Start
pnpm install -g @vivero/poda
poda ~/ProjectsThat's it. Poda scans your projects, shows what's reclaimable, and lets you browse and select what to prune.
What It Does
$ poda ~/Projects
Scanned 47 projects (31.5 GB in node_modules)
🔴 Stale 12.0 GB 32 projects (reclaimable)
🟡 Aging 6.7 GB 18 projects
🟢 Active 12.9 GB 47 projects
── Top reclaimable ─────────────────────────────────────
1.0 GB old-api-server 1 year ago
848 MB react-prototype 3 months ago
690 MB design-system (3 pkgs) 6 months ago
668 MB iot-dashboard 11 months ago
...
? What would you like to do?
> Browse & select projects to clean
Clean all stale projects (12.0 GB)
Show full report
ExitBrowse & select opens an interactive full-screen TUI:
poda — 47 projects, 31.5 GB total
🔴 Stale 12.0 GB (32) 🟡 Aging 6.7 GB (18) 🟢 Active 12.9 GB (47)
[x] 🔴 1.0 GB old-api-server 1 year ago
[x] 🔴 848 MB react-prototype 3 months ago
[ ] 🔴 690 MB design-system (3 pkgs) 6 months ago
> [ ] 🟡 245 MB my-saas-app 2 months ago
[ ] 🟢 198 MB current-project 2 days ago
─────────────────────────────────────────────────────
Selected: 2 projects — 1.8 GB ↑↓ navigate ␣ toggle a stale Enter confirm q quit- Arrow keys — navigate
- Space — toggle selection
- a — toggle all stale projects
- Enter — confirm and proceed to deletion
- q / Esc — abort
Features
- Monorepo-aware: Detects
pnpm-workspace.yamlandpackage.jsonworkspaces, groups workspace packages under their monorepo root instead of listing each one separately - Smart staleness detection: Checks git commit history, package.json mtime, and source file mtimes — takes the most recent signal
- Build directory filtering: Skips
.next,dist,build,.turbo,.cache,.git,.nuxt,.output— no false positives from build artifacts - Minimum size threshold: Hides entries under 1 MB by default (configurable)
- Safe by default: Always confirms before deleting, shows exactly what will be removed
- Dry-run support:
poda clean --dry-runto preview without deleting
Commands
poda [path] (default)
The main workflow. Scans, summarizes, and presents an action menu.
poda # Scan from cwd or config defaultPath
poda ~/Projects # Scan from specified path
poda --top 20 # Show top 20 reclaimable in summarypoda scan [path]
Full detailed report — every project in a table.
poda scan ~/Projects # Full table, sorted by size
poda scan --sort age # Sort by staleness (oldest first)
poda scan --sort name # Sort alphabetically
poda scan --stale-only # Only show stale projects
poda scan --top 10 # Limit to top 10
poda scan --min-size 50 # Only show projects >= 50 MBpoda clean [path]
Batch cleaning with flags for automation.
poda clean --stale # Delete all stale node_modules
poda clean --older-than 6m # Delete anything inactive > 6 months
poda clean --older-than 30d # Inactive > 30 days
poda clean --all --dry-run # Preview deleting everything
poda clean --stale --no-confirm # Skip confirmation (use with care)Duration formats: d (days), w (weeks), m (months), y (years).
poda config
View and edit configuration stored at ~/.poda/config.json.
poda config # Show current config
poda config set defaultPath ~/Projects # Set default scan path
poda config set staleThresholdDays 90 # Change stale threshold
poda config set minSizeMB 10 # Only show projects >= 10 MB
poda config add excludePaths ~/Projects/my-monorepo
poda config remove excludePaths ~/Projects/my-monorepo
poda config reset # Reset to defaultsConfiguration
Stored at ~/.poda/config.json. Created automatically on first run.
| Field | Default | Description |
|-------|---------|-------------|
| defaultPath | "." | Default scan path when no argument given. Supports ~. |
| excludePaths | [] | Paths to skip during scanning (prefix matching). |
| staleThresholdDays | 60 | Days of inactivity before a project is "stale" (🔴). |
| agingThresholdDays | 14 | Days of inactivity before a project is "aging" (🟡). |
| minSizeMB | 1 | Minimum size in MB to show in results. |
Staleness Detection
Poda checks multiple signals per project and uses the most recent:
- Git last commit —
git log -1 --format=%ci - package.json mtime — filesystem modification time
- Source directory mtime — checks
src/,lib/,app/ - Most recent source file — newest
.ts,.js,.tsx,.jsxin source dirs
Classification:
- Active (🟢) — within
agingThresholdDays(default 14) - Aging (🟡) — between aging and stale thresholds
- Stale (🔴) — beyond
staleThresholdDays(default 60)
Architecture
src/
├── cli.ts # Commander setup, default + scan/clean/config commands
├── scanner.ts # Recursive directory walker, finds node_modules
├── analyzer.ts # Size (du -sk), staleness heuristic, concurrency-limited
├── grouper.ts # Monorepo detection, groups workspace packages
├── interactive.ts # Full-screen TUI with scrollable selection list
├── display.ts # Table formatting, compact report, summaries
├── cleaner.ts # Deletion with progress, dry-run, error handling
├── config.ts # ~/.poda/config.json management
├── utils.ts # Pure utility functions (duration parsing, path helpers)
└── types.ts # Shared TypeScript interfacesInstallation
From Source
git clone <repo>
cd poda
pnpm install
pnpm run build
pnpm link --globalDevelopment
pnpm run build # Compile TypeScript via tsup
pnpm run test # Run test suite (167 tests)
pnpm run lint # Type check with tsc --noEmitDependencies
Runtime (4):
commander— CLI argument parsinginquirer— Confirmation prompts and action menuchalk— Terminal colorsora— Spinner during scanning/analysis
Dev (4): typescript, @types/node, tsup, vitest
The interactive TUI uses zero additional dependencies — built with raw ANSI escape codes and Node's readline.
Platform Support
- macOS — Primary target
- Linux — Supported
- Windows — Best-effort (not actively tested)
Design Decisions
Architectural decisions are documented as ADRs in docs/decisions/. Key decisions:
- No nested scanning (ADR-005) — The scanner does not descend into
node_modules. Nested modules are deleted with their parent. - Monorepo grouping (ADR-002) — Workspace packages are grouped, not listed individually.
- Summary-first display (ADR-004) — Overview first, detail on demand.
- Default command is the ritual (ADR-001) —
podaalone = scan → summary → act. - Interactive TUI over checkbox prompt (ADR-008) — Full-screen browsable list with live totals.
- Raw ANSI over frameworks (ADR-008) — No Ink/Blessed, following npkill's minimal approach.
License
MIT
