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.
Expert guidance for building Go CLI applications with Cobra and Viper, covering command-first architecture, configuration management, and in-memory testing.
Builds, reviews, and refactors Go CLIs that manage processes, daemons, job runners, and concurrent workloads, covering signal handling, child process control, and graceful shutdown.