Building CLI Tools That Don't Suck
Building CLI Tools That Don't Suck
I've been building CLI tools for years now, and I've noticed a pattern: most tools are either a joy to use or make you want to throw your laptop out the window. There's rarely an in-between.
The difference usually comes down to a few principles that are easy to implement but often overlooked.
Good Defaults Win
The best CLI tools work out of the box with zero configuration. If someone runs your tool without any flags, something useful should happen. Don't make them read a man page just to get started.
Here's what I mean. This is how I structure most of my Go CLI tools:
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// Sensible defaults - works without any flags
verbose := flag.Bool("v", false, "verbose output")
output := flag.String("o", "", "output file (default: stdout)")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] <file>\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Process a file and output results.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " %s input.txt # Process file\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s -o out.txt input # Save to file\n", os.Args[0])
}
flag.Parse()
// If no args, show help instead of cryptic error
if flag.NArg() == 0 {
flag.Usage()
os.Exit(1)
}
// ... rest of the tool
}Notice the custom Usage function with examples. That's the secret sauce. Examples are worth a thousand words in documentation.
Progressive Disclosure
Don't dump every option in the main help text. Most users only need 3-4 flags. Hide the advanced stuff behind --help-advanced or group them clearly.
I structure my help like this:
- One-line description of what the tool does
- Basic usage pattern
- The 3-4 most common flags
- A couple of examples
- "For more options, see --help-all"
Fail Gracefully
When something goes wrong, tell the user:
- What happened (not a stack trace)
- Why it happened (if you know)
- What they can do about it
Bad: Error: ENOENT
Good: Error: Config file not found at ~/.myapp/config.yaml. Run 'myapp init' to create one.
The 5-Second Rule
If a user can't figure out how to do the most common task within 5 seconds of running --help, you've failed. Test this with someone who hasn't seen your tool before. Watch them struggle. Fix what confuses them.
That's it. Good defaults, progressive disclosure, graceful failures, and a 5-second test. These aren't revolutionary ideas, but they're the difference between a tool people love and one they tolerate.