AgentStack
SKILL verified MIT Self-run

Golang Cli Cobra Viper

skill-bobmatnyc-claude-mpm-skills-golang-cli-cobra-viper · by bobmatnyc

Building production-quality CLI tools with Cobra command framework and Viper configuration management

No reviews yet
0 installs
18 views
0.0% view→install

Install

$ agentstack add skill-bobmatnyc-claude-mpm-skills-golang-cli-cobra-viper

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Golang Cli Cobra Viper? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Go CLI Development with Cobra & Viper

Overview

Cobra and Viper are the industry-standard libraries for building production-quality CLIs in Go. Cobra provides command structure and argument parsing, while Viper manages configuration from multiple sources with clear precedence rules.

Key Features:

  • 🎯 Cobra Commands: POSIX-compliant CLI with subcommands (app verb noun --flag)
  • ⚙️ Viper Config: Unified configuration from flags, env vars, and config files
  • 🔄 Integration: Seamless Cobra + Viper plumbing patterns
  • 🐚 Shell Completion: Auto-generated completions for bash, zsh, fish, PowerShell
  • Production Ready: Battle-tested by kubectl, docker, gh, hugo

Used By: Kubernetes (kubectl), Docker CLI, GitHub CLI (gh), Hugo, Helm, and 100+ major projects

When to Use This Skill

Activate this skill when:

  • Building multi-command CLI tools with subcommands
  • Creating developer tools, project generators, or scaffolding utilities
  • Implementing admin CLIs for services or infrastructure
  • Requiring flexible configuration (flags > env vars > config files > defaults)
  • Adding shell completion for frequently-used CLIs
  • Building DevOps automation tools or deployment scripts

Cobra Framework

Command Structure Pattern

Cobra follows the APPNAME VERB NOUN --FLAG pattern popularized by git and kubectl.

// cmd/root.go
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "A powerful CLI tool for developers",
    Long: `MyApp is a CLI tool that demonstrates best practices
for building production-quality command-line applications.

Complete documentation is available at https://myapp.example.com`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    // Persistent flags (available to all subcommands)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)")
    rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

    // Bind persistent flags to viper
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, err := os.UserHomeDir()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        viper.AddConfigPath(home)
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName(".myapp")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        if viper.GetBool("verbose") {
            fmt.Println("Using config file:", viper.ConfigFileUsed())
        }
    }
}

Subcommands with Arguments

// cmd/deploy.go
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy application to specified environment",
    Long: `Deploy the application to the specified environment.
Supports: dev, staging, production`,
    Args: cobra.ExactArgs(1),
    ValidArgs: []string{"dev", "staging", "production"},
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation logic runs before RunE
        env := args[0]
        if env == "production" && !viper.GetBool("force") {
            return fmt.Errorf("production deploys require --force flag")
        }
        return nil
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        env := args[0]
        region := viper.GetString("region")
        force := viper.GetBool("force")

        fmt.Printf("Deploying to %s in region %s (force=%v)\n", env, region, force)

        // Actual deployment logic
        return deploy(env, region, force)
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup or notifications
        fmt.Println("Deployment complete")
        return nil
    },
}

func init() {
    rootCmd.AddCommand(deployCmd)

    // Local flags (only for this command)
    deployCmd.Flags().StringP("region", "r", "us-east-1", "AWS region")
    deployCmd.Flags().BoolP("force", "f", false, "Force deployment without confirmation")

    // Bind flags to viper
    viper.BindPFlag("region", deployCmd.Flags().Lookup("region"))
    viper.BindPFlag("force", deployCmd.Flags().Lookup("force"))
}

func deploy(env, region string, force bool) error {
    // Implementation
    return nil
}

Persistent vs. Local Flags

// Persistent flags: Available to command and all subcommands
rootCmd.PersistentFlags().String("config", "", "config file path")
rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

// Local flags: Only available to this specific command
deployCmd.Flags().String("region", "us-east-1", "deployment region")
deployCmd.Flags().Bool("force", false, "force deployment")

// Required flags
deployCmd.MarkFlagRequired("region")

// Flag dependencies
deployCmd.MarkFlagsRequiredTogether("username", "password")
deployCmd.MarkFlagsMutuallyExclusive("json", "yaml")

PreRun/PostRun Hooks

Cobra provides execution hooks for setup and cleanup:

var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Start API server",

    // Execution order (all optional):
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before PreRunE, inherited by subcommands
        return setupLogging()
    },
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation and setup before RunE
        return validateConfig()
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        // Main command logic
        return startServer()
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup after RunE
        return cleanup()
    },
    PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
        // Runs after PostRunE, inherited by subcommands
        return flushLogs()
    },
}

Important: Use RunE, PreRunE, PostRunE (error-returning versions) instead of Run, PreRun, PostRun.

Viper Configuration Management

Configuration Priority

Viper follows a strict precedence order (highest to lowest):

  1. Explicit Set (viper.Set("key", value))
  2. Command-line Flags (bound with viper.BindPFlag)
  3. Environment Variables (MYAPP_KEY=value)
  4. Config File (~/.myapp.yaml, ./config.yaml)
  5. Key/Value Store (etcd, Consul - optional)
  6. Defaults (viper.SetDefault("key", value))
func initConfig() {
    // 1. Set defaults (lowest priority)
    viper.SetDefault("port", 8080)
    viper.SetDefault("database.host", "localhost")
    viper.SetDefault("database.port", 5432)

    // 2. Config file locations (checked in order)
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("/etc/myapp/")
    viper.AddConfigPath("$HOME/.myapp")
    viper.AddConfigPath(".")

    // 3. Environment variables (prefix + automatic mapping)
    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv() // MYAPP_PORT, MYAPP_DATABASE_HOST, etc.

    // 4. Read config file (optional)
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found - use defaults and env vars
        } else {
            // Config file found but error reading it
            return err
        }
    }

    // 5. Flags will be bound in init() functions (highest priority)
}

Environment Variable Mapping

Viper automatically maps environment variables with prefix and dot notation:

viper.SetEnvPrefix("MYAPP") // Prefix for env vars
viper.AutomaticEnv()        // Enable automatic mapping

// Config key → Environment variable
// "port"              → MYAPP_PORT
// "database.host"     → MYAPP_DATABASE_HOST
// "database.port"     → MYAPP_DATABASE_PORT
// "aws.s3.region"     → MYAPP_AWS_S3_REGION

Manual mapping for non-standard env var names:

viper.BindEnv("database.host", "DB_HOST")          // Custom env var name
viper.BindEnv("database.password", "DB_PASSWORD")  // Different naming convention

Config File Formats

Viper supports multiple formats: YAML, JSON, TOML, HCL, INI, envfile, Java properties.

config.yaml:

port: 8080
log_level: info

database:
  host: localhost
  port: 5432
  user: postgres
  ssl_mode: require

aws:
  region: us-east-1
  s3:
    bucket: my-app-bucket

Accessing config values:

port := viper.GetInt("port")                    // 8080
dbHost := viper.GetString("database.host")      // "localhost"
s3Bucket := viper.GetString("aws.s3.bucket")    // "my-app-bucket"

// Type-safe access
if viper.IsSet("database.ssl_mode") {
    sslMode := viper.GetString("database.ssl_mode")
}

// Unmarshal into struct
type Config struct {
    Port     int    `mapstructure:"port"`
    LogLevel string `mapstructure:"log_level"`
    Database struct {
        Host    string `mapstructure:"host"`
        Port    int    `mapstructure:"port"`
        User    string `mapstructure:"user"`
        SSLMode string `mapstructure:"ssl_mode"`
    } `mapstructure:"database"`
}

var config Config
if err := viper.Unmarshal(&config); err != nil {
    return err
}

Cobra + Viper Integration

Critical Integration Pattern

The key to Cobra + Viper integration is binding flags to Viper keys:

// cmd/root.go
func init() {
    cobra.OnInitialize(initConfig) // Load config before command execution

    // Define flags
    rootCmd.PersistentFlags().String("config", "", "config file")
    rootCmd.PersistentFlags().String("log-level", "info", "log level")
    rootCmd.PersistentFlags().Int("port", 8080, "server port")

    // Bind flags to Viper (critical step!)
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level"))
    viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
}

func initConfig() {
    // This runs BEFORE command execution via cobra.OnInitialize
    if cfgFile := viper.GetString("config"); cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath("$HOME/.myapp")
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    viper.ReadInConfig() // Ignore errors - config file is optional
}

Flag binding strategies:

// Strategy 1: Bind each flag individually (explicit)
viper.BindPFlag("log_level", rootCmd.Flags().Lookup("log-level"))

// Strategy 2: Bind all flags automatically (convenient)
viper.BindPFlags(rootCmd.Flags())

// Strategy 3: Hybrid approach (recommended)
// - Bind persistent flags globally
// - Bind local flags in each command's init()
rootCmd.PersistentFlags().String("config", "", "config file")
viper.BindPFlags(rootCmd.PersistentFlags())

deployCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("deploy.region", deployCmd.Flags().Lookup("region"))

PersistentPreRun for Config Loading

Use PersistentPreRunE to load and validate configuration:

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "My application",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before ALL commands (inherited by subcommands)

        // 1. Validate required config
        if !viper.IsSet("api_key") {
            return fmt.Errorf("API key not configured (set MYAPP_API_KEY or add to config file)")
        }

        // 2. Setup logging based on config
        logLevel := viper.GetString("log_level")
        if err := setupLogging(logLevel); err != nil {
            return fmt.Errorf("invalid log level: %w", err)
        }

        // 3. Initialize clients/services
        apiKey := viper.GetString("api_key")
        if err := initAPIClient(apiKey); err != nil {
            return fmt.Errorf("failed to initialize API client: %w", err)
        }

        return nil
    },
}

Shell Completion

Cobra generates shell completion scripts for bash, zsh, fish, and PowerShell.

Adding Completion Command

// cmd/completion.go
package cmd

import (
    "os"

    "github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generate shell completion script",
    Long: `Generate shell completion script for myapp.

To load completions:

Bash:
  $ source > ~/.bashrc

Zsh:
  $ source > ~/.zshrc

Fish:
  $ myapp completion fish | source
  # To load automatically:
  $ myapp completion fish > ~/.config/fish/completions/myapp.fish

PowerShell:
  PS> myapp completion powershell | Out-String | Invoke-Expression
  # To load automatically, add to PowerShell profile.
`,
    DisableFlagsInUseLine: true,
    ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
    Args:                  cobra.ExactValidArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        switch args[0] {
        case "bash":
            return cmd.Root().GenBashCompletion(os.Stdout)
        case "zsh":
            return cmd.Root().GenZshCompletion(os.Stdout)
        case "fish":
            return cmd.Root().GenFishCompletion(os.Stdout, true)
        case "powershell":
            return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
        }
        return nil
    },
}

func init() {
    rootCmd.AddCommand(completionCmd)
}

Custom Completion Functions

Provide dynamic completions for command arguments:

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy to environment",
    Args:  cobra.ExactArgs(1),
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
        // Return available environments
        envs := []string{"dev", "staging", "production"}
        return envs, cobra.ShellCompDirectiveNoFileComp
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy(args[0])
    },
}

// Custom flag completion
deployCmd.RegisterFlagCompletionFunc("region", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    regions := []string{"us-east-1", "us-west-2", "eu-west-1"}
    return regions, cobra.ShellCompDirectiveNoFileComp
})

CLI Best Practices

User-Friendly Error Messages

// ❌ BAD: Technical jargon
return fmt.Errorf("db connection failed: EOF")

// ✅ GOOD: Actionable error message
return fmt.Errorf("cannot connect to database at %s:%d\nPlease check:\n  - Database is running\n  - Credentials are correct (MYAPP_DB_PASSWORD)\n  - Network connectivity", host, port)

// ✅ GOOD: Suggest remediation
if !viper.IsSet("api_key") {
    return fmt.Errorf("API key not configured\nSet environment variable: export MYAPP_API_KEY=your-key\nOr add to config file: ~/.myapp.yaml")
}

Progress Indicators

import "github.com/briandowns/spinner"

func deploy(env string) error {
    s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
    s.Suffix = " Deploying to " + env + "..."
    s.Start()
    defer s.Stop()

    // Deployment logic
    if err := performDeployment(env); err != nil {
        s.Stop()
        return err
    }

    s.Stop()
    fmt.Println("✓ Deployment successful")
    return nil
}

Output Formatting

import (
    "encoding/json"
    "github.com/olekukonko/tablewriter"
)

func displayResults(items []Item, format string) error {
    switch format {
    case "json":

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [bobmatnyc](https://github.com/bobmatnyc)
- **Source:** [bobmatnyc/claude-mpm-skills](https://github.com/bobmatnyc/claude-mpm-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.