Building a command-line tool in Go

Everything I have written about building command-line tools in Go, in the order it makes sense to read it. A five-part tutorial that leaves you with something that works, then the decisions underneath it and the mistakes that produced them.

18 posts5-part tutorialDocsSource

Start here

Why this exists, and which library to reach for before you write anything.

  1. go-tool-base: I got tired of reinventing the wheelHow a pile of separate Go CLI plumbing packages became one library, with a typed container for dependencies and a generator on top. First Light
  2. Which CLI library should you start with?Choosing a CLI library in Go and Rust. Go is a ladder you climb from the standard library; Rust asks a different question much earlier. Orienteering

The tutorial, in order

Five parts. Each one leaves you with a tool that runs, so you can stop wherever you like.

  1. Building a CLI with go-tool-base, part 1: scaffold and your first commandInstall gtb, scaffold a releasable Go project, understand the generated and owned file split, then add your first command and run it. Orienteering
  2. Building a CLI with go-tool-base, part 2: configuration your tool can trustConfiguration for a Go CLI: precedence of flags over env over files, per-command embedded defaults, layered config merging and a strict mode. Orienteering
  3. Building a CLI with go-tool-base, part 3: expose your CLI to AI agentsExpose an existing Go CLI to AI agents over MCP without writing any AI code, using built-in commands. The agent reach equals the CLI reach. Orienteering
  4. Building a CLI with go-tool-base, part 4: an AI dungeon masterBuild an AI dungeon master in Go: keep the model to prose only by putting the rules in the system prompt and the mechanics in typed tool calls. Orienteering
  5. Building a CLI with go-tool-base, part 5: a CLI that updates itselfAdd self-update to a Go CLI: resolve releases per platform, compare versions, verify checksums and migrate config on upgrade. Orienteering

Designing the command surface

How the commands get defined, wired and generated, and why a flag is not the same thing as a setting.

  1. Design your whole CLI in one filego-tool-base keeps an entire CLI command tree in one manifest file and generates the wiring from it, without locking you out of hand-written Go. Pioneering
  2. A flag is not a settingReviewing a scaffolder turned up a command name that quietly conflated two different things. A flag and a setting are not the same object. Pioneering
  3. Middleware for CLI commands, not just web serversTiming, auth, recovery and logging are cross-cutting in a CLI too. Wrapping Cobra commands in web-style middleware rather than PreRun hooks. Pioneering
  4. Registering commands without life before mainRust has no init or pre-main phase, so self-registering commands need another mechanism. Using a distributed slice to get the same shape. Pioneering
  5. The scaffolder that won't hand you code that doesn't compileA Rust scaffolder with an AI codegen path that drafts a real command, then refuses to hand it over until it compiles and passes lint. Pioneering
  6. Generate a command from a script or a sentence with go-tool-baseGenerating a Go command from a shell script or a plain sentence, and the repair agent that has to make the result build before you see it. Orienteering

Making it usable by someone else

The parts that decide whether anyone can actually live with the thing: output, secrets, consent, and agents.

  1. Half your users don't have eyesThe other audience for a CLI is the script that greps its output and breaks. Giving every command a JSON output mode with one response envelope. Pioneering
  2. Where should a CLI keep your API keys?Credential storage for a Go CLI: an env-var reference by default, an opt-in OS keychain, and plaintext only as a last resort and banned in CI. Pioneering
  3. Telemetry that asks firstOpt-in telemetry for a command-line tool: off until someone says yes, no personal data, a pluggable backend, and a consent flow that meets GDPR. Pioneering
  4. Your CLI is already an AI toolA well-built CLI already has what an agent needs: named operations, descriptions and typed parameters. Exposing one over MCP takes no AI code. Pioneering

When it goes wrong

One war story, kept because the failure is more instructive than the fix.

  1. The cobra hook I was sure I'd enabledAn audit found that a Cobra option had never been enabled, so the root command hooks had silently not run on any subcommand for months. Pioneering

Where to next

Everything, newest first →