CLI reference
Shipped
Every turbobase command, its flags, an example invocation, and the shape of
what it prints. The first column is the full command path; run any command with
--help for the same surface from the binary itself.
Commands are grouped under twelve nouns — auth, project, ext, apps, guardrails, kb,
skills, release, sandbox, tables, processes and data — plus two standalone leaves, logs and mcp.
Reach for turbobase <group> --help when you know the area but not the verb.
| Command | Description | Flags |
|---|---|---|
auth login |
Authenticate as an instance admin and store a personal access token (PAT) in ~/.turbobase/credentials. |
--base-url <url> (required), --token <tb_pat_…> (required), --no-verify |
auth status |
Print the stored instance(s) and which one is active (the default for every command). | (none) |
auth use |
Switch the active instance when more than one credential is stored (omit the argument to arrow-pick from stored instances on a terminal). | [base-url] |
auth logout |
Remove a stored instance credential (the active one, or --base-url to pick). |
--base-url <url>, --all |
project init |
Scaffold a Claude-Code extension project in the current dir. | --base-url <url> |
project types |
Generate types.ts from the signed-in instance’s catalog. |
--base-url <url>, -o, --out <file>, --records-only |
project grounding |
Write the instance’s grounding brief (source primer + record anatomy + KB retrieval) to a file for agent sessions. | --base-url <url>, --records <list>, --query <text>, -o, --out <file> |
project adopt |
Write the app-developer adopt notes — what changed in recent releases that this project should act on — to a file. | --base-url <url>, --since <version>, -o, --out <file> |
project skills |
Rewrite the platform’s own skills in .claude/skills from this CLI, so what the platform learned since the project was created reaches its sessions. Never touches the project’s own skills, or a platform skill it edited. |
--dir <path> |
project samples |
Generate samples/<recordType>.json fixtures from the live instance catalog. |
--base-url <url>, --out <dir>, --event <event>, --force |
project ci |
Write the reusable release-deploy GitHub Action workflow file (non-destructive; won’t clobber an existing file). | (none) |
ext new hook |
Scaffold a hook file under hooks/. |
--record <type> (required), --event <event>, --template <name>, --field <name>, --regex <pattern>, --source <source>, --id <id> |
ext new conduit |
Scaffold a conduit file under conduits/ (cross-source bridge). |
--source <source> (required), --record <type> (required), --to <source> (required), --to-record <type>, --event <event>, --id <id> |
ext new process |
Scaffold a process file under processes/ (an invoked multi-record sequence). |
--record <type> (required), --then <type>, --on-failure <mode> (stop|rollback), --id <id> |
ext test |
Dry-run an extension against samples/ fixtures (production sandbox parity). |
<file>, --samples <dir>, --reporter <fmt> (junit|json), --ci |
ext lint |
Statically lint a hook/conduit/process for semantic foot-guns (offline). | <file>, --ci, --reporter <fmt> (junit|json) |
ext replay |
Dry-run a conduit through dispatch→transform→apply (offline against a fixture). | <file>, --fixture <file>, --against <target>, --trace, --reporter <fmt> |
ext deploy |
Deploy one extension (POST /v1/extensions) — it prints new for a new one and replaced existing when it replaced a copy already stored under the same id (the API’s outcome is written / duplicate), and your new code is live either way; the stored copy is keyed on the first id: '…' literal in the file, and a deploy whose first literal is not the module’s own id is refused with extension_id_mismatch and nothing stored — or this project’s WHOLE config with --to (/v1/config/apply). The desired state is the project tree — hooks/, conduits/, turbobase/sync.json, turbobase/schedules.json — falling back to a checked-in turbobase/bundle.json for a project that authors none. Adds and updates only: it does NOT remove what the project no longer declares, and prints what it declined to remove. Pass --prune to take those too. |
[file], --base-url <url>, --to <env>, --prune |
ext plan |
Diff this project (hooks/, conduits/, turbobase/) against a target env — add, change AND destroy. Writes nothing. Run it on a pull request to see what merging would change. |
--from <env>, --to <env>, --base-url <url> |
ext promote |
Promote the bundle env→env behind a confirm-slug gate (fail-closed). | --from <env> (required), --to <env> (required), --confirm <slug> (required), --base-url <url>, --prune |
ext export |
Pull the live instance into turbobase/bundle.json. This is the ADOPT path, not the authoring one: use it to bring an instance that predates repo-first config into a repo, then move its extensions into hooks/. |
--base-url <url>, --out <dir> |
ext binding |
List conduit source bindings (--status divergent) or re-enqueue one (--retry <id>). |
--base-url <url>, --status <status>, --retry <id>, --limit <n> |
ext proposals list |
List hook proposals and the evidence behind each (defaults to the pending queue). | --base-url <url>, --status <list>, --provenance <list>, --record-type <type>, --source <system>, --limit <n>, --json |
ext proposals promote |
Arm hook proposals so the loader enforces them — by id, or by filter with --auto. |
[ids...], --base-url <url>, --auto, --apply, --provenance <list>, --record-type <type>, --source <system>, --limit <n>, --all, -y, --yes, --json |
ext proposals retract |
Send armed hooks back to proposed — inert immediately, source kept, re-promotable. | <ids...>, --base-url <url> |
ext proposals reject |
Decline proposals for good — the id becomes the denylist. Prefer retract if you may want it back. |
<ids...>, --base-url <url>, --reason <text> |
ext disable |
Take armed extensions out of service — inert on the next reload, definition kept. Works on any kind, including a deployed conduit. | <ids...>, --base-url <url>, --json |
ext enable |
Put disabled extensions back into service. | <ids...>, --base-url <url>, --json |
ext remove |
Delete extension definitions. Run history and what they already wrote are kept, and the same id deploys again — unlike proposals reject, which denylists it. |
<ids...>, --base-url <url>, --json |
ext install |
Install an extension from the marketplace into your instance’s extension store. | <spec> (name|name@version), --base-url <url>, --connection <id> |
ext publish |
Publish an authored extension to the public marketplace (publisher allowlist required). | <file>, --name <name> (required), --pkg-version <version> (required), --manifest <file> |
ext extras install |
Install the optional native packages test and replay need (esbuild, isolated-vm) into $TURBOBASE_HOME (needs npm). |
(none) |
ext extras status |
Report which optional native packages are installed. | (none) |
apps init |
Scaffold a new app project locally — the same scaffold a proposal-generated app uses (Vite + React + TS, auth-wired client, tokenised src/styles.css, base resolved at build time, defaulting to /a/<slug>/), the rest of the deployable surface (hooks/ with a worked example, conduits/, turbobase/sync.json, turbobase/schedules.json), the Claude Code wiring (CLAUDE.md, .mcp.json for both MCP servers, .claude/ skills, the Turbobase consultant output style, a SessionStart refresh of types/grounding/ADOPT.md, and a PostToolUse rule-placement check), the deploy workflow (.github/workflows/turbobase-app-deploy.yml — app projects only), and a dev proxy pointed at your instance. Writes files only, never overwrites; creates nothing on the instance. |
<app>, [dir], --title <text>, --base-url <url>, --no-claude, --force |
apps create |
Create a new hosted app (a bare app row) so a build can be shipped to it. | <slug>, --name <name>, --description <text>, --base-url <url> |
apps list |
List the hosted apps on your instance, with whether each is live and its latest source revision. | --base-url <url>, --json |
apps push |
Record the local source tree as the app’s newest source revision, so the instance holds the source of record. Source only — does NOT build or deploy (POST /v1/apps/:id/push, scope extensions:deploy). Skips node_modules/, dist/, build/, .git/, .claude/, any non-text file, and credential files (.mcp.json, .env* except .env.example) — those are never uploaded. |
<app>, [dir], --base-url <url>, --note <text>, --proposal <id> |
apps deploy |
Build the app (if needed) and deploy it — cuts a new live version at /a/<slug>. Builds when the directory has a package.json, uploads as-is when it’s already a bundle, and pulls generated source into an empty directory. With --preview, serves the build at its own URL and leaves /a/<slug> untouched until you promote it; the preview URL opens for anyone who can already open the app, and expires after 7 days. --preview requires a build this CLI ran (a prebuilt bundle has its asset paths pinned to /a/<slug>/ and would render blank at a preview URL), so it is rejected with --no-build. The build runs on your machine — nothing is built server-side. With --replace, the build lands on an OPEN preview instead of opening a second one: the URL is unchanged, so whoever holds the link sees the new build. --replace-label <text> is the idempotent form for CI — it re-deploys onto the open preview carrying that label and opens one when there is none, so a pull request keeps a single preview URL for its whole life. Deploying also refuses when a guardrail denies a write the manifest declares, naming the rule. Two deploys of one app may run at the same moment — each takes its own version number and keeps its own copy of the files. A deploy from a directory holding a package.json ALSO records that directory as the app’s newest source revision, through the same collector apps push uses and with the same exclusions — after the version is cut, so a failed collection costs the revision and never the deploy. --no-source turns that off; nothing is recorded from a prebuilt bundle or from a directory this command pulled the source into. |
<app>, [dir], --base-url <url>, --no-build, --no-install, --no-pull, --no-source, --entry <file>, --create, --preview, --label <text>, --replace [preview-id], --replace-label <text>, --proposal <id> |
apps plan |
Preflight this app’s public/turbobase.json against the instance: the tables block’s merge/conflict report, a guardrail verdict for every write the writes block declares, AND a per-role verdict (create / add-capabilities / update-scopes / no-op / error) for every role the roles block declares — including the content channels each role would gain or lose, the restrictions it would set, values an admin changed that the deploy will leave alone, and a warning for a role that holds read:memory without content (or the reverse) or restricts on a dimension no table carries. Writes NOTHING and grants nothing — in particular a planned role is not created, and a created role has no holders until an admin assigns it. Exits non-zero on a table conflict, a denied write, or a role that cannot be created or updated (a name colliding with a built-in, or a declared restriction that conflicts with a different one an admin or another app already set), so it works as a PR check. apps deploy re-runs every check and enforces it — a CI step can be skipped, a deploy cannot. Pass --app <slug> (or --as app:<slug>; two different slugs are refused): the role half needs the app’s slug to tell its own earlier scopes declarations from another app’s, exactly as the deploy does. Without it the plan never fails on that question: every scope dimension an app has already declared on a role is left out of the plan with a notice on that role (the deploy decides it, and may still refuse), every other dimension is planned exactly, and nothing the deploy would retire is listed — so a manifest whose roles carry scopes and no slug prints a warning naming the flag. With it the plan also lists what "roles": [] retires. The role report prints a value the role already held as adopted (the admin’s, never removed by the app), and a verdict this CLI does not recognise as a change to upgrade for, never as up to date. |
[dir], --base-url <url>, --as <claimant>, --app <slug> |
apps promote |
Make a preview the live version at /a/<slug>. Promotes the exact reviewed bytes — nothing is rebuilt — and fires the effects a preview deliberately skipped (release ledger, relationship activation, proposal completion, knowledge ingest). |
<app>, <preview-id>, --base-url <url> |
apps preview list |
List an app’s previews, newest first, with each one’s URL and expiry date. Expired previews are listed rather than hidden. --json emits the raw array (including []) for scripting — the human format is prose and breaks any regex once a label contains two spaces. |
<app>, --base-url <url>, --json |
apps preview discard |
Close a preview. The URL stops resolving; the build stays in deploy history and can still be promoted. | <app>, <preview-id>, --base-url <url> |
apps pull |
Write an app’s source tree to disk — whether it was generated from a proposal or pushed by you (GET /v1/apps/:id/pull, scope extensions:deploy, read-only). Always returns the newest source revision. |
[app] (omit with --proposal), [dir], --base-url <url>, --force, --proposal <id> |
tables list |
List the custom tables on your instance — their fields, the physical cf_sa_ column each field maps to, and which apps claim each table. |
--base-url <url>, --json |
tables plan |
Preview what this project’s public/turbobase.json tables block would do. Writes NOTHING, and exits 1 with a conflict report when another claimant already declares one of your field names at a different type — so a CI step catches it before the deploy does. |
[dir], --base-url <url>, --as <claimant> |
tables apply |
Apply this project’s declared tables to the instance. Additive: an existing table gains the fields you declare and keeps the ones other claimants declared. | [dir], --base-url <url>, --as <claimant> |
tables create |
Create a single table without authoring a manifest — the CLI twin of the Records page’s “New custom table”. Field spec is name:type[:required], where type is text|number|date|bool|select. |
<slug>, -f, --field <name:type> (required, repeatable), --name <displayName>, --restrict-to-owner, --base-url <url> |
tables drop |
Drop a table and every row in it. Irreversible, and fail-closed: --confirm must match the slug exactly. |
<slug>, --confirm <slug> (required), --base-url <url> |
relationships list |
List the linked record pairs your instance has discovered: the line table, the field on the line that holds its parent, the parent table, and each of the two “re-read” behaviours as on — with when it was switched on — or off. The app only marks a record whose pair has one switched on, on Data → Records; this is where the list is. An empty list means no pair has been discovered yet, which happens when a source is reconciled. |
--source <system>, --base-url <url>, --json |
relationships refresh |
Switch ONE of a pair’s two re-reads on or off. The pair is one argument, CHILD:KEY:PARENT, exactly as relationships list prints the three names. Both halves are required and nothing is defaulted: --children re-reads the lines when their parent is saved, --parent re-reads the parent when one of its lines is saved. A pair your instance has not discovered is refused and the refusal names what IS declared on that line table — nothing is created here. Each switched-on pair costs one query to the connected system per parent save plus one re-read per line. |
<CHILD:KEY:PARENT>, --children, --parent, --on, --off, --source <system>, --base-url <url>, --json |
guardrails posture |
Show or set the GLOBAL default posture. No mode prints the current one. allow = deny-list (unmatched writes allowed-with-audit, carve exceptions with --decision deny rules); deny = allow-list (unmatched writes denied, open records with --decision allow rules). Under deny-by-default, opening a record for writes does NOT open it for deletes — <record>.delete is a separate allow. |
[mode] (allow|deny), --base-url <url> |
guardrails list |
List the write guardrails on your instance, in evaluation order: what each one decides, its priority, the caller and row it narrows to, and whether it is active, proposed or inactive. These are the same rules the Compliance → Guardrails page shows. | --base-url <url>, --json |
guardrails create |
Create a rule gating PG→source writes that match it. --decision is deny (default), allow, allow_with_audit, or require_confirm — which holds the write for operator approval rather than refusing it. First match wins and lower --priority is evaluated first, so “open one record, block the rest” is an allow rule at a low priority plus a deny rule at a higher one. Guardrails sit UNDER the sync.bidirectional.<source> entitlement and cannot open a write path your plan does not include. An unconditional deny matching every source AND every record needs --confirm-freeze-all. |
<name>, --source <system>, --record <type>, --op <glob>, --target <glob>, --actor <glob>, --decision <d>, --priority <n>, --when <expr>, --reason <text>, --disabled, --confirm-freeze-all, --base-url <url> |
guardrails edit |
Change a rule’s decision, priority, row predicate or reason. Widening an allowlist is a NEW create, not an edit — that way each opened record type stays its own named, individually revocable row. |
<name>, --decision <d>, --priority <n>, --when <expr>, --reason <text>, --base-url <url> |
guardrails activate |
Arm a parked or proposed rule. Rules the chat agent proposes land proposed and gate nothing until you activate them. |
<name>, --base-url <url> |
guardrails deactivate |
Park a rule without deleting it — the reversible move mid-rollout. Writes it was gating fall through to the remaining rules immediately. | <name>, --base-url <url> |
guardrails delete |
Remove a rule. Prefer deactivate if you may want it back. |
<name>, --base-url <url> |
guardrails test |
Dry run: what happens to a write to this (source, record) — allowed, held for approval, or denied, and by which rule? Runs the same evaluator the write path runs and writes nothing. Exits 1 when the write would not proceed, so CI can gate on it. --phase picks which of the four gates to simulate (defaults to worker, the last one that can still hold a write); pass --fields to decide rules whose --when reads a row column. To test a DELETE, pass --op delete — a delete is gated separately as <record>.delete, and without it the dry run evaluates <record>.write. Do not spell it by appending .delete to --record: that builds <record>.delete.write, which matches no rule and reports a misleading deny (the response echoes the op it evaluated, so you can see this). |
--source <system> (required), --record <type> (required), --actor <actor>, --external-id <id>, --fields <json>, --op <op>, --phase <phase>, --base-url <url>, --json |
kb push |
Write the findings recorded in your standing markdown into your knowledge base, grouped so every finding about one page is one write. Re-reads your pushed markdown FIRST and prints what that found, so “nothing pending” is a statement about your file rather than about whatever a background job last saw. Prints the plan first — which page each group lands on, the exact markdown it appends, and which published pages a correction withdraws from readers — then asks. New pages land unpublished under a “From app developers” page; existing pages are appended to, never rewritten. Writes nothing until you confirm; admin only. | --base-url <url>, --app <slug-or-id>, --section <section> (suggested_kb_edit|kb_correction), --limit <n>, --dry-run, --diff / --no-diff, --no-group, --yes |
skills push |
Write the hard rules in your standing markdown — the ## Agent rules findings — as draft agent skills, grouped by their ### sub-heading (the rules under one heading become one skill, named after it; --no-group writes one skill per rule). Re-reads your pushed markdown first, like kb push. --reapply also re-emits rules already applied, which is how a CORRECTED ### grouping reaches the instance: re-filing a rule under a new heading is the same finding by identity, so it queues nothing on its own. The standing-rule twin of kb push: that writes what an agent can look up, this writes what every agent is told on every turn. Prints the plan and asks first. Rules land DRAFT — pushing is not publishing; admin only, on extensions:deploy. |
--base-url <url>, --app <slug-or-id>, --limit <n>, --dry-run, --diff / --no-diff, --no-group, --reapply, --yes |
skills list |
What agent skills exist and which are ACTUALLY injected — draft, unreleased, live. Worth reading before writing another rule. Needs skills:publish; admin only. |
--base-url <url>, --status <draft|published> |
skills publish |
Publish skills — live by the time it returns. Sets visibility and cuts a release scoped to these skills, so publishing one rule cannot ship a colleague’s half-reviewed one. Same one-step shape as publishing a knowledge-base page. Needs skills:publish; admin only. |
<slug...>, --base-url <url>, --yes |
skills unpublish |
The inverse of publish — retracts from every prompt immediately and keeps the text, its history and its author. The reversible counterpart to delete; prefer it when a rule is merely wrong for now. Needs skills:publish; admin only. |
<slug...>, --base-url <url>, --yes |
skills releases |
Publish/unpublish history, newest first — the rollback targets. Needs skills:publish; admin only. |
--base-url <url> |
skills rollback |
Re-point the current skill release at an earlier one. Restores CONTENT only: a skill an operator has since unpublished stays off. Needs skills:publish; admin only. |
<version>, --base-url <url>, --yes |
skills delete |
Delete a skill. Permanent, and it stops being injected immediately — no release needed. Prior releases keep their own copy of the text, so a rollback still works. Needs skills:publish; admin only. |
<slug>, --base-url <url>, --yes |
kb list |
What knowledge-base pages exist and which are ACTUALLY readable — draft, unreleased, live. A page flipped to published but never released reaches nobody, and visibility alone does not tell you that. Needs docs:publish; admin only. |
--base-url <url>, --status <draft|published> |
kb publish |
Publish pages — readable by the time it returns. Sets visibility and cuts a release scoped to these pages, so publishing one cannot ship a colleague’s half-written one. Needs docs:publish; admin only. |
<slug...>, --base-url <url>, --yes |
kb unpublish |
The inverse of publish — drops the pages out of the reader snapshot immediately and keeps the text and its history. Needs docs:publish; admin only. |
<slug...>, --base-url <url>, --yes |
kb versions |
Knowledge-base release history, newest first — the rollback targets, with the current one marked. Needs docs:publish; admin only. |
--base-url <url> |
kb rollback |
Re-point the current knowledge-base release at an earlier one. Needs docs:publish; admin only. |
<version>, --base-url <url>, --yes |
kb delete |
Delete a page and its subtree, and stop serving it in the same call. SOFT — restorable from the knowledge-base trash, unlike skills delete. Needs docs:publish; admin only. |
<slug>, --base-url <url>, --yes |
release deploy |
Enqueue a server-side release deploy. UPLOADS the SDF project’s src/ from your checkout when present; otherwise a solution-only live-fetch deploy. --target approved requires --approved-by. |
--target <mode> (required, auto|approved), --solution <id>, --project <dir>, --git-repo <repo>, --git-ref <ref>, --ref <committish>, --pr-url <url>, --approved-by <email>, --base-url <url>, --follow, --interval-ms <ms> |
release status |
Poll a release’s current deploy job. | <id>, --base-url <url>, --follow, --interval-ms <ms> |
release rollback |
Roll back a live release (LIFO-guarded; server-side reverse deploy). | <id>, --base-url <url> |
release list |
List releases, newest-first, with delivered titles + latest status. | --base-url <url> |
release pull |
Write the project a failed release built into a directory: owned/ (your solutions authored these — edit them) and context/ (the rest of the bundle — leave alone). Includes the failure log. Read-only. |
<id>, --dir <path> (required), --base-url <url> |
release push |
Ship the edited project as a new release delivering the same solution. Refuses if a context/ file changed. --target approved requires --approved-by. |
<id>, --dir <path> (required), --target <mode> (auto|approved), --approved-by <email>, --base-url <url> |
sandbox create |
Stand up a fresh local sandbox seeded with a sampled+redacted slice of an instance. | --from <instance> (required), --ttl <duration>, --sample <pct>, --base-url <url> |
sandbox destroy |
Drop a sandbox database and remove it from the registry. | <name> |
sandbox list |
List active sandboxes with TTL/expiry; reaps any past their TTL. | (none) |
processes list |
Every process deployed on the instance with its trigger (manual, or scheduled with the cron AND the timezone), whether it is switched on, and its last five runs. |
--base-url <url>, --json |
processes runs |
One process’s whole run history, newest first — the read behind “when did this stop working”. | <process-id>, --base-url <url>, --json |
processes run |
One run and its step ledger: each step’s disposition, what it wrote, and why it refused. | <run-id>, --base-url <url>, --json |
processes invoke |
Start a run of a deployed process. The input is a JSON object, passed whole as the process input. Queued by default (prints the run id); --sync waits, bounded by the instance’s budget, and prints the outcome and each step. Needs an extensions:deploy token owned by an administrator. |
<process-id>, --input <json>, --input-file <path>, --sync, --base-url <url>, --json |
processes cancel |
Stop one run. A queued run never executes; a running one stops at its next step boundary and keeps what it already wrote. | <run-id>, --base-url <url> |
data settle |
Link a row to the record the connected system already created, after a write_settle_conflict (POST /v1/data/<recordType>/settle). The id comes from the row’s own write state, never from you; a row not in that state answers 409 NOT_SETTLE_CONFLICT. |
<record-type>, <pg-row-id>, --base-url <url>, --json |
processes rollback |
Undo a finished run: delete, newest first, only the records its ledger shows it created — never one it updated. Source deletes are paced, so the run reads its progress until they are all sent. | <run-id>, --base-url <url> |
processes resume |
Carry on a run that stopped waiting on the connected system: the same run picks up at the step it stopped on, so nothing it already wrote is written again. Only a run that ended committed with work outstanding; anything else answers 409 naming the state, and a per-source-row process is invoked again instead. |
<run-id>, --base-url <url> |
logs |
Read the platform event log — guardrails, hooks, scheduled tasks, conduits and sync in one stream. --extension / --conduit / --process-run absorb the old ext logs and follow a process run. |
--base-url <url>, --subsystem <s>, --severity <s>, --code <code>, --since <window>, --correlation-id <id>, --extension <id>, --conduit <id>, --process-run <runId>, --follow, --interval-ms <ms>, --limit <n>, --json |
mcp |
Run the local stdio authoring MCP server, for clients pointed at a process rather than a URL. | --base-url <url> |
When a command fails
Section titled “When a command fails”Every command reports a failure as one line carrying what the instance said, and exits non-zero. Two of those lines are worth recognising on sight:
this instance is temporarily unavailable — it is upgrading or restarting. Retry shortly.Your instance is not answering — it is being upgraded, it is restarting, or it has been paused. Nothing is wrong with what you ran and nothing was written; retrying in a minute is the whole fix. Every command says it the same way, so a script can match on it.the request did not reach the instance — a proxy or edge answered with an HTML page, so the server gave no detail.Something between you and your instance answered instead of it. Check the--base-urlyou passed and any proxy on the way out.
A refusal your instance wrote arrives whole. When the instance refuses
something — a deploy that would leave a recurring job running a process you
removed, a module that does not compile — the reason is its own sentence, and
the command prints the whole of it, so the half that says what to do about it is
never cut off. Only a runaway reason stops short, at 2,000 characters: a build
error can carry a compiler’s entire output, and TURBOBASE_DEBUG=1 below prints
even that in full. A body that did NOT come from your instance is still
shortened to one line, which is where a proxy’s error page would otherwise land.
Set TURBOBASE_DEBUG=1 to put the full form back on any failure: the
method, the path, the status, and the server’s response body untruncated — the
form worth pasting into a bug report. 0, false and empty are off.
TURBOBASE_DEBUG=1 turbobase kb push --dry-runauth — sign in, and pick which instance you are talking to
Section titled “auth — sign in, and pick which instance you are talking to”Credentials live in ~/.turbobase/credentials, keyed by base URL. One of them is
the active instance: the default every other command uses when you omit
--base-url.
auth login
Section titled “auth login”turbobase auth login --base-url https://acme.turbobase.app --token tb_pat_xxxxturbobase auth login --base-url https://acme.turbobase.app --token "$TURBOBASE_PAT" # CI--token is required — there is no browser sign-in leg, so the same invocation
works on a laptop, a headless box, and a CI runner.
Stores the PAT keyed by base URL, makes that instance the active default, and
prints signed in to <url> — now the active instance (with a count of any other
stored credentials). After storing the token the CLI probes the instance — it
checks that the URL actually serves the /v1 API and that the token
authenticates — and warns loudly on a wrong host or a rejected token instead of
silently claiming success; --no-verify skips the probe. See the
overview for the full sign-in model.
project — the local project, and the files an agent session reads
Section titled “project — the local project, and the files an agent session reads”Everything under project writes into the current directory. project init
scaffolds the project; project types, project grounding and project adopt
generate the files a Claude Code session reads before it starts working, and
project skills keeps the platform’s own skills in .claude/skills current —
the scaffolded session hook runs all four every session.
project types
Section titled “project types”turbobase project types --out types.tsturbobase project types --records-only # skip the OpenAPI halfWrites generated TypeScript types for the instance’s catalog and prints
wrote <path> (<n> lines).. --records-only emits only record-shape
interfaces.
The OpenAPI half (/v1/catalog/openapi.json) is session-gated and a PAT cannot
authenticate it, so a headless run prints a note and degrades to records-only
rather than failing the whole generation. The record shapes — the half apps
generate against — come from catalog:read and are unaffected.
Until 1.3.x the CLI worked around this by trading the PAT for a session at
/auth/pat-exchange, which required a data:read scope. Both were removed: that
session carried the owner’s write access, which is far too much to spend on
fetching a schema document.
ext — author, verify, ship, and observe extensions
Section titled “ext — author, verify, ship, and observe extensions”The extension lifecycle end to end: scaffold with ext new, verify offline with
ext test / ext lint / ext replay, ship with ext deploy / ext plan /
ext promote, then read what ran with turbobase logs. ext install and
ext publish are the marketplace ends of the same object.
ext new hook
Section titled “ext new hook”turbobase ext new hook --record salesOrder --event before_save \ --template require-field --field memoWrites a hook file under hooks/ and prints wrote <path>. Refuses to
overwrite an existing file. Templates: require-field, enforce-format,
blank. See Writing hooks.
ext new conduit
Section titled “ext new conduit”turbobase ext new conduit --source shopify --record order --to netsuite \ --event after_canonicalize_inboundWrites a conduit file under conduits/ (<from>2<to> bridge) and prints
wrote <path>. --to-record defaults to the trigger record type. See
Writing conduits.
ext new process
Section titled “ext new process”turbobase ext new process --record purchaseOrder --then vendorBill --on-failure rollbackWrites a process file under processes/ and prints wrote <path>. --then
adds a second step that waits for the source’s verdict on the first and reads
the id it minted; --on-failure defaults to stop. See
Writing processes.
ext test
Section titled “ext test”turbobase ext test hooks/customer.before_save.tsturbobase ext test hooks/customer.before_save.ts --reporter junit > results.xmlRuns the extension against every fixture in samples/ through the same
isolated-vm sandbox production uses. Human output is one PASS/FAIL line
per sample plus an N/M sample(s) passed. summary; exits non-zero on any
failure. --reporter junit|json emits a machine-readable artifact for CI. See
Sandboxes and testing.
ext lint
Section titled “ext lint”turbobase ext lint conduits/shop2ns.order.tsturbobase ext lint conduits/shop2ns.order.ts --ciOffline static analysis of a hook, conduit or process for the semantic foot-guns
the Zod schema can’t catch. No instance / DB / network. --ci exits non-zero
when any warning fires.
Each rule applies to the kinds it is actually about. The deploy-identity check —
your module’s own id must be the first id: literal in the file — applies to
all three. The wiring checks (weak idempotencyKey, missing createdFrom,
extreme cycleBudget, a same-source “conduit”) describe a cross-source bridge,
so they apply to a conduit only.
ext replay
Section titled “ext replay”turbobase ext replay conduits/shop2ns.order.ts --fixture samples/order.json --traceDrives a conduit through its lifecycle (dispatch guards → from.when →
transform → apply-branch → bindings) in dry-run, against a JSON fixture
(offline, default) or --against sandbox (online, requires a local DB).
--trace emits the per-stage trace; --reporter emits one case per stage.
ext deploy
Section titled “ext deploy”turbobase ext deploy hooks/customer.before_save.ts # single fileturbobase ext deploy --to prod # whole bundleSingle-file mode parses the extension and POSTs it; prints
deployed <id> → status=<status> (new|replaced existing).. The id is the
first id: '…' literal in the file; when the module defines a different id
the deploy is refused (extension_id_mismatch) and the CLI names both ids. Whole-bundle mode (--to)
applies turbobase/bundle.json via /v1/config/apply and prints
applied bundle to <env>: +<add> ~<change> -<destroy>. A non-admin PAT gets a
403. See Deploy, plan, promote.
ext plan
Section titled “ext plan”turbobase ext plan --to prodPrints a terraform-style diff of the local bundle vs the target env’s live config — no writes:
plan against prod: + hook ns:customer:require-name ~ conduit shop2ns:order
1 to add, 1 to change, 0 to destroy.ext promote
Section titled “ext promote”turbobase ext promote --from sandbox --to prod --confirm acmePromotes the bundle behind a fail-closed confirm-slug gate: if --confirm
doesn’t match the target instance’s slug, nothing is POSTed. On success prints
promoted <from> → <to> (<slug>): +<add> ~<change> -<destroy>.
ext export
Section titled “ext export”turbobase ext exportSnapshots the live instance’s deployable surfaces (hooks, conduits, sync
fields/tables, schedules) into turbobase/bundle.json and prints
wrote turbobase/bundle.json (<n> lines).. The bundle is your config-as-code
source of truth that plan / deploy --to / promote ride.
ext binding
Section titled “ext binding”turbobase ext binding --status divergentturbobase ext binding --retry bnd_123 # admin-only re-enqueueLists txn_source_link bindings (filter by --status =
ok|pending|blocked|divergent|muted) or, with --retry <id>, re-enqueues a
divergent/blocked binding’s fan-out write. --retry requires an admin PAT (403
otherwise); a non-retryable binding returns 409. See
Writing conduits.
ext install
Section titled “ext install”turbobase ext install acme-recipes@1.2.0turbobase ext install acme-recipes --connection conn_123 # bind a connectionFetches a published bundle from the marketplace and deploys it with
provenance:'marketplace' (lands active). If the manifest declares a
{ type:'connection' } input you must pass --connection <id> to bind one of
your instance’s connections.
Needs the registry credential — TURBOBASE_REGISTRY_URL +
TURBOBASE_REGISTRY_TOKEN, both, from the same place. Ask Turbobase support
to issue a registry token for your instance; it is separate from your PAT and
from your instance’s internal credentials, and can be revoked on its own. The
CLI will not pair one variable with a credential it found elsewhere in your
environment, and the URL must be https:// (loopback excepted) because the
token travels to it as an Authorization header. See
Marketplace.
ext publish
Section titled “ext publish”turbobase ext publish hooks/recipe.ts --name acme-recipes --pkg-version 1.0.0 \ --manifest manifest.jsonPublishes an authored extension to the public marketplace. Your instance must be
on the publisher allowlist (403 not_a_publisher otherwise); a
duplicate version returns 409. A published bundle is connection-agnostic —
the connection it touches is bound at install time, never baked in.
Needs the same registry credential as ext install — see
Marketplace.
apps — hosted apps served from your instance
Section titled “apps — hosted apps served from your instance”A hosted app is a static single-page app that Turbobase serves from inside your
instance at /a/<slug>. Because it is same-origin, it inherits the instance’s
auth: no sign-in page, no callback, no CORS, no app-owned keys. The build runs on
your machine — nothing is built server-side — and the CLI uploads the result.
The whole group authorises on one scope, extensions:deploy, and additionally
requires the caller to be an admin at the time of the call, so a demoted
admin’s still-valid token cannot deploy.
apps create
Section titled “apps create”turbobase apps create reorder-desk --name "Reorder desk"Registers the app so a build can be shipped to it. Required before the first
deploy, unless you pass apps deploy --create. New apps are private until you
share them. The slug is the URL and is fixed once created — nothing redirects
the old one, so to change it, create a new app.
apps init
Section titled “apps init”turbobase apps init reorder-desk ./reorder-deskScaffolds the project locally — Vite + React + TypeScript, an auth-wired client,
a starter read that respects row-level security, and a dev proxy so
localhost:3000/a/<slug>/ behaves same-origin. Unless you pass --no-claude it
also writes CLAUDE.md, a 0600 .mcp.json, and the preview-app / ship-app
skills. It writes files only — nothing is created on the instance, and it works
signed out (with placeholders).
apps push
Section titled “apps push”turbobase apps push reorder-desk ./reorder-desk --note "tighten the filters"Records the local tree as a new source revision so the instance holds the source of record and the app can be pulled from another machine. Source only — it does not build and does not change what is live. Every push appends a revision; nothing is overwritten.
apps deploy records source too, from the same directory and through the same
collector, so this verb is for recording WITHOUT deploying: a tree you want
stored before it is ready to go live, or a deploy you ran with --no-source.
It is also what supplies the knowledge ingest for a --no-source deploy. The
findings a deploy reads are the ones in the newest recorded revision, so push
before deploying whenever you have edited INSTANCE-LEARNINGS.md or
TURBOBASE-FEEDBACK.md and are not letting the deploy record them for you.
apps deploy
Section titled “apps deploy”turbobase apps deploy reorder-desk ./reorder-deskturbobase apps deploy reorder-desk ./reorder-desk --preview --label "new filters"turbobase apps deploy reorder-desk ./reorder-desk --replaceBuilds and cuts a new live version at /a/<slug>. With --preview the build is
served at its own URL and /a/<slug> is left untouched — see below.
--preview opens a NEW preview URL every time, so the second build of the same
change is a second link and whoever was sent the first one is still looking at
the old bytes. --replace re-deploys onto an existing preview instead: the URL,
and the label it carries, are unchanged. Pass the handle
(--replace <preview-id>, from apps preview list) or omit it when the app has
exactly one open preview — with several open, the CLI lists them and asks which.
--replace implies --preview, keeps the reviewed-but-superseded build in
deploy history, and resets the 7-day expiry, so it is also how you bring an
expired preview link back.
Two deploys of the same app may run at the same moment. Each takes its own
version number and keeps its own copy of the files, so a CI job does not have to
serialise them. Which one ends up at /a/<slug> is decided by whichever
FINISHES last, not by whichever you started first — so when the order matters,
still deploy one after the other.
A deploy from a source directory also records that directory as the app’s
newest source revision, through the same collector apps push uses. The
version is cut first and the source recorded after it, so a collection that
fails costs you the revision and never the deploy — it prints
recorded N source files on the instance (rev R), or a line beginning
source not recorded: naming what to do. Credential files are withheld by name
on stderr (.env, .env.* and .mcp.json; .env.example is a template and
travels), and node_modules/, dist/, build/, .git/ and .claude/ are
skipped, so what lands is the project rather than its output.
Pass --no-source to cut the version and upload nothing from the working tree.
Nothing is recorded from a directory with no package.json either — a prebuilt
bundle has no source tree — nor from one this command pulled the source into,
which is already the newest revision.
An app with no source revision cannot be changed from a user’s in-app change request: there is no copy on the instance to edit, and a built bundle cannot be turned back into a project. App Management says so per app, and the widget tells the person who filed the request.
A live deploy also queues the knowledge ingest, and prints which source revision
it queued it against — it reads what the recorded source holds, not the bundle
this command just built. It warns when the app has never been pushed, and when
your local INSTANCE-LEARNINGS.md / TURBOBASE-FEEDBACK.md are newer than that
revision.
apps preview list / apps preview discard
Section titled “apps preview list / apps preview discard”turbobase apps preview list reorder-deskturbobase apps preview discard reorder-desk <preview-id>Previews expire after 7 days; list shows them newest-first with each URL and
expiry date. A preview URL grants no new access — it opens only for people who
could already open the app. discard stops the URL resolving; the build stays in
deploy history and can still be promoted.
apps promote
Section titled “apps promote”turbobase apps promote reorder-desk <preview-id>Makes a preview the live version. Nothing is rebuilt — the bundle’s asset URLs
name the build rather than the page it is served from, so promotion is a pointer
write and the reviewed bytes are the shipped bytes. Promotion also fires the
effects a preview deliberately skips: applying the manifest’s name and
description, the release ledger, relationship activation, proposal completion,
and knowledge ingest. It also applies the manifest’s declared roles — the
capabilities they gain and any scopes change — and prints what changed, so a
Slack channel a role loses is in the promote output, not only in the plan.
apps pull
Section titled “apps pull”turbobase apps pull reorder-desk ./reorder-deskturbobase apps pull --proposal <id>Writes the app’s newest source revision to disk — whether it was generated from a
proposal or pushed by you. Read-only; it never deploys. With --proposal the app
is derived from the proposal, and its request context is written alongside the
source.
apps list
Section titled “apps list”turbobase apps listturbobase apps list --jsonEvery hosted app with whether it is live, its latest source revision, and its
audience. A — in the source column means the app was deployed but never pushed,
so there is nothing to pull.
kb — write findings into the knowledge base
Section titled “kb — write findings into the knowledge base”kb push
Section titled “kb push”turbobase kb push --dry-runturbobase kb pushturbobase apps push uploads your standing markdown and parses it into a
pending queue on the instance. kb push is the only thing that turns those
findings into knowledge-base pages — recording what you learned and changing the
company’s documentation are deliberately separate decisions.
It re-reads your pushed markdown before planning and prints what that found:
2 file(s) scanned · 1 changed · 6 new finding(s) · 15 already knownRead that line before anything else. 0 file(s) scanned means your markdown
never reached the instance — you deployed without pushing, and apps deploy
uploads the built bundle, which contains no .md files at all. 15 already known means your edit landed and those findings were already dealt with; a
finding’s identity is its normalized text, so reformatting, renumbering or
re-heading one is deliberately the same finding.
The unit is a page, not a finding: a dozen findings about one page become one appended section and one write, so you approve “19 pages”, never “53 things”. The plan is printed in full before anything is written, and the command asks. New pages land unpublished so a human can fold them into the taxonomy; existing pages are appended to, never rewritten; and a page whose unpublished work belongs to someone else is skipped rather than overwritten.
--dry-run prints the plan and exits. --yes skips the prompt; without it, a
non-interactive run refuses rather than assuming. If the queue moves while you are
reading the plan, the write is rejected and nothing lands.
Grouping is a judgement — two findings that name the same subject are assumed to
be about the same page. When they are not, --no-group plans every finding on its
own: the same pages are targeted, but N findings for one page become N sequential
appends instead of one merged block. The plan also warns when two NEW pages look
like the same subject written two ways; it never merges them for you.
A correction whose page ref matches no page is skipped rather than turned into
a new page — a correction needs a page to correct. The plan names the ref that
failed, and those findings stay pending until you fix it. A skipped group is
counted and shown as a skip throughout: it is not in the “N page(s)” tally, it
gets no write-set under --diff, and it is not in the numbers the confirmation
prompt quotes. If everything in scope is skipped there is nothing to confirm, so
you are not asked.
skills — write standing rules into the instance’s agents
Section titled “skills — write standing rules into the instance’s agents”skills push
Section titled “skills push”turbobase skills push --dry-runturbobase skills pushThe other half of the same queue. An authoring session records two kinds of
finding: the narrative that should be findable when someone asks the right
question, and the hard rules that must apply on every turn whether or not
anyone thinks to ask. kb push writes the first kind into the knowledge base;
skills push writes the second as agent skills.
Rules under one ### sub-heading become ONE skill, named after that heading.
The plan is printed in full before anything is written; --dry-run prints it and
exits, --yes skips the prompt, and without it a non-interactive run refuses
rather than assuming. Like kb push, it re-reads your pushed markdown first and
prints what that found.
--reapply — landing a corrected grouping
Section titled “--reapply — landing a corrected grouping”A finding’s identity is its normalized text, so re-filing a rule under a
different ### heading is deliberately the same finding: it queues nothing, and
without --reapply there is nothing for a push to act on. That is the right
default — the queue is a work list, not a history — but it means a grouping you
fixed after the first push cannot reach the instance on its own.
--reapply re-emits rules already applied, so the new grouping (and any body
text you tightened) lands. It is safe to run more than once: the write is keyed
on the skill’s slug, a finding already present in a rule is not appended twice,
and a finding you dismissed stays dismissed — that status is the one a human
set deliberately to mean no.
Rules land draft and disabled, and there is deliberately no flag to change
that. A PAT that can deploy an app is not thereby authorised to change what
every agent on the instance is told. Publishing is a separate act on its own
opt-in scope — turbobase skills publish, or the Skills page in the app.
kb list | publish | unpublish | versions | rollback | delete
Section titled “kb list | publish | unpublish | versions | rollback | delete”turbobase kb listturbobase kb publish month-end-closeturbobase kb unpublish month-end-closeturbobase kb versionsturbobase kb rollback 12turbobase kb delete month-end-closeThe knowledge-base editor, from a terminal. All six need a PAT carrying
docs:publish — a dedicated, opt-in scope that a token minted before this
release will not have. kb push is unchanged and still runs on
extensions:deploy: filing a draft and arming what every reader (and every AI
answer) sees are different powers, so they carry different keys.
kb list reports whether each page is actually readable, not just its
visibility flag. A page flipped to published but never included in a release is
approved text nobody can find, and it is the single most confusing state the
knowledge base has.
kb publish is one act: it sets visibility and cuts a release scoped to the
pages you name, so the page is live when the command returns and publishing one
page cannot ship a colleague’s half-written one. kb unpublish is the exact
inverse and takes effect immediately.
kb delete is soft — the page and its subtree drop out of the tree and out
of every reader release, and stay restorable from the knowledge base’s trash.
This differs from skills delete, which is permanent, and the asymmetry is
deliberate: a skill’s text survives in every release that carried it, while a
page is the only copy of prose a human wrote.
release — ship your project from your own CI
Section titled “release — ship your project from your own CI”Your repo, your runner, your credential. release deploy uploads the bytes in
your checkout; Turbobase clones nothing and holds no GitHub App key. The only
credential is a PAT you mint with the releases:write scope.
turbobase project ci scaffolds a GitHub Action that runs exactly this, and
the CLI is the equal front door — neither is privileged over the other.
release deploy
Section titled “release deploy”turbobase release deploy --target auto --project . --ref HEAD --followUploads <--project>/src/ as {path → contents} and enqueues the server-side
deploy. --target auto applies immediately; --target approved fails closed
with 403 approval_required unless --approved-by <email> records an approval.
Omitting an SDF src/ requires --solution <id> (the live-fetch path).
--ref, --git-repo, --git-ref and --pr-url are provenance links only —
recorded against the release, never dereferenced. --ref is resolved locally
with git rev-parse, so a branch, tag or short sha all normalise to a full one.
They default from $GITHUB_SHA / $GITHUB_REPOSITORY / $GITHUB_REF / $PR_URL,
so a CI run fills them in with no flags. Anything that can’t be derived is simply
left null and never blocks the deploy.
--follow polls to completion and exits non-zero if the deploy fails.
release status
Section titled “release status”turbobase release status rel_abc123 --followPrints the release’s current stage and percent, or streams to completion with
--follow.
release rollback
Section titled “release rollback”turbobase release rollback rel_abc123Server-side reverse deploy, LIFO-guarded: a release that is not live, or that a newer release has advanced past, is refused with 409 rather than rolled back.
release list
Section titled “release list”turbobase release listReleases newest-first with delivered titles and latest status.
sandbox — ephemeral local instances
Section titled “sandbox — ephemeral local instances”A sandbox is a throwaway database seeded with a sampled, redacted slice of a real
instance — somewhere to run ext replay --against sandbox without going near
production data.
sandbox create is not available yet: it refuses and creates nothing.
Seeding needs a per-column PII classification for the source instance’s tables,
and redaction fails closed on any column it cannot prove is safe. ext test and
ext replay --fixture are the offline paths that work today.
sandbox create
Section titled “sandbox create”turbobase sandbox create --from acme --ttl 72h --sample 2%Stands up a fresh local authoring sandbox seeded with a sampled + redacted
slice of --from, mounts the working tree’s hooks/ + conduits/, and prints
the sandbox name, source, URL, expiry, and mount counts. The sandbox process
forces TURBOBASE_SANDBOX=1 so every source resolves to a mock, so no code path
reaches a real Oracle NetSuite or Stripe account.
sandbox list
Section titled “sandbox list”turbobase sandbox listLists active sandboxes (name from=… expires=… url) and opportunistically
reaps any past their TTL.
sandbox destroy
Section titled “sandbox destroy”turbobase sandbox destroy acme-72h-abcDrops the sandbox database and removes it from the registry; prints
destroyed sandbox <name> (dropped <database>)..
See Sandboxes and testing for the two distinct senses of “sandbox” (the code sandbox vs. an account sandbox).
guardrails — decide which write-backs are allowed to leave
Section titled “guardrails — decide which write-backs are allowed to leave”turbobase guardrails posture # the global defaultturbobase guardrails list # every rule, in evaluation orderturbobase guardrails create "open invoices" --source netsuite --record invoice --decision allow --priority 10turbobase guardrails test --source netsuite --record invoice # what would happen to this writeturbobase guardrails deactivate "open invoices" # park it, reversiblyThe same rules the Compliance → Guardrails page shows, from the command
line. posture is the one global switch: allow makes the rules a deny-list,
deny makes them an allow-list. Everything else is per rule — create a rule,
edit its decision or priority, activate or deactivate it, delete it.
test is the one to reach for before you change anything: it runs the same
evaluator the write path runs, against a write you describe, and writes nothing.
It exits 1 when the write would not proceed, so your own CI can gate on it.
Guardrails sit under the per-source write entitlement and cannot open a write path your plan does not include. First match wins, lower priority is evaluated first, and a delete is gated separately from a write.
relationships — which linked record pairs re-read each other
Section titled “relationships — which linked record pairs re-read each other”turbobase relationships list # every pair, and which re-read is onturbobase relationships list --source netsuite # one connected systemturbobase relationships refresh cr_delivery_line:custrecord_line_delivery:cr_delivery --children --onturbobase relationships refresh cr_delivery_line:custrecord_line_delivery:cr_delivery --parent --offSome systems save a parent’s lines as part of the parent’s own save and raise no event on the lines, so the parent reaches your instance in seconds and its lines wait for the scheduled catch-up. The mirror image happens too: saving a line makes the system silently rewrite totals on the parent. Turbobase can close either gap by re-reading the other side, and these two verbs are how you switch that on. The app does not list them: a record on Data → Records carries a re-read mark when one of its pairs has a re-read switched on, and these verbs are where the three names, the time and the switch itself live.
list prints one row per pair your instance has discovered: the line table, the
field on the line that holds its parent, the parent table, and each of the two
re-reads as on — with when it was switched on — or off. An empty list is
a fact about discovery rather than about the behaviours: a pair appears once a
source is reconciled.
refresh takes the pair as one argument, CHILD:KEY:PARENT, exactly as list
prints the three names. Then say which behaviour and which way — --children
(re-read the lines when their parent is saved) or --parent (re-read the parent
when one of its lines is saved), and --on or --off. Both halves are
required: nothing is defaulted, because a default would switch a behaviour you
did not name. A pair your instance has not discovered is refused and the refusal
names what is declared on that line table — nothing is created here. Replaying
a call that changes nothing says so, and leaves the original time alone.
Each switched-on pair has a running cost — one query to the connected system
per parent save, plus one re-read per line — so switch on the pairs that are
actually arriving late (turbobase logs, and the Late arrivals page, say
which) rather than all of them. Both verbs need a token with the
extensions:deploy permission and an administrator behind it.
tables — custom tables of your own
Section titled “tables — custom tables of your own”turbobase tables list # the custom tables on your instanceturbobase tables create expenses -f amount:number -f spent_on:dateturbobase tables plan # what this project's manifest would doturbobase tables apply # apply itturbobase tables drop expenses --confirm expensesTables you add alongside your mirrored records, for the data your business keeps that no connected system holds. They are the CLI twin of the New custom table button on the Records page, and they behave the same way: a table is additive, so applying a manifest gives an existing table the fields you declare and leaves the fields other projects declared alone.
plan writes nothing and exits 1 with a conflict report when another claimant
already declares one of your field names at a different type — run it in CI and
a clash is caught before the deploy. drop deletes the table and every row in
it, and will not run unless --confirm matches the slug exactly.
logs — every log, under one noun
Section titled “logs — every log, under one noun”turbobase logs --since 10m # everything, last 10 minutesturbobase logs --subsystem guardrails --severity warnturbobase logs --correlation-id 5f3c… # one write, across subsystemsturbobase logs --follow # poll for new rowsturbobase logs --extension ext_abc123 # was `ext logs ext_abc123`turbobase logs --conduit cnd_xyz --follow # was `ext logs cnd_xyz --follow`turbobase logs --process-run 1111… --follow # one process run, until it finishesAll log access under one noun. With no target flag this reads the unified
platform event log (GET /v1/events) — guardrails, hooks, scheduled tasks,
conduits and sync in one stream, one envelope, one correlation id. One line per
row: timestamp, severity, outcome, event code, where it happened, the
correlation id, the typed detail, and a redacted=[…] marker naming which
redaction rule groups fired (never what they matched).
--severity is a threshold, not an equality filter: warn includes
error. --since takes a relative window (10m, 2h, 3d) or an ISO
timestamp. --follow polls every --interval-ms (default 2000) from a cursor,
so it never re-prints a row.
--extension <id> reads that extension’s bounded log ring buffer and
--conduit <id> reads that conduit’s conduit_event run ledger — the two modes
the removed ext logs had. Each stays its own read rather than a filter,
because each is the system of record for its subsystem and the platform log is
derived from it.
--process-run <runId> is the third, and it is the one that ENDS. A conduit has
no end, so --follow on one polls until you interrupt it; a process run reaches
a terminal state, so following one prints each step as it lands and returns when
the run finishes. Following a run is a flag here rather than a processes logs
verb because all log access unifies under one noun.
Rows are redacted at WRITE, so secrets, absolute paths, stack frames and SQL
never reach the stored row for anyone — while your hook’s rejection messages,
your guardrail reasons and your custbody_* / cr_* / cf_* ids survive
verbatim. The log is sampled (high-volume events carry an occurrences count)
and expires: 90 days for error, 30 for warn, 7 for info, 1 for debug.
processes — what runs in the background
Section titled “processes — what runs in the background”turbobase processes list # what is deployed, and how it firesturbobase processes runs acme:reorder-suggestions # that process's whole historyturbobase processes run 1111… # one run and its step ledgerturbobase processes invoke acme:reorder-suggestions --input '{"warehouse":"east"}' # start oneturbobase processes cancel 1111… # stop itturbobase processes rollback 1111… # undo what a finished run createdprocesses list is the answer to “is the nightly job actually scheduled?” — it
prints each process’s trigger as manual, or as scheduled with both the
cron expression and the timezone it is read in, because 0 2 * * * is a
different moment in Europe/London and in America/New_York. A process an
administrator has switched off is marked [off], and each row carries its last
five runs with state, start and duration.
processes runs <id> is the whole history rather than those five, and
processes run <runId> opens one run: every step, its disposition, the record
it wrote and the refusal that stopped it.
processes invoke <id> starts a run, exactly as an app’s call to POST /v1/processes/:id does. Pass the input with --input '<json>' or
--input-file <path> — a JSON object, sent as the whole input and checked
against the schema the process declares; with neither, the input is {}. By
default the run is queued and the command prints its id; add --sync to wait
for it and print its state, anything still outstanding, and each step’s
disposition. A synchronous wait is bounded by the instance’s budget: a run that
outlasts it is still running, not failed, and the command prints its id and
how to watch it. The run is the token owner’s — it writes as that person, and
the token must carry extensions:deploy and belong to an administrator.
processes cancel <runId> stops a queued run before it executes, and stops a
running one at its next step boundary — the step in flight finishes its
write rather than being killed halfway through, and what already went upstream
stays. A cancel is not a failure, so nothing is rolled back.
processes rollback <runId> undoes a run that has finished — committed,
failed or cancelled. It deletes, newest first, only the records the run’s own
ledger shows it created; a record it updated, a row an earlier run had already
migrated, and a step declared rollback: 'keep' are left alone. A run still
queued or running is refused: cancel it first. Rolling back a run that is
already rolled_back does nothing. The deletes reach your connected system at
the instance’s rollback pace rather than all at once, so a large run’s reason
says its deletes are still being sent until they are; if the source refuses one,
that record comes back and the run reads failed with the refusal named.
To watch a run as it goes, use the log noun: turbobase logs --process-run <runId> --follow.
data — act on one record
Section titled “data — act on one record”turbobase data settle nonInventorySaleItem 2b7c… # link the row to the record the source already createddata settle is the way out of a write_settle_conflict: the connected system
created the record and handed back its id, and linking that id onto the row
here failed, so the row reads as unsynced over a record that exists. Saving the
row again would create a second record upstream; this links it instead. It reads
the id from the row’s record_write_state (never from the command line),
discards the inbound copy of the same record if one arrived meanwhile — only an
untouched one; a copy someone edited is left alone and the command refuses with
409 — marks the row synced and resolves the failure. Running it again on a
row it already linked answers the same success. It needs a token with the
rest:write permission, and only links a row its owner could write in the app.
mcp — the local authoring server
Section titled “mcp — the local authoring server”turbobase mcp --base-url https://acme.turbobase.appRuns the local stdio authoring MCP server so Claude Code can drive the same
library functions the CLI does. Not usually a command you type: it is the
process .mcp.json spawns, launched on demand by the file project init
generated. See the
overview.