Purpose
Build robust Go CLI tools using Cobra for command structure, Viper for config, and idiomatic Go patterns for I/O and error handling.
When to use this skill
- creating a new Go CLI binary with subcommands
- wiring Cobra commands with Viper config file and env var binding
- adding
--output json|table|textformatting - structuring a multi-command Go CLI project
Do not use this skill when
- building an interactive terminal UI — prefer
bubbletea-go - building a Python CLI — prefer
cli-development-python - only writing shell scripts — prefer
bash
Procedure
- Scaffold with Cobra-CLI — run
cobra-cli initandcobra-cli add <cmd>to generate command files. - Define root command — set
PersistentPreRunEon root for global setup (config loading, logger init). - Bind flags to Viper — call
viper.BindPFlag("key", cmd.Flags().Lookup("flag"))ininit(). - Load config — use
viper.SetConfigName(".myapp"),viper.AddConfigPath("$HOME"),viper.AutomaticEnv(). - Structure output — accept
--outputflag; useencoding/jsonfor JSON,text/tabwriterfor tables. - Handle errors idiomatically — return
errorfromRunE, let Cobra print usage on bad args. Usefmt.Errorf("verb: %w", err). - Add completions — Cobra auto-generates
completionsubcommand; add customValidArgsFunctionfor dynamic completions. - Build and test —
go build -ldflags "-X main.version=$(git describe)"for version embedding; test withcobra.Command.ExecuteC().
Project layout
myapp/
cmd/
root.go # root command + global flags
serve.go # subcommand
migrate.go # subcommand
internal/
config/config.go # Viper wrapper
output/output.go # JSON/table formatter
main.go # cmd.Execute()
.myapp.yaml # default config
Key patterns
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Does the thing",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
viper.SetConfigName(".myapp")
viper.AddConfigPath(".")
viper.AutomaticEnv()
viper.SetEnvPrefix("MYAPP")
_ = viper.ReadInConfig()
return nil
},
}
func init() {
rootCmd.PersistentFlags().StringP("output", "o", "text", "Output format: text|json|table")
viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output"))
}
Decision rules
- Use
RunE(notRun) so errors propagate toos.Exit(1)via Cobra. - Bind every flag to Viper so env vars and config files work as overrides.
- Write to
cmd.OutOrStdout()instead ofos.Stdoutfor testability. - Validate args in
Args:field (e.g.,cobra.ExactArgs(1)) not insideRunE. - Prefer
--outputflag over multiple commands (list-json,list-table).
References
Related skills
cobra-go— focused Cobra command patternsbubbletea-go— interactive TUI on top of CLIrelease-binaries— cross-compiling and distributing the binary
