I Built 25 CLI Tools in One Month — Here's Everything I Learned
Over the past month, I built and published 25 CLI tools on npm. Not toy projects — real tools that solve real problems: performance monitoring, dependency auditing, port scanning, environment validation, log processing, API health checking, and more.
Along the way, I discovered patterns that work, patterns that don't, and the non-obvious decisions that separate professional tools from weekend projects. This article distills everything into actionable lessons.
The Tools
Here's what I built, roughly in order:
- websnap-reader — Reader mode for the terminal via Chrome DevTools Protocol
- ghbounty — GitHub bounty aggregator
- devpitch — Developer portfolio generator
- pricemon — Price monitoring CLI
- repo-readme-gen — Auto-generate README files
- gitpulse — Git repository health checker
- depcheck-ai — Smart dependency auditor
- ghprofile-stats — GitHub profile analytics
- urlmeta-cli — URL metadata extractor
- repocard-cli — Terminal repo cards
- gitignore-gen — Auto-detecting .gitignore generator
- api-health — API endpoint monitor
- envcheck-cli — Environment variable validator
- licensegen-cli — LICENSE file generator
- pkgjson-cli — package.json query tool
- perfwatch-cli — Web performance monitor (Lighthouse)
- portscan-cli — Port scanner and process manager
- envguard-cli — CI/CD env var validation
- logstream-cli — Stream-based log processor
- bountywatch-cli — Real-time bounty monitor
- changelog-gen-cli — Changelog from conventional commits
- badge-gen-cli — README badge generator
- npmsearch-cli — npm registry search
- devstats-cli — Cross-platform developer stats
- gitstats-cli — Local git repository analytics
Lesson 1: The 80/20 of Dependencies
Every tool uses the same 3-4 dependencies:
{
"dependencies": {
"commander": "^12.1.0", // Every tool
"chalk": "^5.3.0", // Every tool
"cli-table3": "^0.6.5" // Most tools
}
}
That's it. Commander for argument parsing, chalk for colors, cli-table3 for formatted tables. Occasionally I added ora for spinners or @inquirer/prompts for interactive input. But 90% of tools need nothing more than these three.
Takeaway: Don't over-research dependencies. Pick a standard stack and reuse it.
Lesson 2: The Template That Works
Every tool follows the same structure:
my-tool/
bin/my-tool.js # Entry point with shebang
lib/ # Core logic (separated from CLI)
package.json # With bin, files, engines
README.md
The critical separation: bin/ handles CLI parsing, lib/ handles logic. This means your logic is testable and reusable without the CLI layer.
// bin/my-tool.js — thin CLI layer
import { program } from 'commander';
import { doTheThing } from '../lib/core.js';
program.command('run').action(async (options) => {
const result = await doTheThing(options); // Testable
console.log(formatResult(result)); // CLI-specific
});
Lesson 3: ESM from Day One
Every tool uses "type": "module". No CommonJS, no dual publishing, no compatibility dance. For CLI tools (not libraries), there's zero reason to support require().
The setup: add "type": "module" to package.json. Use import/export. Replace __dirname with import.meta.dirname. Done.
Lesson 4: The package.json That Sells
{
"name": "my-tool",
"version": "1.0.0",
"description": "One clear sentence — what it does",
"type": "module",
"bin": { "my-tool": "./bin/my-tool.js" },
"files": ["bin", "lib"],
"keywords": ["relevant", "searchable", "terms"],
"engines": { "node": ">=18" },
"author": "Your Name <email>",
"license": "MIT"
}
Key fields: bin (makes it installable as a command), files (keeps the package small), keywords (makes it findable), engines (sets expectations).
Lesson 5: --json Flag Is Non-Negotiable
Every command that produces output MUST support --json. This is what makes your tool usable in scripts, CI pipelines, and by other tools. Without it, your tool is interactive-only.
if (options.json) {
console.log(JSON.stringify(results, null, 2));
} else {
printFormattedReport(results);
}
Lesson 6: Exit Codes Are Your CI API
0 = success
1 = tool found problems
2 = invalid input
3 = network error
CI systems read exit codes, not your pretty terminal output. A tool that always exits 0 is useless in pipelines.
Lesson 7: README Template
# tool-name
One sentence description.
## Install
npm install -g tool-name
## Usage
tool-name command [options]
## Examples
(3-5 real examples with output)
## Options
(table of flags)
## License
MIT
That's the entire README. No philosophy, no architecture diagrams, no "why I built this" section. Developers decide to install in 10 seconds.
Lesson 8: The Publishing Checklist
Before every npm publish:
-
npm pack --dry-run— verify no secrets leak -
chmod +x bin/*.js— executable permission - Shebang present:
#!/usr/bin/env node -
filesfield in package.json — whitelist only what's needed - Test
npx your-tool --help— first-run experience
Lesson 9: What Sells (and What Doesn't)
Tools that got the most Dev.to engagement:
- Performance-related (perfwatch, web vitals) — developers care about speed
- GitHub-related (ghprofile-stats, repocard) — developers love their GitHub
- AI-related (AI integration article) — hot topic
- Security-related (credential handling) — appeals to senior devs
Tools that got less interest:
- License generators, .gitignore generators — too commodity
- Log processors — less visual appeal
Lesson 10: Volume Is the Strategy
One tool might fail. 25 tools create a portfolio. One article might not land. 43 articles create a brand. One pitch might get ignored. 40 pitches create statistical certainty.
Every tool I built served multiple purposes:
- A product — sellable on Gumroad
- An article topic — pitched to paid publications
- A portfolio piece — demonstrates expertise for consulting
- An npm package — drives discoverability
The Numbers
After one month:
- 25 tools published on npm
- 52 articles on Dev.to
- 40+ pitches to paid publications
- 180+ article views (and growing)
- $0 revenue (yet — pipeline is loaded)
The revenue will come. The pipeline is built. Every tool, every article, every pitch is a seed planted. Some will grow.
What I'd Do Differently
- Start with the article, not the tool — write the tutorial first, then build the tool to match. The article is the marketing.
- Focus on 5 great tools, not 25 okay ones — depth beats breadth for Gumroad sales.
- Set up Gumroad payments immediately — I built tools before I could sell them.
- Target fewer publications more carefully — 5 tailored pitches beat 40 spray-and-pray.
Getting Started
If you want to build CLI tools for income:
- Solve a problem you have today
- Build it in an afternoon (Commander + chalk + 200 lines)
- Publish to npm (
npm publish --access public) - Write an article about it (2000 words, tutorial-style)
- Pitch the article to a paid publication
- List the tool on Gumroad
- Repeat
The first tool is the hardest. After that, you have a template, a process, and momentum.
Wilson Xu built 25 CLI tools in one month and published them on npm. Follow his journey at dev.to/chengyixu.
Top comments (0)