Skip to content

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>

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-url you 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.

Terminal window
TURBOBASE_DEBUG=1 turbobase kb push --dry-run

auth — 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.

Terminal window
turbobase auth login --base-url https://acme.turbobase.app --token tb_pat_xxxx
turbobase 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.

Terminal window
turbobase project types --out types.ts
turbobase project types --records-only # skip the OpenAPI half

Writes 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.

Terminal window
turbobase ext new hook --record salesOrder --event before_save \
--template require-field --field memo

Writes a hook file under hooks/ and prints wrote <path>. Refuses to overwrite an existing file. Templates: require-field, enforce-format, blank. See Writing hooks.

Terminal window
turbobase ext new conduit --source shopify --record order --to netsuite \
--event after_canonicalize_inbound

Writes a conduit file under conduits/ (<from>2<to> bridge) and prints wrote <path>. --to-record defaults to the trigger record type. See Writing conduits.

Terminal window
turbobase ext new process --record purchaseOrder --then vendorBill --on-failure rollback

Writes 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.

Terminal window
turbobase ext test hooks/customer.before_save.ts
turbobase ext test hooks/customer.before_save.ts --reporter junit > results.xml

Runs 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.

Terminal window
turbobase ext lint conduits/shop2ns.order.ts
turbobase ext lint conduits/shop2ns.order.ts --ci

Offline 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.

Terminal window
turbobase ext replay conduits/shop2ns.order.ts --fixture samples/order.json --trace

Drives 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.

Terminal window
turbobase ext deploy hooks/customer.before_save.ts # single file
turbobase ext deploy --to prod # whole bundle

Single-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.

Terminal window
turbobase ext plan --to prod

Prints 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.
Terminal window
turbobase ext promote --from sandbox --to prod --confirm acme

Promotes 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>.

Terminal window
turbobase ext export

Snapshots 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.

Terminal window
turbobase ext binding --status divergent
turbobase ext binding --retry bnd_123 # admin-only re-enqueue

Lists 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.

Terminal window
turbobase ext install acme-recipes@1.2.0
turbobase ext install acme-recipes --connection conn_123 # bind a connection

Fetches 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.

Terminal window
turbobase ext publish hooks/recipe.ts --name acme-recipes --pkg-version 1.0.0 \
--manifest manifest.json

Publishes 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.

Terminal window
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.

Terminal window
turbobase apps init reorder-desk ./reorder-desk

Scaffolds 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).

Terminal window
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.

Terminal window
turbobase apps deploy reorder-desk ./reorder-desk
turbobase apps deploy reorder-desk ./reorder-desk --preview --label "new filters"
turbobase apps deploy reorder-desk ./reorder-desk --replace

Builds 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.

Terminal window
turbobase apps preview list reorder-desk
turbobase 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.

Terminal window
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.

Terminal window
turbobase apps pull reorder-desk ./reorder-desk
turbobase 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.

Terminal window
turbobase apps list
turbobase apps list --json

Every 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”
Terminal window
turbobase kb push --dry-run
turbobase kb push

turbobase 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 known

Read 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”
Terminal window
turbobase skills push --dry-run
turbobase skills push

The 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”
Terminal window
turbobase kb list
turbobase kb publish month-end-close
turbobase kb unpublish month-end-close
turbobase kb versions
turbobase kb rollback 12
turbobase kb delete month-end-close

The 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.

Terminal window
turbobase release deploy --target auto --project . --ref HEAD --follow

Uploads <--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.

Terminal window
turbobase release status rel_abc123 --follow

Prints the release’s current stage and percent, or streams to completion with --follow.

Terminal window
turbobase release rollback rel_abc123

Server-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.

Terminal window
turbobase release list

Releases newest-first with delivered titles and latest status.

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.

Terminal window
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.

Terminal window
turbobase sandbox list

Lists active sandboxes (name from=… expires=… url) and opportunistically reaps any past their TTL.

Terminal window
turbobase sandbox destroy acme-72h-abc

Drops 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”
Terminal window
turbobase guardrails posture # the global default
turbobase guardrails list # every rule, in evaluation order
turbobase guardrails create "open invoices" --source netsuite --record invoice --decision allow --priority 10
turbobase guardrails test --source netsuite --record invoice # what would happen to this write
turbobase guardrails deactivate "open invoices" # park it, reversibly

The 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”
Terminal window
turbobase relationships list # every pair, and which re-read is on
turbobase relationships list --source netsuite # one connected system
turbobase relationships refresh cr_delivery_line:custrecord_line_delivery:cr_delivery --children --on
turbobase relationships refresh cr_delivery_line:custrecord_line_delivery:cr_delivery --parent --off

Some 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.

Terminal window
turbobase tables list # the custom tables on your instance
turbobase tables create expenses -f amount:number -f spent_on:date
turbobase tables plan # what this project's manifest would do
turbobase tables apply # apply it
turbobase tables drop expenses --confirm expenses

Tables 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.

Terminal window
turbobase logs --since 10m # everything, last 10 minutes
turbobase logs --subsystem guardrails --severity warn
turbobase logs --correlation-id 5f3c… # one write, across subsystems
turbobase logs --follow # poll for new rows
turbobase 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 finishes

All 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.

Terminal window
turbobase processes list # what is deployed, and how it fires
turbobase processes runs acme:reorder-suggestions # that process's whole history
turbobase processes run 1111… # one run and its step ledger
turbobase processes invoke acme:reorder-suggestions --input '{"warehouse":"east"}' # start one
turbobase processes cancel 1111… # stop it
turbobase processes rollback 1111… # undo what a finished run created

processes 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.

Terminal window
turbobase data settle nonInventorySaleItem 2b7c… # link the row to the record the source already created

data 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.

Terminal window
turbobase mcp --base-url https://acme.turbobase.app

Runs 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.