Cobra

A powerful library for creating modern CLI applications in Go. Inspired by tools like git and kubectl.

goclicommand-linesubcommands

GitHub Overview

spf13/cobra

A Commander for modern Go CLI interactions

Stars41,221
Watchers369
Forks2,966
Created:September 3, 2013
Language:Go
License:Apache License 2.0

Topics

clicli-appcobracobra-generatorcobra-librarycommandcommand-cobracommand-linecommandlinegogolanggolang-applicationgolang-libraryposixposix-compliant-flagssubcommands

Star History

spf13/cobra Star History
Data as of: 7/25/2025, 02:05 AM

Framework

Cobra

Overview

Cobra is a library for creating powerful modern command-line interfaces (CLI) in Go. It's used by many well-known projects including Kubernetes, Hugo, and GitHub CLI. It provides features such as subcommand-based CLIs, POSIX-compliant flags, and automatic help generation, making it easy to build complex CLI applications like Git. The latest version is v1.8.1 (updated June 2024), establishing its position as the most trusted CLI building library in the Go ecosystem.

Details

Cobra is built around three core concepts: Commands, Args, and Flags. Applications follow the structure APPNAME COMMAND ARG --FLAG, providing an intuitive and consistent interface. Developed by spf13 (Steve Francia), who is also known as the Product Lead of the Go programming language at Google.

Dominant Position in Go Ecosystem

Why Cobra is the most popular Go CLI library:

  • Industry Standard Adoption: Used by major projects like Kubernetes, Hugo, GitHub CLI, Docker CLI
  • Complete Feature Set: Supports everything from simple flag parsing to complex subcommand structures
  • Excellent Developer Experience: Auto-completion, help generation, and error messages come standard
  • Mature Design: API refined through years of development and practical use
  • Framework-Library Balance: Provides rich functionality while maintaining control

Key Features

  • Subcommand-based CLIs: Hierarchical command structure like app server or app fetch
  • POSIX-compliant Flags: Support for both short and long forms (-n and --name)
  • Nested Subcommands: Create more organized and intuitive CLI structures
  • Automatic Help Generation: Automatically generates help for all commands and flags
  • Intelligent Suggestions: Suggests correct commands for typos (using Levenshtein distance)
  • Shell Autocompletion: Auto-generates completion scripts for Bash, Zsh, Fish, and PowerShell
  • Command Aliases: Change command names without breaking existing workflows
  • Flag Groups: Define relationships between flags (required together, mutually exclusive, etc.)

Advanced Features

  • Custom Completion: Dynamic completion via ValidArgsFunction and RegisterFlagCompletionFunc
  • Active Help: Hint messages to guide users during shell completion
  • Lifecycle Hooks: Hook functions like PreRun, PostRun, PersistentPreRun
  • Viper Integration: Easy integration with the Viper configuration management library (now independent)

Latest Changes (2024-2025)

  • Lightweight Dependencies: Removed Viper and its indirect dependencies from the core library
  • Cobra-cli Separation: Code generation tool moved to independent repository (spf13/cobra-cli)
  • Release Strategy Change: Moved from seasonal releases to generic point release targets
  • Enhanced Active Help: Improved inline warnings and hints during Tab completion
  • Continuous Completion Improvements: Ongoing enhancements to shell completion capabilities

Pros and Cons

Pros

  • Ideal for large, complex CLI applications
  • Rich features and customization options
  • Proven track record in famous projects (Kubernetes, Hugo, GitHub CLI, etc.)
  • Excellent documentation and community support
  • Superior UX with auto-completion and help generation
  • Pluggable architecture
  • Automatic code generation and scaffolding via cobra-cli
  • Mature ecosystem with Viper integration

Cons

  • May be overkill for small projects (lighter alternatives like urfave/cli exist)
  • Somewhat complex initial setup (high learning cost especially for beginners)
  • Dependency on pflag library (incompatible with standard flag package)
  • Different API from standard flag library
  • Complexity due to many features (understanding all features can be overwhelming)

Key Links

Code Examples

Basic Structure

package main

import (
    "fmt"
    "os"
    
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "myapp",
        Short: "MyApp is an awesome CLI application",
        Long: `MyApp is an awesome CLI application that provides
various useful features.`,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Hello from MyApp!")
        },
    }
    
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

Subcommands and Flags Example

package main

import (
    "fmt"
    "os"
    
    "github.com/spf13/cobra"
)

var (
    verbose bool
    source  string
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "app",
        Short: "A brief description of your application",
    }
    
    // Global flag
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
    
    // serve subcommand
    var port int
    var serveCmd = &cobra.Command{
        Use:   "serve",
        Short: "Start the server",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Starting server on port %d...\n", port)
            if verbose {
                fmt.Println("Verbose mode enabled")
            }
        },
    }
    serveCmd.Flags().IntVarP(&port, "port", "p", 8080, "server port")
    
    // config subcommand
    var configCmd = &cobra.Command{
        Use:   "config",
        Short: "Manage configuration",
    }
    
    var configSetCmd = &cobra.Command{
        Use:   "set KEY VALUE",
        Short: "Set a configuration value",
        Args:  cobra.ExactArgs(2),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Setting %s = %s\n", args[0], args[1])
        },
    }
    
    // Build command hierarchy
    rootCmd.AddCommand(serveCmd, configCmd)
    configCmd.AddCommand(configSetCmd)
    
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

Dynamic Completion Example

package main

import (
    "fmt"
    "strings"
    
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "kubectl",
        Short: "Kubernetes cluster manager",
    }
    
    var getCmd = &cobra.Command{
        Use:   "get TYPE [NAME]",
        Short: "Display resources",
        ValidArgs: []string{"pod", "service", "deployment", "node"},
        Args: cobra.MinimumNArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Getting %s...\n", args[0])
        },
    }
    
    // Dynamic completion function
    getCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        if len(args) == 0 {
            // Resource type completion
            return []cobra.Completion{
                {CompletionText: "pod", Description: "Pod resources"},
                {CompletionText: "service", Description: "Service resources"},
                {CompletionText: "deployment", Description: "Deployment resources"},
                {CompletionText: "node", Description: "Node resources"},
            }, cobra.ShellCompDirectiveNoFileComp
        }
        
        if len(args) == 1 {
            // Resource name completion (would fetch from actual cluster)
            return getResourceNames(args[0], toComplete), cobra.ShellCompDirectiveNoFileComp
        }
        
        return nil, cobra.ShellCompDirectiveNoFileComp
    }
    
    // Flag dynamic completion
    var outputFormat string
    getCmd.Flags().StringVarP(&outputFormat, "output", "o", "json", "Output format")
    getCmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        return []cobra.Completion{
            {CompletionText: "json", Description: "JSON format"},
            {CompletionText: "yaml", Description: "YAML format"},
            {CompletionText: "table", Description: "Table format"},
        }, cobra.ShellCompDirectiveNoFileComp
    })
    
    rootCmd.AddCommand(getCmd)
    rootCmd.Execute()
}

func getResourceNames(resourceType, prefix string) []cobra.Completion {
    // In real applications, would call API to get resource names
    mockResources := map[string][]string{
        "pod": {"nginx-pod", "mysql-pod", "redis-pod"},
        "service": {"nginx-svc", "mysql-svc", "redis-svc"},
    }
    
    var completions []cobra.Completion
    if resources, ok := mockResources[resourceType]; ok {
        for _, r := range resources {
            if strings.HasPrefix(r, prefix) {
                completions = append(completions, cobra.Completion{CompletionText: r})
            }
        }
    }
    return completions
}

Flag Groups Example

package main

import (
    "fmt"
    "github.com/spf13/cobra"
)

func main() {
    var (
        username string
        password string
        token    string
        json     bool
        yaml     bool
        xml      bool
    )
    
    rootCmd := &cobra.Command{
        Use:   "api",
        Short: "API client",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Calling API...")
        },
    }
    
    // Authentication flags
    rootCmd.Flags().StringVar(&username, "username", "", "Username")
    rootCmd.Flags().StringVar(&password, "password", "", "Password")
    rootCmd.Flags().StringVar(&token, "token", "", "Auth token")
    
    // Output format flags
    rootCmd.Flags().BoolVar(&json, "json", false, "Output in JSON")
    rootCmd.Flags().BoolVar(&yaml, "yaml", false, "Output in YAML")
    rootCmd.Flags().BoolVar(&xml, "xml", false, "Output in XML")
    
    // Flag group configuration
    rootCmd.MarkFlagsRequiredTogether("username", "password") // username and password required together
    rootCmd.MarkFlagsMutuallyExclusive("json", "yaml", "xml") // only one output format
    rootCmd.MarkFlagsOneRequired("json", "yaml", "xml") // output format is required
    
    // Only one auth method
    rootCmd.MarkFlagsMutuallyExclusive("token", "username")
    
    rootCmd.Execute()
}

Lifecycle Hooks Example

package main

import (
    "fmt"
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "app",
        Short: "Application",
        PersistentPreRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("Loading configuration...")
        },
        PreRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("Initializing...")
        },
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Running main process...")
        },
        PostRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("Cleaning up...")
        },
        PersistentPostRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("Saving logs...")
        },
    }
    
    rootCmd.Execute()
}

Practical CLI Development Tool Example

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "time"
    
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var (
    configFile string
    verbose    bool
    outputFormat string
)

func main() {
    rootCmd := &cobra.Command{
        Use:   "devtool",
        Short: "Developer utility tool",
        Long: `devtool is a CLI tool designed to streamline daily development tasks.
It provides functionality for project management, file operations, build automation, and more.`,
        PersistentPreRun: func(cmd *cobra.Command, args []string) {
            initConfig()
            if verbose {
                fmt.Printf("Config file: %s\n", viper.ConfigFileUsed())
                fmt.Printf("Output format: %s\n", outputFormat)
            }
        },
    }

    // Global flags
    rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.devtool.yaml)")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
    rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "output format (table, json, yaml)")

    // Add subcommands
    rootCmd.AddCommand(projectCmd(), buildCmd(), deployCmd())

    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

func projectCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "project",
        Short: "Project management commands",
        Long:  "Initialize projects, create templates, and manage project settings.",
    }

    initCmd := &cobra.Command{
        Use:   "init [NAME]",
        Short: "Initialize a new project",
        Args:  cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            projectName := args[0]
            template, _ := cmd.Flags().GetString("template")
            force, _ := cmd.Flags().GetBool("force")
            
            if verbose {
                fmt.Printf("Initializing project: %s (template: %s)\n", projectName, template)
            }
            
            if !force && directoryExists(projectName) {
                fmt.Fprintf(os.Stderr, "Error: Directory '%s' already exists. Use --force to overwrite.\n", projectName)
                os.Exit(1)
            }
            
            createProject(projectName, template)
            fmt.Printf("Project '%s' created successfully.\n", projectName)
        },
    }
    
    initCmd.Flags().StringP("template", "t", "basic", "project template (basic, web, api, cli)")
    initCmd.Flags().BoolP("force", "f", false, "overwrite existing directory")
    
    listCmd := &cobra.Command{
        Use:   "list",
        Short: "List available templates",
        Run: func(cmd *cobra.Command, args []string) {
            templates := []string{"basic", "web", "api", "cli", "microservice"}
            
            fmt.Println("Available templates:")
            for _, template := range templates {
                fmt.Printf("  - %s\n", template)
            }
        },
    }

    cmd.AddCommand(initCmd, listCmd)
    return cmd
}

func buildCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "build",
        Short: "Build project",
        Long:  "Build the project and generate artifacts.",
    }

    var (
        target     string
        optimize   bool
        parallel   int
    )

    buildCmd := &cobra.Command{
        Use:   "run [TARGET...]",
        Short: "Build specified targets",
        Run: func(cmd *cobra.Command, args []string) {
            targets := args
            if len(targets) == 0 {
                targets = []string{"default"}
            }
            
            fmt.Printf("Starting build (parallelism: %d, optimize: %t)\n", parallel, optimize)
            
            for _, target := range targets {
                if verbose {
                    fmt.Printf("  Building target '%s'...\n", target)
                }
                
                // Simulate build process
                time.Sleep(time.Millisecond * 500)
                
                switch outputFormat {
                case "json":
                    fmt.Printf(`{"target": "%s", "status": "success", "duration": "500ms"}%s`, target, "\n")
                case "yaml":
                    fmt.Printf("target: %s\nstatus: success\nduration: 500ms\n---\n", target)
                default:
                    fmt.Printf("✓ %s (500ms)\n", target)
                }
            }
            
            fmt.Println("Build completed")
        },
    }
    
    buildCmd.Flags().StringVarP(&target, "target", "t", "", "specify target")
    buildCmd.Flags().BoolVarP(&optimize, "optimize", "O", false, "enable optimized build")
    buildCmd.Flags().IntVarP(&parallel, "parallel", "j", 4, "number of parallel builds")
    
    cleanCmd := &cobra.Command{
        Use:   "clean",
        Short: "Clean build artifacts",
        Run: func(cmd *cobra.Command, args []string) {
            if verbose {
                fmt.Println("Cleaning build artifacts...")
            }
            
            // Simulate cleanup process
            time.Sleep(time.Millisecond * 200)
            fmt.Println("Cleanup completed")
        },
    }

    cmd.AddCommand(buildCmd, cleanCmd)
    return cmd
}

func deployCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "deploy",
        Short: "Deploy application",
        Long:  "Deploy the built application to specified environment.",
    }

    var (
        environment string
        dryRun      bool
        confirm     bool
    )

    deployCmd := &cobra.Command{
        Use:   "run",
        Short: "Execute deployment",
        PreRun: func(cmd *cobra.Command, args []string) {
            if environment == "production" && !confirm {
                fmt.Print("Are you sure you want to deploy to production? (y/N): ")
                var response string
                fmt.Scanln(&response)
                if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" {
                    fmt.Println("Deployment cancelled")
                    os.Exit(0)
                }
            }
        },
        Run: func(cmd *cobra.Command, args []string) {
            if dryRun {
                fmt.Printf("[Dry run] Simulating deployment to %s environment\n", environment)
            } else {
                fmt.Printf("Deploying to %s environment...\n", environment)
            }
            
            // Simulate deployment process
            steps := []string{"Config validation", "Upload", "Service restart", "Health check"}
            for i, step := range steps {
                if verbose {
                    fmt.Printf("  [%d/%d] %s...\n", i+1, len(steps), step)
                }
                time.Sleep(time.Millisecond * 300)
            }
            
            if !dryRun {
                fmt.Printf("✓ Deployment to %s environment completed\n", environment)
            } else {
                fmt.Printf("✓ Dry run completed (no actual deployment was performed)\n")
            }
        },
    }
    
    deployCmd.Flags().StringVarP(&environment, "env", "e", "staging", "deployment environment (staging, production)")
    deployCmd.Flags().BoolVarP(&dryRun, "dry-run", "n", false, "dry run mode")
    deployCmd.Flags().BoolVarP(&confirm, "yes", "y", false, "skip confirmation prompt")
    
    // Environment validation
    deployCmd.MarkFlagRequired("env")
    deployCmd.RegisterFlagCompletionFunc("env", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        return []cobra.Completion{
            {CompletionText: "staging", Description: "Staging environment"},
            {CompletionText: "production", Description: "Production environment"},
            {CompletionText: "development", Description: "Development environment"},
        }, cobra.ShellCompDirectiveNoFileComp
    })

    statusCmd := &cobra.Command{
        Use:   "status",
        Short: "Check deployment status",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Current deployment status:\n")
            fmt.Printf("  Environment: %s\n", environment)
            fmt.Printf("  Last deployment: 2024-01-15 14:30:00\n")
            fmt.Printf("  Version: v1.2.3\n")
            fmt.Printf("  Status: Healthy\n")
        },
    }
    
    statusCmd.Flags().StringVarP(&environment, "env", "e", "staging", "environment to check")

    cmd.AddCommand(deployCmd, statusCmd)
    return cmd
}

func initConfig() {
    if configFile != "" {
        viper.SetConfigFile(configFile)
    } else {
        home, err := os.UserHomeDir()
        cobra.CheckErr(err)
        
        viper.AddConfigPath(home)
        viper.SetConfigType("yaml")
        viper.SetConfigName(".devtool")
    }
    
    viper.AutomaticEnv()
    
    if err := viper.ReadInConfig(); err == nil && verbose {
        fmt.Printf("Using config file: %s\n", viper.ConfigFileUsed())
    }
}

func directoryExists(path string) bool {
    info, err := os.Stat(path)
    if os.IsNotExist(err) {
        return false
    }
    return info.IsDir()
}

func createProject(name, template string) {
    // Simulate project creation
    os.MkdirAll(name, 0755)
    
    switch template {
    case "web":
        os.MkdirAll(filepath.Join(name, "static"), 0755)
        os.MkdirAll(filepath.Join(name, "templates"), 0755)
    case "api":
        os.MkdirAll(filepath.Join(name, "handlers"), 0755)
        os.MkdirAll(filepath.Join(name, "models"), 0755)
    case "cli":
        os.MkdirAll(filepath.Join(name, "cmd"), 0755)
    }
    
    // Create basic files
    os.WriteFile(filepath.Join(name, "README.md"), []byte("# "+name+"\n"), 0644)
    os.WriteFile(filepath.Join(name, ".gitignore"), []byte("*.log\n*.tmp\n"), 0644)
}

Cobra-CLI Tool Usage Example

# Install cobra-cli
go install github.com/spf13/cobra-cli@latest

# Initialize new CLI application
cobra-cli init myapp

# Add subcommands
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd'

# Generated file structure
# myapp/
# ├── cmd/
# │   ├── root.go
# │   ├── serve.go
# │   ├── config.go
# │   └── create.go
# ├── main.go
# └── go.mod