cb — Command-Line Tool

cb is the management CLI for Circuit Breaker. It wraps the systemd units (native installs) or the containers (Docker and Compose installs), the health checks, and the database backup so you do not have to remember unit or container names.

Two scripts implement it — deploy/cli/cb on native systemd installs and the repo-root cb on docker/compose/binary installs. They expose the same commands; only the mechanics underneath differ, and the one command that is not shared is called out in the table below.


Installation

The native installer copies deploy/cli/cb to /usr/local/bin/cb during its "Installing Management CLI" stage (deploy/setup.sh, stage 9). That is the only automatic install.

No installer ships the repo-root cb. install.sh --docker deploys the compose stack and stops there, and the packages install the circuit-breaker binary but not this CLI, so on docker/compose/binary installs you install cb yourself from a checkout. See Known gaps.

Install method How to get cb
One-line installer (install.sh), native path deploy/cli/cb installed automatically at /usr/local/bin/cb
install.sh --docker / Docker Compose / deb-rpm-apk Manual: sudo install -Dm755 ./cb /usr/local/bin/cb from a checkout

If the native copy fails, the installer falls back to telling you to use systemctl status circuitbreaker.target and journalctl -u 'circuitbreaker-*' directly.

Running cb with no arguments prints the command list.


Command availability by install mode

Command Native Docker Compose Binary
status, doctor, logs, restart, update, backup, config validate, version, uninstall
migrate, token, user, agent
restore
vault-recover

migrate, token, user and agent are the headless administration commands (SRV-06): schema state, scoped API tokens, local accounts and the agent fleet, without a browser session. Both scripts route them to the backend's own administration CLI — python -m app.cli <group> inside the backend container on docker/compose, and the packaged binary's --admin passthrough on native and binary, after sourcing the environment file that holds the database credentials and the vault key. They need the database, so the stack must be reachable; config validate is the only command that is deliberately offline.

vault-recover writes a vault key into the deployment's env file for installs made by the container/binary installer path. Native installs keep that key in /etc/circuitbreaker/.env, which the systemd units and deploy/setup.sh own, so the native cb does not ship the command — edit that file and restart circuitbreaker.target instead.

restore is likewise absent from the native cb (deploy/cli/cb). A native install restores with deploy/scripts/restore.sh, which cb restore drives in binary mode and which stays directly callable — see Backup & Restore.

That script takes either of the two artifacts this product produces, and does with each one exactly what it contains:

Give it It came from What it replaces
cb-snapshot-*.tar.gz cb backup, the admin endpoint, the nightly job Database, uploads, vault key, and native config
pre-upgrade-*.sql (or .sql.gz) install.sh --upgrade, before it migrates The database, and nothing else

The second is the rollback for an upgrade that went wrong, and it is deliberately the narrower artifact: a migration changes the schema, not the uploads and not the vault key, so this host's copies of those already match the database being replayed. install.sh --upgrade prints the exact command next to the path it just wrote. cb restore does not take a bare dump — it verifies through the backend's snapshot verifier, which is a snapshot-shaped check — so a database-only rollback is run through restore.sh directly.

Everything else behaves the same way from the outside; where the mechanics differ (which port is probed, what a backup contains) it is noted in the command's own section below.


Configuration

On native installs cb has no config file of its own. It sources /etc/circuitbreaker/.env — the same environment file the services use — on every run, so it picks up CB_DATA_DIR, CB_DB_PASSWORD, CB_REDIS_PASSWORD, CB_PORT, CB_FQDN, and DOCKER_PROXY_ENABLED from your install.

On docker/compose/binary installs cb reads ~/.circuit-breaker/install.conf (falling back to /etc/circuit-breaker/install.conf). That file sets CB_MODE, CB_CONTAINER, CB_BACKEND_CONTAINER, CB_DATA_DIR, CB_PORT, CB_IMAGE, CB_BINARY_ENV_FILE, and the compose paths, and every command dispatches on CB_MODE.

You write install.conf yourself — no installer writes it (see Known gaps). Without it cb prints Install config not found at … — using defaults. and assumes CB_MODE=docker with the container named circuit-breaker on port 8080, which is wrong for a compose or binary install. A minimal file for each mode:

# ~/.circuit-breaker/install.conf — compose
CB_MODE=compose
CB_BACKEND_CONTAINER=cb-backend
CB_INSTALL_DIR=$HOME/.circuitbreaker        # where install.sh --docker put the stack
CB_COMPOSE_FILE=$HOME/.circuitbreaker/docker-compose.yml
CB_PORT=8080
# ~/.circuit-breaker/install.conf — binary (deb/rpm/apk)
CB_MODE=binary
CB_PORT=8080
# Optional; defaults to the path packaging/postinstall.sh generates:
# CB_BINARY_ENV_FILE=/etc/circuit-breaker/circuit-breaker.env

cb update (docker mode) also reads ~/.circuit-breaker/env and passes it to docker run --env-file. That file is operator-supplied too; nothing creates it — and cb update refuses to run without it. Recreating the container discards the environment it was created with, so a replacement built from CB_IMAGE alone would start with no CB_JWT_SECRET, CB_VAULT_KEY, CB_DB_PASSWORD or NATS_AUTH_TOKEN and exit FATAL on the first check in docker/entrypoint-mono.sh. If you created the container with docker run -e …, write those same values into ~/.circuit-breaker/env (one KEY=value per line, chmod 600) before upgrading.


Commands

cb status

Prints a table of every Circuit Breaker systemd unit with its active state and the time it entered that state.

cb status

Units covered: circuitbreaker-postgres, circuitbreaker-pgbouncer, circuitbreaker-redis, circuitbreaker-nats, circuitbreaker-backend, the workers (circuitbreaker-worker@discovery, @notification, @telemetry, @monitor_scheduler, @monitor_poll), and nginx. circuitbreaker-docker-proxy is added to the list when DOCKER_PROXY_ENABLED=true.

On container installs it prints the container's image, start time and status instead (docker), or the cb-* container table (compose); on binary it shells out to systemctl status circuit-breaker.


cb doctor

Runs a top-down health check of the whole stack, ordered so that the first failure is usually the cause of every failure below it. On native installs, if you are not root it re-executes itself under sudo automatically; on a host with no sudo it says so and runs the checks it can rather than aborting.

cb doctor

Checks performed on a native install, in order:

Check What it proves
PostgreSQL (5432) The database is listening
pgbouncer (6432) The pooler is listening
DB connection psql can authenticate through pgbouncer as breaker
Redis (6379) Redis answers PING with the configured password
Redis data dir ownership ${CB_DATA_DIR}/redis is owned by the Redis service user, so BGSAVE can write
SELinux context on Redis data dir The directory is labelled redis_var_lib_t (SELinux hosts only)
NATS (4222) The internal bus is listening
Backend API (8000) /api/v1/health answers directly
Docker proxy (2375) Only when DOCKER_PROXY_ENABLED=true
nginx (443, or CB_PORT when TLS is off) The web front end is listening
nginx → backend proxy A request actually routes through nginx to the API — catches the SELinux httpd_can_network_connect case that leaves every other check green while the UI 502s
firewalld allows the port The port is open off-box (only when firewalld is running)
SELinux allows nginx on the port The port carries an SELinux port label (only when semanage is present)

When a check fails, cb doctor prints a fix hint and then automatically runs a read-only diagnostic — usually the tail of the relevant journalctl unit — so you do not have to go look it up. It shows 30 lines by default; set CB_DOCTOR_LINES to change that:

CB_DOCTOR_LINES=100 sudo cb doctor

The installer does not gate on cb doctor. When an install stage fails it names cb doctor as a re-check hint and runs it as one of the diagnostics it prints at that point; on a clean install it is never invoked. Running it afterwards is the operator's job — and because it exits non-zero when any check fails, a wrapper script can act on the result.

On docker and compose installs the same idea is applied to the containers:

Check What it proves
Docker daemon Docker is installed and the daemon answers — nothing below can be checked without it
Container <name> The backend container is in docker ps
Container not restarting It is not crash-looping (the reason is tailed from docker logs)
Backend API /api/v1/health answers inside the container (8080 for the mono image, 8000 for the compose backend)
Database psql connects with the credentials the app itself uses (CB_DB_URL, else CB_DB_PASSWORD)
Free space on the data dir At least 1 GiB free — Postgres, the WAL and uploads share that volume
Vault key persisted A key is present in the environment or ${CB_DATA_DIR}/.env, so restarts stay decryptable
Published port http://127.0.0.1:${CB_PORT}/api/v1/health answers from the host — the only thing a user actually touches

On binary installs it checks the circuit-breaker unit, the API on CB_PORT, free space on CB_DATA_DIR, and CB_DB_URL (when psql is installed). CB_DOCTOR_LINES works the same way in both scripts, and cb doctor exits non-zero when any check fails.


cb logs

Tails the logs of every Circuit Breaker unit live. It takes no flags and always follows — press Ctrl+C to stop.

cb logs

Under the hood this is a single journalctl -f across the postgres, pgbouncer, redis, nats, backend, circuitbreaker-worker@*, and nginx units.

On container installs it is docker logs --tail 100 against the backend container and it does not follow unless you ask: cb logs -f. On binary it is journalctl -u circuit-breaker.


cb restart

Restarts the whole stack through the systemd target, waits a few seconds, then prints cb status.

cb restart

On container installs it restarts the container (or each cb-* container of the compose stack); on binary it restarts the circuit-breaker unit through sudo systemctl.


cb update

Re-runs the official installer in upgrade mode (install.sh --upgrade), fetched from the project repository. Requires outbound internet access.

cb update

On docker it pulls CB_IMAGE and recreates the container; on compose it runs docker compose pull and up -d. binary installs cannot self-update — re-run install.sh.

The docker path is written so a failed upgrade is recoverable:

  • It refuses before it destroys anything if ~/.circuit-breaker/env is missing (see Configuration above) — without that file the replacement container has no secrets and cannot start, and the old one is already gone.
  • The container that was serving is renamed to <CB_CONTAINER>-prev rather than removed, and is only removed once the new one answers /api/v1/livez. If the upgrade fails, roll back with:

docker rm -f circuit-breaker; docker rename circuit-breaker-prev circuit-breaker \ && docker start circuit-breaker

The first separator is ; and not && on purpose: when docker run fails at creation there is no circuit-breaker to remove, docker rm exits non-zero, and an && would short-circuit before the rename that actually restores service. * If that rename cannot be done, cb update stops there rather than continuing. It prints what docker rename said and leaves the stopped container in place — an upgrade with no rollback is not one worth starting. Running cb update when no such container exists is a different case and still works: it creates one from CB_IMAGE, and says that this upgrade has nothing to roll back to. * Success is the application answering /api/v1/livez, not docker run exiting 0 — the entrypoint's secret checks and the migration all run after the container is created, so a crash-looping upgrade used to be reported as a success. cb update polls for up to two minutes and fails with the rollback command above if the application never comes back. Without curl on the host the poll is skipped and the command says so.


cb backup

On a native install, runs pg_dump through pgbouncer on 127.0.0.1:6432 as the breaker user and writes an uncompressed SQL dump:

cb backup

Output path: ${CB_DATA_DIR}/backups/cb-backup-<YYYYmmdd-HHMMSS>.sql. This is a database dump only — it does not capture uploads, the vault key, or config files.

On docker/compose/binary installs it writes the full-state snapshot — the same artifact POST /admin/db/snapshot and the nightly scheduled job produce, because all three call the one builder (services/backup/snapshot.py) through python -m app.cli snapshot create. It lands in ${CB_BACKUP_DIR:-~/.circuit-breaker/backups} as cb-snapshot-<YYYYmmdd-HHMMSS>.tar.gz containing:

Member What it is
db.sql.gz pg_dump of the whole database, gzip-compressed, with a recorded sha256
uploads/ The uploads directory
config/ Native-install config files, when there are any
vault.key The vault key in plaintext — without it the dump's encrypted columns are unreadable. Resolved through the same chain the server uses (CB_VAULT_KEY cross-checked against app_settings.vault_key_hash, then $CB_DATA_DIR/.env, then the database), never straight from the environment: cb backup runs the builder in a fresh docker exec process, whose environment can still hold the key the container was created with rather than the one the database is encrypted with
manifest.json Format version, install mode, cb version, timestamp, database name, the uploads count, the db.sql.gz sha256 and the config files captured

The archive is written mode 0600 because it contains secrets — treat the file itself as one. Verify one with python -m app.cli snapshot verify <archive>; restore it with cb restore. See Backup & Restore.

--encrypt-to age1… — the copy that may leave the host

The snapshot above carries the vault key in plaintext, which is the right shape for an archive that stays put and the wrong shape for one that is copied to a NAS, a bucket, or another machine.

cb backup --encrypt-to age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

writes a second artifact beside the first — cb-snapshot-<stamp>.tar.gz.age, mode 0600 — encrypted to that age X25519 recipient. The plaintext archive is still written and still reported: encrypting a copy for transport is not a reason to stop having a local one.

The recipient is a public key. The identity that opens the result must live somewhere this host is not — generate the pair with age-keygen -o age-identity.txt on your workstation, keep age-identity.txt, and pass only the age1… line printed on stderr (or age-keygen -y age-identity.txt) to --encrypt-to. A host that holds both halves has an encrypted backup that protects it from nothing.

This is the same derivative the scheduled S3 upload sends, produced by the same encryptor (services/backup/age_encryption.py), so what you restore from a bucket and what you restore from --encrypt-to are one format with one verifier. age is a hard dependency of the deb and the rpm and ships in the mono image; nothing else has to be installed.

Restore it with cb restore --identity age-identity.txt <archive>.tar.gz.age. Lose the identity and the archive is unrecoverable — that is the property being bought, and there is no recovery path around it.

An archive produced by cb backup before this change (database.sql + manifest.txt) is a different, older format: it carried no vault key and no uploads, and nothing restores it.


cb restore

Restores a full-state snapshot. Destructive — it replaces the database, the uploads, and the vault key of the install it is run on.

cb restore [--identity age-identity.txt] <archive.tar.gz[.age]> [--yes] [--force] [--no-safety-snapshot]
Flag Effect
--yes Skip the interactive RESTORE confirmation
--force Proceed even though the snapshot is from a newer Circuit Breaker version
--no-safety-snapshot Do not take a snapshot of the current state first
--identity PATH Decrypt an age-encrypted off-host snapshot before verifying it

The order is the safety property: verify → confirm → safety snapshot → stop → restore → start → verify. The archive is checked by the backend's own verifier before anything is stopped, so an unrestorable archive costs a failed command rather than an outage, and a fresh cb backup of the current state is taken before anything is destroyed. Declining the confirmation changes nothing.

On docker and compose the same preflight also checks free space, because everything the restore unpacks stages on the data volume rather than in /tmp (/tmp is a 100 MB tmpfs in the shipped mono container, which a real snapshot does not fit in). The volume transiently holds the copied archive, the tree it untars to and the uncompressed db.sql, so cb restore wants at least twice the archive size free on $CB_DATA_DIR and refuses before it copies anything if that is not there. Twice is a floor, not a promise: an uncompressed dump is several times its gzipped size. A container whose df cannot answer is a warning rather than a refusal — a recovery is not blocked on a measurement.

It takes the snapshot tarball only. A bare pre-upgrade-*.sql from install.sh --upgrade is a different shape and is restored with deploy/scripts/restore.sh — see the mode table above.

On docker and compose the archive is replayed inside the backend container (Postgres stays up — it is what is being restored into). supervisorctl stops backend-api and every worker-* program by its real name from docker/supervisord.mono.conf, and a stop that fails aborts the restore instead of warning. On binary it drives deploy/scripts/restore.sh, the native implementation, rather than growing a second one beside it — passing it the deb/rpm/apk layout's env file, systemd unit and database roles, because that script's own defaults are the install.sh layout, which binary mode is not. CB_RESTORE_SCRIPT in install.conf overrides where cb looks for that script.

Full procedure, per-mode mechanics, and what each archive member replaces: Backup & Restore.


cb migrate

Database schema state, and applying it. Both are read from the same migration chain the server runs at startup, so the CLI cannot develop a different opinion about what "up to date" means.

cb migrate status [--json]
cb migrate upgrade

status reports the database's revision against this build's. It exits 3 when the database is behind — a distinct code from "cannot reach the database", so a deployment script can tell the two apart without parsing prose. --json emits machine-readable output instead of a table.

upgrade applies pending migrations through the server's own upgrade path, which takes the transaction-scoped advisory lock in migrations/env.py. That makes it safe to run while the stack is coming up and racing the API's own auto-migrate phase — one of them takes the lock, the other waits.

Downgrade is not offered, because it is not supported: restore a verified pre-upgrade backup instead. See Compatibility policy § Downgrade and rollback.


cb token

Scoped API tokens and service accounts, without a browser session.

cb token create --label LABEL (--expires-in-days N | --never-expires)
                [--scopes SCOPE ...] [--preset NAME] [--actor EMAIL] [--json]
cb token list [--json]
cb token rotate TOKEN_ID [--overlap-hours N] [--actor EMAIL] [--json]
cb token revoke TOKEN_ID [--actor EMAIL]
Flag Effect
--label Required. What the token is for — it is the only thing list can show you later
--scopes Explicit grants, e.g. --scopes read:* write:telemetry. The grantable set is in the API reference
--preset A named scope set instead of --scopes: read_only, telemetry_ingest, read_write, full_access
--expires-in-days / --never-expires One is required. A token with no expiry has to be asked for explicitly; there is no silent default
--overlap-hours On rotate, keep the previous secret working for N hours so holders can be updated first. Never past the token's own expiry. Default 0 — immediate cutover
--actor The administrator the change is recorded against. Optional when the install has exactly one active administrator; required otherwise

The secret is printed once, at creation or rotation, and never again. list shows the label, scopes, expiry and last use — never the secret, because only a salted HMAC of it is stored.


cb user

Local accounts. The role hierarchy is viewer < editor < admin.

cb user list [--json]
cb user create --email EMAIL [--role admin|editor|viewer] [--display-name NAME]
               [--password-stdin] [--actor EMAIL] [--json]
cb user set-role EMAIL ROLE [--actor EMAIL]
cb user disable EMAIL [--actor EMAIL]
cb user enable EMAIL [--actor EMAIL]

--role defaults to viewer — the least privilege that is still an account.

There is deliberately no --password flag. argv is visible to every process on the host, so a password either arrives on stdin (--password-stdin, first line) or is generated and printed once:

printf '%s' "$NEW_PASSWORD" | cb user create --email ops@example.com --role editor --password-stdin

disable deactivates the account and revokes its sessions, so it takes effect immediately rather than at the next token expiry.


cb agent

Fleet administration without the UI. The approval decision is the security boundary described in cb-agent § Enrollment — approve a fingerprint you recognise, not one that merely appeared.

cb agent list [--status pending|active|revoked|rejected] [--json]
cb agent approve AGENT_ID [--actor EMAIL]
cb agent revoke AGENT_ID [--reason TEXT] [--actor EMAIL]

approve applies the default capability grantshost_telemetry, remote_probe and local_discovery with their shipped defaults. The per-capability opt-outs the approval modal offers are a UI affordance; to approve with a narrower set, use the UI or adjust the capabilities afterwards with PUT /api/v1/agents/{agent_id}/capabilities.

A revoked device key is refused at /enroll outright — there is no silent re-enrollment. See Runbook 3 — duplicate agent if you meant to re-enrol a host.


cb config validate

Validates the configuration before it is used, rather than discovering an invalid combination when the service refuses to boot. It runs the backend's own validator, so cb never develops a second, drifting opinion about what a valid configuration is.

cb config validate
Mode What it runs
Native /opt/circuitbreaker/bin/circuit-breaker --config-validate, after re-sourcing /etc/circuitbreaker/.env (and /run/circuitbreaker/vault.env) with set -a so the validator sees exactly the environment the unit starts with
Docker / Compose python -m app.cli config validate inside the backend container
Binary /usr/local/bin/circuit-breaker --config-validate, after sourcing /etc/circuit-breaker/circuit-breaker.env (the file packaging/postinstall.sh generates and the units name in EnvironmentFile=; override with CB_BINARY_ENV_FILE)

Exit status is 0 when the configuration is valid, 1 when it is not, and 2 if you pass anything other than validate. Secret values are never printed — only the name of the setting at fault.


cb vault-recover

Recovery tool for a vault stuck in "ephemeral" state — no key was ever persisted, so anything encrypted before the next restart becomes unreadable. Under normal operation the OOBE wizard generates this key for you; you should only need this after a crash, a headless deploy, or a deleted .env.

cb vault-recover

It generates a key, writes CB_VAULT_KEY into the deployment's env file (inside the container for docker/compose, CB_BINARY_ENV_FILE — by default /etc/circuit-breaker/circuit-breaker.env — for binary), and restarts the service. If a key is already present it asks for confirmation first — overwriting one breaks decryption of every secret already stored.

Not available on native installs; see Command availability by install mode.


cb version

Prints the contents of /opt/circuitbreaker/share/VERSION, or unknown if that file is missing.

cb version

On container installs it reads /app/VERSION from the backend container and falls back to the image reference; on binary it runs circuit-breaker --version.


cb uninstall

Removes Circuit Breaker and all of its data. It prompts for confirmation first (Remove Circuit Breaker and ALL data? [y/N]) and does nothing unless you answer y.

sudo cb uninstall

What it removes:

  • Stops and disables every circuitbreaker-* unit, the circuitbreaker.target, and nginx
  • Kills any remaining processes owned by the breaker user
  • Deletes the systemd unit, timer, target, and slice files, then reloads systemd
  • Deletes /opt/circuitbreaker, /etc/circuitbreaker, /etc/nats, and $CB_DATA_DIR
  • Deletes /etc/nginx/conf.d/circuitbreaker.conf, /usr/local/bin/nats-server, and /usr/local/bin/cb
  • Deletes the breaker system user and its home directory
  • Removes the /etc/hosts entry for CB_FQDN
  • Removes the nginx package itself and the NodeSource apt repository files
  • Stops and removes the cb-docker-proxy container, if one exists

This deletes your data directory

$CB_DATA_DIR holds the database, uploads, and TLS certificates. Take a backup before running this if there is anything you want to keep.

On docker/compose/binary installs cb uninstall hands off to the repo's uninstall.sh. It looks for, in order:

  1. /usr/local/bin/uninstall-circuit-breaker — only present if you put it there; no installer creates it (see Known gaps);
  2. uninstall.sh sitting next to the cb script — the checkout case;
  3. otherwise it prints the two ways to run it and exits non-zero:
bash uninstall.sh                                                                    # from a checkout
curl -fsSL https://raw.githubusercontent.com/BlkLeg/circuitbreaker/main/uninstall.sh | bash

The other two entry points

cb is a wrapper. Underneath it there are two more surfaces, and on some installs they are the only ones available.

The packaged binary

The deb/rpm/apk packages and the tarball install circuit-breaker, a frozen build of app/start.py. A packaged host has no python -m app.cli, so the binary carries the flags itself:

Flag Effect
--host HOST Override the listen host (default 127.0.0.1, or HOST)
--port PORT Override the listen port (default 8080, or PORT)
--workers N Uvicorn worker processes (default 1, or UVICORN_WORKERS)
--ssl-certfile PATH, --ssl-keyfile PATH Serve TLS from the backend itself. Both are required together, or startup exits
--config PATH The native config file. A .toml path is also forwarded to the config.toml loader; anything else is read as the native YAML tier
--worker-type TYPE Run as a background worker instead of the API server: discovery, notification, telemetry, monitor_scheduler, monitor_poll, monitor_probe_dispatch
--version Print the resolved version and exit
--config-validate Validate the configuration and exit non-zero if invalid. What cb config validate runs in native and binary mode
--snapshot-create [--out DIR] Build a full-state snapshot and print its path. What cb backup runs in binary mode
--snapshot-verify ARCHIVE Verify a snapshot archive. What cb restore runs before it stops anything

The four --config-validate / --snapshot-* flags are handled before argument parsing and before anything binds a port, creates a directory or runs a migration — so validating a broken configuration cannot itself fail on the very dependency it is checking, and a one-shot snapshot does not start a scheduler it has no use for.

python -m app.cli

On a source checkout or inside the container, the same code is reachable as a module. This is what cb executes in docker and compose mode.

Group Actions Notes
config validate --config PATH picks the config.toml. Offline by construction — the pass replaces name resolution for its duration, so it cannot reach Postgres, Redis, NATS or a resolver
snapshot create, verify create --out DIR overrides BACKUP_DIR; verify ARCHIVE prints the manifest as JSON and exits non-zero if the archive cannot be restored
migrate status, upgrade Database schema state and forward migration
token create, list, rotate, revoke Scoped API tokens and service accounts
user list, create, set-role, disable, enable Local accounts
agent list, approve, revoke Fleet administration without a browser

Each group has its own cb command documented above, except snapshot, which cb backup and cb restore drive on your behalf.

The parser is the authority on flags

Run python -m app.cli <group> --help — or <group> <action> --help — for the exact flags of any of these. That output is generated from the parser and cannot go stale, which a table eventually can.


Known gaps

Recorded here rather than papered over. None of these are wired up today:

Gap Effect Workaround
No installer writes install.conf cb on docker/compose/binary installs falls back to CB_MODE=docker defaults and prints a NOTICE Write it by hand — see Configuration
No installer delivers the repo-root cb The docker/compose/binary CLI needs a git checkout sudo install -Dm755 ./cb /usr/local/bin/cb
Nothing creates /usr/local/bin/uninstall-circuit-breaker cb uninstall cannot use that path Run uninstall.sh from a checkout, or let cb uninstall find it next to the script

Closing the first two means teaching install.sh and the packages to emit install.conf and copy cb; that is a change to the installers, not to the CLI, and has not been made.