Skip to content
Platform Status

CLI Generation

Distribute a CLI

CLI distribution currently uses generated GoReleaser configuration and release scripts rather than the standard speakeasy configure publishing workflow.

The CLI target generates the artifacts needed for cross-platform binary distribution: a GoReleaser configuration, a GitHub Actions release workflow, install scripts for Linux, macOS, and Windows, optional Homebrew tap publishing, optional WinGet publishing, and optional Linux package artifacts via nFPM.

Method
GoReleaser + GitHub Releases
Description
Automated cross-platform binary builds triggered by git tags. Produces archives, checksums, and a detached GPG signature for the checksum file.
Best for
Production releases
Homebrew tap
Description
Optional Homebrew formula publishing through a customer-owned tap repository. Users install with brew install org/tap/cli.
Best for
macOS/Linux package-manager installs
WinGet
Description
Optional Windows Package Manager publishing through a generated PR to microsoft/winget-pkgs. Users install with winget install Publisher.Package.
Best for
Windows-native installs
Linux packages (nFPM)
Description
Optional .deb, .rpm, or .apk packages attached to GitHub Releases. Users download the package artifact from a release and install it manually.
Best for
Linux-first distribution
Install scripts
Description
Generated install.sh and install.ps1 scripts that download the latest or a pinned release from GitHub.
Best for
End-user installation
go install
Description
Install directly from the Go module path. Best suited for Go developers or internal tooling users who already have Go installed.
Best for
Developer-first distribution

With cli.generateRelease: true (the default), the generator produces:

  • .goreleaser.yaml
  • .github/workflows/release.yaml
  • scripts/install.sh
  • scripts/install.ps1
  • checksums.txt plus a detached GPG signature for the checksum artifact at release time
  • optional Homebrew tap publishing when cli.distribution.homebrew.enabled: true
  • optional WinGet publishing when cli.distribution.winget.enabled: true
  • optional Linux package artifacts (.deb, .rpm, .apk) when cli.distribution.nfpm.enabled: true

To manage releases with separate tooling, disable this behavior:

cli:
generateRelease: false

The generated .goreleaser.yaml builds binaries for the major desktop/server targets:

Operating system
Linux
Architectures
amd64, arm64
macOS (Darwin)
Architectures
amd64, arm64
Windows
Architectures
amd64, arm64

The generated build metadata is derived from the CLI config, especially cliName. For example, with cliName: "petstore", GoReleaser will build from ./cmd/petstore and publish a binary named petstore.

Enable Homebrew formula generation in gen.yaml:

cli:
generateRelease: true
distribution:
homebrew:
enabled: true
tap: myorg/homebrew-petstore

When enabled, GoReleaser updates a formula in the configured tap repository as part of the release.

One-time setup:

  • Create a tap repository such as github.com/myorg/homebrew-petstore
  • Add a HOMEBREW_TAP_GITHUB_TOKEN secret to the CLI repository
  • For classic personal access tokens, grant public_repo for public taps or repo for private taps
  • For fine-grained personal access tokens, grant Contents: Read and write on the tap repository

End users install the CLI with:

Terminal window
brew install myorg/petstore/petstore

Enable WinGet manifest generation in gen.yaml:

cli:
generateRelease: true
distribution:
winget:
enabled: true
publisher: MyOrg
repositoryOwner: mygithubuser
publisherUrl: https://example.com
packageIdentifier: MyOrg.Petstore
license: Apache-2.0

When enabled, GoReleaser generates a WinGet manifest, pushes a branch, and opens a PR against microsoft/winget-pkgs.

One-time setup:

  • Fork microsoft/winget-pkgs under a GitHub account or organization
  • Set distribution.winget.repositoryOwner to the owner of that fork
  • Add a WINGET_GITHUB_TOKEN secret to the CLI repository
  • Use a classic GitHub personal access token with public_repo scope for the fork owner account
  • Ensure the configured distribution.winget.packageIdentifier matches the desired publisher/package name

End users install the CLI with:

Terminal window
winget install MyOrg.Petstore

Enable Linux package generation in gen.yaml:

cli:
generateRelease: true
distribution:
nfpm:
enabled: true
formats: deb,rpm
maintainer: "Speakeasy <oss@speakeasy.com>"
license: "Apache-2.0"

When enabled, GoReleaser attaches Linux packages to the same GitHub Release as the archive artifacts.

Important: this does not create an apt or yum repository automatically. Users download the package from GitHub Releases and install it manually.

version: 2
builds:
- id: petstore
main: ./cmd/petstore
binary: petstore
env:
- CGO_ENABLED=0
goos: [linux, windows, darwin]
goarch: [amd64, arm64]
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.buildTime={{.Date}}
archives:
- id: petstore
formats: [tar.gz]
format_overrides:
- goos: windows
formats: [zip]
checksum:
name_template: checksums.txt
signs:
- artifacts: checksum

The generated release workflow signs the checksum file with GPG. This produces:

  • checksums.txt
  • checksums.txt.sig

Add these repository secrets before pushing a release tag:

  • CLI_GPG_SECRET_KEY
  • CLI_GPG_PASSPHRASE

Use an ASCII-armored private key for CLI_GPG_SECRET_KEY. The generated workflow imports that key, exports its fingerprint to GPG_FINGERPRINT, and GoReleaser signs the checksum artifact.

The generated GitHub Actions workflow runs GoReleaser when a version tag is pushed:

Terminal window
git tag v0.1.0
git push origin v0.1.0

This creates a GitHub Release and uploads the compiled archives and checksums.

  • GitHub Actions permissions: the release workflow needs contents: write
  • Repository visibility/access: private repositories require appropriate access for anyone downloading release assets
  • Checksum signing secrets: set CLI_GPG_SECRET_KEY and CLI_GPG_PASSPHRASE. CLI_GPG_SECRET_KEY must contain an ASCII-armored private key used to sign the generated checksum file.
  • Homebrew tap secret: when Homebrew publishing is enabled, set HOMEBREW_TAP_GITHUB_TOKEN. For classic PATs use public_repo (public tap) or repo (private tap). For fine-grained PATs grant Contents: Read and write on the tap repository.
  • WinGet secret: when WinGet publishing is enabled, set WINGET_GITHUB_TOKEN using a classic PAT with public_repo scope for the account that owns the winget-pkgs fork, and set distribution.winget.repositoryOwner to that fork owner.

The generated install scripts download a release archive from GitHub, extract the binary, and place it into a sensible install location.

Terminal window
curl -fsSL https://raw.githubusercontent.com/{org}/{repo}/main/scripts/install.sh | bash

The script supports two environment variables derived from the configured envVarPrefix:

  • <PREFIX>_INSTALL_DIR — override the install directory
  • <PREFIX>_VERSION — install a specific release tag instead of latest

Example:

Terminal window
PETSTORE_INSTALL_DIR=/opt/bin curl -fsSL .../install.sh | bash
PETSTORE_VERSION=v0.2.0 curl -fsSL .../install.sh | bash

By default, the installer tries /usr/local/bin first and falls back to ~/.local/bin when it does not have write access.

Terminal window
iwr -useb https://raw.githubusercontent.com/{org}/{repo}/main/scripts/install.ps1 | iex

The PowerShell installer uses the same environment-variable pattern:

  • <PREFIX>_INSTALL_DIR
  • <PREFIX>_VERSION

By default, it installs under %LOCALAPPDATA%\Programs\<cliName> and updates the user PATH when needed.

If Homebrew tap publishing is enabled for the CLI, users can install it with:

Terminal window
brew install myorg/petstore/petstore

This requires the publisher to maintain a tap repository named homebrew-<name> and configure HOMEBREW_TAP_GITHUB_TOKEN in the CLI repository’s release workflow.

If WinGet publishing is enabled for the CLI, Windows users can install it with:

Terminal window
winget install MyOrg.Petstore

This requires the publisher to fork microsoft/winget-pkgs, set distribution.winget.repositoryOwner to that fork owner, configure WINGET_GITHUB_TOKEN in the CLI repository’s release workflow, and supply the required publisher/package metadata in gen.yaml.

After downloading a release artifact from GitHub Releases, install it with the matching package manager.

Terminal window
sudo dpkg -i petstore_*_amd64.deb
Terminal window
sudo rpm -i petstore_*_amd64.rpm
Terminal window
sudo apk add --allow-untrusted ./petstore_*_amd64.apk

Only document the package formats actually published for the CLI. End users should see installation steps that match the assets attached to the release they download.

Users with the Go toolchain can install the CLI directly from the module:

Terminal window
go install {packageName}/cmd/{cliName}@latest

Example:

Terminal window
go install github.com/acme/petstore-cli/cmd/petstore@latest

This command is derived from the packageName and cliName settings.

Note

go install does not inject the version metadata that the GoReleaser build adds with linker flags. Version output may therefore appear as (devel).

Generated CLIs include Cobra’s built-in completion command, so after installation users can enable completions with:

Terminal window
petstore completion bash
petstore completion zsh
petstore completion fish
petstore completion powershell

This works regardless of whether the CLI was installed from a release archive, an install script, or go install.

Issue
GoReleaser fails with permission errors
Solution
Ensure the GitHub Actions workflow has contents: write permission and that the repository token has enough scope to create releases and upload assets.
Install script fails on private repos
Solution
The generated scripts download from GitHub release URLs. Private repositories require repository access, and an additional authenticated installation flow may be needed in the target environment.
Checksums or archives differ from local builds
Solution
Use the generated GoReleaser workflow for official builds. Manual builds can differ because of environment and linker-flag differences.
go install shows (devel) version output
Solution
This is expected because go install does not apply the GoReleaser ldflags used in release builds.