Skip to main content
This page has two halves. The first states what our own pipeline runs today, so you can see the CLI used in a real one. The second is guidance for putting it into yours.

What this repository’s CI runs today

On every pull request and on pushes to the trunk, one job runs these steps in order:
That is the whole of it. Read what it implies:
  • The docs regenerate and the tree must not move. gen:docs rebuilds the reference pages from the code, and git diff --exit-code fails if that produced any change. A tool, scope, error code, route or CLI command that changed without a regeneration turns this red. It is the only thing stopping these pages from quietly describing an older surface.
  • The CLI is not executed in CI. Nothing in that list runs a command against a device. typecheck covers the CLI’s source, so it is compile checked, and the unit tests cover the argument parser, which is pure and needs no process. Neither is the same as running it.
  • An end to end script exercises the whole sequence. corepack pnpm e2e:cli starts a control plane and a stand in device, then runs the full sequence from registering a row through acquiring, observing, acting and releasing, plus the negative cases. It is run on demand rather than on every commit.
Documentation publishing is a separate workflow. It is decoupled from this one on purpose, so that fixing a typo does not rebuild the application. Releasing the CLI to npm is a third workflow, and it is manual: someone dispatches it, and it publishes the version currently written into the source. It runs the typecheck, the unit tests and the build first, and it refuses a version that is already on the registry. So a published version has passed the same gates as any commit on the trunk, and no release ever silently replaces another.

Using the CLI in your own pipeline

Nothing above stops you from running it. These are the constraints that actually apply.

Install it as a package

Your pipeline does not need a checkout of anything. Install the published CLI and call it by name:
Pin the version if your pipeline should not pick up a new release on its own: npm install -g phonebase@<version>, with a version from the package’s own npm page. phonebase --version reports what a runner actually installed. The examples below call phonebase directly.

Authenticate from the environment

A runner has nobody to approve phonebase login, so put an API key in a secret and export it. PHONEBASE_API_KEY takes precedence over any key a login stored, and the CLI never reads a credential from a flag, so a key cannot leak into a command line that a build log echoes.
The hosted control plane is the default; pass --server <url>, or set PHONEBASE_SERVER, for any other. The server must be https unless it is loopback. Sending a credential in the clear to a remote host is refused rather than warned about.

Give the key only what the job needs

Issue a separate key per pipeline, scoped to what that pipeline does and, if the job only ever touches one device, narrowed to that device. A key restricted to a subset cannot see the rest of the fleet, so a mistake in a script cannot reach a device the job was never meant to touch. See Authentication.

Release what you acquire

A lease is exclusive, and a job that exits without releasing blocks the next one until the lease expires on its own. Release in whatever your runner calls a cleanup step, not on the happy path.
Releasing a device you no longer hold is a no operation, so the trap is safe even when the lease already expired.

Parse the machine readable output

Use --json for anything a script reads. It prints the structured result the tool returned, whose shape is published in the tool reference. The default human summary is written for a person and is free to change.

Expect to queue

Devices are exclusive and physical hardware is scarce, so a job may wait for a device rather than getting one immediately. Build for that: fail the step with a clear message rather than retrying an acquire in a tight loop.

Watch a run to completion

If your job hands a goal to the agent tier rather than driving each step, run returns a run id immediately. Pass --watch to poll until the run reaches a terminal state, and the exit code follows the outcome.
Name --executor phonebase in CI. An API key’s default executor is self_hosted, which needs your own worker and a worker URL that a CI log must not hold. From CLI 0.3.0, --watch --json refuses an explicit self_hosted run before starting it, and cancels a run the default made self_hosted. Both exit 2. On 0.2.1, an API key running --watch without --executor gets a self_hosted run that stays queued until its deadline, holding the device, because no worker ever connects. --max-steps can only lower the budget. The control plane’s own ceiling cannot be raised from a client. See Runs.

Do not run two control planes over one device set

Worth stating because CI is where it happens. Two control plane processes over the same devices would each keep their own view of who holds a lease and could hand the same physical device to two callers. The receipt ledger is locked by a single process, so the second one refuses to start rather than racing the first. If a job needs its own control plane, give it its own devices, or its own ledger path.