Every real
ctrlaicommand, grouped by lifecycle. This is the exhaustive reference for the binary inpackages/cli— for the mental model behind the verbs (capability / part / adapter / seam, the lock, the spec-loop), read How Ctrl AI works first.If a command, flag, or behavior isn't documented here, it isn't in the CLI. If a sentence here ever stops matching the code, the code is right and this doc is a bug.
The binary is ctrlai. Run ctrlai --help for the command list, or ctrlai <command> --help for any one command. --version prints the package version.
ctrlai [options] [command]
Every command runs against the current working directory (process.cwd()).
There is no global config file and no implicit network call beyond the registry a
command explicitly reads.
The lifecycle at a glance
The commands fall into four phases. The order below is the order you'd run them in; not every project needs every rung.
| Phase | Commands | What it does |
|---|---|---|
| Compose | new, init, add, wire, plan |
Scaffold a fresh app or install the boundary into an existing repo, bring verified parts in, and scaffold the wiring. |
| Verify | guard, verify, audit, conform, doctor |
Check the boundary, the attestations, the intent, and the wired app. |
| Change | upgrade, eject, remove, provision, migrate |
Move a part to a new version/vendor, take ownership, delete it, or set up infra. |
| Ship | push, deploy, dashboard |
Put the repo on GitHub (turning on CI), deploy it, and read its status. |
What runs in CI vs. locally. A scaffolded Ctrl app (
ctrlai new/create-ctrl-app) ships.github/workflows/ctrlai.yml, which runsctrlai auditthenctrlai conformon every push and PR (SCAFFOLD_CI,packages/core/src/ops/scaffold.ts:78— audit at:96, conform at:98).ctrlai guardis the local pre-commit hook (PRE_COMMIT_HOOK,packages/core/src/templates.ts:42), not a CI step. Don't conflate them:guardcatches drift at commit time on your machine;audit+conformre-check it in the cloud. (ctrlai init, which adds the boundary to an existing repo, installsCI_WORKFLOW—packages/core/src/templates.ts:55— which runsctrlai auditthenctrlai conform, plus an optionaldashboard --push; same gates as a scaffolded app. Seeinitbelow.)
Compose
new — scaffold a new verified backend
Creates a running Next.js app: locked parts, a zero-config local database
(pglite), and the /ctrl control panel. Calls scaffoldApp.
ctrlai new <app-name> [options]
| Flag | Effect |
|---|---|
--pack <name> |
Part pack to vendor: starter (default), saas, ai-api, marketplace, backoffice. |
--capabilities <list> |
Comma-separated parts (e.g. auth.session,billing.subscription:stripe). Overrides --pack. |
--spec <path> |
A ctrl.spec.json from the configurator. Overrides --pack/--capabilities. |
--registry <source> |
Registry source — hosted URL or local directory (default: the hosted registry). |
--allow-community |
Accept community-tier adapters. |
--no-git |
Skip git init + the first commit. |
--no-install |
Skip npm install. |
On disk: writes the app directory (<app-name>/) with parts/,
parts.lock, ctrl.spec.json (written verbatim when scaffolded from a --spec,
so conform checks against exactly what was configured —
packages/core/src/ops/scaffold.ts:45),
AGENTS.md, and .github/workflows/ctrlai.yml (the audit + conform CI). Then
runs npm install unless --no-install. Exits non-zero if the install fails so
a half-finished scaffold isn't mistaken for success.
The published one-shot entry point is
npx create-ctrl-app.ctrlai newis the same scaffolder exposed on the main CLI.
init — install the boundary into an existing repo
The counterpart to new: where new scaffolds a fresh app, init adds the lock
machinery to a repo you already have — no parts yet, just the boundary, the
checks, and the agent contract. Calls initRepo.
ctrlai init [options]
| Flag | Effect |
|---|---|
--registry <source> |
Registry source recorded in the new parts.lock (default: the hosted registry). |
On disk: writes four things into the current repo, each skipped if already
present — an empty parts.lock (pinning the registry source), AGENTS.md (the
in-repo agent contract; a section is appended if the file already exists), the
.git/hooks/pre-commit guard hook, and .github/workflows/ctrlai.yml. The hook
goes in only inside a git repo with no conflicting hook — otherwise init warns
and tells you the one line to add. After it runs, agents that open the repo read
AGENTS.md and ctrlai add brings the first part in.
init's CI is leaner than a scaffolded app's, but runs the same checks. The workflowinitinstalls (CI_WORKFLOW) runsctrlai auditandctrlai conformplus an optionalctrlai dashboard --push(gated on aCTRLAI_TOKENrepo secret).conformis a no-op nudge until the repo captures actrl.spec.json(via the configurator orctrlai add) — once it has one,conformholds the build to it on every push & PR, exactly like a scaffolded app.
add — vendor parts, packs, or part:adapter specs
Vendors parts into parts/. Resolves install order, pulls in requires
dependencies, and skips anything already in parts.lock. Calls addParts.
ctrlai add <targets...> [options]
<targets...> are parts, packs, or part[@version][:adapter] specs — e.g.
saas, email.transactional:postmark, billing.subscription@1.2.0:stripe.
| Flag | Effect |
|---|---|
--adapter <name> |
Adapter for a single part (equivalent to part:adapter). |
--part-version <semver> |
Version for a single part (equivalent to part@version). |
--registry <source> |
Override the registry recorded in parts.lock. |
--allow-community |
Accept community-tier adapters (their conformance is not run in Ctrl's CI). |
--adapter / --part-version apply to exactly one target; with several, use the
part@version:adapter syntax or the command errors.
On disk: copies each part into parts/<name>/, updates parts.lock, adds npm
dependencies to package.json, scaffolds .env.example entries for required
env, and — when a spec is captured — writes/updates ctrl.spec.json. It then
prints the seams you must write and the migration count. It does not run
npm install or migrations for you. Cross-link: this is the agent's sanctioned
"new capability" verb (how-ctrl-ai-works §For your AI agent).
wire — scaffold the real wiring
Writes the wiring so the app boots: route files, the DB executor seam, the
tsconfig alias. Idempotent; never clobbers files you've edited since they were
generated. Calls wireRepo.
ctrlai wire [options]
| Flag | Effect |
|---|---|
--dry-run |
Show what would be written without touching the filesystem. |
--force |
Overwrite even files you've edited since they were generated. |
On disk: creates/updates route files, the executor seam, and the tsconfig
path alias for the detected framework. Each file is reported as
created / updated / unchanged / skipped. A file you've edited is left as-is
unless --force.
plan — resolve capabilities into an install plan
The resolver, exposed read-only. Turns a list of capabilities into a
deterministic, ordered install plan — without changing anything. Calls
resolvePlan over openRegistry.
ctrlai plan <capabilities...> [options]
| Flag | Effect |
|---|---|
--registry <source> |
Registry source (default: parts.lock's, else the hosted registry). |
--allow-community |
Accept community-tier adapters. |
--json |
Print the raw plan JSON. |
On disk: nothing. It prints the ctrlai add … commands it would run, the
required env, the migration count, and the seams to write. Use it to preview
before you add.
Verify
guard — the boundary tripwire (local)
Fails if parts/** no longer matches parts.lock. This is the engine behind the
pre-commit hook. Calls guardRepo.
ctrlai guard [options]
| Flag | Effect |
|---|---|
--staged |
Pre-commit mode: return immediately when nothing under parts/ (or parts.lock) is staged. |
On disk: nothing. Exit 0 if the boundary is intact, exit 1 with the offending
paths and the restore instructions otherwise. The hook the scaffold installs runs
ctrlai guard --staged. This is the local enforcement of the lock; CI uses
audit (next).
verify — attestation integrity + freshness
Re-checks each part's attestation: integrity is a hard fail, freshness is a
warning (a failure under --strict). Calls verifyRepo.
ctrlai verify [options]
| Flag | Effect |
|---|---|
--strict |
Treat staleness and unsigned dev attestations as failures. |
On disk: nothing. Prints per-part findings; exits 1 if integrity fails.
Today's attestations are
dev:unsigned— the check confirms the bytes match the hash they were attested at, not a third-party cryptographic signature. Under--strict, an unsigned dev attestation is itself a failure. Real signing is roadmap (how-ctrl-ai-works §What "verified" means).
audit — did this repo respect its contracts? (CI)
The CI check. Re-hashes every part (the boundary), verifies attestations, and
checks routes/env/sprawl in one pass. Calls auditRepo.
ctrlai audit [options]
| Flag | Effect |
|---|---|
--strict |
Treat staleness and unsigned dev attestations as failures. |
--json |
Print the raw audit result as JSON. |
On disk: nothing. Failures (a broken boundary, a bad attestation) exit 1; warnings (e.g. hand-rolled infrastructure a part already covers) are guidance, not gates. This is the first of the two CI steps the scaffold ships.
conform — did your agent build what you configured? (CI)
The spec-loop. Diffs ctrl.spec.json (captured intent) against reality and
reports built to spec: every specified capability installed, locked, on the
vendor you chose, and not hand-rolled. Calls conformSpec.
ctrlai conform [options]
| Flag | Effect |
|---|---|
--strict |
Treat informational / beyond-spec warnings as failures at the gate. |
--json |
Print the raw ConformResult as JSON. |
On disk: nothing. With no ctrl.spec.json, it prints guidance and does not
gate. With a spec, a missing / off-vendor / hand-rolled capability is a real,
mechanical FAIL (exit 1). On success it prints
✔ Built to spec — N/N installed, K/M on your vendors, 0 hand-rolled. This is the
second CI step the scaffold ships, and the moat described in
how-ctrl-ai-works §The spec-loop.
Hand-roll detection is import-based.
conformflags an app file that imports a vendor SDK for a capability your spec pins (e.g. importingstripewhenbilling.subscriptionis specified) — a real, observable import, not an assertion. A capability re-implemented over raw HTTP instead of an SDK import is a known blind spot, not yet caught.
doctor — does the wired app actually work?
Beyond "the parts are intact," this asks "is the app wired and reachable?":
routes mounted, migrations applied, env set + valid, DB seam wired — per part,
worst-first. Mechanical problems FAIL; heuristics WARN. Calls doctorRepo.
ctrlai doctor [options]
| Flag | Effect |
|---|---|
--strict |
Treat warnings (unset env, unconfirmed seams) as failures at the gate. |
--json |
Print the raw DoctorResult as JSON. |
--database-url <url> |
Postgres connection string for the migrations lane (default: $DATABASE_URL). |
On disk: nothing. The migrations lane opens a live pg connection if a URL is
available; an unset or unreachable DB is non-fatal — that lane degrades to a
warning and the rest of doctor runs.
Change
upgrade — new version and/or vendor flip
Upgrades a part's version and/or flips its adapter. Interiors change
mechanically; you only see the seam changes you must make. Calls upgradePart.
ctrlai upgrade <part> [options]
| Flag | Effect |
|---|---|
--adapter <name> |
Switch to this adapter — the one-commit vendor flip. |
--part-version <semver> |
Target version (default: registry latest). |
--registry <source> |
Override the registry recorded in parts.lock. |
--allow-community |
Accept community-tier adapters. |
On disk: replaces parts/<name>/, re-locks it in parts.lock, updates
package.json dependencies, and (when the spec is present) keeps intent and code
moving together. Prints any required seam changes. An adapter flip alone is the
stripe→paddle-style vendor swap; the new vendor still needs credentials via
provision (on-spec ≠ working).
eject — the sanctioned exit
Moves a part out of the boundary and voids its attestation — you own the code from
there. Loud and reviewable on purpose. Calls ejectPart.
ctrlai eject <part> [options]
| Flag | Effect |
|---|---|
--to <dir> |
Destination directory relative to the repo root (default: ejected/<part>). |
On disk: moves parts/<name>/ to ejected/<part> (or --to), drops the
part's entry from the lock, and voids its attestation. This is the legitimate way
out — not a bypass (how-ctrl-ai-works §Eject is the honest exit).
remove — delete a part entirely
The counterpart to eject: where eject keeps the code (you take ownership),
remove deletes it. Calls removePart.
ctrlai remove <part>
On disk: deletes parts/<name>/, drops the part's entry from the lock, and
drops the capability from ctrl.spec.json — so conform stops expecting it
(your build stays built to spec). It warns you to delete any app code that
imported the part's seams (the build won't resolve until you do), and that a
forward-only migration ledger never auto-drops tables the part owned — drop those
yourself if you want them gone. Use eject instead when you want to keep and own
the code.
provision — a real database, or guided vendor setup
Two modes on one command.
ctrlai provision [part] [options]
ctrlai provision db — provisions a real Postgres on your account, wires
the env, and runs migrations. Calls provisionDb, then runMigrations, then
stamps the rung with recordLadder.
| Flag | Effect |
|---|---|
--provider <name> |
DB provider (default: neon). |
--name <project> |
DB project name (default: the app name). |
--json |
Print the raw result as JSON (connection string omitted). |
On disk (db mode): writes the connection env (e.g. DATABASE_URL) into your
env file, runs pending migrations against the new database, and records a
non-secret db rung in .ctrl/ladder.json so the /ctrl panel shows
"persistent database." The database lands on your Neon, never Ctrl's.
(Neon is the only provider implemented today — PROVIDERS in
packages/core/src/ops/provision-db.ts; more are roadmap.)
ctrlai provision [part] — prints the guided vendor setup for an installed
part: each human step with {ENV_VAR} placeholders already substituted to
concrete values (the exact redirect URI, the webhook URL), the step's state, and
the env it verifies. Calls provisionRepo.
On disk (part mode): nothing — it's a read-only, checkable setup flow. Use it after a vendor swap, since on-spec is not the same as working.
migrate — apply pending part-owned migrations
Applies pending database migrations owned by installed parts. Calls
runMigrations (or planMigrations under --dry-run).
ctrlai migrate [options]
| Flag | Effect |
|---|---|
--dry-run |
Print the plan without applying anything. |
--database-url <url> |
Postgres connection string (default: $DATABASE_URL). |
On disk: applies SQL against the connected Postgres and records each migration
in the _part_migrations ledger. Requires a DATABASE_URL (or --database-url)
or it errors. Ledger rows for uninstalled parts are reported and left untouched.
Ship
push — your repo on GitHub, CI on
Creates the repo on your GitHub and pushes it, turning on CI: every push & PR
then runs ctrlai audit + ctrlai conform. The lock becomes a law in CI, not
just a local hook. Calls pushGitHub, then drives git directly.
ctrlai push [options]
| Flag | Effect |
|---|---|
--name <name> |
Repo name (default: the app name). |
--public |
Create a public repo (default: private). |
--token <token> |
GitHub token with repo + workflow scopes (or set GITHUB_TOKEN). |
On disk / remote: requires an existing commit (errors otherwise). Creates the
GitHub repo, sets a clean origin (no token persisted to disk), pushes the
current branch via a one-shot tokenized URL, and records a non-secret ci rung in
.ctrl/ladder.json. The workflow scope is required because the repo ships
.github/workflows/ctrlai.yml — without it, GitHub rejects the push and the CLI
tells you so. The token is redacted from any error output.
deploy — a live URL on your Vercel
Deploys to your Vercel: creates/selects the project, sets the env (DB, auth
secret, vendor keys), and ships a production build. Calls deployVercel.
ctrlai deploy [options]
| Flag | Effect |
|---|---|
--token <token> |
Vercel token (or set VERCEL_TOKEN). |
--name <name> |
Project name (default: the app name). |
--wait |
Wait for the build to finish before returning. |
On disk / remote: creates the deployment on your Vercel account, sets env vars
on the project, and records a non-secret deployed rung in .ctrl/ladder.json.
Without --wait it returns while the build is still running and prints the
inspector URL.
dashboard — your stack at a glance
Inventories installed parts: versions, vendors, upgrades, drift, attestation
health. Reads via inspectRepo. Also scaffolds an in-app dashboard route or
pushes the snapshot to your Ctrl AI account.
ctrlai dashboard [options]
| Flag | Effect |
|---|---|
--json |
Print the raw DashboardStatus as JSON. |
--scaffold |
Write an editable dashboard route into your app (renders parts.lock live) instead of printing. |
--route <segment> |
Route segment for --scaffold (default: dashboard). |
--force |
With --scaffold: overwrite files you've edited since they were generated. |
--dry-run |
With --scaffold: show what would be written without touching the filesystem. |
--push [url] |
Push this snapshot to your Ctrl AI account (default: https://app.ctrlai.com/api/snapshots). |
--token <token> |
API key for --push (or set CTRLAI_TOKEN). |
--app <name> |
App name to record the snapshot under (default: current directory name). |
On disk: nothing by default (it prints). --scaffold writes an editable
dashboard route. --push sends the snapshot (and, if a ctrl.spec.json exists,
the conform verdict alongside it) to the configured URL; it needs a token via
--token or CTRLAI_TOKEN (with PARTKIT_TOKEN honored as a back-compat
fallback). The scaffold's CI runs ctrlai dashboard --push only when a
CTRLAI_TOKEN repo secret is set — with no secret, nothing leaves the runner.
Token & env reference
| Name | Used by | Notes |
|---|---|---|
DATABASE_URL |
migrate, doctor, provision db (after) |
Postgres connection string; --database-url overrides. |
GITHUB_TOKEN |
push |
Scopes repo + workflow; --token overrides. |
VERCEL_TOKEN |
deploy |
--token overrides. |
CTRLAI_TOKEN |
dashboard --push |
PARTKIT_TOKEN is a back-compat fallback. |
CTRLAI_PANEL_ALLOW_REMOTE |
the /ctrl panel (not a CLI flag) |
Opt-in escape hatch for the panel's write API on a trusted remote dev box. |
The
/ctrlpanel write actions are local-dev only. They rewrite the repo underprocess.cwd()and shell out to git, so they're gated to direct loopback requests (panelAccessDenied): refused inNODE_ENV=production, refused through a proxy/tunnel, refused cross-origin. SetCTRLAI_PANEL_ALLOW_REMOTE=1only if you knowingly run dev on a remote box. This is a panel guard, not a CLI command.
What the lock guarantees (read before you trust it)
The CLI's verify/audit/guard checks are real and mechanical, but bounded:
- Today, "verified" = bytes match the attested content hash AND the app is
built to spec. Not a cryptographic signature — attestations are
dev:unsigned. - The lock guarantees legibility / no-silent-drift. An agent that edits a
part's interior is caught:
guard(local) andaudit(CI) go red, not a warning. - It is not yet tamper-proof against a determined writer. Every trust artifact
(
parts.lock, eachATTESTATION.json,ctrl.spec.json) lives in the repo, so an actor who rewrites them too can re-bless an edited part. Hard integrity — signing, server-side hash enforcement, branch protection — needs a root of trust outside the repo and is the cross-repo / team tier.
Keep those two promises separate. Full treatment: how-ctrl-ai-works §The lock.