@odla-ai/cli 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -30,6 +30,8 @@ type ScopedPlatformScope = "platform:ai:policy:read" | "platform:ai:policy:write
30
30
  interface ScopedPlatformTokenOptions {
31
31
  platform: string;
32
32
  scope: ScopedPlatformScope;
33
+ /** Existing odla account email used only when a fresh handshake is required. */
34
+ email?: string;
33
35
  open?: boolean;
34
36
  fetch?: typeof fetch;
35
37
  stdout?: Pick<typeof console, "log">;
@@ -59,6 +61,8 @@ interface AdminAiOptions {
59
61
  maxCallsPerRun?: number;
60
62
  platform?: string;
61
63
  token?: string;
64
+ /** Existing odla account email used only when a fresh handshake is required. */
65
+ email?: string;
62
66
  json?: boolean;
63
67
  open?: boolean;
64
68
  fetch?: typeof fetch;
@@ -91,16 +95,17 @@ declare function adminAi(options: AdminAiOptions): Promise<void>;
91
95
  /** Stable, machine-readable division of work between the CLI, coding agent,
92
96
  * human operator, and Studio. Printed by `odla-ai capabilities --json`. */
93
97
  declare const CAPABILITIES: {
94
- readonly cli: readonly ["register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
98
+ readonly cli: readonly ["start an email-bound device request without accepting a password or user session token", "register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
95
99
  readonly agent: readonly ["install and import the selected odla SDKs", "wrap the Worker with withObservability and choose useful telemetry", "make application-specific schema, rules, auth, UI, and migration decisions", "wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"];
96
- readonly human: readonly ["approve the odla device code and one-off third-party logins", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
97
- readonly studio: readonly ["view telemetry and environment state", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
100
+ readonly human: readonly ["provide the existing odla account email, then sign in and explicitly review/approve the exact device code", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
101
+ readonly studio: readonly ["view telemetry and environment state", "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
98
102
  };
99
103
  type Output = Pick<typeof console, "log">;
100
104
  /** Print the stable responsibility boundary for humans or agent tooling. */
101
105
  declare function printCapabilities(json?: boolean, out?: Output): void;
102
106
 
103
107
  declare const CALENDAR_STATES: readonly ["not_connected", "authorizing", "needs_sync", "initial_sync", "healthy", "degraded", "disconnected", "failed"];
108
+ /** Connection lifecycle reported by the registry; mirrors the platform's calendar states. */
104
109
  type CalendarConnectionState = (typeof CALENDAR_STATES)[number];
105
110
  /** Owner-visible, secret-free calendar connection and sync health. */
106
111
  interface CalendarStatus {
@@ -136,10 +141,15 @@ interface AvailableGoogleCalendar {
136
141
  accessRole?: "freeBusyReader" | "reader" | "writer" | "owner";
137
142
  }
138
143
 
144
+ /** Shared inputs for the calendar lifecycle commands (status, connect,
145
+ * calendars, resync, disconnect): the project config path and env, plus
146
+ * injectable token, output, browser, clock, and polling hooks so agents and
147
+ * tests can drive the flow deterministically. */
139
148
  interface CalendarLifecycleOptions {
140
- configPath: string;
141
- env?: string;
142
- token?: string;
149
+ /** Path to the checked-in odla project config. */ configPath: string;
150
+ /** Configured environment; defaults to dev when present. */ env?: string;
151
+ /** Existing developer-token override. */ token?: string;
152
+ /** Existing odla account email used only for a fresh handshake. */ email?: string;
143
153
  open?: boolean;
144
154
  interactive?: boolean;
145
155
  yes?: boolean;
@@ -206,6 +216,8 @@ interface GoogleCalendarConfig {
206
216
  /** Store normalized attendee addresses, or only SHA-256 match keys. */
207
217
  attendeePolicy?: "full" | "hashed";
208
218
  }
219
+ /** The `calendar` block of `odla.config.mjs` — the Google read-mirror
220
+ * settings; valid only alongside the db service. */
209
221
  interface CalendarConfig {
210
222
  google: GoogleCalendarConfig;
211
223
  }
@@ -309,6 +321,8 @@ interface ProvisionOptions {
309
321
  writeCredentials?: boolean;
310
322
  writeDevVars?: string | boolean;
311
323
  token?: string;
324
+ /** Existing odla account email used only when a fresh device handshake is required. */
325
+ email?: string;
312
326
  /** true forces browser launch, false disables it, undefined auto-opens only for interactive terminals. */
313
327
  open?: boolean;
314
328
  /** Test/embedding override for terminal interactivity. Defaults to stdin/stdout TTY detection. */
@@ -344,6 +358,8 @@ interface SmokeOptions {
344
358
  env?: string;
345
359
  /** Developer-token override used only for owner-visible calendar health. */
346
360
  token?: string;
361
+ /** Existing odla account email used only when a fresh device handshake is required. */
362
+ email?: string;
347
363
  open?: boolean;
348
364
  interactive?: boolean;
349
365
  openApprovalUrl?: (url: string) => Promise<void>;
@@ -351,6 +367,7 @@ interface SmokeOptions {
351
367
  stdout?: Pick<typeof console, "log" | "error">;
352
368
  }
353
369
 
370
+ /** The only Google OAuth scope the platform ever requests: read-only calendar events. */
354
371
  declare const GOOGLE_CALENDAR_READ_SCOPE = "https://www.googleapis.com/auth/calendar.events.readonly";
355
372
  /** Resolve the checked-in calendar block into one controller-safe env payload. */
356
373
  declare function calendarServiceConfig(cfg: LoadedProjectConfig, env: string): CalendarServiceConfig;
@@ -434,14 +451,17 @@ declare function initProject(options: InitOptions): void;
434
451
  * (mode 0600, gitignored) and, when requested, `.dev.vars`.
435
452
  *
436
453
  * The developer token comes from (in order) `options.token`, `$ODLA_DEV_TOKEN`,
437
- * the cached `.odla` token, or a fresh device handshake — which prints a user
438
- * code + approval URL and, in interactive terminals, auto-opens the browser
454
+ * the cached `.odla` token, or a fresh email-bound device handshake — which
455
+ * prints a user code for the same signed-in account to review and, in
456
+ * interactive terminals, auto-opens the browser
439
457
  * (`open: true`/`false` forces/suppresses it). Network error bodies pass through
440
458
  * `redactSecrets` before display.
441
459
  *
442
460
  * @param options.dryRun Print the resolved plan and return before any network call/write.
443
461
  * @param options.rotateKeys Rotate db keys and o11y tokens.
444
462
  * @param options.rotateO11yToken Rotate only the o11y token.
463
+ * @param options.email Existing odla account email for a fresh handshake;
464
+ * this is an identity hint, never a password or session credential.
445
465
  * @param options.pushSecrets Preflight Wrangler, then push each env's configured
446
466
  * Worker secrets immediately after credential provisioning.
447
467
  * @param options.writeDevVars `true`/a path writes `.dev.vars`; `writeCredentials: false` skips the creds file.
@@ -499,6 +519,8 @@ interface SecretsSetOptions {
499
519
  yes?: boolean;
500
520
  /** Developer token override; defaults to $ODLA_DEV_TOKEN, the cached token, or a device handshake. */
501
521
  token?: string;
522
+ /** Existing odla account email used only when a fresh handshake is required. */
523
+ email?: string;
502
524
  open?: boolean;
503
525
  interactive?: boolean;
504
526
  openApprovalUrl?: (url: string) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ type ScopedPlatformScope = "platform:ai:policy:read" | "platform:ai:policy:write
30
30
  interface ScopedPlatformTokenOptions {
31
31
  platform: string;
32
32
  scope: ScopedPlatformScope;
33
+ /** Existing odla account email used only when a fresh handshake is required. */
34
+ email?: string;
33
35
  open?: boolean;
34
36
  fetch?: typeof fetch;
35
37
  stdout?: Pick<typeof console, "log">;
@@ -59,6 +61,8 @@ interface AdminAiOptions {
59
61
  maxCallsPerRun?: number;
60
62
  platform?: string;
61
63
  token?: string;
64
+ /** Existing odla account email used only when a fresh handshake is required. */
65
+ email?: string;
62
66
  json?: boolean;
63
67
  open?: boolean;
64
68
  fetch?: typeof fetch;
@@ -91,16 +95,17 @@ declare function adminAi(options: AdminAiOptions): Promise<void>;
91
95
  /** Stable, machine-readable division of work between the CLI, coding agent,
92
96
  * human operator, and Studio. Printed by `odla-ai capabilities --json`. */
93
97
  declare const CAPABILITIES: {
94
- readonly cli: readonly ["register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
98
+ readonly cli: readonly ["start an email-bound device request without accepting a password or user session token", "register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
95
99
  readonly agent: readonly ["install and import the selected odla SDKs", "wrap the Worker with withObservability and choose useful telemetry", "make application-specific schema, rules, auth, UI, and migration decisions", "wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"];
96
- readonly human: readonly ["approve the odla device code and one-off third-party logins", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
97
- readonly studio: readonly ["view telemetry and environment state", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
100
+ readonly human: readonly ["provide the existing odla account email, then sign in and explicitly review/approve the exact device code", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
101
+ readonly studio: readonly ["view telemetry and environment state", "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
98
102
  };
99
103
  type Output = Pick<typeof console, "log">;
100
104
  /** Print the stable responsibility boundary for humans or agent tooling. */
101
105
  declare function printCapabilities(json?: boolean, out?: Output): void;
102
106
 
103
107
  declare const CALENDAR_STATES: readonly ["not_connected", "authorizing", "needs_sync", "initial_sync", "healthy", "degraded", "disconnected", "failed"];
108
+ /** Connection lifecycle reported by the registry; mirrors the platform's calendar states. */
104
109
  type CalendarConnectionState = (typeof CALENDAR_STATES)[number];
105
110
  /** Owner-visible, secret-free calendar connection and sync health. */
106
111
  interface CalendarStatus {
@@ -136,10 +141,15 @@ interface AvailableGoogleCalendar {
136
141
  accessRole?: "freeBusyReader" | "reader" | "writer" | "owner";
137
142
  }
138
143
 
144
+ /** Shared inputs for the calendar lifecycle commands (status, connect,
145
+ * calendars, resync, disconnect): the project config path and env, plus
146
+ * injectable token, output, browser, clock, and polling hooks so agents and
147
+ * tests can drive the flow deterministically. */
139
148
  interface CalendarLifecycleOptions {
140
- configPath: string;
141
- env?: string;
142
- token?: string;
149
+ /** Path to the checked-in odla project config. */ configPath: string;
150
+ /** Configured environment; defaults to dev when present. */ env?: string;
151
+ /** Existing developer-token override. */ token?: string;
152
+ /** Existing odla account email used only for a fresh handshake. */ email?: string;
143
153
  open?: boolean;
144
154
  interactive?: boolean;
145
155
  yes?: boolean;
@@ -206,6 +216,8 @@ interface GoogleCalendarConfig {
206
216
  /** Store normalized attendee addresses, or only SHA-256 match keys. */
207
217
  attendeePolicy?: "full" | "hashed";
208
218
  }
219
+ /** The `calendar` block of `odla.config.mjs` — the Google read-mirror
220
+ * settings; valid only alongside the db service. */
209
221
  interface CalendarConfig {
210
222
  google: GoogleCalendarConfig;
211
223
  }
@@ -309,6 +321,8 @@ interface ProvisionOptions {
309
321
  writeCredentials?: boolean;
310
322
  writeDevVars?: string | boolean;
311
323
  token?: string;
324
+ /** Existing odla account email used only when a fresh device handshake is required. */
325
+ email?: string;
312
326
  /** true forces browser launch, false disables it, undefined auto-opens only for interactive terminals. */
313
327
  open?: boolean;
314
328
  /** Test/embedding override for terminal interactivity. Defaults to stdin/stdout TTY detection. */
@@ -344,6 +358,8 @@ interface SmokeOptions {
344
358
  env?: string;
345
359
  /** Developer-token override used only for owner-visible calendar health. */
346
360
  token?: string;
361
+ /** Existing odla account email used only when a fresh device handshake is required. */
362
+ email?: string;
347
363
  open?: boolean;
348
364
  interactive?: boolean;
349
365
  openApprovalUrl?: (url: string) => Promise<void>;
@@ -351,6 +367,7 @@ interface SmokeOptions {
351
367
  stdout?: Pick<typeof console, "log" | "error">;
352
368
  }
353
369
 
370
+ /** The only Google OAuth scope the platform ever requests: read-only calendar events. */
354
371
  declare const GOOGLE_CALENDAR_READ_SCOPE = "https://www.googleapis.com/auth/calendar.events.readonly";
355
372
  /** Resolve the checked-in calendar block into one controller-safe env payload. */
356
373
  declare function calendarServiceConfig(cfg: LoadedProjectConfig, env: string): CalendarServiceConfig;
@@ -434,14 +451,17 @@ declare function initProject(options: InitOptions): void;
434
451
  * (mode 0600, gitignored) and, when requested, `.dev.vars`.
435
452
  *
436
453
  * The developer token comes from (in order) `options.token`, `$ODLA_DEV_TOKEN`,
437
- * the cached `.odla` token, or a fresh device handshake — which prints a user
438
- * code + approval URL and, in interactive terminals, auto-opens the browser
454
+ * the cached `.odla` token, or a fresh email-bound device handshake — which
455
+ * prints a user code for the same signed-in account to review and, in
456
+ * interactive terminals, auto-opens the browser
439
457
  * (`open: true`/`false` forces/suppresses it). Network error bodies pass through
440
458
  * `redactSecrets` before display.
441
459
  *
442
460
  * @param options.dryRun Print the resolved plan and return before any network call/write.
443
461
  * @param options.rotateKeys Rotate db keys and o11y tokens.
444
462
  * @param options.rotateO11yToken Rotate only the o11y token.
463
+ * @param options.email Existing odla account email for a fresh handshake;
464
+ * this is an identity hint, never a password or session credential.
445
465
  * @param options.pushSecrets Preflight Wrangler, then push each env's configured
446
466
  * Worker secrets immediately after credential provisioning.
447
467
  * @param options.writeDevVars `true`/a path writes `.dev.vars`; `writeCredentials: false` skips the creds file.
@@ -499,6 +519,8 @@ interface SecretsSetOptions {
499
519
  yes?: boolean;
500
520
  /** Developer token override; defaults to $ODLA_DEV_TOKEN, the cached token, or a device handshake. */
501
521
  token?: string;
522
+ /** Existing odla account email used only when a fresh handshake is required. */
523
+ email?: string;
502
524
  open?: boolean;
503
525
  interactive?: boolean;
504
526
  openApprovalUrl?: (url: string) => Promise<void>;
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  secretsSetClerkKey,
39
39
  smoke,
40
40
  startHostedSecurityJob
41
- } from "./chunk-WDKBW4BE.js";
41
+ } from "./chunk-MWVKOIGR.js";
42
42
  export {
43
43
  AGENT_HARNESSES,
44
44
  CAPABILITIES,
package/llms.txt CHANGED
@@ -31,13 +31,17 @@ For a project you keep working in, install it as a dev dependency so the
31
31
  shorter `npx odla-ai` form works and the version is pinned by your lockfile:
32
32
 
33
33
  ```bash
34
- npm view @odla-ai/cli@0.10.0 version
35
- npm i -D --save-exact @odla-ai/cli@0.10.0
34
+ npm view @odla-ai/cli@0.10.1 version
35
+ npm i -D --save-exact @odla-ai/cli@0.10.1
36
36
  ```
37
37
 
38
38
  ## Prerequisites
39
39
 
40
40
  - Node.js 20 or newer (`node --version`).
41
+ - An existing odla account that has signed in at least once. For any command
42
+ that may need a fresh device grant, pass `--email <account>` or set
43
+ `ODLA_USER_EMAIL`. This value identifies who may review the request; it is not
44
+ a password or session credential, and an agent must never ask for either.
41
45
  - odla apps usually deploy as Cloudflare Workers. If you have never used
42
46
  Cloudflare:
43
47
  1. Create a free account at <https://dash.cloudflare.com/sign-up>.
@@ -63,7 +67,7 @@ npx odla-ai calendar disconnect --env dev --yes
63
67
  npx odla-ai capabilities --json
64
68
  # install SDKs, write the Worker, and create wrangler.jsonc before secret push
65
69
  npx odla-ai provision --dry-run
66
- npx odla-ai provision --write-dev-vars --push-secrets
70
+ npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
67
71
  npx odla-ai smoke --env dev
68
72
  npx odla-ai secrets push --env dev
69
73
  npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
@@ -75,7 +79,7 @@ npx @odla-ai/cli security sources --env dev
75
79
  npx @odla-ai/cli security run --source <source-id> --ref main --env dev --plan-digest <digest-from-security-plan> --ack-redacted-source
76
80
  npx @odla-ai/cli security status <job-id>
77
81
  npx @odla-ai/cli security report <job-id>
78
- npx odla-ai admin ai show
82
+ npx odla-ai admin ai show --email owner@example.com
79
83
  npx odla-ai admin ai models
80
84
  npx odla-ai admin ai set security --discovery-provider anthropic --discovery-model claude-opus-4-8 --validation-provider openai --validation-model gpt-5.5 --enabled
81
85
  npx odla-ai admin ai credentials
@@ -103,8 +107,11 @@ When using `--push-secrets`, create the Worker and its Wrangler config first.
103
107
  The CLI checks that config and `wrangler whoami` before it issues or rotates a
104
108
  shown-once credential.
105
109
 
106
- 1. Gets an `odla_dev_...` token by device handshake, or reuses
107
- `ODLA_DEV_TOKEN` / `.odla/dev-token.json`.
110
+ 1. Gets an `odla_dev_...` token by email-bound device handshake, or reuses
111
+ `ODLA_DEV_TOKEN` / `.odla/dev-token.json`. A fresh handshake requires
112
+ `--email <account>` or `ODLA_USER_EMAIL`; the matching existing account must
113
+ sign in, review the exact code, and approve it. Opening the URL alone does
114
+ not claim the request.
108
115
  In an interactive terminal it opens the approval page in your browser when
109
116
  the code is displayed. Pass `--open` to force browser launch from a
110
117
  non-interactive shell, or `--no-open` to suppress it. Browser launch is
@@ -138,6 +145,26 @@ The CLI preflights Wrangler first and persists the replacement before pushing;
138
145
  if that final push still fails, follow the printed `secrets push --env ...`
139
146
  retry. Do not run the rotation flag again.
140
147
 
148
+ ### Device-grant security and recovery
149
+
150
+ The account email sent during a fresh handshake is a non-secret identity hint,
151
+ not proof of identity. Unknown or never-signed-in accounts never produce a
152
+ claimable Studio request, and the public start/poll shape intentionally does not
153
+ reveal whether an account exists. The signed-in matching user must explicitly
154
+ review the exact code before it becomes pending, and only one claimed pending
155
+ or approved-but-uncollected request can be active for that user.
156
+
157
+ Developer grants are tracked by id, owner, label, scopes, creation/expiry,
158
+ last use, and revocation state. The plaintext token is delivered once to the
159
+ polling client and is never available from the inventory. Every signed-in user
160
+ can inspect and revoke their own grants under **Your agent credentials** in
161
+ Studio; platform admins additionally see the global token inventory and
162
+ metadata-only attempt evidence. Future requests with a revoked token fail.
163
+ Deleting
164
+ `.odla/dev-token.json` or `.odla/admin-token.local.json` removes the local copy
165
+ but does not revoke a token already copied elsewhere, so use Studio revocation
166
+ when compromise or accidental approval is possible.
167
+
141
168
  `smoke` verifies a provisioned environment from local credentials. It fetches
142
169
  the platform public config, checks the configured AI provider, fetches the live
143
170
  odla-db schema with the tenant key, compares expected schema entities, and runs
@@ -431,7 +458,7 @@ If schema is present and rules are omitted, `defaultRules: "deny"` generates
431
458
  deny-all rules for every schema entity. That is the safe default for Workers
432
459
  that mediate reads and writes with an app key.
433
460
 
434
- ## API reference (generated from dist/index.d.ts, v0.10.0)
461
+ ## API reference (generated from dist/index.d.ts, v0.10.1)
435
462
 
436
463
  ```ts
437
464
  import { SecurityReport, PlatformSecurityRun } from '@odla-ai/security';
@@ -466,6 +493,8 @@ type ScopedPlatformScope = "platform:ai:policy:read" | "platform:ai:policy:write
466
493
  interface ScopedPlatformTokenOptions {
467
494
  platform: string;
468
495
  scope: ScopedPlatformScope;
496
+ /** Existing odla account email used only when a fresh handshake is required. */
497
+ email?: string;
469
498
  open?: boolean;
470
499
  fetch?: typeof fetch;
471
500
  stdout?: Pick<typeof console, "log">;
@@ -495,6 +524,8 @@ interface AdminAiOptions {
495
524
  maxCallsPerRun?: number;
496
525
  platform?: string;
497
526
  token?: string;
527
+ /** Existing odla account email used only when a fresh handshake is required. */
528
+ email?: string;
498
529
  json?: boolean;
499
530
  open?: boolean;
500
531
  fetch?: typeof fetch;
@@ -527,16 +558,17 @@ declare function adminAi(options: AdminAiOptions): Promise<void>;
527
558
  /** Stable, machine-readable division of work between the CLI, coding agent,
528
559
  * human operator, and Studio. Printed by `odla-ai capabilities --json`. */
529
560
  declare const CAPABILITIES: {
530
- readonly cli: readonly ["register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
561
+ readonly cli: readonly ["start an email-bound device request without accepting a password or user session token", "register the app and enable configured services per environment", "issue, persist, and explicitly rotate configured service credentials", "write local Worker values and push deployed Worker secrets through Wrangler stdin", "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var", "push db schema/rules and configure platform AI, auth, and deployment links", "apply read-only Google calendar mirror and public booking-page config, then drive state-bound consent, status, discovery, resync, and disconnect flows", "validate config offline and smoke-test a provisioned db environment", "run app-attributed hosted security discovery and independent validation without provider keys", "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys", "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"];
531
562
  readonly agent: readonly ["install and import the selected odla SDKs", "wrap the Worker with withObservability and choose useful telemetry", "make application-specific schema, rules, auth, UI, and migration decisions", "wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"];
532
- readonly human: readonly ["approve the odla device code and one-off third-party logins", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
533
- readonly studio: readonly ["view telemetry and environment state", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
563
+ readonly human: readonly ["provide the existing odla account email, then sign in and explicitly review/approve the exact device code", "review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow", "consent to production changes with --yes", "request destructive credential rotation explicitly", "review application semantics, security findings, releases, and merges", "approve redacted-source disclosure before hosted security reasoning", "approve GitHub App repository access separately from redacted-snippet disclosure"];
564
+ readonly studio: readonly ["view telemetry and environment state", "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them", "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens", "perform manual credential recovery when the CLI's local shown-once copy is unavailable", "configure system AI purposes and view app/environment/run-attributed usage", "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"];
534
565
  };
535
566
  type Output = Pick<typeof console, "log">;
536
567
  /** Print the stable responsibility boundary for humans or agent tooling. */
537
568
  declare function printCapabilities(json?: boolean, out?: Output): void;
538
569
 
539
570
  declare const CALENDAR_STATES: readonly ["not_connected", "authorizing", "needs_sync", "initial_sync", "healthy", "degraded", "disconnected", "failed"];
571
+ /** Connection lifecycle reported by the registry; mirrors the platform's calendar states. */
540
572
  type CalendarConnectionState = (typeof CALENDAR_STATES)[number];
541
573
  /** Owner-visible, secret-free calendar connection and sync health. */
542
574
  interface CalendarStatus {
@@ -572,10 +604,15 @@ interface AvailableGoogleCalendar {
572
604
  accessRole?: "freeBusyReader" | "reader" | "writer" | "owner";
573
605
  }
574
606
 
607
+ /** Shared inputs for the calendar lifecycle commands (status, connect,
608
+ * calendars, resync, disconnect): the project config path and env, plus
609
+ * injectable token, output, browser, clock, and polling hooks so agents and
610
+ * tests can drive the flow deterministically. */
575
611
  interface CalendarLifecycleOptions {
576
- configPath: string;
577
- env?: string;
578
- token?: string;
612
+ /** Path to the checked-in odla project config. */ configPath: string;
613
+ /** Configured environment; defaults to dev when present. */ env?: string;
614
+ /** Existing developer-token override. */ token?: string;
615
+ /** Existing odla account email used only for a fresh handshake. */ email?: string;
579
616
  open?: boolean;
580
617
  interactive?: boolean;
581
618
  yes?: boolean;
@@ -642,6 +679,8 @@ interface GoogleCalendarConfig {
642
679
  /** Store normalized attendee addresses, or only SHA-256 match keys. */
643
680
  attendeePolicy?: "full" | "hashed";
644
681
  }
682
+ /** The `calendar` block of `odla.config.mjs` — the Google read-mirror
683
+ * settings; valid only alongside the db service. */
645
684
  interface CalendarConfig {
646
685
  google: GoogleCalendarConfig;
647
686
  }
@@ -745,6 +784,8 @@ interface ProvisionOptions {
745
784
  writeCredentials?: boolean;
746
785
  writeDevVars?: string | boolean;
747
786
  token?: string;
787
+ /** Existing odla account email used only when a fresh device handshake is required. */
788
+ email?: string;
748
789
  /** true forces browser launch, false disables it, undefined auto-opens only for interactive terminals. */
749
790
  open?: boolean;
750
791
  /** Test/embedding override for terminal interactivity. Defaults to stdin/stdout TTY detection. */
@@ -780,6 +821,8 @@ interface SmokeOptions {
780
821
  env?: string;
781
822
  /** Developer-token override used only for owner-visible calendar health. */
782
823
  token?: string;
824
+ /** Existing odla account email used only when a fresh device handshake is required. */
825
+ email?: string;
783
826
  open?: boolean;
784
827
  interactive?: boolean;
785
828
  openApprovalUrl?: (url: string) => Promise<void>;
@@ -787,6 +830,7 @@ interface SmokeOptions {
787
830
  stdout?: Pick<typeof console, "log" | "error">;
788
831
  }
789
832
 
833
+ /** The only Google OAuth scope the platform ever requests: read-only calendar events. */
790
834
  declare const GOOGLE_CALENDAR_READ_SCOPE = "https://www.googleapis.com/auth/calendar.events.readonly";
791
835
  /** Resolve the checked-in calendar block into one controller-safe env payload. */
792
836
  declare function calendarServiceConfig(cfg: LoadedProjectConfig, env: string): CalendarServiceConfig;
@@ -870,14 +914,17 @@ declare function initProject(options: InitOptions): void;
870
914
  * (mode 0600, gitignored) and, when requested, `.dev.vars`.
871
915
  *
872
916
  * The developer token comes from (in order) `options.token`, `$ODLA_DEV_TOKEN`,
873
- * the cached `.odla` token, or a fresh device handshake — which prints a user
874
- * code + approval URL and, in interactive terminals, auto-opens the browser
917
+ * the cached `.odla` token, or a fresh email-bound device handshake — which
918
+ * prints a user code for the same signed-in account to review and, in
919
+ * interactive terminals, auto-opens the browser
875
920
  * (`open: true`/`false` forces/suppresses it). Network error bodies pass through
876
921
  * `redactSecrets` before display.
877
922
  *
878
923
  * @param options.dryRun Print the resolved plan and return before any network call/write.
879
924
  * @param options.rotateKeys Rotate db keys and o11y tokens.
880
925
  * @param options.rotateO11yToken Rotate only the o11y token.
926
+ * @param options.email Existing odla account email for a fresh handshake;
927
+ * this is an identity hint, never a password or session credential.
881
928
  * @param options.pushSecrets Preflight Wrangler, then push each env's configured
882
929
  * Worker secrets immediately after credential provisioning.
883
930
  * @param options.writeDevVars `true`/a path writes `.dev.vars`; `writeCredentials: false` skips the creds file.
@@ -935,6 +982,8 @@ interface SecretsSetOptions {
935
982
  yes?: boolean;
936
983
  /** Developer token override; defaults to $ODLA_DEV_TOKEN, the cached token, or a device handshake. */
937
984
  token?: string;
985
+ /** Existing odla account email used only when a fresh handshake is required. */
986
+ email?: string;
938
987
  open?: boolean;
939
988
  interactive?: boolean;
940
989
  openApprovalUrl?: (url: string) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Agent-operable CLI for odla provisioning, calendar consent and sync lifecycle, System AI administration, Worker secrets, security jobs, and smoke checks.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://odla.ai/docs/packages/cli",
@@ -61,7 +61,7 @@
61
61
  "dependencies": {
62
62
  "@odla-ai/ai": "^0.3.3",
63
63
  "@odla-ai/apps": "^0.8.0",
64
- "@odla-ai/db": "^0.6.2",
64
+ "@odla-ai/db": "^0.6.3",
65
65
  "@odla-ai/security": "^0.2.2"
66
66
  },
67
67
  "devDependencies": {
@@ -37,6 +37,9 @@ State which path you're taking and what you'll build in one line; get a nod.
37
37
  `.odla/credentials.local.json`, or `.odla/dev-token.json` — use `ls -l` to
38
38
  confirm they exist (and are `0600`). The only value a human ever pastes is a
39
39
  Clerk **publishable** key (`pk_…`) — public by design.
40
+ A fresh odla device request also needs the existing account email via
41
+ `--email` or `ODLA_USER_EMAIL`; email is a non-secret identifier. Never ask
42
+ for an odla password, Clerk token, or browser session.
40
43
  2. **Dev before prod.** Keep `envs: ["dev"]` and verify the tenant
41
44
  (`<appId>--dev`) before any write or deploy. Add `prod` only at the explicit
42
45
  production checkpoint. The first prod
@@ -66,8 +69,10 @@ guessing which platform/credential steps need manual work. Then:
66
69
  `withObservability`. The CLI will refuse secret delivery until the Wrangler
67
70
  target exists.
68
71
  3. **provision** ⏸ —
69
- `npx @odla-ai/cli provision --write-dev-vars --push-secrets`. A device code
70
- prints; the human approves it at https://odla.ai/studio. It creates the app,
72
+ `npx @odla-ai/cli provision --email <existing-odla-account>
73
+ --write-dev-vars --push-secrets`. A device code prints; that same account
74
+ signs in, reviews the exact code, and approves it at https://odla.ai/studio.
75
+ Opening the link alone is inert. It creates the app,
71
76
  enables services, issues or reuses configured credentials (db key + o11y
72
77
  ingest token when enabled), pushes schema + rules, writes `.dev.vars`, and
73
78
  transfers Worker secrets through Wrangler stdin. With calendar enabled it
@@ -37,11 +37,13 @@ service source or online documentation is not setup context.
37
37
  ## 3. Provision ⏸ device-code approval
38
38
 
39
39
  ```
40
- npx @odla-ai/cli provision --write-dev-vars --push-secrets
40
+ npx @odla-ai/cli provision --email <existing-odla-account> --write-dev-vars --push-secrets
41
41
  ```
42
42
 
43
- Prints a short device code and a link. The human opens https://odla.ai/studio, signs
44
- in, and approves the code no secret passes through the chat. Provision then:
43
+ The email is an account identifier, never a password or session credential.
44
+ Prints a short device code and a link. That same account opens the link, signs
45
+ in, explicitly reviews the exact code, and approves it — loading the URL alone
46
+ does not claim access, and no secret passes through the chat. Provision then:
45
47
  creates the app, enables its services, issues or reuses the db key (and the
46
48
  o11y ingest token when o11y is enabled), pushes schema + rules, writes
47
49
  `.dev.vars`, and transfers configured Worker secrets through Wrangler stdin.
@@ -38,6 +38,9 @@ installed skill at `../odla/SKILL.md` instead.
38
38
  3. Never print, paste, or commit a secret. Never `cat` .dev.vars,
39
39
  .odla/credentials.local.json, or .odla/dev-token.json. Read
40
40
  references/secrets-map.md BEFORE any command that touches a credential.
41
+ A fresh device request requires the existing odla account email via
42
+ `--email` or `ODLA_USER_EMAIL`; email is a non-secret identifier. Never ask
43
+ for an odla password, Clerk token, or browser session.
41
44
  4. Never `git add -A` without reading `git status` first.
42
45
  5. Never widen a db rule to silence a 403 — default-deny is the design.
43
46
  Any rules change is a human checkpoint.
@@ -4,14 +4,15 @@ Goal: the app registered on the platform, a dev odla-db tenant with
4
4
  schema + deny-all rules, the db key in the dev worker, and first
5
5
  `/api/*` routes live in dev.
6
6
 
7
- Human obligation: sign in at https://odla.ai/studio and approve the handshake
8
- code when the provision run prints it (it also opens the approval page
9
- in the browser in interactive terminals).
7
+ Human obligation: provide the existing odla account email (not a password or
8
+ session credential), sign in at https://odla.ai/studio, explicitly review the
9
+ exact handshake code, and approve it. The provision run prints the code and
10
+ opens the review page in interactive terminals; loading it alone is inert.
10
11
 
11
12
  ## Steps
12
13
 
13
- 1. Require `npm view @odla-ai/cli@0.10.0 version` to succeed, then run
14
- `npm i -D --save-exact @odla-ai/cli@0.10.0` and `npm i @odla-ai/db`.
14
+ 1. Require `npm view @odla-ai/cli@0.10.1 version` to succeed, then run
15
+ `npm i -D --save-exact @odla-ai/cli@0.10.1` and `npm i @odla-ai/db`.
15
16
  2. `npx @odla-ai/cli init --app-id <id> --name "<Name>" --env dev --services db`
16
17
  Review `odla.config.mjs`. Keep `envs: ["dev"]` — prod is Phase 5. Set
17
18
  `links.dev` to the URL the Phase 1 `wrangler deploy` **actually printed** —
@@ -32,7 +33,8 @@ in the browser in interactive terminals).
32
33
  directly. Loosening a rule is a human checkpoint.
33
34
  5. `npx @odla-ai/cli doctor` until clean.
34
35
  6. `npx @odla-ai/cli provision --dry-run` — show the plan to the human.
35
- 7. `npx @odla-ai/cli provision --write-dev-vars --push-secrets` — handshake,
36
+ 7. `npx @odla-ai/cli provision --email <existing-odla-account>
37
+ --write-dev-vars --push-secrets` — handshake,
36
38
  app registration, dev db key, schema + rules push; writes
37
39
  `.odla/credentials.local.json` (0600) and `.dev.vars`, then pipes configured
38
40
  Worker secrets to Wrangler over stdin. Both local files are gitignored by
@@ -8,8 +8,9 @@
8
8
  | Clerk webhook secret (`whsec_…`) | tenant vault (`clerk_webhook_secret`) | only for auth mode "full"; piped with `secrets set clerk_webhook_secret --env <env> --stdin`, or entered in Studio; never wrangler, never chat |
9
9
  | Clerk secret key (`sk_test_`/`sk_live_`) | not needed for this journey | if a later feature needs user resolution/invites, pipe it with `secrets set-clerk-key --env <env>` (reserved `$clerk_secret`, write-only); never chat |
10
10
  | LLM provider key | tenant vault | env var in the HUMAN's shell for one provision run; never wrangler vars, never git, never chat |
11
+ | `ODLA_USER_EMAIL` / `--email` | process environment or one command invocation | non-secret identifier for the existing signed-in odla account allowed to review a fresh request; never substitute a password or session token |
11
12
  | `odla_sk_…` (tenant db key) | wrangler secret `ODLA_API_KEY` + `.odla/credentials.local.json` (0600) + `.dev.vars` | present when db is enabled; move it only with the pipeline below |
12
- | `odla_dev_…` (developer token) | `.odla/dev-token.json` (0600) | ~24h lifetime, provision-time only; never deployed |
13
+ | `odla_dev_…` (developer token) | `.odla/dev-token.json` (0600) | tracked/revocable, ~24h lifetime, provision-time only; never deployed; deleting this cache does not revoke a copied token |
13
14
  | scoped `odla_dev_…` (admin-approved platform capability) | `.odla/admin-token.local.json` (0600) | separate policy/credential/usage/self-audit scope, ~15m lifetime, denied on owner routes, never deployed |
14
15
  | `o11y_…` (o11y ingest token) | wrangler secret `ODLA_O11Y_TOKEN` + `.odla/credentials.local.json` (0600) + `.dev.vars` | only if the app enables o11y; provision issues/reuses it and moves it alongside the db key; never a var, never chat |
15
16
  | `ODLA_ENDPOINT` / `ODLA_TENANT` / `ODLA_PLATFORM` / `ODLA_APP_ID` / `ODLA_ENV` | wrangler `vars` | not secrets; keep them set in every env block |
@@ -30,7 +30,9 @@ prints what it verified against.
30
30
  Cause: the `odla_dev_…` token expired (~24h) or the handshake was never
31
31
  approved.
32
32
  Fix: re-run `npx @odla-ai/cli provision` — it starts a fresh handshake; the
33
- human approves the new code at the printed URL. Use `--no-open` in
33
+ matching existing account must be supplied with `--email` or
34
+ `ODLA_USER_EMAIL`, then signs in, reviews, and approves the exact code at the
35
+ printed URL. Never ask for a password or session token. Use `--no-open` in
34
36
  non-interactive shells if the browser launch misbehaves.
35
37
 
36
38
  ## `smoke` fails: missing credentials
@@ -29,9 +29,13 @@ GET https://odla.ai/o11y/<appId>/<endpoint>?env=prod
29
29
  Authorization: Bearer <developer-token>
30
30
  ```
31
31
 
32
- Get `<developer-token>` from the device handshake (`POST https://odla.ai/handshake`
33
- → approve in the Studio → `POST /handshake/poll`). `env` is `prod` (default) or
34
- `dev`. If a read returns `{"error":"ae_not_configured"}` (501), the app's metrics
32
+ Get `<developer-token>` from `@odla-ai/cli` or the device handshake: start with
33
+ `POST https://odla.ai/handshake` and
34
+ `{"email":"<existing-odla-account>","label":"o11y debugger"}`, show the returned
35
+ verification URL, then poll `POST /handshake/poll`. Email is a non-secret
36
+ identifier; never ask for a password or session token. The matching account
37
+ must sign in and explicitly review and approve the exact code. `env` is `prod`
38
+ (default) or `dev`. If a read returns `{"error":"ae_not_configured"}` (501), the app's metrics
35
39
  reads aren't turned on yet — errors/traces still work; say so and fall back to them.
36
40
 
37
41
  ## Endpoints