Go CLI + TUI
Command-line applications and terminal interfaces distributed as binaries.
Cobra · Bubble Tea when needed · GoReleaser
Complete guide (Markdown) · Structured record (JSON)
Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. Check scope. 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.
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.
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.
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.
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.
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.
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.
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.
Distribution: GoReleaser
Target builds and release artifact automation.
Review generated release actions, supported targets, signing and updater policy. cgo can complicate cross-building.
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.
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.
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.
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.
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.
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.
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.
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?