Janeiro 2026 · ~13 min

Go CLI: Criando Ferramentas de Linha de Comando Poderosas

Como criar CLI com Golang em português: aprenda flags, subcomandos, Cobra, configuração, autocomplete, testes e distribuição de binários Go.

Criar uma CLI com Golang é um dos melhores projetos para aprender Go de forma prática: você treina main, pacotes, flags, erros, testes, build e distribuição sem depender de servidor web, banco de dados ou framework pesado. Uma boa ferramenta de linha de comando também vira peça real de portfólio, porque resolve tarefas repetitivas no terminal e mostra cuidado com UX de desenvolvedor.

Ferramentas de linha de comando (CLI) são essenciais no arsenal de qualquer desenvolvedor. Desde gerenciadores de pacotes como npm e pip até infraestrutura como kubectl e terraform, CLIs bem projetadas aumentam a produtividade exponencialmente. Go é uma linguagem forte para criar CLIs rápidas, portáteis e eficientes.

Neste guia completo, você aprenderá a construir CLI tools profissionais usando Go, desde flags simples até ferramentas complexas com subcomandos e auto-complete.

Se você ainda está começando na linguagem, siga antes a trilha de Go tutorial em português e revise Go Modules. Se já sabe criar um programa básico, este tutorial mostra o caminho natural: primeiro flag, depois organização de comandos, depois Cobra, configuração, testes e release.

Por Que Go para CLI Tools?

Vantagens Competitivas

┌─────────────────────────────────────────────────────────────────┐
│  CARACTERÍSTICA     │  GO    │  PYTHON  │  NODE.JS  │  RUST   │
├─────────────────────────────────────────────────────────────────┤
│  Startup            │  ~5ms  │  ~100ms  │  ~200ms   │  ~10ms  │
├─────────────────────────────────────────────────────────────────┤
│  Binário único      │  ✅    │  ❌      │  ❌       │  ✅     │
├─────────────────────────────────────────────────────────────────┤
│  Cross-compile      │  ✅    │  ❌      │  ❌       │  ✅     │
├─────────────────────────────────────────────────────────────────┤
│  Memória            │  Baixa │  Alta    │  Média    │  Baixa  │
├─────────────────────────────────────────────────────────────────┤
│  Curva de aprend.   │  Média │  Baixa   │  Baixa    │  Alta   │
└─────────────────────────────────────────────────────────────────┘

Casos de Sucesso

  • Docker: Container runtime escrito em Go
  • Kubernetes: Orquestração de containers
  • Hugo: Gerador de sites estáticos
  • Terraform: Infrastructure as Code
  • Cobra: Framework CLI usado por Kubernetes, etcd, e muitos outros

Fundamentos de CLI em Go

Flags Nativas com flag Package

Go inclui um pacote flag na biblioteca padrão para parsing de argumentos:

// cmd/simple-cli/main.go
package main

import (
	"flag"
	"fmt"
	"os"
)

type Config struct {
	Name    string
	Verbose bool
	Count   int
	Output  string
}

func main() {
	config := parseFlags()
	
	if config.Verbose {
		fmt.Println("Modo verbose ativado")
	}
	
	fmt.Printf("Olá, %s! Contagem: %d\n", config.Name, config.Count)
	
	if config.Output != "" {
		fmt.Printf("Saída será salva em: %s\n", config.Output)
	}
}

func parseFlags() Config {
	var config Config
	
	// Definir flags
	flag.StringVar(&config.Name, "name", "Mundo", "Nome para saudar")
	flag.BoolVar(&config.Verbose, "verbose", false, "Modo detalhado")
	flag.IntVar(&config.Count, "count", 1, "Número de saudações")
	flag.StringVar(&config.Output, "output", "", "Arquivo de saída (opcional)")
	
	// Parse
	flag.Parse()
	
	return config
}

Executando

# Compilar
go build -o mycli cmd/simple-cli/main.go

# Uso básico
./mycli -name="Go" -count=3
# Output: Olá, Go! Contagem: 3

# Modo verbose
./mycli -verbose -name="Desenvolvedor"
# Output:
# Modo verbose ativado
# Olá, Desenvolvedor! Contagem: 1

# Ajuda automática
./mycli -help
# Usage of ./mycli:
#   -count int
#       Número de saudações (default 1)
#   -name string
#       Nome para saudar (default "Mundo")
#   -output string
#       Arquivo de saída (opcional)
#   -verbose
#       Modo detalhado

Limitações do Pacote flag

O pacote flag é funcional, mas limitado para CLIs complexos:

  • ❌ Sem subcomandos nativos
  • ❌ Sem auto-complete
  • ❌ Sem documentação automática
  • ❌ Sintaxe de flags limitada (apenas -flag ou --flag)
  • ❌ Sem validação de argumentos integrada

Para CLIs profissionais, usamos o Cobra.

Cobra: O Framework CLI Definitivo

Instalação e Setup

# Instalar CLI do Cobra
go install github.com/spf13/cobra-cli@latest

# Inicializar projeto
mkdir myapp && cd myapp
cobra-cli init --pkg-name myapp

# Estrutura criada:
# myapp/
# ├── cmd/
# │   └── root.go
# ├── main.go
# ├── go.mod
# └── LICENSE

Estrutura do Projeto Cobra

myapp/
├── cmd/                    # Comandos da aplicação
│   ├── root.go            # Comando raiz
│   ├── serve.go           # Subcomando: serve
│   ├── config.go          # Subcomando: config
│   └── version.go         # Subcomando: version
├── internal/              # Código interno
│   ├── config/
│   │   └── config.go
│   └── server/
│       └── server.go
├── pkg/                   # Bibliotecas reutilizáveis
│   └── utils/
│       └── utils.go
├── main.go
└── go.mod

Comando Raiz (root.go)

// cmd/root.go
package cmd

import (
	"fmt"
	"os"

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

var cfgFile string

// rootCmd representa o comando base
var rootCmd = &cobra.Command{
	Use:   "myapp",
	Short: "Uma aplicação CLI de exemplo",
	Long: `MyApp é uma ferramenta de linha de comando que demonstra
as melhores práticas para construir CLIs profissionais em Go.

Complete documentation is available at http://example.com`,
	// Executado antes de qualquer subcomando
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		// Inicialização global
		fmt.Println("Inicializando aplicação...")
	},
	// Executado quando nenhum subcomando é especificado
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Use 'myapp --help' para ver comandos disponíveis")
	},
}

// Execute adiciona todos os subcomandos
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
	// Inicialização antes de parsing de flags
	cobra.OnInitialize(initConfig)

	// Flags persistentes (disponíveis em todos os subcomandos)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", 
		"arquivo de config (default é $HOME/.myapp.yaml)")
	
	// Flags locais (apenas para comando raiz)
	rootCmd.Flags().BoolP("toggle", "t", false, 
		"Help message for toggle")

	// Viper binding (para ler de arquivo de config)
	viper.BindPFlag("toggle", rootCmd.Flags().Lookup("toggle"))
}

// initConfig lê arquivo de configuração
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		home, err := os.UserHomeDir()
		cobra.CheckErr(err)

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

	viper.AutomaticEnv()

	if err := viper.ReadInConfig(); err == nil {
		fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
	}
}

Subcomandos

Criando Subcomandos

# Gerar novo subcomando
cobra-cli add serve
cobra-cli add config
cobra-cli add version

Exemplo: Subcomando serve

// cmd/serve.go
package cmd

import (
	"fmt"
	"log"
	"net/http"

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

// Flags específicas do comando serve
var (
	port    int
	host    string
	env     string
)

// serveCmd representa o comando serve
var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Inicia o servidor HTTP",
	Long: `Inicia o servidor HTTP com as configurações especificadas.

Exemplos:
  # Servidor padrão
  myapp serve

  # Servidor em porta específica
  myapp serve --port=8080

  # Modo desenvolvimento
  myapp serve --env=development --verbose`,
	
	// Validação de argumentos
	Args: cobra.NoArgs, // Não aceita argumentos posicionais
	
	// Execução principal
	Run: func(cmd *cobra.Command, args []string) {
		startServer()
	},
}

func init() {
	rootCmd.AddCommand(serveCmd)

	// Definir flags
	serveCmd.Flags().IntVarP(&port, "port", "p", 8080, 
		"Porta do servidor")
	serveCmd.Flags().StringVar(&host, "host", "localhost", 
		"Host do servidor")
	serveCmd.Flags().StringVarP(&env, "env", "e", "production", 
		"Ambiente (development|staging|production)")

	// Viper binding para ler de config file também
	viper.BindPFlag("serve.port", serveCmd.Flags().Lookup("port"))
	viper.BindPFlag("serve.host", serveCmd.Flags().Lookup("host"))
	viper.BindPFlag("serve.env", serveCmd.Flags().Lookup("env"))
}

func startServer() {
	addr := fmt.Sprintf("%s:%d", host, port)
	
	fmt.Printf("🚀 Iniciando servidor em %s (%s)\n", addr, env)
	
	if env == "development" {
		fmt.Println("📋 Modo desenvolvimento ativado")
		fmt.Println("   - Hot reload habilitado")
		fmt.Println("   - Logs detalhados")
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "MyApp Server - %s\n", env)
	})
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]string{
			"status": "healthy",
			"env":    env,
		})
	})

	log.Printf("Servidor rodando em http://%s", addr)
	log.Fatal(http.ListenAndServe(addr, mux))
}

Exemplo: Subcomando config

// cmd/config.go
package cmd

import (
	"fmt"

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

var configCmd = &cobra.Command{
	Use:   "config",
	Short: "Gerencia configurações da aplicação",
}

var configGetCmd = &cobra.Command{
	Use:   "get [chave]",
	Short: "Obtém valor de uma configuração",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		key := args[0]
		value := viper.Get(key)
		if value == nil {
			fmt.Printf("Configuração '%s' não encontrada\n", key)
			return
		}
		fmt.Printf("%s: %v\n", key, value)
	},
}

var configSetCmd = &cobra.Command{
	Use:   "set [chave] [valor]",
	Short: "Define valor de uma configuração",
	Args:  cobra.ExactArgs(2),
	Run: func(cmd *cobra.Command, args []string) {
		key, value := args[0], args[1]
		viper.Set(key, value)
		
		if err := viper.WriteConfig(); err != nil {
			// Criar config se não existir
			viper.SafeWriteConfig()
		}
		
		fmt.Printf("✅ %s = %s\n", key, value)
	},
}

var configListCmd = &cobra.Command{
	Use:   "list",
	Short: "Lista todas as configurações",
	Run: func(cmd *cobra.Command, args []string) {
		settings := viper.AllSettings()
		if len(settings) == 0 {
			fmt.Println("Nenhuma configuração definida")
			return
		}
		
		fmt.Println("Configurações atuais:")
		for k, v := range settings {
			fmt.Printf("  %s: %v\n", k, v)
		}
	},
}

func init() {
	rootCmd.AddCommand(configCmd)
	
	// Adicionar sub-subcomandos
	configCmd.AddCommand(configGetCmd)
	configCmd.AddCommand(configSetCmd)
	configCmd.AddCommand(configListCmd)
}

Validação de Argumentos

Tipos de Validação

// Sem argumentos
cobra.NoArgs

// N argumentos exatos
cobra.ExactArgs(2)

// Range de argumentos
cobra.RangeArgs(1, 3)

// Mínimo de argumentos
cobra.MinimumNArgs(1)

// Máximo de argumentos
cobra.MaximumNArgs(3)

// Argumentos arbitrários (default)
cobra.ArbitraryArgs

// Validação customizada
func(cmd *cobra.Command, args []string) error {
    if len(args) != 2 {
        return fmt.Errorf("requer exatamente 2 argumentos, recebeu %d", len(args))
    }
    return nil
}

Validação Customizada

// cmd/user.go
package cmd

import (
	"fmt"
	"regexp"

	"github.com/spf13/cobra"
)

var userCmd = &cobra.Command{
	Use:   "user [email]",
	Short: "Busca usuário por email",
	Args:  validateEmail,
	Run: func(cmd *cobra.Command, args []string) {
		email := args[0]
		fmt.Printf("Buscando usuário: %s\n", email)
		// ...
	},
}

func validateEmail(cmd *cobra.Command, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("uso: myapp user [email]")
	}
	
	email := args[0]
	emailRegex := regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
	
	if !emailRegex.MatchString(email) {
		return fmt.Errorf("'%s' não é um email válido", email)
	}
	
	return nil
}

Arquivos de Configuração com Viper

Formatos Suportados

# $HOME/.myapp.yaml
app:
  name: "MyApp"
  version: "1.0.0"

server:
  host: "0.0.0.0"
  port: 8080
  timeout: 30

database:
  host: "localhost"
  port: 5432
  name: "myapp"
  ssl_mode: "disable"

features:
  - auth
  - logging
  - metrics
// .myapp.json
{
  "server": {
    "port": 8080,
    "host": "localhost"
  },
  "debug": true
}

Uso no Código

package config

import (
	"github.com/spf13/viper"
)

type Config struct {
	App      AppConfig
	Server   ServerConfig
	Database DatabaseConfig
}

type AppConfig struct {
	Name    string
	Version string
}

type ServerConfig struct {
	Host    string
	Port    int
	Timeout int
}

type DatabaseConfig struct {
	Host    string
	Port    int
	Name    string
	SSLMode string
}

func Load() (*Config, error) {
	var cfg Config
	
	if err := viper.Unmarshal(&cfg); err != nil {
		return nil, err
	}
	
	return &cfg, nil
}

// Uso
func main() {
	cfg, err := config.Load()
	if err != nil {
		log.Fatal(err)
	}
	
	fmt.Printf("Servidor: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
}

Exemplo Real: Ferramenta de Backup

Vamos criar uma CLI completa para backups:

// cmd/backup/main.go
package main

import "backup/cmd"

func main() {
	cmd.Execute()
}
// cmd/root.go
package cmd

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

var rootCmd = &cobra.Command{
	Use:   "backup",
	Short: "Ferramenta de backup automático",
	Long: `Backup CLI é uma ferramenta poderosa para realizar backups
automáticos de arquivos e bancos de dados.

Suporta:
  - Backups locais e remotos (S3, GCS, Azure)
  - Compressão automática
  - Criptografia
  - Agendamento via cron
  - Notificações (email, Slack, Discord)`,
}

func Execute() {
	rootCmd.Execute()
}

func init() {
	rootCmd.AddCommand(createCmd)
	rootCmd.AddCommand(listCmd)
	rootCmd.AddCommand(restoreCmd)
	rootCmd.AddCommand(scheduleCmd)
}
// cmd/create.go
package cmd

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/briandowns/spinner"
	"github.com/fatih/color"
	"github.com/spf13/cobra"
)

var (
	source      string
	destination string
	compress    bool
	encrypt     bool
	verbose     bool
)

var createCmd = &cobra.Command{
	Use:   "create",
	Short: "Cria um novo backup",
	Long:  `Cria um backup dos arquivos especificados.`,
	Example: `  # Backup simples
  backup create --source=/home/user/docs --dest=/backup

  # Backup com compressão e criptografia
  backup create --source=/data --dest=/backup --compress --encrypt

  # Backup verbose
  backup create -s /data -d /backup -v`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runBackup()
	},
}

func init() {
	createCmd.Flags().StringVarP(&source, "source", "s", "", 
		"Diretório fonte (obrigatório)")
	createCmd.Flags().StringVarP(&destination, "dest", "d", "", 
		"Diretório de destino (obrigatório)")
	createCmd.Flags().BoolVarP(&compress, "compress", "c", false, 
		"Comprimir backup")
	createCmd.Flags().BoolVarP(&encrypt, "encrypt", "e", false, 
		"Criptografar backup")
	createCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, 
		"Modo verbose")

	createCmd.MarkFlagRequired("source")
	createCmd.MarkFlagRequired("dest")
}

func runBackup() error {
	// Validar source
	if _, err := os.Stat(source); os.IsNotExist(err) {
		return fmt.Errorf("diretório fonte não existe: %s", source)
	}

	// Criar diretório de destino se não existir
	if err := os.MkdirAll(destination, 0755); err != nil {
		return fmt.Errorf("falha ao criar diretório de destino: %w", err)
	}

	// Gerar nome do backup
	timestamp := time.Now().Format("20060102_150405")
	backupName := fmt.Sprintf("backup_%s", timestamp)
	
	if compress {
		backupName += ".tar.gz"
	} else {
		backupName += ".tar"
	}
	
	if encrypt {
		backupName += ".enc"
	}

	backupPath := filepath.Join(destination, backupName)

	// Spinner para feedback visual
	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
	s.Suffix = " Preparando backup..."
	s.Start()

	// Simular operações (em produção, implementar backup real)
	time.Sleep(500 * time.Millisecond)
	
	if verbose {
		s.Suffix = " Listando arquivos..."
	}
	time.Sleep(500 * time.Millisecond)
	
	if compress {
		s.Suffix = " Comprimindo arquivos..."
		time.Sleep(1 * time.Second)
	}
	
	if encrypt {
		s.Suffix = " Criptografando backup..."
		time.Sleep(1 * time.Second)
	}
	
	s.Suffix = " Finalizando..."
	time.Sleep(300 * time.Millisecond)
	
	s.Stop()

	// Criar arquivo (placeholder)
	f, err := os.Create(backupPath)
	if err != nil {
		return fmt.Errorf("falha ao criar arquivo de backup: %w", err)
	}
	f.Close()

	// Output bonito
	green := color.New(color.FgGreen, color.Bold)
	green.Printf("✓ Backup concluído com sucesso!\n\n")
	
	fmt.Printf("Detalhes:\n")
	fmt.Printf("  Arquivo: %s\n", backupPath)
	fmt.Printf("  Origem:  %s\n", source)
	fmt.Printf("  Tamanho: %s\n", getSize(backupPath))
	
	if compress {
		fmt.Printf("  Compressão: ativada\n")
	}
	if encrypt {
		fmt.Printf("  Criptografia: ativada\n")
	}

	return nil
}

func getSize(path string) string {
	info, err := os.Stat(path)
	if err != nil {
		return "desconhecido"
	}
	
	size := info.Size()
	if size < 1024 {
		return fmt.Sprintf("%d B", size)
	} else if size < 1024*1024 {
		return fmt.Sprintf("%.2f KB", float64(size)/1024)
	} else {
		return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
	}
}

Auto-Complete

Geração de Scripts

// cmd/completion.go
package cmd

import (
	"os"

	"github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
	Use:   "completion [bash|zsh|fish|powershell]",
	Short: "Gera script de auto-complete",
	Long: `Gera scripts de auto-complete para shells.

Para usar:

Bash:
  $ source <(myapp completion bash)
  # Ou permanentemente:
  $ myapp completion bash > /etc/bash_completion.d/myapp

Zsh:
  $ source <(myapp completion zsh)
  # Ou:
  $ myapp completion zsh > "${fpath[1]}/_myapp"

Fish:
  $ myapp completion fish | source
  # Ou:
  $ myapp completion fish > ~/.config/fish/completions/myapp.fish

PowerShell:
  PS> myapp completion powershell | Out-String | Invoke-Expression
  # Ou:
  PS> myapp completion powershell > myapp.ps1`,
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
	Args:                  cobra.ExactValidArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		switch args[0] {
		case "bash":
			cmd.Root().GenBashCompletion(os.Stdout)
		case "zsh":
			cmd.Root().GenZshCompletion(os.Stdout)
		case "fish":
			cmd.Root().GenFishCompletion(os.Stdout, true)
		case "powershell":
			cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
		}
	},
}

Documentação Automática

Gerar Markdown

// tools/gendocs/main.go
package main

import (
	"log"

	"myapp/cmd"
	"github.com/spf13/cobra/doc"
)

func main() {
	err := doc.GenMarkdownTree(cmd.RootCmd, "./docs")
	if err != nil {
		log.Fatal(err)
	}
}

Testes

Testando Comandos

// cmd/serve_test.go
package cmd

import (
	"bytes"
	"testing"

	"github.com/spf13/cobra"
)

func TestServeCommand(t *testing.T) {
	tests := []struct {
		name    string
		args    []string
		wantErr bool
	}{
		{
			name:    "sem flags",
			args:    []string{},
			wantErr: false,
		},
		{
			name:    "com porta",
			args:    []string{"--port=9090"},
			wantErr: false,
		},
		{
			name:    "porta inválida",
			args:    []string{"--port=invalid"},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := &cobra.Command{}
			cmd.AddCommand(serveCmd)
			
			buf := new(bytes.Buffer)
			cmd.SetOut(buf)
			cmd.SetErr(buf)
			cmd.SetArgs(append([]string{"serve"}, tt.args...))
			
			err := cmd.Execute()
			if (err != nil) != tt.wantErr {
				t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

Distribuição

Cross-Compilation

# Makefile
BINARY_NAME=backup
VERSION=$(shell git describe --tags --always --dirty)
LDFLAGS=-ldflags "-X cmd.Version=$(VERSION) -s -w"

.PHONY: all build clean

all: build

build:
	go build $(LDFLAGS) -o bin/$(BINARY_NAME) ./cmd/backup

# Cross-compilation
build-all:
	# Linux
	GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o bin/$(BINARY_NAME)-linux-amd64 ./cmd/backup
	GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o bin/$(BINARY_NAME)-linux-arm64 ./cmd/backup
	
	# macOS
	GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o bin/$(BINARY_NAME)-darwin-amd64 ./cmd/backup
	GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o bin/$(BINARY_NAME)-darwin-arm64 ./cmd/backup
	
	# Windows
	GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o bin/$(BINARY_NAME)-windows-amd64.exe ./cmd/backup

clean:
	rm -rf bin/

Release com GoReleaser

# .goreleaser.yaml
project_name: backup

builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - amd64
      - arm64

archives:
  - format: tar.gz
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
    format_overrides:
      - goos: windows
        format: zip

checksum:
  name_template: 'checksums.txt'

snapshot:
  name_template: "{{ incpatch .Version }}-next"

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'

Checklist para CLI Profissional

  • Nome consistente - Comando curto e memorável
  • --help completo - Todos os comandos documentados
  • Validação - Erros claros para input inválido
  • Feedback visual - Progress indicators para operações longas
  • Exit codes - 0 para sucesso, não-zero para erros
  • Config files - Suporte a YAML/JSON/TOML
  • Auto-complete - Scripts para bash/zsh/fish
  • Version flag - --version funcional
  • Logs estruturados - Output consistente
  • Testes - Cobertura de comandos principais

Próximos Passos

Continue sua jornada:

  1. Go e PostgreSQL - Persistência de dados
  2. Go e gRPC - APIs de alta performance
  3. Go Observability - Logs e métricas
  4. Go para Microserviços - Arquitetura distribuída

Crie CLIs poderosas com Go. Compartilhe sua ferramenta!

Perguntas frequentes

Como criar uma CLI com Golang do zero?

Comece com um pacote main, leia argumentos com o pacote flag, valide entradas, retorne exit codes previsíveis e compile com go build. Depois que o comando básico funcionar, extraia a lógica para funções testáveis e só então adicione Cobra se precisar de subcomandos.

Preciso usar Cobra para criar CLI em Go?

Não. O pacote flag resolve CLIs pequenas com poucas opções. Cobra faz sentido quando a ferramenta tem subcomandos, help rico, autocomplete, configuração por arquivo ou uma estrutura parecida com kubectl e hugo.

Go é uma boa linguagem para ferramentas de linha de comando?

Sim. Go gera binário único, compila rápido, faz cross-compilation com facilidade e tem uma biblioteca padrão forte. Por isso muitas ferramentas de infraestrutura, como Docker, Kubernetes, Terraform e Hugo, usam Go.