Skip to content
Back to skills

Cobra

ASecurity

Builds command-line tools in Go with Cobra, the library behind kubectl, Hugo and the GitHub CLI: subcommands, flags, argument validation, generated help and shell completions. Use when a user asks to create a Go CLI, add subcommands or flags, validate arguments, bind flags to config files and environment variables with Viper, or generate bash, zsh, fish or PowerShell completions.

  • 142 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added May 27, 2026
toolsgoshellbashgitapi

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill cobra --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cobra?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cobra
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-cobra/badge)](https://www.skillsdirectory.com/skills/terminalskills-cobra)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cobra
description: >-
  Builds command-line tools in Go with Cobra, the library behind kubectl, Hugo and the GitHub CLI: subcommands, flags, argument validation, generated help and shell completions. Use when a user asks to create a Go CLI, add subcommands or flags, validate arguments, bind flags to config files and environment variables with Viper, or generate bash, zsh, fish or PowerShell completions.
license: Apache-2.0
compatibility: "Go 1.15+ (cobra v1.10.x). Optional: spf13/viper for configuration files and environment variables."
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: development
  tags:
    - go
    - cli
    - command-line
    - flags
    - subcommands
  repository: https://github.com/spf13/cobra
---

# Cobra — Go CLI Framework

## Overview

Cobra structures a Go CLI as a tree of commands (`app`, `app deploy`, `app config set`), each with its own flags, argument rules and help text. It generates `--help`, usage output, "did you mean" suggestions and shell completion scripts. The current release is v1.10.2 (December 2025); Viper v1.21 is the usual companion for config files and environment variables.

## Instructions

### Installation and scaffolding

```bash
go mod init github.com/acme/deployctl
go get github.com/spf13/cobra@latest
go get github.com/spf13/viper@latest          # only if you want config files and env vars

go install github.com/spf13/cobra-cli@latest  # optional generator
cobra-cli init                                # writes main.go and cmd/root.go
cobra-cli add deploy                          # writes cmd/deploy.go
```
`cobra-cli` is a separate, rarely updated tool (v1.3.0, 2022). It only writes boilerplate with placeholder text you must replace; writing the files by hand is just as easy.

### Layout

`main.go` only calls `cmd.Execute()`; every command lives in its own file under `cmd/` and registers itself with `rootCmd.AddCommand(...)` in `init()`.

### Root command with Viper

```go
// cmd/root.go
package cmd

import (
	"fmt"
	"strings"

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

var cfgFile string

var rootCmd = &cobra.Command{
	Use:          "deployctl",
	Short:        "Deploy and inspect services",
	SilenceUsage: true, // do not dump usage when RunE returns an error
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		return initConfig(cmd)
	},
}

func Execute() error { return rootCmd.Execute() }

func init() {
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default ./deployctl.yaml)")
	rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
}

func initConfig(cmd *cobra.Command) error {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		viper.AddConfigPath(".")
		viper.SetConfigName("deployctl")
	}
	viper.SetEnvPrefix("DEPLOYCTL")
	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
	viper.AutomaticEnv()
	if err := viper.ReadInConfig(); err != nil {
		if _, missing := err.(viper.ConfigFileNotFoundError); !missing {
			return fmt.Errorf("reading config: %w", err)
		}
	}
	return viper.BindPFlags(cmd.Flags()) // flag > env var > config file > flag default
}
```
```go
// main.go
func main() {
	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
}
```
Cobra already prints the error returned by `RunE`; do not print it again in `main`.

### Subcommand with arguments, flags and completion

```go
// cmd/deploy.go
package cmd

import (
	"fmt"

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

var deployCmd = &cobra.Command{
	Use:   "deploy SERVICE",
	Short: "Deploy a service to an environment",
	Example: `  deployctl deploy api --env production --version v2.1.0
  deployctl deploy worker --dry-run`,
	Args: cobra.ExactArgs(1),
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) != 0 {
			return nil, cobra.ShellCompDirectiveNoFileComp
		}
		return []string{"api", "worker", "scheduler"}, cobra.ShellCompDirectiveNoFileComp
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		env, version := viper.GetString("env"), viper.GetString("version")
		if viper.GetBool("dry-run") {
			fmt.Fprintf(cmd.OutOrStdout(), "DRY RUN: would deploy %s@%s to %s\n", args[0], version, env)
			return nil
		}
		return runDeploy(cmd.Context(), args[0], env, version)
	},
}

func init() {
	rootCmd.AddCommand(deployCmd)
	deployCmd.Flags().StringP("env", "e", "staging", "target environment")
	deployCmd.Flags().String("version", "latest", "version to deploy")
	deployCmd.Flags().Bool("dry-run", false, "print the plan without deploying")
	_ = deployCmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
		return []string{"staging", "production"}, cobra.ShellCompDirectiveNoFileComp
	})
}
```

Other building blocks: `Args` validators `cobra.NoArgs`, `MinimumNArgs(n)`, `MaximumNArgs(n)`, `RangeArgs(a, b)`, `MatchAll(...)`; `cmd.MarkFlagRequired("env")`; `MarkFlagsMutuallyExclusive("json", "yaml")`; `MarkFlagsRequiredTogether`; `Aliases: []string{"st"}`; `Hidden: true`; `Deprecated: "use deploy instead"`; `rootCmd.AddGroup(&cobra.Group{ID: "ops", Title: "Operations"})` with `GroupID: "ops"` on commands; `rootCmd.ExecuteContext(ctx)` to pass a cancellable context to `cmd.Context()`.

### Shell completions

Cobra adds a `completion` command (bash, zsh, fish, powershell) to any app that has subcommands:

```bash
source <(deployctl completion bash)                       # current bash session
deployctl completion zsh > "${fpath[1]}/_deployctl"       # zsh, then restart the shell
deployctl completion fish > ~/.config/fish/completions/deployctl.fish
deployctl __complete deploy ""                            # debug: prints candidates for `deployctl deploy <TAB>`
```

## Examples

### Example 1: "Add a `status` command that completes service names"

```go
var statusCmd = &cobra.Command{
	Use:               "status [SERVICE]",
	Short:             "Show the status of one or all services",
	Aliases:           []string{"st"},
	Args:              cobra.MaximumNArgs(1),
	ValidArgsFunction: completeServices,
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) == 0 {
			return printAllStatus(cmd.OutOrStdout())
		}
		return printStatus(cmd.OutOrStdout(), args[0])
	},
}

func init() { rootCmd.AddCommand(statusCmd) }
```
`deployctl st api` now runs the same command, and `deployctl status <TAB>` offers the service names returned by `completeServices`.

### Example 2: "Let users set the environment from a file or a variable"

```bash
echo "version: v2.1.0" > deployctl.yaml
DEPLOYCTL_ENV=production deployctl deploy api --dry-run
# DRY RUN: would deploy api@v2.1.0 to production
deployctl deploy api --dry-run -e staging
# DRY RUN: would deploy api@v2.1.0 to staging
```
The flag wins over the environment variable, which wins over the config file, which wins over the flag default (this behavior was run against cobra v1.10.2 and viper v1.21.0 with the code above).

## Guidelines

- Use `RunE` instead of `Run` so errors go through Cobra; set `SilenceUsage: true` on the root so a runtime failure does not print the whole usage text.
- A child's `PersistentPreRun(E)` replaces the parent's instead of adding to it. Either define it only on the root, call the parent's hook yourself, or set `cobra.EnableTraverseRunHooks = true` so all of them run.
- Call `viper.BindPFlags` inside `PersistentPreRunE`, not in `init()`: binding the whole command's flag set in `init()` would mix flags of different subcommands that share a name.
- Global variables for flags are fine for small tools; for larger ones, put them in a struct created per command so tests can run commands in parallel with `cmd.SetArgs(...)` and `cmd.SetOut(buf)`.
- Write command output to `cmd.OutOrStdout()` and diagnostics to `cmd.ErrOrStderr()` so tests can capture them.
- Keep secrets out of flags (they appear in `ps` and shell history); read tokens from environment variables.
- For a tiny one-command script, the standard `flag` package is enough; Cobra pays off with subcommands.

Files in this skill

  • SKILL.md5.4 KB
  • _scores.json1.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…