@odla-ai/cli
v0.57.8
Published
Agent-operable CLI for bounded session orientation, project runbooks, provisioning, System AI, security jobs, secret transfer, and smoke checks.
Maintainers
Readme
@odla-ai/cli
⚠️ Early access — pre-1.0. Agents work from bounded runbooks; humans approve credentials, production changes, releases, and merges. APIs and exact package availability can change. Review the documented guarantees and limitations; this software is MIT-licensed and provided without warranty.
Project-neutral provisioning CLI for odla apps. It creates and validates an
odla.config.mjs, then uses that config to register an app, enable services,
push odla-db schema/rules, configure hosted app AI (or explicit BYOK), configure Clerk auth, record
deployment links, compose declared npm capability integrations, connect Google Calendar booking, provision o11y ingest credentials, and transfer local or
deployed Worker secrets without printing them. It also gives platform admins a
scoped System AI control surface and gives app owners a provider-key-free
hosted security command.
Provisioning does not know about any specific app. App identity, environments, services, schema, rules, integrations, auth, AI mode/model, and links all come from config. Operator commands can also receive explicit platform/app/env context so remote agents are not forced to manufacture a project checkout.
Separate developer runtimes on one shared sandbox
Several developers can share one Stripe test account without sharing a webhook
signing secret, pointing Stripe at whichever preview deployed last, or creating
a separate database for every person. Keep the shared database in envs, then
declare each provider runtime independently:
npx --yes @odla-ai/cli@latest environment reconcile --runtime coryThe command derives the exact Cloudflare account and Worker name from Wrangler,
declares the desired callback, and reconciles exactly one Stripe endpoint tagged
with the app lifetime, data environment, and runtime. It also binds
ODLA_RUNTIME to the selected Worker. Stripe's returned whsec_… value moves
directly into the shared environment vault as
stripe_webhook_secret__<runtime> and is never printed. The shared data
environment must already contain stripe_secret_key; development reconciliation
refuses live-mode keys.
The Chapter defaults are /api/webhooks/stripe with invoice.paid,
payment_intent.succeeded, charge.refunded, and
customer.subscription.deleted. Override only non-secret delivery intent:
export default {
app: { id: "my-app", name: "My app" },
envs: ["dev"],
services: ["db"],
runtimes: {
cory: {
dataEnvironment: "dev",
wranglerEnvironment: "dev",
origin: "https://my-app-cory.example.com",
},
tori: {
dataEnvironment: "dev",
wranglerEnvironment: "dev",
origin: "https://my-app-tori.example.com",
},
},
stripe: {
webhookPath: "/api/webhooks/stripe",
enabledEvents: ["invoice.paid", "customer.subscription.deleted"],
},
};Both Workers continue to use ODLA_ENV=dev and the same my-app--dev database.
Chapter adds the originating runtime to Stripe objects; each endpoint verifies
its own secret, and sibling runtimes acknowledge without writing shared data.
Use environment list for declared identities, environment status --runtime
cory --json for desired-versus-live drift and custody, environment rotate
--runtime cory --yes to replace only Cory's endpoint/secret, and environment
retire --runtime cory --yes to remove only Cory's managed resources.
Production, unmanaged endpoints, the shared provider key, and sibling runtimes
are outside that mutation boundary. Existing --env alice declarations remain
supported as the legacy one-runtime/one-data-environment form.
Start with the runbooks, not this file
odla's operational procedures — releasing, backups, database work, security setup — are rows in a database, not documents in a package. They are edited the moment a step turns out to be wrong, with no release in between. So the answer this CLI fetches is current, and every written copy of it (including this README, and including your training data) is a snapshot of some earlier day.
Ask before you search the web or work from memory:
npx --yes @odla-ai/cli@latest runbook ask "how do I roll back a bad publish?" # written, cited answer
npx --yes @odla-ai/cli@latest runbook search "restore a tenant" # the passages behind it
npx --yes @odla-ai/cli@latest runbook get release # the whole procedure
npx --yes @odla-ai/cli@latest runbook list # what existsReading works for any signed-in developer and needs no odla.config.mjs — the
advice is most useful before there is a project.
Chapter member-content authoring
The CLI drives the package-owned member-content contract without duplicating
its validator. Supply a bounded tier-authority file containing only id,
name, priceCents, and active; provider Price ids do not belong in authored
content or CLI output.
odla-ai chapter member-content validate --file parent.json --tiers tiers.json --json
odla-ai chapter member-content compose --parent parent.json --chapter chapter.json --tiers tiers.json --json
odla-ai chapter member-content project --parent parent.json --chapter chapter.json --tiers tiers.json --tier community --jsonproject is the disclosure check: locked pages contain metadata and an
eligible live-tier offer, but never their restricted bodyMarkdown. Use
--admin for the exact admin preview. compose is an operator-local authoring
operation and therefore emits the complete effective document supplied in the
local files.
Delivery validates the parent document again, computes its canonical digest, signs the exact body, and calls the dedicated Chapter receiver. The shared secret is never accepted in argv:
MEMBER_CONTENT_SECRET='...' \
odla-ai chapter member-content deliver \
--file parent.json --tiers tiers.json \
--url https://chapter.example --from-env MEMBER_CONTENT_SECRET --json
# Or keep the value out of the environment:
secret-producing-command | odla-ai chapter member-content deliver \
--file parent.json --tiers tiers.json \
--url https://chapter.example --stdin --jsonThese commands require the project’s current @odla-ai/chapter installation.
Outside localhost, delivery refuses plain HTTP and credential-bearing URLs.
Chapter Builder Library
Builder Library commands use Chapter's strict private-reference contract. They accept exactly one Chapter, Built Not Found-only, or global scope per resource; there are no groups. Validation output does not echo private bodies.
odla-ai chapter builder-library validate --file library.json --source-type chapter --source oakland --json
odla-ai chapter builder-library manifest --file library.json --chapter oakland --json
NETWORK_SHARE_SECRET='...' odla-ai chapter builder-library deliver \
--file library.json --url https://builtnotfoundcapital.com \
--from-env NETWORK_SHARE_SECRET --jsonmanifest emits only published resources visible to the named Chapter. Brand
assets remain canonical book/asset references; manifests never carry bytes,
storage credentials, or persistent private URLs. Delivery reuses the existing
Chapter network share secret for that Built Not Found/chapter edge; Builder
Library does not require another credential. The value is accepted only from
stdin or a named environment variable.
When delivering a Built Not Found local document to a Chapter, pass --target
and --environment; delivery projects global resources only.
Selective release planning and approval
Promotion plans are exact, read-only review artifacts. An agent supplies the validated source bundles, target snapshots, mappings, and provider-readiness evidence; Registry returns one immutable digest and target-set digest without changing any target:
npx --yes @odla-ai/cli@latest promotion plan --file promotion-plan.json --env dev --json
npx --yes @odla-ai/cli@latest promotion inspect <plan-id> --env dev --json
npx --yes @odla-ai/cli@latest promotion status <plan-id> --env dev --jsonStudio shows the exact before/after values, exclusions, mappings, blockers, and
provider evidence. A signed-in human approves one apply or rollback action.
An enrolled device can then queue or resume that exact operation; a stable
default idempotency key makes an interrupted retry return the same intent:
npx --yes @odla-ai/cli@latest promotion apply <plan-id> --approval <approval-id> --env dev --json
npx --yes @odla-ai/cli@latest promotion rollback <plan-id> --approval <approval-id> --env dev --json
npx --yes @odla-ai/cli@latest promotion next <plan-id> <operation-id> --env dev --json
npx --yes @odla-ai/cli@latest promotion record <plan-id> <operation-id> <step-id> --file receipt.json --env dev --json
npx --yes @odla-ai/cli@latest promotion cancel <plan-id> <operation-id> --env dev --jsonApproval never floats to a newer plan. A changed source, mapping, readiness
check, target lifetime, target set, action, digest, or expiry fails closed. A
follower leases one target-local step with next, records exact evidence with
record, and may safely reclaim an expired lease after a process exit. Content
activation is always last. cancel stops work only before activation; afterward
an approved rollback publishes a higher audited revision.
Operator context from any directory
Inside an existing configured checkout, a bare invocation is the compact, read-only home for agents:
npx --yes @odla-ai/cli@latestIt reports the CLI version; config, app, environment, and credential-cache
provenance; bounded current and Ready PM windows when usable authority is
already cached; and fully scoped next commands. It makes at most one bounded PM
GET. It never starts authentication, opens a browser, prompts, adopts legacy
files, mints a device session, or writes a cache. Missing, expired, rejected,
offline, and unresolved states are explicit. Use odla-ai --help for the full
command manual.
Before an agent reads PM, Discussions, o11y, runbooks, or identity from a fresh workspace, it can explain the selected context without authenticating:
npx --yes @odla-ai/cli@latest context show --platform https://odla.ai --app <appId> --env prod --json
npx --yes @odla-ai/cli@latest context save production --platform https://odla.ai --app <appId> --env prod
npx --yes @odla-ai/cli@latest context show --context production --jsonFor each value, an explicit flag wins over the matching
ODLA_PLATFORM_URL, ODLA_APP_ID, or ODLA_ENV variable. An explicitly
selected --context (or ODLA_CONTEXT) comes next, followed by a present
odla.config.mjs; the platform defaults to https://odla.ai, and environment
defaults remain command-specific. There is deliberately no ambient “current”
context: a saved profile cannot silently retarget commands. A missing named
context or explicitly named missing --config is always an error.
context list inventories profiles, and context remove <name> --yes removes
metadata without deleting caches or revoking grants. Revoke a grant in Studio
when its authority must end. Set ODLA_CONTEXT_FILE to relocate the default
private ~/.odla/contexts.json file.
For unattended reads, supply the ordinary revocable owner grant through
ODLA_DEV_TOKEN. Without a project config or explicit token, the normal device
flow uses the private ~/.odla/dev-token.json cache. Every named context instead
uses isolated developer and scoped-token caches beneath
~/.odla/profiles/<name>/; set ODLA_DEV_TOKEN_FILE or
ODLA_ADMIN_TOKEN_FILE to override them. Profiles store scope metadata only
and reject unknown fields, so credentials cannot be added to
contexts.json. A checked-in project continues to use its configured cache
when no profile is selected. Each cache records its platform audience, and a
token for another origin is never reused. context show reports provenance and
cache state only; it never returns a credential or starts a device handshake.
When a cached credential expires, the approving human can reuse the agent's
exact handle suffix to bind the replacement credential to the same durable
principal. The approval appends new revisions for the exact projects selected;
it does not automatically revoke the older credential or renew an omitted
project. The installed references/agent-identity.md runbook gives the full
cross-worktree recovery procedure.
ODLA_DEV_TOKEN=... npx --yes @odla-ai/cli@latest pm handoff --app <appId>
ODLA_DEV_TOKEN=... npx --yes @odla-ai/cli@latest discuss list --app <appId> --json
ODLA_DEV_TOKEN=... npx --yes @odla-ai/cli@latest o11y status --app <appId> --env prod --json
ODLA_DEV_TOKEN=... npx --yes @odla-ai/cli@latest whoami --json
npx --yes @odla-ai/cli@latest platform status --context production --json
npx --yes @odla-ai/cli@latest pm handoff --context production --json
npx --yes @odla-ai/cli@latest runbook new deploy --context production --title Deploy --file deploy.mdRunbooks tell an agent how to operate; PM tells every agent what this project is trying to prove, what is active, what was decided, and what is broken. Once the app is registered, start each session with the read-only aligned-work intake, then read open bugs and recent decisions:
npx --yes @odla-ai/cli@latest pm project list --app <productId>
npx --yes @odla-ai/cli@latest pm project add --app <productId> --name "My project"
npx --yes @odla-ai/cli@latest pm project use <projectId>
npx --yes @odla-ai/cli@latest pm brief --app <appId> --project <projectId>
npx --yes @odla-ai/cli@latest pm next --app <appId> --project <projectId>
npx --yes @odla-ai/cli@latest pm handoff --app <appId>
npx --yes @odla-ai/cli@latest pm watch --app <appId> --entity task --jsonl
npx --yes @odla-ai/cli@latest pm bug list --app <appId> --status open
npx --yes @odla-ai/cli@latest pm bug list --platform-wide --status open
npx --yes @odla-ai/cli@latest pm bug get <uuid-or-unique-prefix> --platform-wide
npx --yes @odla-ai/cli@latest pm goal list --app <appId> --status open
npx --yes @odla-ai/cli@latest pm decision list --app <appId> --limit 20pm brief is the bounded agent intake: exact totals, current-principal work,
the top Ready candidates, only their context goals, and explicit expansion
commands. pm next --brief and pm handoff --brief are aliases for that
projection. pm handoff without --brief remains the authoritative full
unresolved-state view: unmet goals, every
non-done task, and every open or triaged bug. File bugs with actionable context
(pm bug add ... --description "reproduction/evidence"); a title-only bug is
rejected. When an accepted decision resolves or explicitly retains a bug, link
it with pm bug done <id> --decision <decision-id> or
pm bug set <id> --status triaged --decision <decision-id>.
Read the active task, linked goal, and comments before changing code. Without
--app, list commands span every project the account co-owns, which is the
right first view when coordinating several efforts. A platform administrator
can approve the dedicated platform:pm:read capability used by
pm <goal|task|decision|bug> list|get --platform-wide; it reads core records
across current app lifetimes but cannot read comments/history or perform any PM
mutation. get accepts a unique UUID prefix and refuses missing or ambiguous
matches. Scope every write to the
exact appId from that project's config. PM requires a registered app. Before
the first provision, use a focused commit/checkpoint, then initialize PM and
backfill that evidence immediately after registration. Never use a parallel
status diary or put a secret in PM.
Before project-mutating work, match the request to an open goal. If it is not
aligned, discuss that with the user before creating an outcome or implementing.
Refine a Backlog task with --goal (or an accepted
--alignment-decision), --description, and --acceptance, then mark it Ready
and start it:
npx --yes @odla-ai/cli@latest pm task ready <id> --expected-revision <n>
npx --yes @odla-ai/cli@latest pm start --app <appId>Ready is stored as todo for API compatibility. pm start claims the
top-ranked Ready task (or the one you name) in a single call: it reads the
current revision, compare-and-swaps against it server-side, and reloads once if
the task moved, returning the task, its goal, and the acceptance criteria. It
cannot mark work Ready, so an agent cannot authorize its own work. Claims are
atomic and assign the authenticated principal; stale or concurrent claims fail.
pm task claim <id> --expected-revision <n> remains for a caller that has
already read the revision. Use pm task
release for an explicit recovery handoff instead of leaving Doing as a vague
status. The complete runbook is installed as
references/pm-work-intake.md by odla-ai skill install.
pm watch is the durable wake-up path for goal/task/decision/bug changes and
comments attached to those work items. Its first request creates an opaque
checkpoint; persist later checkpoint JSONL records and deduplicate
at-least-once events by eventId. Use --action comment.created for comment
traffic. A Ready event or comment is not a claim: reload the task and goal, then
claim the current revision. General Discussion topics and mentions remain on
discuss watch until PM topics gain context-aware capability enforcement.
Use pm task ref <id> (or the equivalent goal/decision/bug command) to print
copy-ready structured markup for Discussion. The same markup may be pasted into
pm <entity> comment <id> --body "…"; PM stores it as a structured reference,
and pm <entity> comments <id> prints copy-ready markup instead of flattening
the link back to a title. For ordinary chat and handoffs, pm task link <id>
prints a normal Markdown link to the record's durable Studio route. Successful
create and lifecycle commands print that title-first link directly. Lead with
the PM type and linked title, explain the state or next action, and keep the id
hidden in the URL; a bare id list is not a useful handoff.
When diagnosing a deployed app, agents can request one parseable observability snapshot instead of scraping Studio or composing collector routes themselves:
npx --yes @odla-ai/cli@latest o11y status --app <appId> --env prod --minutes 60 --jsonSchema v9 keeps application RED, an applicationVersions breakdown of
request/error/latency evidence by exact Cloudflare Worker version, reconciled
live-sync load/freshness and
subscription recompute/fanout/payload/commit-to-send performance, the exact
latest protected commit-to-visible canary, collector ingest/scheduler trust,
the live Cloudflare Worker read with per-isolate memory headroom, same-window
request-total reconciliation, and
30-day-retained provider snapshot history with a seven-day maximum per read
separate. For odla-db, providerCapacity adds redacted account-scoped
Durable Object, D1, and R2 evidence: storage, query/operation/row volume,
worst database-hour latency, socket/message load, objects/uploads, source
ages, and per-dataset truncation. Other app scopes return an explicit
not-applicable envelope without reading account data. The reconciliation ends five minutes behind real time and grades
monitoring completeness without treating a coverage gap as application
failure. Scheduled collection failures, source age, expected cadence,
collection lag, and history truncation remain machine-readable. It derives one
healthy, degraded, or unhealthy verdict with stable reason codes while
retaining every source response for diagnosis. Provider unavailability,
adaptive sampling, publication delay, collector loss, quota rejection, stale
jobs, missing canary configuration, stale probes, and partial
socket-reconciliation coverage remain explicit fields. Authentication progress
is stderr-only, so stdout is a single JSON document.
Repository-controlled SLO monitoring lives under o11y.monitoring. Objectives
can consume Kitesurf routes or bounded observations of existing o11y error,
latency, and protected-canary signals. A minimal route policy detects both
short outages and sustained error-budget burn, and sends incident and digest
email:
export default {
services: ["o11y"],
links: { prod: "https://example.com" },
o11y: {
monitoring: {
probes: [{
id: "home", route: "/", envs: ["prod"], every: "5m",
expect: { status: 200, titleIncludes: "Example", textIncludes: ["Welcome"] },
}],
slos: [{
id: "availability", name: "Public availability",
indicator: { type: "probe-success", probes: ["home"] },
target: 0.999, window: "28d",
alerts: {
spike: { badChecks: 2, withinChecks: 3, recoverAfter: 2 },
trend: { burnRate: 1, shortWindow: "6h", longWindow: "3d", minBadChecks: 2 },
},
}],
notifications: {
prod: {
email: ["[email protected]"], timezone: "America/Costa_Rica",
daily: "08:00", weekly: { day: "monday", at: "08:00" },
},
},
},
},
};For a service-performance objective, omit probes and select an existing o11y
metric. The comparator describes a successful observation, so this example
requires each five-minute request p95 to be at most 500ms:
slos: [{
id: "request-latency", name: "Request p95 at or below 500ms",
indicator: {
type: "o11y-metric", metric: "latency_p95", comparator: "lte",
threshold: 500, every: "5m", observationWindow: "5m",
},
target: 0.99, window: "28d",
}]error_rate, latency_p95, synthetic_success, and
synthetic_publish_to_visible are supported. The checked-in odla-db first
rollout is ops/monitoring/odla-db.config.mjs:
npx --yes @odla-ai/cli@latest monitor plan --config ops/monitoring/odla-db.config.mjs --env prod --jsonPreview and apply the normalized policy, then inspect or exercise it through stable JSON:
npx --yes @odla-ai/cli@latest monitor plan --env prod --json
npx --yes @odla-ai/cli@latest monitor apply --env prod --yes --json
npx --yes @odla-ai/cli@latest monitor status --app <appId> --env prod --json
npx --yes @odla-ai/cli@latest monitor run home --app <appId> --env prod --json
npx --yes @odla-ai/cli@latest monitor incidents --app <appId> --env prod --runs --json
npx --yes @odla-ai/cli@latest monitor report --app <appId> --env prod --period weekly --jsonOnly scheduled good and bad outcomes enter the application SLO denominator.
Kitesurf failures, missing Analytics Engine evidence, stale canaries, query
failures, and scheduler gaps are retained as monitor_error/missed and
degrade monitoring health separately, so no traffic or a broken monitor cannot
manufacture success or downtime. Probe responses retain bounded metadata only;
page content and accessibility trees are never returned by the CLI or stored.
Each applied desired-state revision starts a fresh SLO event series and closes
open incidents as config_revision_changed; results produced by different
thresholds, routes, assertions, or notification policy are never blended.
For platform-wide operations, platform status --json requests only the
short-lived, admin-approved platform:status:read capability and reads the
same odla.platform-status/v1 snapshot as Studio. It works outside a project
checkout, derives its complete six-Worker inventory from the deployment
catalog, probes private services over bindings, and joins observed release
identity with retained Cloudflare request/error/CPU/wall-time/memory evidence.
Unavailable health, credentials, versions, or provider snapshots remain
explicit rather than becoming zero; the document includes stable reason codes
and exact next actions. The latest successful main deployment persists its
normalized receipt in Registry D1 after exact-version probes pass. Fleet status
therefore compares that verified expectation with the Worker answering now and
reports matched, drifted, or unknown; an empty or unavailable receipt
ledger degrades instead of silently treating observed health as intended state.
For repository-managed settings, compare checked-in intent with the live owner-visible Registry projection before provisioning:
npx --yes @odla-ai/cli@latest config diff --json
npx --yes @odla-ai/cli@latest config plan --json > .odla/config-plan.json
npx --yes @odla-ai/cli@latest config apply --plan .odla/config-plan.json --json
npx --yes @odla-ai/cli@latest operations wait <operation-id> --jsondiff and plan are read-only and request only a short-lived,
owner-approvable app:config:read capability. It opens only exact-id
GET /registry/apps/<appId> reads for apps the approver owns; app listing,
writes, PM, Discussions, directory access, data-plane delegation, and every
other Registry route remain closed. diff emits separate SHA-256 revisions for
desired and observed state, explicit in_sync, different, or unmanaged
status, field provenance, and a bounded coverage declaration. Runtime auth or
links omitted from project config stay visible as unmanaged rather than
being silently overwritten.
plan v2 freezes those content revisions, the monotonic Registry
registry:<number> revision, and ordered actions into a stable digest.
Enable/configure actions are dependency-first; proposed disablements are
dependent-first, high-risk, approval-required, and Studio-only.
config apply accepts only that saved JSON plan. Before requesting authority it
recomputes the digest, verifies the selected app/platform, confirms checked-in
intent has not changed, and rejects every production, destructive,
approval-marked, high-risk, Studio, auth, creation, or disable action. The
short-lived app:config:write grant reaches only this app's operation
reserve/resume/read routes; it cannot enumerate apps or call direct mutation
routes. A deterministic default idempotency key makes retrying the same plan
resume the same durable journal entry, while the Registry CAS revision rejects
stale state.
Apply prints the operation receipt, including ordered step attempts and its
canonical environment-settings recovery URL. Studio preserves the exact
operation id in that URL and renders the matching
operations get <id> --json recovery command. Plans that require Studio now
link to the explicit app/environment settings scope instead of an ambiguous
app-level Settings page. operations get reads one exact id without replaying it;
operations wait polls that id for 60 seconds by default (--timeout and
--interval are explicit seconds). A still-running bounded wait exits 75 after
printing its last receipt. Every terminal receipt is independently
digest-verified by the CLI before success. Schema, rules, integration seeds,
credentials, vault or Worker secrets, OAuth connection state, and opaque
controller-assigned service fields remain outside this boundary.
Runbooks are procedure; JSDoc is API. What a function takes, returns, and
guarantees is documented on the export itself, ships in the installed
.d.ts, and renders per package at https://odla.ai/docs. A runbook will tell
you when to call something and never invents a signature. Most real questions
need both halves — take the steps from the runbook and the arguments from the
JSDoc, and neither from memory.
After you change something, ask which runbooks you invalidated:
npx --yes @odla-ai/cli@latest runbook impact # vs origin/main, uncommitted work included
npx --yes @odla-ai/cli@latest runbook impact --base HEAD~1It diffs the working tree, works out which surfaces moved — a package, its exported API, a JSDoc block, a console area — and names the runbooks whose steps describe them, with the command to read each one. Changing an exported API or its JSDoc is exactly when a procedure quoting that API goes stale, so run it before you call the change finished. Fixing what it finds is one command and takes effect immediately:
npx --yes @odla-ai/cli@latest runbook edit db --note "retract replaced the null write"Platform runbook edits need admin or the platform:runbook:write capability,
which the CLI requests for you; a plain handshake token is never admin by
design. odla-ai whoami reports the actual credential-bound principal by
friendly name, handle, and kind; an agent's manager; the credential kind; and
the accountable human owner separately. Manager metadata explains organization
but never grants authority.
Is the corpus itself still true?
npx --yes @odla-ai/cli@latest runbook lintBecause a runbook is edited without a release, its text can name a command that
only a newer CLI has — and no gate in any repository can see that, since the
text is a row in a database rather than a file. lint closes that: it holds
every odla-ai … command the runbooks name against this CLI's actual command
surface, and against the minimum versions each runbook declares in requires.
A runbook may record the versions its steps assume, as name@version specs read
as minimums (@odla-ai/[email protected]). When yours is older, runbook get says so
on stderr — never stdout, so piping the procedure into an agent still yields
the procedure and nothing else.
Install
No install needed — the package ships a single odla-ai binary, so npx can run
it directly. Agents and one-off setup should resolve the current published CLI
explicitly:
npx --yes @odla-ai/cli@latest init --app-id my-app --name "My App"For a project you keep working in, install it as a dev dependency so the
lockfile records the CLI/runtime graph used by that application. Use
npx --no-install odla-ai only when deliberately testing that pinned graph;
agent orchestration examples use @latest so a stale workspace or cache does
not silently select an older CLI. ODLA is under active development: use a
normal dependency declaration, commit the lockfile, and let npm ci reproduce
the resolved graph:
npm view @odla-ai/cli version
npm i -D @odla-ai/cli
npm ls @odla-ai/cliPrerequisites
- Node.js 20 or newer (
node --version). - An existing odla account that has signed in at least once. For any command
that may need a fresh device grant, pass
--email <account>or setODLA_USER_EMAIL. This value identifies who may review the request; it is not a password or session credential, and an agent must never ask for either. - odla apps usually deploy as Cloudflare Workers. If you have never used
Cloudflare:
- Create a free account at https://dash.cloudflare.com/sign-up.
- You do not need to install anything:
npx wrangler loginopens the browser once to link your account,npx wrangler devruns a Worker locally, andnpx wrangler deployships it. - The
.dev.varsfile this CLI writes withprovision --write-dev-varsis wrangler's local secrets file —wrangler devpicks it up automatically. It is chmod0600and gitignored; never commit it or paste its contents intowrangler.toml.
Commands
npx --yes @odla-ai/cli@latest auth login --app my-app --email [email protected] --no-open --wait 600
npx --yes @odla-ai/cli@latest runbook ask "how do I roll back a bad publish?"
npx --yes @odla-ai/cli@latest runbook search "restore a tenant" --limit 5
npx --yes @odla-ai/cli@latest runbook get release
npx --yes @odla-ai/cli@latest runbook list
npx --yes @odla-ai/cli@latest runbook impact --base origin/main
npx --yes @odla-ai/cli@latest runbook lint
npx --yes @odla-ai/cli@latest runbook edit db --note "what changed"
npx --yes @odla-ai/cli@latest runbook comment release --body "step 4 no longer applies"
npx --yes @odla-ai/cli@latest context show --app my-app --env prod --json
npx --yes @odla-ai/cli@latest whoami
npx --yes @odla-ai/cli@latest setup
npx --yes @odla-ai/cli@latest init --app-id my-app --name "My App"
npx --yes @odla-ai/cli@latest doctor
npx --yes @odla-ai/cli@latest ai models --env dev # exact models this app may select; no admin grant
npx --yes @odla-ai/cli@latest config diff --json
npx --yes @odla-ai/cli@latest config plan --json > .odla/config-plan.json
npx --yes @odla-ai/cli@latest config apply --plan .odla/config-plan.json --json
npx --yes @odla-ai/cli@latest operations get <operation-id> --json
npx --yes @odla-ai/cli@latest operations wait <operation-id> --timeout 60 --json
npx --yes @odla-ai/cli@latest calendar status --env dev
npx --yes @odla-ai/cli@latest calendar calendars --env dev
npx --yes @odla-ai/cli@latest calendar connect --env dev --no-open
npx --yes @odla-ai/cli@latest calendar disconnect --env dev --yes
npx --yes @odla-ai/cli@latest capabilities --json
npx --yes @odla-ai/cli@latest code connect --platform https://odla.ai --app-id my-app --env dev --email [email protected] --no-open
npx --yes @odla-ai/cli@latest code connect --env prod --once --no-open # bounded enrollment + heartbeat proof
# install SDKs, write the Worker, and create wrangler.jsonc before secret push
npx --yes @odla-ai/cli@latest provision --dry-run
npx --yes @odla-ai/cli@latest provision --email [email protected] --write-dev-vars --push-secrets --no-open --wait 600
# recovery when the selected agent credential has no live app.manage grant
npx --yes @odla-ai/cli@latest provision --request-grant --email [email protected] --write-dev-vars --push-secrets --no-open --wait 600
npx --yes @odla-ai/cli@latest smoke --env dev
npx --yes @odla-ai/cli@latest agent jobs --env dev --state dead_letter --json
npx --yes @odla-ai/cli@latest agent retry <job-id> --env dev --json
npx --yes @odla-ai/cli@latest discuss watch <topic-id> --jsonl
npx --yes @odla-ai/cli@latest discuss watch <topic-id> --cursor <saved-cursor> --jsonl
npx --yes @odla-ai/cli@latest secrets push --env dev
npx --yes @odla-ai/cli@latest secrets set clerk_webhook_secret --env dev --stdin
npx --yes @odla-ai/cli@latest secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY
npx --yes @odla-ai/cli@latest security run . --env dev --ack-redacted-source
npx --yes @odla-ai/cli@latest security github connect --env dev --no-open # infers owner/name from git origin
npx --yes @odla-ai/cli@latest security plan --env dev
npx --yes @odla-ai/cli@latest security sources --env dev
npx --yes @odla-ai/cli@latest security run --source <source-id> --ref main --env dev --plan-digest <digest-from-security-plan> --ack-redacted-source
npx --yes @odla-ai/cli@latest security status <job-id>
npx --yes @odla-ai/cli@latest security report <job-id>
npx --yes @odla-ai/cli@latest admin ai show --context production --email [email protected] --no-open
npx --yes @odla-ai/cli@latest admin ai models --context production
npx --yes @odla-ai/cli@latest admin ai set security --context production --discovery-provider anthropic --discovery-model claude-opus-4-8 --validation-provider openai --validation-model gpt-5.5 --enabled
npx --yes @odla-ai/cli@latest admin ai credentials --context production
npx --yes @odla-ai/cli@latest admin ai credential set anthropic --context production --from-env ANTHROPIC_API_KEY
npx --yes @odla-ai/cli@latest admin ai usage --context production --app-id my-app --env prod --limit 50
npx --yes @odla-ai/cli@latest admin ai audit --context production --limit 25
npx --yes @odla-ai/cli@latest skill install
npx --yes @odla-ai/cli@latest setup --hooks install # opt-in bounded SessionStart context
npx --yes @odla-ai/cli@latest setup --hooks status
npx --yes @odla-ai/cli@latest setup --hooks remove
npx --yes @odla-ai/cli@latest versionBefore a non-dry provisioning run, the executable verifies two independent
things: its CLI version matches the current release, and every external
@odla-ai/* runtime module resolves to the exact version pinned in that CLI's
manifest. The command graph is imported only after both checks pass. A stale
workspace-linked module is reported with its resolved package path and the
agent is told to update/rebase the worktree, run npm ci, and rebuild. Released
CLI dependencies are exact pins, so the printed npm exec --package=...
recovery command installs the same dependency graph exercised by the release
tarball test rather than reusing an arbitrary compatible older module.
agent jobs is the remote-operator view of commit-trigger delivery. It reads
content-free lifecycle metadata—job id, trigger, entity reference, state,
attempts, timing, and bounded error/status codes—using an audience-bound
developer token for an owner of the selected environment. agent retry accepts one exact
dead-lettered id and resets its attempt lease; it does not replay successful or
currently running work. Successful receipts are retained for seven days (and
bounded to the newest 1,000 per tenant); unresolved dead letters remain until
an operator requeues them.
discuss watch waits at a server-issued snapshot boundary instead of comparing
client clocks or repeatedly reading a topic's oldest 200 posts. It is unbounded
unless --timeout <seconds> is supplied. --jsonl emits versioned
checkpoint, event, heartbeat, and degraded status records; persist the
latest cursor only after consuming its record and pass it back with --cursor
after a restart. Event IDs are stable for at-least-once deduplication. Transient
429/5xx/network failures retry with bounded exponential backoff without
advancing the cursor. A restore or truncated history exits 3 and emits an
explicit replacement checkpoint; an explicit timeout exits 75, and an
exhausted remote retry exits 6.
discuss read <topic> --json follows the server's bounded forward post pages
before emitting one complete document. A long-lived agent can therefore watch
an event and then read the full topic rather than receiving an apparently
successful response capped to its oldest 200 posts. Use --limit <n>
--offset <n> to request one bounded page instead; complete reads fail closed
after 10,000 posts or three continuously changing scans rather than consuming
memory forever or claiming an inconsistent result.
code connect is the first-party personal Code terminal runtime. Run the exact
command copied from Studio → Code → Terminal while your shell is in the
local Git checkout. --platform, --app-id, and --env make the requested
scope unambiguous; without an explicit app, the CLI can infer it from
odla.config.mjs or the Git remote. The terminal proves its isolation engine,
builds the CLI-bundled Pi adapter into a content-addressed local image, prepares
the digest-pinned build images, and sends an outbound
app:code:host:connect request to odla. The app owner approves that real
request in Studio. The browser never mints or displays a host bearer.
After approval, the foreground process secret-filters tracked and non-ignored
source into a bounded staged snapshot. Harness operates on that frozen copy;
the original checkout is never mounted into Pi. Pi receives no network,
provider credential, or container-engine socket and accesses source only
through the typed CaMeL broker. The session records the trusted Git base,
developer-patch digest, snapshot digest, and the combined checkpoint patch.
Later local edits do not silently alter an active session. The Pi adapter does
not come from a private registry and code connect never logs the developer
into one; the only first-run image download is its public, digest-pinned Node
base. Use --engine docker only after reviewing that daemon boundary. --once
proves enrollment and one heartbeat without accepting queued work.
The approval and host credential records live in odla-ai/db and store hashes,
not plaintext. The collected host bearer exists only in the running CLI
process: no Code credential file is created under .odla/. A subsequent
approved code connect rotates the database-backed credential and invalidates
the previous bearer.
init writes:
odla.config.mjssrc/odla/schema.mjssrc/odla/rules.mjs.gitignoreentries for local credentials
New configs contain only the dev environment. After the development flow is
healthy, initialize the existing app's live instance without editing the config:
npx --yes @odla-ai/cli@latest provision --live --dry-run
npx --yes @odla-ai/cli@latest provision --live --yes --push-secrets--live targets only prod, enables every configured service, provisions its
credentials/schema/integrations, and refuses to create an app when the sandbox
does not exist yet. Add prod to envs only when normal provision runs should
manage both environments. Calendar apps may predeclare
calendar.google.availabilityCalendars.prod while envs remains dev-only;
live still uses its own Google consent. Every production mutation requires
--yes. Afterwards, explicit live operations such as smoke --env prod,
secrets push --env prod --yes, and app go-live work while unqualified
commands continue to default to the sandbox.
provision performs the standard safe setup flow:
When using --push-secrets, create the Worker and its Wrangler config first.
The CLI checks that config and wrangler whoami before it issues a shown-once
credential set.
Gets an
odla_dev_...token by email-bound device handshake, or reusesODLA_DEV_TOKEN/.odla/dev-token.json. A fresh handshake requires--email <account>orODLA_USER_EMAIL; the matching existing account must sign in, review the exact code, and approve it. Opening the URL alone does not claim the request. Studio shows the immutable project-derived agent handle, project, and capability request without mutation controls. The owner approves that exact request or declines it with an explanation which the CLI returns to the calling agent; a different identity or access set requires a new request.provisionincludesapp.managefor the configured exact app. That permits the non-lifecycle app configuration and sibling-service tenant administration needed by the run; it does not permit ownership, rename/category, archive, restore, or purge. A cached or pending baseline handshake withoutapp.manageis not reused for provision: the CLI starts a fresh request for owner review. An explicit--tokenorODLA_DEV_TOKENhas no trustworthy local grant metadata, so a server refusal cannot be repaired by retrying it. Runprovision --request-grant --email <account>to ignore the ambient and cached credentials, open a new exact-project review, collect the approved replacement, and continue the same provisioning run. If the exact project id does not exist yet, approval reserves that id for this credential instead of failing: the credential may create only that app once, and Registry binds its reviewed grant to the new app incarnation. Every real CLI handshake prints exactly one canonical/studio?code=…URL. Interactive humans may use the CLI's best-effort browser launch. Agents use the current published CLI with--no-open --wait 600, immediately surface that exact URL and code as a clickable human approval action, and preserve the same foreground process:npx --yes @odla-ai/cli@latest auth login --app <appId> --email <odla-account> --no-open --wait 600The CLI owns protocol polling. An agent must not call OS
open, use browser control, curl a handshake endpoint, build a shell wait loop, detach the process, or start a substitute handshake. The device code stays only in the running process. If that process exits 75, the old code cannot be collected; a later invocation requests a new code and never resumes an older request from.odla/.Creates the platform app if needed. For a new id, this consumes the exact-id reservation approved in step 1; it is not ambient project-creation authority.
Enables configured services in every configured environment. Calendar config is normalized per env and fixed to Google read-only access.
For local development, mints or reuses that developer's configured service credentials. Multiple developers may hold independent DB/o11y credentials for the same shared sandbox tenant.
Collision-checks and merges declared integration schema/rule fragments, pushes the composed database contract, and creates integration seeds only when their natural-key rows are absent.
Configures hosted app AI by default. An explicit BYOK config stores its provider key in the tenant vault when the configured key env var is set.
For calendar, reads owner-visible connection status and, when needed, asks the platform for a state-bound Google authorization URL and opens only the exact Google OAuth endpoint (or a same-platform interstitial). The human completes consent there; provision follows the exact attempt through initial sync. OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
Local-only provisioning writes
.odla/credentials.local.jsonwith mode0600. A deployment run with--push-secretsdoes not write runtime credential plaintext there.With
--write-dev-vars, writes the local Worker values to.dev.vars; with--push-secrets, binds a fresh additive credential set to the exact Cloudflare account/script/environment, transfers both values in onewrangler secret bulkstdin payload, and commits a receipt. A failed push aborts the session and revokes partial issuance.
Plain reruns are idempotent at the tenant/configuration layer. Each deployment
receives new, independently revocable runtime credentials; it never rotates a
sibling developer or Worker. --rotate-o11y-token remains a destructive legacy
recovery operation for locally cached credentials, and --rotate-keys remains
the broader local rotation. Neither rotation flag may be combined with
--push-secrets. Production mutation or secret transfer requires
--yes. --no-write-credentials is valid with --push-secrets because the
deployment path is memory-only by design.
Registry staging and Wrangler installation use a short-lived receipt protocol. The CLI preflights the exact target, issues additive credentials, sends one bulk payload, and commits only after Wrangler succeeds. Failure revokes staged credentials; retrying creates a new bounded session and never invalidates a working sibling runtime.
Inventory and retire one successful deployment without disturbing any sibling:
npx --yes @odla-ai/cli@latest credentials list --env dev
npx --yes @odla-ai/cli@latest credentials revoke <receipt-id>The inventory contains only receipt, target, state, and timestamp metadata. Revocation uses the receipt's exact DB and o11y credential ids; no value is retrieved or rotated.
Verify a deployment that intentionally keeps db/o11y credentials only on the Worker:
npx --yes @odla-ai/cli@latest smoke --env dev --runtimeRuntime smoke never reads .odla/credentials.local.json. It checks Registry
public config, the configured Worker link, anonymous integration probes, and
owner-visible calendar health. Direct schema and aggregate reads remain part
of ordinary local-credential smoke because a runtime credential is deliberately
not retrievable from the Worker.
Application capability integrations
An integration is an npm capability composed into an app, not a hosted
platform service. Put data-only descriptors under integrations in
odla.config.mjs; do not add their ids to services.
import { createCrmIntegration } from "@odla-ai/crm";
import { crm } from "./src/crm.js";
export default {
app: { id: "my-app", name: "My App" },
services: ["db"],
integrations: [createCrmIntegration(crm, { basePath: "/api/crm" })],
links: { dev: "https://dev.example.com" },
};Descriptors are consumed structurally, so capability packages do not depend
on the CLI. provision --dry-run names every integration and its namespace,
seed, and probe counts. Provision accepts an app-authored namespace/rule that
is byte-for-byte structurally identical to an integration fragment, which
makes existing manually merged apps migratable; conflicting definitions fail
before credentials or network mutations. A guarded seed requires a
declared-unique natural key, is queried first, and is created only when absent.
A concurrent create fails uniqueness instead of overwriting runtime owner edits.
doctor remains offline: it checks the composed schema, complete rule coverage,
and seed targets. smoke remains read-only: after the normal db checks it runs
each descriptor's anonymous route probe against links.<env>. Installing the
package, mounting its routes in the Worker, and supplying app authorization are
source-code responsibilities for the agent/developer.
Device-grant security and recovery
The account email sent during a fresh handshake is a non-secret identity hint, not proof of identity. Unknown or never-signed-in accounts never produce a claimable Studio request, and the public start/poll shape intentionally does not reveal whether an account exists. The signed-in matching user must explicitly review the exact code before it becomes pending, and only one claimed pending or approved-but-uncollected request can be active for that user. For an ordinary request, Studio also requires at least one exact project selection. The credential receives project metadata, PM, discussion, and brand-candidate editing grants only for those projects, never ambient owner access, app administration, or brand approval. A selected id that does not yet exist is a one-time bootstrap reservation: the credential may create that exact app, after which the grant is bound to its new lifetime. A 404 from the approve action is a routing regression; missing, archived, foreign, changed, or raced requests must produce a specific conflict or denial. The reservation authorizes only that bootstrap creation; later service, configuration, and administrative mutations still require their normal capabilities and human checkpoints.
Developer grants are tracked by id, owner, label, scopes, creation/expiry,
last use, and revocation state. The plaintext token is delivered once to the
polling client and is never available from the inventory. Every signed-in user
can inspect and revoke their own grants under Your agent credentials in
Studio; platform admins additionally see the global token inventory and
metadata-only attempt evidence. Future requests with a revoked token fail.
Deleting
.odla/dev-token.json or .odla/admin-token.local.json removes the local copy
but does not revoke a token already copied elsewhere, so use Studio revocation
when compromise or accidental approval is possible.
smoke verifies a provisioned environment from local credentials. It fetches
the platform public config, checks configured service credentials and AI
provider, exercises schema and aggregate checks only when db is enabled, checks
owner-visible booking health when calendar is enabled, and executes declared
integration route probes without credentials. An o11y-only project therefore
does not need or mint a db key. It fails early with a clear message when
provisioning has not written .odla/credentials.local.json.
Google Calendar booking
Calendar is a platform-custodied connector, not a Google credential stored by
the application. Enable both db and calendar, configure calendar ids per
environment, then run normal provision. The first run has two distinct human
checkpoints: the odla device code establishes app-owner authority, and the
subsequent server-issued Google page grants calendar.events and
calendar.freebusy consent so availability and booking writes stay
server-side.
For parity with a static Google Appointment Schedule embed/link, set the public
bookingPageUrl per environment. Provision applies it through the owner-only
calendar settings route; it is public configuration, not a credential.
calendar status returns the safe connection/bookability projection; add --json for
agent automation. Once connected, calendar calendars lists ids visible to the
Google identity so the checked-in selection can be refined. calendar connect
applies the checked-in booking-page setting and starts consent when the
connection is absent, failed, disconnected, or degraded (so an agent can
repair revoked credentials); a healthy connection is reused. A connection that
already holds provider credentials is reused without requesting Google consent
again, and an in-flight authorization is awaited rather than duplicated.
Nothing syncs: Google Calendar is the source of truth and odla stores no events
and no attendees, so there is no resync to ask for. calendar disconnect --yes
deletes this app/environment connection's encrypted platform token. It does not
revoke the shared user-to-Google-OAuth-project grant, because that could
invalidate other odla connections for the same user. A future explicitly global
revoke command would need cross-connection accounting and warning. A direct
production connect requires --yes. None of these commands accepts a Google
authorization code, client secret, access token, or refresh token.
The SDK uses the existing server-side db values (ODLA_ENDPOINT,
ODLA_TENANT, ODLA_API_KEY), so calendar adds no Worker secret. Never expose
ODLA_API_KEY to a browser. Trusted Worker code initializes
@odla-ai/calendar for live FreeBusy/upcoming reads and idempotent
create/reschedule/cancel operations; browser code imports only pure helpers and
public configuration from @odla-ai/calendar/client.
These CLI commands manage the provider connection and its non-secret calendar
selection. They intentionally do not create or mutate events: application
backends do that through @odla-ai/calendar. Google is the source of truth and
odla stores no event or attendee mirror.
The bundled build/migration skills add a passive pre-ship gate with
@odla-ai/security: install it with a normal development range, commit the
lockfile, then run npm i -D @odla-ai/security, followed by
npx odla-security scan . --profile odla --out .odla/security/pre-ship --fail-on high --fail-on-candidates critical.
The passive scan remains a separate binary: it makes no model calls and does
not execute target code. After explicit redacted-source approval,
odla-ai security run . --env dev --ack-redacted-source adds hosted discovery
and independent validation. The CLI obtains/reuses app-owner auth and the
platform supplies bounded role grants; it never requests provider keys.
The hosted run accounts for both immutable role ceilings: discovery reserves
one call for recon, caps first-pass hunts to what remains, and uses only
residual calls for retries; validation reviews the highest-risk candidates
first. The console and artifacts show used/skipped calls, and a budget-limited
run is incomplete unless explicitly reviewed with --allow-incomplete.
All hosted-security network calls target the configured odla platform origin
(/registry/security/runs, /registry/ai/extract, and completion). The CLI
never contacts a model-provider host; the odla.ai broker alone owns the typed
security prompts/schemas and resolves the admin-selected route and vaulted
credential. A cached or environment token is audience-bound and is never sent
to a different --platform origin.
Early-access run ceilings are rolling. A platform-ceiling 429 always carries
a conservative Retry-After. A provider-side 429 uses the sanitized
provider_rate_limited code after the provider SDK's bounded retries and
carries a bounded Retry-After only when the upstream supplied one. The CLI
does not echo arbitrary response text or silently change the admin-selected
model.
Confirm npm view @odla-ai/security version succeeds and record the installed
version from npm ls @odla-ai/security with the scan evidence. A registry
failure blocks the preflight; it is not a clean scan. odla's own engineering
environment uses an equivalent internal gate; customer projects should use the
published odla-security command above.
For a server-side, commit-pinned review, connect one repository through the
source-read-only odla GitHub App. The CLI infers owner/name from a safe GitHub HTTPS
or SSH origin; pass --repo owner/name when it cannot. It opens GitHub's
installation/authorization page and polls that exact odla attempt:
npx --yes @odla-ai/cli@latest security github connect --env dev --no-open
npx --yes @odla-ai/cli@latest security plan --env dev
npx --yes @odla-ai/cli@latest security sources --env dev
npx --yes @odla-ai/cli@latest security run --source <source-id> --ref main --env dev --plan-digest <digest-from-security-plan> --ack-redacted-sourcesecurity plan is the owner-readable disclosure preflight. It shows the exact
admin-selected discovery and validation provider/model, immutable policy
versions, per-route call/input/output bounds, prompt bundle,
redaction/report contracts, credential readiness, provider independence,
report retention, the no-target-execution boundary, and a digest covering that
complete processing contract.
security sources includes the same plan (and returns { plan, sources } with
--json). A source run fetches it again and refuses to enqueue while either
route is disabled, lacks a usable platform credential, or is not independently
routed. Copy the displayed digest into --plan-digest; the CLI requires it in
addition to --ack-redacted-source and refuses to let a generic acknowledgement
silently approve a refetched plan. The enqueue request carries the plan digest
and both expected policy versions. After verifying the user-supplied plan
digest, the CLI fetches and prints odla.ai's exact source/ref/profile execution
intent. A second server-computed digest binds that selection, the redacted
disclosure, and the plan digest; the CLI sends it as
expectedExecutionDigest. A concurrent selection or admin change returns
intent_conflict, plan_conflict, policy_conflict, or security_ai_not_ready instead of
silently switching providers or models. Fetch and review a fresh security
plan, then explicitly acknowledge its new digest.
An omitted --ref is previewed as the source's explicit current default-branch
name. If that default changes before enqueue, odla.ai rejects the stale intent;
it never defers the choice to GitHub HEAD at Workflow execution time.
The source job defaults to follow, renders the normalized report, and applies
the same --fail-on high, --fail-on-candidates critical, and incomplete
coverage gate; use --no-follow only to enqueue. security status <job-id> and
security report <job-id> recover or inspect a prior job. security github
disconnect --source <source-id> --env dev --yes revokes odla's saved source
without requiring a GitHub-side uninstall.
The client sends only the opaque odla sourceId, ref, profile, and explicit
sourceDisclosure: "redacted"; it has no PAT, GitHub installation id, provider
key, provider, or model flag. odla.ai verifies repository access, resolves the
ref to an immutable commit SHA, fetches the bounded snapshot server-side, and
routes bounded best-effort credential-pattern-redacted snippets through the admin-selected System AI discovery
and independent-validation models. GitHub read authorization is not source
disclosure consent, which is why --ack-redacted-source remains required.
The retained schema has no archive, file-body, explicit source-excerpt, context
packet, raw provider-response, execution-trace, or reproduction-output field.
The private normalized report does retain bounded best-effort credential-pattern-redacted
model-derived prose and repository-relative paths for up to 90 days, so treat
it as sensitive because that prose can reveal source semantics or an
unrecognized secret. GitHub and
provider retention/residency terms remain separate. GitHub
Checks contain counts/coverage and link to the authenticated Studio report.
Target code is not executed, and even complete coverage is not proof of
security; unscheduled, shallow, blocked, budget-exhausted, and projection-
truncated work stays visible.
admin ai show|models|set|credentials|credential set|usage|audit manages platform-funded System
AI (o11y.triage, security.discovery, security.validation). It uses a
separate, mode-0600 scoped-grant cache: beneath the selected named context,
the project root when a config is loaded, or the global operator directory
otherwise. It never follows an arbitrary shell working directory. Studio displays the
exact scope, only an admin can approve it, and it expires after ~15 minutes;
policy, credential, and usage capabilities are separate and cannot act as the
approver on ordinary app/db/o11y routes. admin ai models reads the server
catalog. For app code and provisioning, ai models [--env dev] [--json] reads
the app-safe public configuration and lists only the hosted models that exact
app may select (or the configured provider's local catalog in BYOK mode), with
the default and generic capabilities. admin ai set security updates discovery and independent validation
together with expected revisions; a concurrent edit returns 409 and reloads
instead of being overwritten. admin ai usage requests its own read-only
capability and lists metadata-only events and status aggregates; narrow it with
--app-id, --env, --run-id, and --limit, or use --json for automation.
admin ai audit separately reads immutable metadata-only policy and credential
change events with actor attribution; credential values never enter that trail.
It never returns prompts, repository source, model output, reports, or provider
credentials. Credential writes accept only
--from-env <NAME> or --stdin and never place the value in argv, output, or
the cache. System AI and hosted app AI have separate routes and accounting;
explicit BYOK has its own app-tenant vault path.
Discovery and validation provider/model settings are authoritative server
policy: a security command has no field that can override either route. Admin
bounds can only narrow the hard ceilings of 64 calls per role/run and, for each
call, 512 KiB input and 32,768 output tokens. For a non-default platform, environment credentials
must also declare the matching ODLA_ADMIN_TOKEN_AUDIENCE.
secrets push --env <env> is the narrower recovery/retry command. It moves the
configured env's odla-db key and/or o11y ingest token from
.odla/credentials.local.json into the deployed Worker by piping each value
over stdin to wrangler secret put; values never appear on argv, in output, or
in an agent's transcript. It preflights wrangler whoami and the presence of a
wrangler config file. The env prod/production targets the top-level
wrangler environment (no --env flag is passed to Wrangler) and requires
--yes; every other env maps to wrangler --env <name>. --dry-run prints a
redacted plan without spawning anything. It is only for an already-saved local
credential. For a new Worker or a lost local plaintext, use
provision --push-secrets: the handshake authorizes new target-bound
credentials and no local production cache is required.
secrets set <name> --env <env> stores one named secret in that env's tenant
vault — the same write-only slot Studio's Secrets panel fills (for example
clerk_webhook_secret for synced auth). The value may come only from
--from-env <NAME> or --stdin, so a producer command pipes straight into the
vault without the secret ever appearing on argv, in output, or in an agent's
transcript; it is encrypted at rest and readable back only by that tenant's
app API key, never by the CLI, Studio, or the developer token. $-prefixed
names are platform-reserved. secrets set-clerk-key --env <env> stores the
app's Clerk secret key in the reserved $clerk_secret slot so the platform can
resolve the app's users (invites, member lookups); reserved secrets are never
readable by app keys. It refuses an sk_test_ key for a prod-named env and
requires --yes to put an sk_live_ key into a non-prod env, since live users
would sync into a non-prod tenant. Both commands authenticate with the
developer token ($ODLA_DEV_TOKEN, the cached token, or a fresh device
approval), and prod-named envs require --yes, matching secrets push.
Who does what
If a change is deterministic from odla.config.mjs, the CLI owns it: app and
service enablement, credential issuance and local persistence, .dev.vars,
schema/rules/auth/AI/calendar configuration, owner-safe consent orchestration,
and Wrangler secret transfer. The coding
agent owns application semantics: installing @odla-ai/o11y, wrapping the
Worker with withObservability, and choosing useful spans, metrics, errors, and
LLM-usage records. The human owns the device approval, production consent, and
explicit destructive rotation. Studio is where people view telemetry and
perform manual recovery, configure System AI, and inspect app/run-attributed
platform usage; its o11y token control is not the normal setup path.
Run npx --yes @odla-ai/cli@latest capabilities for the human-readable contract or add --json
when an agent or tool needs to branch on it without scraping prose.
setup (and skill install) installs one complete offline runbook bundle at
.agents/skills/, then adds thin native adapters for Claude Code
(.claude/skills/), Codex, Cursor (.cursor/rules/), GitHub Copilot
(`.github/copilot
