Designing Your Own CLI Tools with Claude
AI generated
Claude
>_
Claude AI · CLI Design · Developer Tooling
Designing Your Own CLI Tools with Claude
Command structure, error messages, and distribution built on established conventions instead of gut feeling

A CLI tool that feels intuitive almost always follows the same conventions established over decades that experienced users unconsciously expect: consistent flag names, predictable exit codes, helpful error messages instead of cryptic stack traces. Claude works well for systematically thinking through exactly these conventions before the first line of code exists, instead of bolting them on after user complaints arrive. This article shows how to deliberately think through command structure, error handling, and distribution strategy for a custom CLI tool with Claude.

13 min read CLI Design Developer Tooling Cobra Click

1. Why CLI design is more than parsing arguments

The technical part of a CLI tool, reading in flags and arguments, is usually done in a few lines with modern libraries like Cobra for Go or Click for Python. The real difficulty lies elsewhere: in designing a mental model that feels immediately familiar to experienced terminal users, because it follows the same patterns as git, docker, or kubectl, instead of inventing its own seemingly obvious shortcuts that end up needing to be looked up anyway.

Claude works well as a sparring partner during this design phase, because it knows the conventions of numerous established CLI tools and can flag it deliberately when a planned command name or flag deviates from them. It's important to ask these questions before the structure gets used publicly, because a published CLI interface can only be changed later at considerable cost and by breaking existing scripts.

2. Thinking through command structure by POSIX conventions

POSIX conventions establish, among other things, that short flags start with a single dash and a letter while long, descriptive flags use a double dash, say -v as a short form for --verbose. Many modern tools extend this scheme with subcommands following the pattern tool verb noun, say docker container ls, which scales well as a tool grows to cover more functional areas over time.

Claude can be asked deliberately to check a planned feature set against these conventions and point out inconsistencies, say when a flag is called --output in one subcommand and --format in another, even though both refer to the same concept. Such small inconsistencies are easy to miss in your own design because you know your own vocabulary, but they're one of the most common sources of frustration for new users encountering an unfamiliar tool for the first time.


# Have a planned command structure checked against established conventions
claude "I'm planning a CLI tool 'shipctl' for deployment automation with \
  subcommands: deploy, rollback, status, logs. Check the planned flags \
  against kubectl and docker conventions. Show all inconsistencies in \
  flag names between subcommands.

3. Designing subcommand trees after the Cobra and Click pattern

Both Cobra in Go and Click in Python rely on a tree of nested commands, where every node can have its own flags, its own help, and its own subcommands, while shared flags like --verbose or --config get defined on the root and inherited by all subcommands. This pattern avoids duplicating the same flag definition in multiple places in the code, while also ensuring consistent behavior across the whole tool.

Claude can design a matching skeleton for such a command tree from an informal description of the desired functionality, including a sensible grouping of related subcommands and a decision about which flags belong at the root level versus only on a specific subcommand. Doing this design work up front saves considerable refactoring effort later, when a tool initially conceived as flat grows to cover more functional areas.


package cmd

import (
	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "shipctl",
	Short: "Deployment automation for internal services",
}

var deployCmd = &cobra.Command{
	Use:   "deploy [service]",
	Short: "Deploys a service to the given environment",
	Args:  cobra.ExactArgs(1),
	RunE:  runDeploy,
}

func init() {
	rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Verbose output")
	rootCmd.PersistentFlags().String("config", "", "Path to config file")
	deployCmd.Flags().String("env", "staging", "Target environment")
	rootCmd.AddCommand(deployCmd)
}

4. Naming flags and their short forms consistently

Short forms of flags are valuable for daily use, but quickly become a trap when the same shorthand means different things in different subcommands, say -f for --force in one place and for --file elsewhere. Users who use a tool over months build muscle memory for particular shorthands, and an inconsistent shorthand then leads to mistakes that only become apparent after execution, sometimes with destructive consequences.

Claude works well for building a complete list of every planned flag across all subcommands and systematically searching it for shorthands assigned twice with different meanings. It's also worth asking which flags trigger dangerous, potentially destructive operations and should therefore deliberately not get a short, easily mistyped form, say a forced deletion that always has to be spelled out explicitly.

5. Generating helpful error messages and --help text

An error message that just states something failed, without explaining why and what the user should do next, ends up costing considerably more support effort overall than a slightly more elaborate message written up front. Claude can be asked deliberately to turn a technical error cause, say a failed authentication against an API, into a message that names the reason and suggests a concrete next step, instead of just printing the raw error code.

--help text also benefits significantly from deliberate rework: instead of just listing flags, it should include one or two realistic usage examples showing how the most common tasks actually get solved. Claude can derive matching example invocations straight from the flag definitions and specifically highlight combinations that, according to existing support requests or GitHub issues, most often cause confusion.


# Before: cryptic error message with no actionable guidance
Error: 401

# After, written by Claude:
Error: Authentication failed (HTTP 401).
  Your API token has either expired or is invalid.
  Run 'shipctl auth login' to sign in again.
  Details: shipctl auth status

6. Designing exit codes and machine readable output consistently

A CLI tool is rarely used exclusively interactively, it's frequently embedded in scripts and CI pipelines too, where the exit code decides success or failure. A consistent convention, where 0 always means success but different nonzero codes distinguish different error classes, say configuration errors versus network errors, lets calling scripts react deliberately to different kinds of failure instead of treating every error the same way by default.

It's also worth adding a consistent --json flag that delivers the same information as the human readable output in structured form, so the tool can reliably be plugged into other automation. Claude can help design a consistent JSON schema across all subcommands, so that, say, the field for an error text carries the same name in every subcommand, instead of differing from command to command.


# Consistent exit codes for script integration
# 0 = success, 1 = general error, 2 = config error, 3 = network error
shipctl deploy myservice --env production --json | jq -r '.status'
echo "Exit code: $?"  # scriptable without any text parsing

7. Weighing distribution: npm, Homebrew, or binary releases

The choice of distribution channel depends heavily on the target audience and doesn't have a one size fits all answer. A tool used mainly by Node.js developers benefits from an npm release, because that audience is already familiar with npm install -g, while the same choice for a tool meant to be used cross platform by non Node developers as well forces an unnecessary Node.js runtime dependency that puts off many potential users.

Claude works well for describing your own target audience and technical context and deriving a reasoned recommendation from it, instead of the distribution channel just following the developer's personal favorite tool. For broadly spread audiences, a combination often works well: statically compiled binary releases via GitHub Releases as the base, complemented by Homebrew for macOS users and optionally an npm package as a thin wrapper for Node.js developers who prefer their usual install path.

8. Generating shell completion for bash, zsh, and fish

Automatic completion of commands, flags, and in some cases even dynamic values like available environment names has become standard among established CLI tools by now and is silently expected by experienced terminal users. Both Cobra and Click ship built in mechanisms to automatically generate completion scripts for common shells straight from the command definition, without maintaining them by hand.

Claude works well for checking whether the generated completion scripts actually cover every subcommand and flag correctly, and for designing the matching callback code for more complex cases, say dynamic completion of environment names pulled from a live API query, tailored to the respective library. This detail work often only gets picked up after the first release, but it improves a tool's perceived quality disproportionately relative to the implementation effort it takes.


# Cobra generates completion scripts directly from the command definition
shipctl completion bash > /etc/bash_completion.d/shipctl
shipctl completion zsh > "${fpath[1]}/_shipctl"
shipctl completion fish > ~/.config/fish/completions/shipctl.fish

9. Distribution channels compared

The following table compares common distribution channels by target platform and effort required.

Channel Target platform Effort Update mechanism
npm Node.js developers, cross platform Low, existing toolchain reusable npm update -g
Homebrew macOS and Linux users Medium, maintaining your own formula brew upgrade
Binary releases (GitHub) All platforms, no runtime required Medium, needs a cross compile pipeline Manual or self update check
Cargo (crates.io) Rust ecosystem, developers with Cargo Low for an existing Rust project cargo install --force
Docker image CI environments, containerized workflows Low, but extra runtime overhead Running docker pull again
apt/deb package Debian and Ubuntu servers High, needs its own repository apt upgrade

Mironsoft

AI-assisted development, agent workflows, and team processes

Using Claude or other AI tools on the team, but without a clear workflow?

We set up AI-assisted development workflows for teams, from CLAUDE.md conventions to subagent strategies to code review processes that combine human oversight with AI speed.

Workflow Setup

Cleanly set up CLAUDE.md, project conventions, and tool permissions for the team.

Agent Strategy

Build subagent and automation workflows for recurring development tasks.

Team Onboarding

Train developers in productive, safe use of AI coding assistants.

10. Summary

Designing CLI Tools with Claude: The Essentials at a Glance

Core idea

A good CLI tool follows established conventions from well known tools instead of its own seemingly obvious shortcuts.

Key pattern

A command tree modeled on Cobra or Click, with inherited root flags and clearly grouped subcommands.

Biggest lever

Helpful error messages with a concrete next step instead of raw error codes.

Distribution rule

The target audience determines the channel, not the developer's personal favorite tool.

11. FAQ: Designing CLI Tools with Claude: The Essentials at a Glance

1Why isn't it enough to just plug in an argument parsing library?
Because the real difficulty lies in designing a consistent mental model that feels familiar to experienced users.
2What do POSIX conventions establish for CLI flags?
Short flags with a dash and a letter, long flags with a double dash and a descriptive name.
3What is the Cobra and Click pattern?
A tree of nested commands with inherited root flags and its own flags per subcommand.
4Why are inconsistent flag shorthands dangerous?
Because users build muscle memory for shorthands, and mistakes only become apparent after execution.
5What makes a good error message?
It names the reason and suggests a concrete next step, instead of just printing a raw error code.
6What is a consistent --json flag for?
It delivers the same information as the human readable output in structured, script friendly form.
7What does the choice of distribution channel depend on?
Primarily on the target audience and their usual installation paths, not on the developer's personal favorite tool.
8Why does shell completion matter for experienced users?
Because it's silently expected and improves a tool's perceived quality significantly.
9How can Claude specifically help with command structure?
By checking planned flags against established conventions and pointing out inconsistencies between subcommands.
10Why is combining several distribution channels worthwhile?
Because different audiences are used to different install paths, and a single channel rarely reaches all of them.