Let's talk about a silent killer in every developer's life. No, it's not the third cup of coffee before noon or that missing semicolon in a 500-line function. It's the node_modules folder from that tutorial you did six months ago and never looked at again.
If you're like me, your "projects" folder is basically a digital graveyard. You've got half-finished React apps, three Rust "hello worlds," and a Python virtual environment that's somehow taking up more space than your entire operating system. My hard drive was screaming for help, and instead of doing the sensible thing (buying a bigger SSD), I did what any self-respecting developer does: I spent weeks building a tool to solve a problem I could have fixed in ten minutes with manual deletion.
Meet TidyUp. It's a CLI tool written in Go that finds all that developer "junk" and helps you kick it to the curb. Here's the story of how it went from a simple script to a multi-threaded, automated cleaning machine running at version 0.7.3.

The "Why" (Or: Why My 1TB Drive Was Crying)
We live in an era of massive dependencies. You install one package to center a div, and suddenly you've got 400MB of stuff in a folder called node_modules. Multiply that by the fifty "test" projects sitting in your Documents folder, and suddenly your 1TB drive is gasping for air like it just ran a marathon.
I wanted something that could:
- Find these bloated folders across my whole drive
- Tell me if I'd actually worked on the project recently (using "anchor files" like
package.jsonorCargo.toml) - Delete them safely without nuking my system
I chose Go for this because it's fast, produces a single binary I can easily share, and handles concurrency like a champ. Plus, writing CLI tools in Go feels like playing with Legos, but for adults who pay taxes.
Phase 1: The "Don't Break Everything" Rule
The first version was simple. Walk through folders, find a node_modules or target folder, and ask to delete it. But there was a problem: safety. Imagine running a cleaner and accidentally deleting a folder that Windows actually needs to, you know, function.
So I built a robust blocklist. TidyUp is hardcoded to stay far away from things like System32, AppData, or your IDE configurations. It's like a Roomba that's smart enough not to go over the edge of the stairs.
Here's how the safety checks work:
var defaultBlocklist = []string{
"System32",
"Windows",
"Program Files",
"AppData",
".vscode",
".idea",
}
But hardcoding isn't flexible enough. What if you have that one special folder you never want touched? That's where the config file comes in.
Phase 2: YAML Configuration (Because Developers Love Config Files)
I added support for a .tidyup.yaml file that lets you customize everything. You can add your own blocklist entries and define custom matchers for different project types.
Here's an example config:
blocklist:
- "DoNotTouch"
- "Backups"
- "ImportantStuff"
matchers:
- name: "Node.js"
target_dir: "node_modules"
anchor_file: "package.json"
- name: "Rust"
target_dir: "target"
anchor_file: "Cargo.toml"
- name: "Python"
target_dir: ".venv"
anchor_file: "requirements.txt"
The "anchor file" concept is crucial here. TidyUp checks when that anchor file was last modified to determine if the project is "stale." If package.json hasn't been touched in 30 days (configurable, of course), then that 400MB node_modules folder is probably safe to delete.
Phase 3: "Why Is This Taking So Long?" (Enter Multi-Threading)
The early version worked, but it was slow. Like, watching-paint-dry slow. Walking through directories one by one is fine when you have 10 projects, but when you have 100? Not so much.
That's when I discovered Go's goroutines and worker pools. Instead of checking directories one at a time, TidyUp now spins up multiple workers that check directories in parallel.
The result? A 10x speed improvement. What used to take minutes now takes seconds. Here's the secret sauce:
// Create a worker pool that uses all your CPU cores
workers := runtime.NumCPU()
results := make(chan DirectoryInfo)
semaphore := make(chan struct{}, workers)
// Workers scan directories in parallel
for _, dir := range directories {
semaphore <- struct{}{} // Acquire worker
go func(d string) {
defer func() { <-semaphore }() // Release worker
scanDirectory(d, results)
}(dir)
}
This is the beauty of Go. Concurrency is baked right into the language, and you get performance boosts almost for free.
Phase 4: Automation (Because I'm Lazy)
Here's the thing: I kept forgetting to run TidyUp. My drive would fill up again, and I'd be back to square one. So I added scheduling.
On Windows, TidyUp uses Task Scheduler:
tidyup schedule --interval daily
On macOS/Linux, it sets up a cron job:
tidyup schedule --interval weekly
Now the tool runs automatically in the background, and I never have to think about disk space again. It's like having a digital janitor who works for free and never complains.
Phase 5: Making It Easy to Install (Scoop and Homebrew)
Building a cool tool is one thing. Getting people to actually use it is another. Nobody wants to clone a repo and run go build just to clean up their disk.
So I set up distribution through package managers:
For Windows (Scoop):
scoop bucket add tidyup https://github.com/004Ongoro/scoop-bucket
scoop install tidyup
For macOS (Homebrew):
brew tap 004Ongoro/tap
brew install tidyup
Or just use Go:
go install github.com/004Ongoro/tidyup@latest
One command, and you're ready to go. No fuss, no mess.
Deep Scan Mode: When You Want to Get Aggressive
Sometimes you don't care about anchor files. Maybe you just want to nuke all node_modules folders regardless of when you last worked on the project. That's where Deep Scan mode comes in.
tidyup clean --path . --deep --force
This mode ignores the anchor file checks and just looks at directory metadata. It's faster and more aggressive, but also riskier. Use with caution, or you might delete something you still need.
The Interactive Cleanup Experience
One of my favorite features is the interactive mode. Instead of blindly deleting everything, TidyUp shows you a list of what it found and lets you pick what to delete:
tidyup clean --path ~/Projects --days 30
You get a nice checkbox list where you can select folders with your arrow keys and spacebar. It's satisfying in a weird, Marie Kondo way. "Does this node_modules folder spark joy? No? Delete."
Lessons Learned: Building Tools People Actually Use
- Safety first, speed second. Nobody cares how fast your tool is if it deletes their system files.
- Make it easy to install. Package managers matter. A lot.
- Configuration > hardcoding. Let users customize behavior with config files.
- Automation wins. If people have to remember to run your tool, they won't.
- Go is amazing for CLI tools. Fast, portable, and great concurrency support.
What's Next?
I'm working on v0.8.0 with some exciting features:
- Better progress indicators for large scans
- Support for custom cleanup scripts
- A web dashboard (because why not?)
- Integration with cloud storage cleanup
Try It Yourself
TidyUp is open source and MIT licensed. Check it out on GitHub:
- Main repo: github.com/004Ongoro/tidyup
- Scoop bucket: github.com/004Ongoro/scoop-bucket
- Homebrew tap: github.com/004Ongoro/homebrew-tap
Got ideas or found a bug? Open an issue! Want to contribute? PRs are always welcome.
Now if you'll excuse me, I need to go clean up my digital graveyard. Again.
Built with Go, caffeine, and an irrational fear of "disk full" errors. Latest version: 0.7.3

