Upgrading

autocom checks for new versions in the background after every command and can replace its own binary in place. This page covers the user-facing flow, the version-check cache, and how releases are produced.

TL;DR

autocom upgrade --check                # see what's available
autocom upgrade                         # update to latest
autocom upgrade --target 1.2.3          # specific version
brew upgrade autocom                    # if installed via Homebrew

How the version check works

  1. After every successful command, the CLI starts an async goroutine that asks the platform you're logged into "what's the latest version?" via GET <api>/cli/releases. The platform routes to the right release channel (default platform-curated, or a tenant-configured override) and replies with a normalized manifest.
  2. If the platform doesn't yet expose /cli/releases (older AutoCom version, the CliReleases module not installed), the CLI transparently falls back to polling the public mirror at gitlab.wexron.io/autocommerce/core/cli.
  3. The result is cached at ~/.autocom/upgrade-check.json with a 24-hour TTL — repeat invocations within the window don't poll the network.
  4. If a newer release is found, the CLI prints a one-line nudge to stderr after subsequent commands:
↑ autocom 1.2.0 available (you're on 1.1.0)
  Run `autocom upgrade` to update — or `autocom upgrade --check` to see what's new.
  1. The nudge is throttled to once every 6 hours so you don't see it on every command.

The check is fully best-effort. Network failures, offline laptops, and corp proxies are all silent — the CLI never blocks on it.

Opting out

Three ways to disable the background check:

Mechanism Scope
autocom config set no_telemetry true Permanent, all invocations
AUTOCOM_NO_UPGRADE_CHECK=1 Per-shell or per-command env var
Pipe to cat, jq, etc. (non-TTY stdout) Per-command — the nudge is TTY-only

The dev build (run via go run . or go install without -ldflags) skips the check unconditionally, since version comparisons against dev always fail.

autocom upgrade — running the update

autocom upgrade [--check] [--force] [--target <ver>] [--dry-run] [--allow-homebrew]

What happens, step by step:

  1. Resolve target releaseGET <api>/cli/releases against the platform you're logged into, taking your configured tenant into account so per-tenant channels resolve. Falls back to the public mirror if your platform doesn't expose the endpoint.
  2. Homebrew guard — refuse if the running binary path looks like a Homebrew install (/Cellar/, /homebrew/, /.linuxbrew/). Override with --allow-homebrew if you really mean it.
  3. Find asset — match autocom_<version>_<os>_<arch>{.tar.gz,.zip} against the release's asset list (built from cli/.goreleaser.yaml).
  4. Download + verify SHA256 — the manifest carries a per-asset SHA256 when served by the API path; on the GitLab fallback path, the CLI also pulls the SHA256SUMS.txt sidecar. The archive's SHA256 is computed and compared. Mismatch aborts with no changes to disk.
  5. Extract — pull autocom (or autocom.exe on Windows) out of the archive.
  6. Atomic replace — write the new binary to a sibling temp file, chmod 0755, os.Rename over the running binary. On Windows, the running .exe can't be renamed-over, so the old file is moved aside to <bin>.old first.

If the SHA256 check fails or the download is interrupted, the original binary is untouched.

Flags

Flag Effect
--check Read-only — print version comparison + release notes excerpt, don't install
--force Reinstall even when already on latest (useful after a corrupted binary)
--target 1.2.3 Install a specific version instead of latest. Useful for downgrades and pinning.
--dry-run Download + verify the asset, but skip the binary replace
--allow-homebrew Bypass the Homebrew guard. Don't — brew upgrade autocom is what you want

Exit codes

Same as the rest of the CLI — see Troubleshooting → Exit codes. The most common ones for upgrade are 5 (network), 6 (server / SHA mismatch / asset not found), and 1 (file-system errors during the swap).

Versioning

The CLI follows SemVer 2.0. Tags use the cli/v<semver> prefix so the same Git repo can host other release tracks later (e.g. theme/v1.0.0).

Bump When
Patch (1.0.0 → 1.0.1) Bug fixes, no behavior change for end users
Minor (1.0.0 → 1.1.0) New commands, new flags — backward compatible
Major (1.x.x → 2.0.0) Breaking change — flag removed, exit code repurposed, output schema changed

The current version lives at cli/VERSION in the source tree and is injected into the binary at link time:

go build -ldflags="-X gitlab.wexron.io/autocommerce/cli/internal/version.Version=$(cat cli/VERSION) ..."

autocom version prints both the version and the git short-SHA the build came from.

Release pipeline

Releases are produced by goreleaser running in CI on v* tag pushes. The pipeline is defined in two places:

  • In the monorepo: ci/templates/cli-tests.yml runs vet + tests + snapshot-build on every MR / main push that touches cli/**. Snapshot mode skips the publish step — CI verifies that a release WOULD work, but doesn't actually create one.
  • In the standalone mirror repo (autocommerce/core/cli, populated by the existing module-split mechanism): cli/.gitlab-ci.yml runs goreleaser on v* tags, publishes to:
    • GitLab Generic Package Registry — autocommerce/core/cli/-/packages
    • Homebrew tap — autocommerce/homebrew-tap
    • GitLab Release entry — with SHA256SUMS.txt, MANIFEST.json, and the 5 OS/arch archives

CI gates on every CLI MR

Job When Blocks merge?
cli:vet-and-test MR / main push touching cli/** ✓ Yes
cli:version-check Same Advisory — warns if cli/VERSION didn't bump
cli:lint (staticcheck) Same Advisory
cli:build-snapshot Same Advisory — verifies cross-compile

Path filtering: if your MR doesn't touch cli/** or ci/templates/cli-tests.yml, none of these jobs fire. A backend-only or docs-only MR pays zero CLI CI cost.

Cutting a release

# 1. Bump cli/VERSION in a normal MR, get it reviewed + merged
echo "1.1.0" > cli/VERSION
git add cli/VERSION cli/CHANGELOG.md
git commit -m "chore(cli): bump to 1.1.0"

# 2. After merge, tag main
git tag cli/v1.1.0
git push gitlab-main cli/v1.1.0

# 3. Pipeline does the rest — goreleaser builds, publishes,
#    creates the GitLab Release entry, updates Homebrew tap.

The first time you cut a release, you also need to:

  1. Pre-create the autocommerce/core/cli GitLab project (it's the subtree-split mirror target).
  2. Pre-create the autocommerce/homebrew-tap GitHub or GitLab project.
  3. Set HOMEBREW_TAP_TOKEN as a CI/CD variable on the mirror project.

After that, every tag push is hands-off.

Where the binary actually comes from

The manifest hands the CLI a download URL that always points at the platform itself — never directly at GitLab, S3, or wherever the binary really lives:

https://api.acme.com/api/v1/cli/releases/1.2.0/download/autocom_1.2.0_macos_arm64.tar.gz

The platform's behaviour at that endpoint depends on the resolved channel:

Channel adapter What the proxy does
gitlab (default) Fetches from the configured GitLab project server-side using CLI_RELEASES_GITLAB_TOKEN, then streams bytes back. End users never see the token; the GitLab project can stay private.
s3 The asset URL in the manifest is already public (or pre-signed). Proxy 302-redirects there so bytes flow direct CDN→client and the platform doesn't pay the egress.
static Same as s3 — the tenant-supplied URL is taken as-is and 302'd through.

The CLI verifies SHA256 either way (the manifest carries it per-asset), so a compromised proxy or CDN can't substitute a tampered binary.

Release channels

Each AutoCom platform serves its own CLI release channel. There are three sources the platform can read from, picked per-tenant:

Source When to use Storage
Default (GitLab proxy) Most installs — points at the AutoCom-hosted CLI mirror Server-side service token reads our private GitLab
Static manifest URL Tenant pins to a curated version list, or runs an internal mirror Any HTTPS-reachable URL serving the published JSON schema
S3 bucket White-label CLI distribution / fully-tenant-owned channel Tenant's own bucket (AWS S3, Cloudflare R2, MinIO, anything S3-compatible)

By default tenants inherit the platform default. To override, an admin PUTs to /api/v1/cli/channel with one of three adapter shapes:

# 1. Static manifest URL — easiest, works with any HTTPS source
curl -X PUT https://api.acme.com/api/v1/cli/channel \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant: acme" \
  -d '{"adapter":"static","config":{"manifest_url":"https://internal.acme.com/cli/manifest.json"}}'

# 2. S3 bucket — UX preferred when team already publishes to S3/R2
curl -X PUT https://api.acme.com/api/v1/cli/channel \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant: acme" \
  -d '{"adapter":"s3","config":{"bucket":"acme-cli-releases","region":"us-east-1","manifest_key":"stable/manifest.json"}}'

# 3. GitLab — point at any GitLab project (self-hosted or gitlab.com)
curl -X PUT https://api.acme.com/api/v1/cli/channel \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant: acme" \
  -d '{"adapter":"gitlab","config":{"project":"acme/cli","api_url":"https://gitlab.acme.com/api/v4","token":"glpat-xxx"}}'

The encrypted-at-rest config column means tokens / secrets are never stored plaintext.

Manifest schema

Anything serving as a CLI release source must publish a JSON document matching this shape (schema_version: 1):

{
  "schema_version": 1,
  "channel": "stable",
  "latest": "1.2.0",
  "min_supported": "1.0.0",
  "releases": [
    {
      "version": "1.2.0",
      "released_at": "2026-05-04T10:00:00Z",
      "notes": "release notes (markdown)",
      "notes_url": "https://...",
      "assets": [
        {
          "os": "darwin",
          "arch": "arm64",
          "url": "https://cdn.example.com/autocom_1.2.0_macos_arm64.tar.gz",
          "size": 3865251,
          "sha256": "abc123…",
          "filename": "autocom_1.2.0_macos_arm64.tar.gz"
        }
      ]
    }
  ]
}

Required per-asset fields: os, arch, url. Recommended: sha256 (otherwise the CLI can't verify the download). Optional: size, filename (filename is auto-generated using the goreleaser convention if absent).

Self-hosted forks

If you deploy AutoCom yourself, releases go through your own platform's /api/v1/cli/releases endpoint automatically — there's nothing to configure on the CLI side. Point your platform at your preferred upstream:

// backend/.env on your platform
CLI_RELEASES_DEFAULT_ADAPTER=gitlab           # or 'static'
CLI_RELEASES_GITLAB_PROJECT=your-group/cli
CLI_RELEASES_GITLAB_API_URL=https://gitlab.your-co.io/api/v4
CLI_RELEASES_GITLAB_TOKEN=glpat-xxx           # read_api scope on the project

# Or, point at a static manifest:
CLI_RELEASES_DEFAULT_ADAPTER=static
CLI_RELEASES_STATIC_MANIFEST_URL=https://releases.your-co.io/autocom/manifest.json

For air-gapped environments where the CLI can never reach api.autocom.wexron.io, you can also rebuild the CLI binary itself with a hardcoded fallback URL:

go build -trimpath -ldflags="\
  -X gitlab.wexron.io/autocommerce/cli/internal/version.Version=1.2.0 \
  -X gitlab.wexron.io/autocommerce/cli/internal/upgrade.APIBaseURL=https://gitlab.your-co.io \
  -X gitlab.wexron.io/autocommerce/cli/internal/upgrade.ProjectPath=your-group%2Fcli \
  -s -w" \
  -o autocom ./

The CLI doesn't ship a runtime override flag for the GitLab fallback URL — by design, so a malicious env var can't redirect the user to a hostile binary source. The platform-served /cli/releases endpoint IS overridable by setting --api-url because it goes through your authenticated session.

Quick reference

# Inspect
autocom version                                    # what version is this binary
autocom upgrade --check                            # what's available
cat ~/.autocom/upgrade-check.json                  # cached check result

# Apply
autocom upgrade                                    # latest
autocom upgrade --target 1.0.5                     # specific
autocom upgrade --dry-run                          # verify only

# Disable the nudge
export AUTOCOM_NO_UPGRADE_CHECK=1                  # per-shell
autocom config set no_telemetry true               # permanent