DEV Community

Wilson Xu
Wilson Xu

Posted on

I Built 25 CLI Tools in One Month — Here's Everything I Learned

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:

  1. websnap-reader — Reader mode for the terminal via Chrome DevTools Protocol
  2. ghbounty — GitHub bounty aggregator
  3. devpitch — Developer portfolio generator
  4. pricemon — Price monitoring CLI
  5. repo-readme-gen — Auto-generate README files
  6. gitpulse — Git repository health checker
  7. depcheck-ai — Smart dependency auditor
  8. ghprofile-stats — GitHub profile analytics
  9. urlmeta-cli — URL metadata extractor
  10. repocard-cli — Terminal repo cards
  11. gitignore-gen — Auto-detecting .gitignore generator
  12. api-health — API endpoint monitor
  13. envcheck-cli — Environment variable validator
  14. licensegen-cli — LICENSE file generator
  15. pkgjson-cli — package.json query tool
  16. perfwatch-cli — Web performance monitor (Lighthouse)
  17. portscan-cli — Port scanner and process manager
  18. envguard-cli — CI/CD env var validation
  19. logstream-cli — Stream-based log processor
  20. bountywatch-cli — Real-time bounty monitor
  21. changelog-gen-cli — Changelog from conventional commits
  22. badge-gen-cli — README badge generator
  23. npmsearch-cli — npm registry search
  24. devstats-cli — Cross-platform developer stats
  25. 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Lesson 6: Exit Codes Are Your CI API

0 = success
1 = tool found problems
2 = invalid input
3 = network error
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. npm pack --dry-run — verify no secrets leak
  2. chmod +x bin/*.js — executable permission
  3. Shebang present: #!/usr/bin/env node
  4. files field in package.json — whitelist only what's needed
  5. 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:

  1. A product — sellable on Gumroad
  2. An article topic — pitched to paid publications
  3. A portfolio piece — demonstrates expertise for consulting
  4. 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

  1. Start with the article, not the tool — write the tutorial first, then build the tool to match. The article is the marketing.
  2. Focus on 5 great tools, not 25 okay ones — depth beats breadth for Gumroad sales.
  3. Set up Gumroad payments immediately — I built tools before I could sell them.
  4. Target fewer publications more carefully — 5 tailored pitches beat 40 spray-and-pray.

Getting Started

If you want to build CLI tools for income:

  1. Solve a problem you have today
  2. Build it in an afternoon (Commander + chalk + 200 lines)
  3. Publish to npm (npm publish --access public)
  4. Write an article about it (2000 words, tutorial-style)
  5. Pitch the article to a paid publication
  6. List the tool on Gumroad
  7. 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)