From go-agent-skills
Builds command-line tools in Go: flag handling, subcommands, stdout/stderr discipline, exit codes, signal handling, and Cobra/Viper usage. Use when building a CLI, adding subcommands, or parsing flags.
How this skill is triggered — by the user, by Claude, or both
Slash command
/go-agent-skills:go-cliThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A good CLI is a well-behaved Unix citizen: flags before magic, stdout
A good CLI is a well-behaved Unix citizen: flags before magic, stdout for data, stderr for diagnostics, exit codes that scripts can trust, and Ctrl+C that actually stops it.
func main() {
ctx, stop := signal.NotifyContext(context.Background(),
os.Interrupt, syscall.SIGTERM)
defer stop()
if err := run(ctx, os.Args[1:], os.Stdin, os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
func run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
fs := flag.NewFlagSet("mytool", flag.ContinueOnError)
fs.SetOutput(stderr)
verbose := fs.Bool("v", false, "verbose output")
out := fs.String("o", "-", "output file (- for stdout)")
if err := fs.Parse(args); err != nil {
return err
}
// ...
_ = verbose
_ = out
return nil
}
signal.NotifyContext makes Ctrl+C cancel the context — every
long operation takes ctx and stops cleanly.run receives args and streams — tests call it directly with
strings.Reader/bytes.Buffer, no subprocess needed.os.Exit only in main (it skips defers).--json or detecting a pipe (!term.IsTerminal(int(os.Stdout.Fd())))
should silence decorations, never change the data.// ✅ Good — result to stdout, progress to stderr
fmt.Fprintf(stderr, "processed %d files\n", n)
fmt.Fprintln(stdout, result)
// ❌ Bad — mixing both into stdout breaks every pipe
fmt.Printf("processing...\ndone: %s\n", result)
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic runtime failure |
| 2 | Usage error (bad flags/arguments) — flag package's convention |
| >2 | Tool-specific, documented meanings (e.g. grep's 1 = no match) |
Map errors to codes in one place (main), not scattered os.Exit
calls. If scripts will branch on distinct failures, define sentinel
errors and translate: errors.Is(err, ErrNoMatch) → 1.
mytool -v convert input.yaml, not mytool --input=input.yaml.-h/-help output is your primary UX.- as "stdin/stdout" for file arguments.--force), never default-on.ps leaks argv).Standard library, fine up to a handful of commands:
switch fs.Arg(0) {
case "serve":
return runServe(ctx, fs.Args()[1:], stdout, stderr)
case "migrate":
return runMigrate(ctx, fs.Args()[1:], stdout, stderr)
default:
fmt.Fprintln(stderr, usage)
return fmt.Errorf("unknown command %q", fs.Arg(0))
}
Adopt Cobra when you need nested commands, generated help/completions, and many flags — the structure pays for the dependency:
var rootCmd = &cobra.Command{Use: "mytool", SilenceUsage: true}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the server",
RunE: func(cmd *cobra.Command, args []string) error {
return serve(cmd.Context(), addr) // RunE returns errors; no os.Exit
},
}
func init() {
serveCmd.Flags().StringVar(&addr, "addr", ":8080", "listen address")
rootCmd.AddCommand(serveCmd)
}
Cobra rules: always RunE (never Run + os.Exit), set
SilenceUsage: true so runtime errors don't dump help, pass
cmd.Context() down. Add Viper only when layered config
(flags > env > file) is a real requirement — for most tools
flag + os.Getenv is enough.
--json flag for machine consumption; table/text default for humans.NO_COLOR
is set.run(ctx, args, stdin, stdout, stderr) pattern — logic testable without subprocesssignal.NotifyContext wired; long operations respect ctx cancellationos.Exit only in main-h output reviewed- accepted for stdin/stdout where files are takennpx claudepluginhub eduardo-sl/go-agent-skills --plugin go-agent-skillsDevelops, extends, and audits Go CLI applications using Cobra, Viper, and urfave/cli — covering command structure, flag handling, configuration layering, version embedding, I/O patterns, signal handling, and shell completion.
Builds, reviews, and refactors Go CLIs that manage processes, daemons, job runners, and concurrent workloads, covering signal handling, child process control, and graceful shutdown.
Use when building, scaffolding, or expanding Go CLIs for APIs, SaaS, Homebrew distribution, or Kong-based command surfaces, following a phased research-to-ship quality loop.