Docs

CLI reference

Every real ctrlai command, grouped by lifecycle. This is the exhaustive reference for the binary in packages/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 runs ctrlai audit then ctrlai conform on every push and PR (SCAFFOLD_CI, packages/core/src/ops/scaffold.ts:78 — audit at :96, conform at :98). ctrlai guard is the local pre-commit hook (PRE_COMMIT_HOOK, packages/core/src/templates.ts:42), not a CI step. Don't conflate them: guard catches drift at commit time on your machine; audit + conform re-check it in the cloud. (ctrlai init, which adds the boundary to an existing repo, installs CI_WORKFLOWpackages/core/src/templates.ts:55 — which runs ctrlai audit then ctrlai conform, plus an optional dashboard --push; same gates as a scaffolded app. See init below.)


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 new is 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 workflow init installs (CI_WORKFLOW) runs ctrlai audit and ctrlai conform plus an optional ctrlai dashboard --push (gated on a CTRLAI_TOKEN repo secret). conform is a no-op nudge until the repo captures a ctrl.spec.json (via the configurator or ctrlai add) — once it has one, conform holds 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. conform flags an app file that imports a vendor SDK for a capability your spec pins (e.g. importing stripe when billing.subscription is 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 stripepaddle-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 /ctrl panel write actions are local-dev only. They rewrite the repo under process.cwd() and shell out to git, so they're gated to direct loopback requests (panelAccessDenied): refused in NODE_ENV=production, refused through a proxy/tunnel, refused cross-origin. Set CTRLAI_PANEL_ALLOW_REMOTE=1 only 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) and audit (CI) go red, not a warning.
  • It is not yet tamper-proof against a determined writer. Every trust artifact (parts.lock, each ATTESTATION.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.