# Golang Cli Cobra Viper

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

- **Type:** Skill
- **Install:** `agentstack add skill-bobmatnyc-claude-mpm-skills-golang-cli-cobra-viper`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [bobmatnyc](https://agentstack.voostack.com/s/bobmatnyc)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [bobmatnyc](https://github.com/bobmatnyc)
- **Source:** https://github.com/bobmatnyc/claude-mpm-skills/tree/main/toolchains/golang/golang-cli-cobra-viper

## Install

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

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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.

```go
// 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

```go
// 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

```go
// 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:

```go
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)`)

```go
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:

```go
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:

```go
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**:
```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**:
```go
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:

```go
// 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**:

```go
// 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:

```go
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

```go
// 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:

```go
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

```go
// ❌ 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

```go
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

```go
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.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-bobmatnyc-claude-mpm-skills-golang-cli-cobra-viper
- Seller: https://agentstack.voostack.com/s/bobmatnyc
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
