# Go CLI + TUI

Command-line applications and terminal interfaces distributed as binaries.

Cobra · Bubble Tea when needed · GoReleaser

[Complete guide (Markdown)](/stacks/go-cli/guide.md) · [Structured record (JSON)](/stacks/go-cli/record.json)

Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. [Check scope](/research-freshness.json). Dependency combinations and setup need project-specific qualification.

## Use cases

- Shipped command-line products

- infrastructure administration tools

- terminal dashboards

## Choose it when

- Want compiled distribution with Go ecosystem access

- Command structure and terminal UX matter more than tight low-level control

## Consider another path when

- Primarily a short disposable script

- Host SDK or native UI framework mandates another language

- Strict no-GC constraints

## Working practices

- Keep command parsing separate from domain operations. Test help, exit codes, stdout and stderr.

- Respect cancellation, timeouts and non-interactive input; do not require a TUI for scripting.

- Skip persistence for stateless tools. Delegate long-lived work to a service with a durable job ID.

- Verify release binaries on each supported OS and architecture, including the required runtime dependencies.

## Tools by job

### Command parsing: Cobra

Command/help/completion structure plus event-driven terminal UI.

Keep noninteractive and machine-readable modes; test TTY absence, cancellation and terminal restoration.

- [https://github.com/spf13/cobra](https://github.com/spf13/cobra)

- [https://github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea)

- [https://pkg.go.dev/flag](https://pkg.go.dev/flag)

### Terminal UI: Bubble Tea only for interactive screens

Command/help/completion structure plus event-driven terminal UI.

Keep noninteractive and machine-readable modes; test TTY absence, cancellation and terminal restoration.

- [https://github.com/spf13/cobra](https://github.com/spf13/cobra)

- [https://github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea)

- [https://pkg.go.dev/flag](https://pkg.go.dev/flag)

### HTTP client: net/http Client

Standard client with contexts and transport reuse.

Set timeouts, bound responses and close bodies; retry only when operation semantics allow.

- [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)

### Persistence and migrations: None for stateless tools

Avoid introducing a database into a command wrapper.

Local durable state requires a separately selected storage/schema policy; never assume flat-file writes are atomic.

- [https://pkg.go.dev/os](https://pkg.go.dev/os)

### Configuration: Typed settings loaded with os.LookupEnv

Explicit startup validation and standard structured logs.

Define precedence; reject malformed required values; redact secrets. Environment access alone is not validation or secret management.

- [https://pkg.go.dev/os](https://pkg.go.dev/os)

- [https://pkg.go.dev/flag](https://pkg.go.dev/flag)

- [https://pkg.go.dev/log/slog](https://pkg.go.dev/log/slog)

### Application logging: log/slog

Explicit startup validation and standard structured logs.

Define precedence; reject malformed required values; redact secrets. Environment access alone is not validation or secret management.

- [https://pkg.go.dev/os](https://pkg.go.dev/os)

- [https://pkg.go.dev/flag](https://pkg.go.dev/flag)

- [https://pkg.go.dev/log/slog](https://pkg.go.dev/log/slog)

### Tests and static checks: testing; go vet; race detector; govulncheck

Standard toolchain feedback plus exercised concurrency and known-vulnerability checks.

Race detector requires supported target/toolchain and exercised code; no scanner proves absence of vulnerabilities.

- [https://pkg.go.dev/testing](https://pkg.go.dev/testing)

- [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)

- [https://golangci-lint.run/docs/welcome/quick-start/](https://golangci-lint.run/docs/welcome/quick-start/)

- [https://go.dev/doc/security/vuln/](https://go.dev/doc/security/vuln/)

### Durable work: Delegate long jobs to a service; persist its job ID

Work survives the terminal process only through an explicit durable owner.

A goroutine or TUI background command dies with the process; reconnection needs a stable job identity.

- [https://riverqueue.com/docs/transactional-enqueueing](https://riverqueue.com/docs/transactional-enqueueing)

- [https://github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea)

### Distribution: GoReleaser

Target builds and release artifact automation.

Review generated release actions, supported targets, signing and updater policy. cgo can complicate cross-building.

- [https://goreleaser.com/getting-started/quick-start/](https://goreleaser.com/getting-started/quick-start/)

### Machine-readable output: encoding/json/v2 on a supporting pinned Go toolchain

Stable JSON output lets scripts consume commands without parsing terminal text

Add an explicit --json/output mode and document schema/exit codes; reserve stdout for results, stderr for diagnostics, and disable interactive/color output for pipelines. Confirm API availability on the pinned Go version; v2 changes encoding/decoding defaults, so test schema/null/omission compatibility before migrating.

- [https://pkg.go.dev/encoding/json](https://pkg.go.dev/encoding/json)

- [https://pkg.go.dev/encoding/json/v2](https://pkg.go.dev/encoding/json/v2)

### Cancellation and shutdown: signal.NotifyContext

User interrupts should cancel outbound work and leave terminal/files usable

Propagate cancellation through work; bound cleanup time and test interrupts during writes. Avoid adding an async runtime to a synchronous Rust CLI solely for signals.

- [https://pkg.go.dev/os/signal](https://pkg.go.dev/os/signal)

Build with the stack’s strengths

## Architecture and developer workflow.

Recommendation: keep the command tree as an adapter over testable application functions. A noninteractive command is a versioned automation interface; a Bubble Tea view is an optional interactive client of the same functions. The executable owns stream, exit-status, cancellation and configuration contracts.

Version scope: Go 1.27 toolchain documentation, Cobra/pflag current guide, Bubble Tea upstream API and GoReleaser v2 documentation. Pin compatible module/tool versions; Bubble Tea major/module-path changes need explicit review. No release matrix was built. · Reviewed 2026-10-11

### Architecture boundaries

- Recommendation: Cobra parses and validates arguments, then RunE calls domain operations that return typed results/errors. Formatters own text versus JSON output; diagnostics and progress go to stderr, leaving stdout usable by scripts.

- Recommendation: expose a schema/version field in machine-readable output and configuration. Centralize environment/flag/file precedence and secret redaction; preserve old names through an explicit deprecation period.

- Recommendation: use Bubble Tea Model/Update/View and commands for terminal interaction. Keep interactive state separate from persisted application configuration, and disable prompts/spinners for pipes or explicit noninteractive mode.

- Recommendation: use the established module workflow and GoReleaser target matrix for binary distribution. Native libraries, signing, notarization and OS behavior require per-target checks even when cross-compilation succeeds.

### Tools and feedback

- Recommendation: inspect existing flags, help output, scripts and configuration before changing the CLI. A new project can start with go mod init and Cobra CLI generation when desired; generation is optional, not an architecture.

- Recommendation: run gofmt, go vet ./... and go test ./...; add subprocess-level contract fixtures that capture stdout, stderr and exit status. Domain unit tests alone miss terminal and shell behavior.

- Recommendation: exercise --help, invalid flags, piped input, --output json (if implemented), interrupted work and no-TTY execution. Choose and document application exit codes; Cobra does not define every business error code.

- Recommendation: after installing a pinned GoReleaser v2, review .goreleaser.yaml, run goreleaser check and goreleaser release --snapshot --clean to prepare local artifacts. These are review commands, not a published release; verify clean-machine install and upgrade on the supported targets. Current GoReleaser OSS installation docs require Go 1.27 when installing the tool from source; a prebuilt tool or a compatible pinned older tool is a separate choice from the application minimum Go version.

### Translations to avoid

- Avoid logging or progress bars on machine-readable stdout; one extra line can break the calling script.

- Avoid os.Exit inside domain functions or cleanup scopes: os.Exit skips deferred calls. Map errors to a code after cleanup in the entry point.

- Avoid requiring a TTY for every command; automation needs explicit inputs and a prompt-free path.

- Avoid rewriting configuration in place without a version/backup; users may need to downgrade the executable.

- Avoid silently switching encoding/json to Go 1.27 encoding/json/v2 during a rewrite; changed serialization defaults require compatibility fixtures and an explicit protocol decision.

[cobra.dev →](https://cobra.dev/docs/how-to-guides/working-with-flags/)
[github.com →](https://github.com/charmbracelet/bubbletea)
[pkg.go.dev →](https://pkg.go.dev/encoding/json#Decoder.DisallowUnknownFields)
[pkg.go.dev →](https://pkg.go.dev/os#Exit)
[pkg.go.dev →](https://pkg.go.dev/golang.org/x/sync/errgroup)
[pkg.go.dev →](https://pkg.go.dev/os/signal#NotifyContext)
[goreleaser.com →](https://goreleaser.com/getting-started/how-it-works/)
[goreleaser.com →](https://goreleaser.com/customization/builds/go/)
[goreleaser.com →](https://goreleaser.com/customization/sign/notarize/)
[goreleaser.com →](https://goreleaser.com/customization/publish/snapshots/)
[goreleaser.com →](https://goreleaser.com/getting-started/quick-start/)
[goreleaser.com →](https://goreleaser.com/getting-started/install/oss/)
[go.dev →](https://go.dev/doc/go1.27)

For an existing application

## Keep the contracts while you change the stack.

These are documented options and proposed checks. Inspect the actual source application before adopting a transition.

CLI and pipe contracts

### Inspect the source

Capture scripts consuming current flags, positional arguments, stdout/stderr, exit codes and completion output.

### During coexistence

Recommendation: retain old subcommands/aliases as thin adapters while introducing an explicit JSON mode. Mark renamed flags deprecated through Cobra instead of silently changing their meaning.

### Target design

Recommendation: Cobra RunE plus centralized result/error mapping; versioned JSON on stdout and diagnostics on stderr. Freeze documented application codes for success, usage and operational failure.

### Rollback

Recommendation: install the preceding binary and continue serving the old protocol. Keep newly introduced machine schemas behind explicit versions so callers can choose a compatible response.

### Benefits and prerequisites

**A stable CLI protocol permits automation and humans to share one executable.**
Requires: Consumer fixtures cover streams/status; formatting and error categories are explicit; deprecation schedule preserves callers.
Tradeoffs: Versioned output requires ongoing compatibility ownership; usage errors and remote failures may need different handling.

### Checks before transfer

- Run old calling scripts against the new binary and compare all three output channels.

- Verify invalid flags perform no effects and produce the documented exit code.

- Pipe JSON to a parser; help/progress/logging must not contaminate its data.

[cobra.dev →](https://cobra.dev/docs/how-to-guides/working-with-flags/)
[pkg.go.dev →](https://pkg.go.dev/encoding/json#Decoder.DisallowUnknownFields)
[pkg.go.dev →](https://pkg.go.dev/os#Exit)

Data and schema

### Inspect the source

Inventory config paths, default values, flag/environment precedence, secret fields and old readers.

### During coexistence

Recommendation: parse both old and versioned schemas through adapters; display migration diagnostics on stderr and offer an explicit migration command with backup. Keep the old file untouched until conversion passes.

### Target design

Recommendation: a typed versioned config decoded deliberately, with unknown-field policy and semantic validation. Write a same-directory temporary file, validate it and replace only after success; qualify filesystem/OS behavior and cross-process ownership.

### Rollback

Recommendation: restore the backup and preceding executable together. Preserve old readable configuration throughout the downgrade window; do not roll back after newer-only values without an explicit mapping.

### Benefits and prerequisites

**A versioned configuration contract makes upgrades and later rollback predictable.**
Requires: Old fixtures exist; migration preserves semantics and backups; one writer and tested replacement behavior are defined.
Tradeoffs: Strict unknown-field rejection can hinder mixed-version use; atomic replacement does not establish crash durability or coordinate concurrent writers.

### Checks before transfer

- Load representative old config, migrate and compare effective settings.

- Reject unknown unsupported schema versions before modifying files.

- Interrupt conversion and verify either the original or complete new file remains; test permissions on each target OS.

[pkg.go.dev →](https://pkg.go.dev/encoding/json#Decoder.DisallowUnknownFields)
[cobra.dev →](https://cobra.dev/docs/how-to-guides/working-with-flags/)

Jobs and schedules

### Inspect the source

Preserve per-item effects, retry decisions, ordering and interrupt behavior for batch commands; a process-local queue has no restart history.

### During coexistence

Recommendation: keep the sequential executor available and compare the new bounded executor on fixture inputs. Route one operation class at a time; reuse stable operation IDs across retries.

### Target design

Recommendation: signal.NotifyContext plus errgroup limit or a fixed worker pool. Pass context through clients, close resources after workers finish, and persist a checkpoint/receipt when interrupted effects need later recovery.

### Rollback

Recommendation: return to sequential execution using the same checkpoint contract. Reconcile ambiguous outcomes first; restarting from item zero is not a safe general rollback.

### Benefits and prerequisites

**Bounded fan-out can overlap independent I/O while maintaining a clear cancellation boundary.**
Requires: Concurrency quota and total deadline are set; downstream calls honor cancellation; completed and ambiguous effects can be reconciled.
Tradeoffs: No throughput gain is measured; cancellation cannot undo effects; a retry can duplicate a write without provider or business idempotency.

### Checks before transfer

- Interrupt midway through a batch and verify completed, pending and ambiguous items are reported separately.

- Simulate one failed item; verify the selected stop/continue policy and bounded parallelism.

- Resume using stable IDs and prove acknowledged effects are not repeated.

[pkg.go.dev →](https://pkg.go.dev/golang.org/x/sync/errgroup)
[pkg.go.dev →](https://pkg.go.dev/os/signal#NotifyContext)
[pkg.go.dev →](https://pkg.go.dev/os#Exit)

Delivery and recovery

### Inspect the source

Preserve command name, install path, OS/architecture support, update channel and downgrade compatibility.

### During coexistence

Recommendation: produce snapshot artifacts beside the old release and smoke-test them without changing the default download. Review generated package-manager changes and publish channels only after target verification.

### Target design

Recommendation: pinned GoReleaser v2 matrix with metadata/checksums, OS-specific install tests and an explicit signing path. CGO dependencies and macOS signing/notarization need separate toolchain/credential setup; the documented native package notarization flow is Pro; qualify the chosen binary or package flow separately.

### Rollback

Recommendation: repoint the owned download/package channel to the previous verified release and keep its artifacts available. Restore compatible configuration with the binary; do not silently overwrite unrelated install locations.

### Benefits and prerequisites

**A repeatable artifact matrix reduces manual release assembly.**
Requires: Build inputs/tool versions are pinned; each promised target is installed and exercised; signing credentials and package metadata are owned.
Tradeoffs: Cross-compilation is not execution evidence; CGO can require native toolchains; selected signing/notarization automation may require a paid tier.

### Checks before transfer

- Install on clean Linux, macOS and Windows hosts for every advertised architecture.

- Verify version metadata, checksums, permissions and executable path.

- Upgrade from the old package and downgrade with preserved config; inspect macOS Gatekeeper behavior for the actual downloaded artifact.

[goreleaser.com →](https://goreleaser.com/getting-started/how-it-works/)
[goreleaser.com →](https://goreleaser.com/customization/builds/go/)
[goreleaser.com →](https://goreleaser.com/customization/sign/notarize/)
[goreleaser.com →](https://goreleaser.com/customization/publish/snapshots/)

[Read the migration workflow →](/rewrite/guide.md)

## Setup

- Start in a source checkout; go mod init example.com/tool using its intended module path.

- Run go get github.com/spf13/cobra; resolve a supported Bubble Tea release only if a TUI is required.

- Build cmd/tool with injectable I/O, configuration validation, stable exit codes and explicit noninteractive behavior.

- Write command tests covering malformed input, stdout/stderr, cancellation and safe retries; use isolated temporary paths.

- Run goreleaser init, then review and pin the generated configuration and target matrix. No setup or publishing has been executed.

## Verification

Run these in the configured application; they are instructions, not recorded passes.

```
gofmt -l .
go test ./...
go test -race ./...
go vet ./...
govulncheck ./...
go build ./cmd/tool
goreleaser check
goreleaser build --snapshot --clean
```

## Open questions

- Which OS/architectures and terminals must be supported?

- What stable exit codes, JSON schema, configuration precedence and noninteractive behavior are required?

- Which signing, package managers and update mechanism will own distribution?

## Sources

- [Cobra](https://github.com/spf13/cobra)

- [Bubble Tea](https://github.com/charmbracelet/bubbletea)

- [Go flag](https://pkg.go.dev/flag)

- [Go net/http](https://pkg.go.dev/net/http)

- [Go os configuration primitives](https://pkg.go.dev/os)

- [Go structured logging](https://pkg.go.dev/log/slog)

- [Go testing](https://pkg.go.dev/testing)

- [Go data race detector](https://go.dev/doc/articles/race_detector)

- [golangci-lint quick start](https://golangci-lint.run/docs/welcome/quick-start/)

- [Go vulnerability management](https://go.dev/doc/security/vuln/)

- [River transactional enqueueing](https://riverqueue.com/docs/transactional-enqueueing)

- [GoReleaser quick start](https://goreleaser.com/getting-started/quick-start/)

- [Go JSON API](https://pkg.go.dev/encoding/json)

- [Go signal API](https://pkg.go.dev/os/signal)

- [Go JSON v2](https://pkg.go.dev/encoding/json/v2)

---

Canonical page: https://stack.menu/stacks/go-cli/
