Skip to main content

CLI: releasing & distribution

The runbook for shipping @jentrix/cli to npm and Homebrew — how a release is cut, the one-time credentials to set up, and the exact steps to take the CLI live for the first time. For using the published CLI see install; the CLI overview is the hub.

This mirrors the maintainer file cli/RELEASING.md and the workflow .github/workflows/release-cli.yml; those two are the source of truth in the repo.

Package identity

FieldValue
npm name@jentrix/cli (scoped, published with --access public)
binjentrix
Node≥ 20
LicenseMIT
Published filesdist/main.js (bundled, deps inlined), surface.json, LICENSE, README.md
HomebrewNot a channel today — the jentrix-au/homebrew-tap repo does not exist and HOMEBREW_TAP_TOKEN is unset, so every release's tap job is a clean skip. npm is the only install channel. See Homebrew details to enable it.

The package lives in cli/ as a standalone package (its own lockfile, excluded from the app's tsconfig/eslint). The prepack script rebuilds dist/ on every pack/publish, so the tarball is always fresh.

Local runtime onboarding also ships agents/ as @jentrix/runner. The same release tag publishes it at the CLI version; its prepack bundles runner-cli.ts and the existing workflow-runner.ts, while Claude/Codex SDK dependencies remain normal runner-package dependencies. Before publishing, run pnpm pack, install the tarball in a clean temporary directory, and exercise jentrix-runner version --json, probe --json, and stubbed run --once. Do not copy runner code or provider SDKs into cli/.

How a release works

Publishing is tag-triggered, never on merge. Pushing a tag matching cli-v* (e.g. cli-v0.3.0) runs the release workflow, which:

  1. Verifies release lockstepnode cli/scripts/check-version.mjs asserts the tag, both package versions, CLI_VERSION, and RUNNER_VERSION agree.
  2. Typechecks and tests both packages and cold-installs the packed runner.
  3. Publishes both npm packages over trusted publishing (OIDC) — no npm credential exists in this repo or its secrets; the registry authorizes the publish by validating a short-lived token GitHub mints for this exact repo and workflow file. Each prepack rebuilds dist/ first.
  4. Creates a GitHub Release for the tag.
  5. Bumps the Homebrew tap — if HOMEBREW_TAP_TOKEN is set, it renders Formula/stacks.rb from the published tarball's sha256 and pushes it to jentrix-au/homebrew-tap; otherwise this step is skipped cleanly and you bump the formula by hand (below).

One-time setup

1. npm (required)

  1. Create the jentrix organization on npmjs.com — free for public packages — or use your own npm user scope.

  2. Enable 2FA on the npm account. The workflow never touches the account, but editing trusted-publisher configuration is one of the operations npm gates on 2FA — and without it the manual fallback below cannot publish at all.

  3. Configure a trusted publisher for each package — @jentrix/cli and @jentrix/runner — under the package's Settings → Trusted publisher → GitHub Actions:

    FieldValue
    Organization or userjentrix-au
    Repositorytask-manager
    Workflow filenamerelease-cli.yml
    Environment(leave empty)

    Both packages need their own entry; a release that publishes one and fails the other is worse than one that fails first. Renaming the workflow file breaks publishing until the trusted publisher is re-pointed at the new name — the filename is part of what the registry validates.

There is no NPM_TOKEN. Publishing tokens were retired here deliberately: npm is phasing out 2FA-bypass tokens (they lost account and package management in early August 2026 and lose direct publishing around January 2027), and OIDC removes the secret rather than rescheduling its rotation.

2. Homebrew (optional, NOT set up — brew is not an install channel today)

Neither step below has been done, so brew install jentrix-au/tap/jentrix does not work and the install docs deliberately do not mention it. The tap job in every release so far has been a clean skip. To enable it:

  1. Create an empty repo jentrix-au/homebrew-tap (the homebrew- prefix is what makes brew install jentrix-au/tap/jentrix resolve).
  2. Generate a fine-grained PAT with contents: read/write on just that repo and add it as the secret HOMEBREW_TAP_TOKEN.

Without HOMEBREW_TAP_TOKEN the release still publishes to npm and creates the GitHub Release — only the automatic formula bump is skipped. That skip exits 0 and the job reports success, so a green release run is NOT evidence the formula moved; read the job log, or check the tap, before claiming it did. Re-add brew to cli-install.md, cli.md, docs/README.md, first-session.md and the workflow's release notes once the tap actually exists.

Go-live runbook (0.3.0)

From the reviewed release commit on main:

# 0. Prerequisite: a trusted publisher is configured for BOTH packages (see
#    One-time setup). Confirm the package and binary versions are consistent
#    locally before tagging:
node cli/scripts/check-version.mjs 0.3.0
#    -> "version 0.3.0 OK (CLI and runner packages + binaries match)"

# 1. Push the branch so the workflow exists on GitHub.
git push origin main

# 2. Tag and push the tag — this is what triggers the release.
git tag cli-v0.3.0
git push origin cli-v0.3.0

Watch the release-cli workflow run in the repo's Actions tab. On success:

npm view @jentrix/cli version        # -> 0.3.0
npm view @jentrix/runner version     # -> 0.3.0
npm install -g @jentrix/cli
jentrix --version                            # -> 0.3.0 (surface: … tools, …)

brew install jentrix-au/tap/jentrix does not work — the tap has never been created. If you set it up, it works once the bump-homebrew job has actually pushed the formula (verify by reading the tap, not the job's exit status).

Cutting a subsequent release

  1. Bump the version in all four places (a unit test and the workflow enforce they match the tag):
    • cli/package.jsonversion
    • cli/src/client.tsCLI_VERSION
    • agents/package.jsonversion
    • agents/lib/version.tsRUNNER_VERSION
  2. If the MCP tool surface changed, run pnpm gen:cli-surface from the repo root and commit every generated diff. The command regenerates cli/surface.json, the CLI help goldens, the application tool-count constant, and the marked tool-count snippets in the user/product docs. The root surface-sync test rebuilds the manifest/count/docs in memory; the CLI package tests render help through Commander and fail if a golden is stale.
  3. Commit, then tag and push:
    git tag cli-v0.3.0
    git push origin cli-v0.3.0
    

The check-version guard means a forgotten bump in any place fails the release loudly instead of shipping a CLI whose --version disagrees with its package version.

Homebrew details

The formula installs the published npm package (it depends_on "node"), so a Homebrew release always follows the npm publish. The source-of-truth template is cli/homebrew/stacks.rb; the live copy lives at Formula/stacks.rb in the tap repo.

Automatic bump (when HOMEBREW_TAP_TOKEN is set) happens as part of the release. Manual bump, in a checkout of jentrix-au/homebrew-tap after the npm publish is live:

node /path/to/task-manager/cli/scripts/render-homebrew-formula.mjs 0.3.0 \
  > Formula/stacks.rb
git commit -am "stacks 0.3.0" && git push

The render script fetches the published tarball, computes its sha256, and writes the formula — so the url + sha256 always match the exact artifact on npm.

Verifying a release

npm view @jentrix/cli                # version, tarball, files, dist-tags
npm install -g @jentrix/cli && jentrix --version

Verify the artifacts, never the workflow's colour. The tap job in particular succeeds while doing nothing when HOMEBREW_TAP_TOKEN is absent, and a green run has been mistaken for a bumped formula more than once.

Troubleshooting

SymptomCause & fix
npm ERR! 402 Payment RequiredScoped package published without public access. The workflow passes --access public; if publishing by hand, add it.
npm ERR! 403 …you do not have permissionThe publishing identity doesn't own the @jentrix scope. Create/join the jentrix org, or use a scope you own.
npm error need auth in the release workflowNo trusted publisher for that package, or it names a different repo/workflow filename. OIDC has no fallback by design — check the package's Settings → Trusted publisher on npmjs.com.
403 …Two-factor authentication or granular access token with bypass 2fa enabled is required when publishing locallyThe npm account has 2FA disabled, so the registry refuses an unprotected publish and --otp cannot help — there is no code to give. Enable 2FA on the account, npm login again, and retry with a real one-time code.
npm ERR! 403 …cannot publish over previously published versionThat version already exists on npm — bump the version (npm versions are immutable).
Release fails at the version-guard stepcli-v<X> tag, package.json version, and CLI_VERSION disagree. Run node cli/scripts/check-version.mjs <X> locally, fix, re-tag.
npm error need auth when publishing locallyNo npm credentials on the machine. npm login, or rely on CI (which needs no credential at all).
brew install can't find the formulaThe tap repo must be named homebrew-tap under jentrix-au, with the formula at Formula/stacks.rb.
Workflow jobs die in seconds with "recent account payments have failed or your spending limit needs to be increased"GitHub Actions billing, not the release. Fix billing (Settings → Billing & plans) — a rerun changes nothing until you do, and since publishing moved to OIDC, CI is the only path that needs no account changes. If the release genuinely can't wait: enable 2FA on the npm account (the registry refuses an unprotected publish), run the workflow's own gates by hand (pnpm typecheck + pnpm test in both packages, RUNNER_PACK_SMOKE=1 node --import tsx --test runner-pack.smoke.test.ts in agents/), npm login, then npm publish --access public --otp=<code> in agents/ and cli/ with a fresh code each, and gh release create the tag by hand. Do NOT rerun the failed workflow afterwards — npm refuses duplicate versions, so it fails at publish; the Homebrew tap catches up on the next tagged release (0.4.8 shipped this way 2026-08-08; 0.4.23 hit this on 2026-08-12 and shipped from CI once billing was fixed).

npm provenance

Nothing to configure — id-token: write is already on the publish job for trusted publishing, npm generates provenance automatically over that same OIDC token, and --provenance is neither needed nor passed.

It is not being generated today, and that is a repository-visibility limit rather than a configuration mistake: provenance requires a public repository, and jentrix-au/task-manager is private. Trusted publishing itself is unaffected — the OIDC exchange works fine on a private repo; only the attestation is skipped. Make the repo public and provenance starts appearing with no workflow change.

Security notes

  • There is no npm publishing credential — not in this repo, not in Actions secrets, not on a developer machine. Trusted publishing replaced it with a short-lived OIDC token scoped to this repo and workflow file, so there is nothing to leak, rotate, or forget to revoke. HOMEBREW_TAP_TOKEN remains an Actions secret and lives nowhere else.
  • The release workflow takes untrusted input only from the tag name, which it reads via the $GITHUB_REF_NAME runner env var (never templated into a shell) and validates to a strict semver before publishing — closing the standard workflow-injection vector.

Deferred follow-ups

These were intentionally left out of the initial distribution setup and can be added later without disturbing the above:

  • npm provenance — already wired; starts working the moment the repo is public (above), with no change here.
  • Standalone binary packaging (e.g. a bundled executable with no Node dependency) and a matching Homebrew bottle.
  • latest vs next dist-tags for prereleases.