@odla-ai/cli 0.8.0 → 0.10.0

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/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @odla-ai/cli — LLM context
2
2
 
3
- > Agent-operable CLI for odla provisioning, System AI administration, commit-pinned GitHub security jobs, Worker secrets, and smoke checks.
3
+ > Agent-operable CLI for odla provisioning, calendar consent and sync lifecycle, System AI administration, Worker secrets, security jobs, and smoke checks.
4
4
 
5
5
  > ⚠️ **Early access — pre-1.0.** Agents work from bounded runbooks; humans
6
6
  > approve credentials, production changes, releases, and merges. APIs and exact
@@ -10,7 +10,7 @@
10
10
  Project-neutral provisioning CLI for odla apps. It creates and validates an
11
11
  `odla.config.mjs`, then uses that config to register an app, enable services,
12
12
  push odla-db schema/rules, configure per-app BYOK AI, configure Clerk auth, record
13
- deployment links, provision o11y ingest credentials, and transfer local or
13
+ deployment links, connect read-only Google Calendar mirrors, provision o11y ingest credentials, and transfer local or
14
14
  deployed Worker secrets without printing them. It also gives platform admins a
15
15
  scoped System AI control surface and gives app owners a provider-key-free
16
16
  hosted security command.
@@ -31,8 +31,8 @@ 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.8.0 version
35
- npm i -D --save-exact @odla-ai/cli@0.8.0
34
+ npm view @odla-ai/cli@0.10.0 version
35
+ npm i -D --save-exact @odla-ai/cli@0.10.0
36
36
  ```
37
37
 
38
38
  ## Prerequisites
@@ -55,12 +55,19 @@ npm i -D --save-exact @odla-ai/cli@0.8.0
55
55
  npx odla-ai setup
56
56
  npx odla-ai init --app-id my-app --name "My App"
57
57
  npx odla-ai doctor
58
+ npx odla-ai calendar status --env dev
59
+ npx odla-ai calendar calendars --env dev
60
+ npx odla-ai calendar connect --env dev
61
+ npx odla-ai calendar resync --env dev
62
+ npx odla-ai calendar disconnect --env dev --yes
58
63
  npx odla-ai capabilities --json
59
64
  # install SDKs, write the Worker, and create wrangler.jsonc before secret push
60
65
  npx odla-ai provision --dry-run
61
66
  npx odla-ai provision --write-dev-vars --push-secrets
62
67
  npx odla-ai smoke --env dev
63
68
  npx odla-ai secrets push --env dev
69
+ npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
70
+ npx odla-ai secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY
64
71
  npx @odla-ai/cli security run . --env dev --ack-redacted-source
65
72
  npx @odla-ai/cli security github connect --env dev # infers owner/name from git origin
66
73
  npx @odla-ai/cli security plan --env dev
@@ -103,14 +110,18 @@ shown-once credential.
103
110
  non-interactive shell, or `--no-open` to suppress it. Browser launch is
104
111
  best-effort; the printed URL and code always remain the fallback.
105
112
  2. Creates the platform app if needed.
106
- 3. Enables configured services in every configured environment.
113
+ 3. Enables configured services in every configured environment. Calendar
114
+ intent is normalized per env and fixed to Google read-only access.
107
115
  4. Mints or reuses each env's configured service credentials: an odla-db app
108
116
  key for `db` and an ingest token for `o11y`.
109
117
  5. Pushes the configured schema and rules.
110
118
  6. Configures platform AI and stores provider keys in the tenant vault when the
111
119
  configured key env var is set.
112
- 7. Writes `.odla/credentials.local.json` with mode `0600`.
113
- 8. With `--write-dev-vars`, writes the local Worker values to `.dev.vars`; with
120
+ 7. With calendar enabled, starts a separate state-bound Google consent attempt
121
+ when needed and follows it through initial sync. The human completes OAuth;
122
+ codes and tokens never enter the CLI, repository, chat, or app.
123
+ 8. Writes `.odla/credentials.local.json` with mode `0600`.
124
+ 9. With `--write-dev-vars`, writes the local Worker values to `.dev.vars`; with
114
125
  `--push-secrets`, transfers the configured service secrets to the matching
115
126
  deployed Worker environment over Wrangler stdin.
116
127
 
@@ -133,6 +144,39 @@ odla-db schema with the tenant key, compares expected schema entities, and runs
133
144
  a count aggregate against the first entity. It fails early with a clear message
134
145
  when provisioning has not written `.odla/credentials.local.json`.
135
146
 
147
+ ### Read-only Google Calendar
148
+
149
+ Calendar is a platform-custodied connector, not a Google credential stored by
150
+ the application. Enable both `db` and `calendar`, configure calendar ids and an
151
+ optional public Appointment Schedule `bookingPageUrl` per environment, then run
152
+ normal provision. The first run has two human checkpoints: the odla device code
153
+ establishes app-owner authority, then Google grants read-only Calendar scopes.
154
+
155
+ `calendar status --json` returns the safe connection/sync projection.
156
+ `calendar calendars --json` lists ids visible after consent; update checked-in
157
+ config and re-provision. A resulting `needs_sync` state reuses the installed
158
+ provider credential and resyncs without another OAuth attempt; an existing
159
+ authorization or initial sync is awaited rather than duplicated. `calendar
160
+ resync` requests an immediate sync, while
161
+ `calendar disconnect --yes` stops watches and deletes this app/environment
162
+ connection's encrypted platform token and cursors without purging retained
163
+ mirror rows, which therefore become stale. It does not revoke the shared
164
+ user-to-Google-OAuth-project grant, because that could invalidate other odla
165
+ connections for the same user. A future explicitly global revoke command would
166
+ need cross-connection accounting and warning. Production resync requires the
167
+ CLI's explicit `--yes` checkpoint, and disconnect always requires it. None of
168
+ these commands accepts a Google code, client secret, access token, or refresh
169
+ token.
170
+
171
+ The application uses its existing server-side `ODLA_ENDPOINT`, `ODLA_TENANT`,
172
+ and `ODLA_API_KEY`; calendar adds no app Worker secret. Never expose the app key
173
+ to a browser. Provider write actions are outside this read-only slice.
174
+
175
+ These commands operate the current read/embed reconciliation slice. The CLI has
176
+ no native slot-picker or create/reschedule/cancel command, and exposes no
177
+ rolling-window/age-out, quota-backoff, retention/purge, or provider-derived
178
+ mutation-id control. Public health remains aggregate rather than per calendar.
179
+
136
180
  The bundled build/migration skills add a passive pre-ship gate with
137
181
  `@odla-ai/security`: after the exact-version availability check, run
138
182
  `npm i -D --save-exact @odla-ai/security@0.2.2`, then
@@ -268,6 +312,22 @@ redacted plan without spawning anything. For the normal path, use
268
312
  `provision --write-dev-vars --push-secrets` so service enablement, credential
269
313
  issuance, local config, and deployed secret transfer stay one composition.
270
314
 
315
+ `secrets set <name> --env <env>` stores one named secret in that env's tenant
316
+ vault — the same write-only slot Studio's Secrets panel fills (for example
317
+ `clerk_webhook_secret` for synced auth). The value may come only from
318
+ `--from-env <NAME>` or `--stdin`, so a producer command pipes straight into the
319
+ vault without the secret ever appearing on argv, in output, or in an agent's
320
+ transcript; it is encrypted at rest and readable back only by that tenant's
321
+ app API key, never by the CLI, Studio, or the developer token. `$`-prefixed
322
+ names are platform-reserved. `secrets set-clerk-key --env <env>` stores the
323
+ app's Clerk secret key in the reserved `$clerk_secret` slot so the platform can
324
+ resolve the app's users (invites, member lookups); reserved secrets are never
325
+ readable by app keys. It refuses an `sk_test_` key for a prod-named env and
326
+ requires `--yes` to put an `sk_live_` key into a non-prod env, since live users
327
+ would sync into a non-prod tenant. Both commands authenticate with the
328
+ developer token (`$ODLA_DEV_TOKEN`, the cached token, or a fresh device
329
+ approval), and prod-named envs require `--yes`, matching `secrets push`.
330
+
271
331
  ## Who does what
272
332
 
273
333
  If a change is deterministic from `odla.config.mjs`, the CLI owns it: app and
@@ -324,7 +384,7 @@ export default {
324
384
  dbEndpoint: process.env.ODLA_ENDPOINT ?? process.env.ODLA_DB_ENDPOINT ?? "https://db.odla.ai",
325
385
  app: { id: "my-app", name: "My App" },
326
386
  envs: ["dev"], // add "prod" deliberately; provisioning it requires --yes
327
- services: ["db", "ai", "o11y"],
387
+ services: ["db", "ai", "o11y", "calendar"],
328
388
  db: {
329
389
  schema: "./src/odla/schema.mjs",
330
390
  rules: "./src/odla/rules.mjs",
@@ -335,6 +395,20 @@ export default {
335
395
  provider: process.env.ODLA_AI_PROVIDER ?? "anthropic",
336
396
  keyEnv: "ANTHROPIC_API_KEY",
337
397
  },
398
+ calendar: {
399
+ google: {
400
+ calendars: { dev: ["primary"] },
401
+ bookingPageUrl: {
402
+ dev: "https://calendar.google.com/calendar/appointments/schedules/…",
403
+ },
404
+ match: {
405
+ summaryPrefix: "Intro", // optional
406
+ organizerSelf: true,
407
+ requireAttendees: true,
408
+ },
409
+ attendeePolicy: "full", // or "hashed"
410
+ },
411
+ },
338
412
  auth: {
339
413
  // Publishable key (public). Created + pulled by the Clerk CLI (`npx clerk`);
340
414
  // See the installed odla-migrate phase 3. Inline `pk_…` or "$ENV_VAR".
@@ -357,10 +431,12 @@ If schema is present and rules are omitted, `defaultRules: "deny"` generates
357
431
  deny-all rules for every schema entity. That is the safe default for Workers
358
432
  that mediate reads and writes with an app key.
359
433
 
360
- ## API reference (generated from dist/index.d.ts, v0.8.0)
434
+ ## API reference (generated from dist/index.d.ts, v0.10.0)
361
435
 
362
436
  ```ts
363
437
  import { SecurityReport, PlatformSecurityRun } from '@odla-ai/security';
438
+ import { HostedSecurityJob, GitHubConnectionStatus, GitHubSecuritySource, HostedSecurityIntentResponse, HostedSecurityPlan, HostedSecurityReport, HostedSecurityJobStatus } from '@odla-ai/security/hosted';
439
+ export { GitHubConnectionAttempt, GitHubConnectionStatus, GitHubSecuritySource, HostedSecurityCounts, HostedSecurityCoverage, HostedSecurityIntent, HostedSecurityIntentResponse, HostedSecurityJob, HostedSecurityJobStatus, HostedSecurityPlan, HostedSecurityPlanRoute, HostedSecurityReport, HostedSecurityReportFinding, HostedSecurityRoute } from '@odla-ai/security/hosted';
364
440
 
365
441
  /** Injectable side-effect boundaries used to run and test the command dispatcher. */
366
442
  interface CliDependencies {
@@ -451,79 +527,78 @@ declare function adminAi(options: AdminAiOptions): Promise<void>;
451
527
  /** Stable, machine-readable division of work between the CLI, coding agent,
452
528
  * human operator, and Studio. Printed by `odla-ai capabilities --json`. */
453
529
  declare const CAPABILITIES: {
454
- 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", "push db schema/rules and configure platform AI, auth, and deployment links", "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"];
455
- 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"];
456
- readonly human: readonly ["approve the odla device code and one-off third-party logins", "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"];
457
- readonly studio: readonly ["view telemetry and environment state", "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"];
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"];
531
+ 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"];
458
534
  };
459
535
  type Output = Pick<typeof console, "log">;
460
536
  /** Print the stable responsibility boundary for humans or agent tooling. */
461
537
  declare function printCapabilities(json?: boolean, out?: Output): void;
462
538
 
463
- interface DoctorOptions {
464
- configPath: string;
465
- stdout?: Pick<typeof console, "log" | "error">;
539
+ declare const CALENDAR_STATES: readonly ["not_connected", "authorizing", "needs_sync", "initial_sync", "healthy", "degraded", "disconnected", "failed"];
540
+ type CalendarConnectionState = (typeof CALENDAR_STATES)[number];
541
+ /** Owner-visible, secret-free calendar connection and sync health. */
542
+ interface CalendarStatus {
543
+ env: string;
544
+ enabled: boolean;
545
+ /** True when the platform has provider credentials for this connection. */
546
+ connected: boolean;
547
+ provider: "google" | null;
548
+ status: CalendarConnectionState;
549
+ access?: "read";
550
+ calendars: string[];
551
+ attendeePolicy?: "full" | "hashed";
552
+ /** True once a booking projection may have established the DB privacy policy. */
553
+ attendeePolicyLocked?: boolean;
554
+ bookingPageUrl?: string | null;
555
+ grantedScopes: string[];
556
+ attemptId?: string;
557
+ lastSuccessfulSyncAt?: number;
558
+ lastFullSyncAt?: number;
559
+ watchExpiresAt?: number;
560
+ bookingCount?: number;
561
+ error?: {
562
+ code?: string;
563
+ message?: string;
564
+ };
565
+ }
566
+ /** One Google calendar the connected identity may select for mirroring. */
567
+ interface AvailableGoogleCalendar {
568
+ id: string;
569
+ summary?: string;
570
+ primary?: boolean;
571
+ selected?: boolean;
572
+ accessRole?: "freeBusyReader" | "reader" | "writer" | "owner";
466
573
  }
467
- /**
468
- * Validate and summarize an odla project config entirely offline — no network
469
- * calls and no file writes. Loads `configPath`, builds the provision plan, and
470
- * resolves the schema and rules (synthesizing deny-all rules from the schema
471
- * when `db.rules` is omitted and `db.defaultRules` isn't `false`), then prints a
472
- * summary line for the app, envs, services, schema entity count, rules namespace
473
- * count, and AI provider.
474
- *
475
- * It then collects warnings for the mistakes provision can't fix silently: a
476
- * schema with no entities, a rules namespace with no matching entity, the `ai`
477
- * service enabled without an `ai.provider`, `auth.clerk` entries that reference
478
- * an unset env var (`$FOO`), an `ai.keyEnv` that isn't set (provider key upload
479
- * will be skipped), and — when `o11y` is enabled — any env whose local
480
- * credentials lack an ingest token. It also folds in the shared rule linter,
481
- * git-tracked secret-shaped files, and wrangler config warnings. Prints `ok`
482
- * when clean, otherwise the warning list. Purely advisory: never throws on
483
- * warnings and never mutates anything.
484
- *
485
- * @param options.configPath Path to the `odla.config.mjs` to inspect.
486
- * @param options.stdout Optional console-like sink (defaults to `console`).
487
- */
488
- declare function doctor(options: DoctorOptions): Promise<void>;
489
574
 
490
- interface InitOptions {
491
- rootDir?: string;
492
- configPath?: string;
493
- appId: string;
494
- name: string;
495
- envs?: string[];
496
- services?: string[];
497
- aiProvider?: string;
498
- force?: boolean;
575
+ interface CalendarLifecycleOptions {
576
+ configPath: string;
577
+ env?: string;
578
+ token?: string;
579
+ open?: boolean;
580
+ interactive?: boolean;
581
+ yes?: boolean;
582
+ json?: boolean;
583
+ fetch?: typeof fetch;
499
584
  stdout?: Pick<typeof console, "log" | "error">;
585
+ openConsentUrl?: (url: string) => Promise<void>;
586
+ wait?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
587
+ pollTimeoutMs?: number;
588
+ now?: () => number;
589
+ signal?: AbortSignal;
500
590
  }
501
- /**
502
- * Scaffold a new odla project into `rootDir` (default: cwd). Writes an
503
- * `odla.config.mjs` (default name, overridable via `configPath`) plus starter
504
- * `src/odla/schema.mjs` (a single `notes` entity) and `src/odla/rules.mjs`
505
- * (deny-all), and creates the `src/odla` and `.odla` directories. Also runs
506
- * `ensureGitignore` so the local token/credentials paths are ignored.
507
- *
508
- * Fully local and synchronous — no network calls. The config file is only
509
- * written when it doesn't already exist unless `force` is set (otherwise it
510
- * throws); the schema/rules files are written only when missing (never
511
- * overwritten, even with `force`). `appId` must match `^[a-z0-9][a-z0-9-]*$` or
512
- * it throws. Defaults: `envs` → `["dev"]`, `services` → `["db","ai"]`,
513
- * `aiProvider` → `"anthropic"` (which also picks the `keyEnv`, e.g.
514
- * `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`/`GOOGLE_API_KEY`).
515
- *
516
- * @param options.appId Slug for the app (lowercase alphanumerics and hyphens).
517
- * @param options.name Human-readable app name embedded in the config.
518
- * @param options.rootDir Project root (defaults to `process.cwd()`).
519
- * @param options.configPath Config filename relative to root (default `odla.config.mjs`).
520
- * @param options.envs Environment names (default `["dev"]`; production is an explicit opt-in).
521
- * @param options.services Enabled services (default `["db","ai"]`).
522
- * @param options.aiProvider AI provider slug (default `"anthropic"`).
523
- * @param options.force Overwrite an existing config file instead of throwing.
524
- * @param options.stdout Optional console-like sink (defaults to `console`).
525
- */
526
- declare function initProject(options: InitOptions): void;
591
+ /** Read the owner-visible Google connection and mirror health for one env. */
592
+ declare function calendarStatus(options: CalendarLifecycleOptions): Promise<CalendarStatus>;
593
+ /** List calendars visible to the connected Google identity for config authoring. */
594
+ declare function calendarCalendars(options: CalendarLifecycleOptions): Promise<AvailableGoogleCalendar[]>;
595
+ /** Apply the public booking page setting, then complete Google consent + initial sync. */
596
+ declare function calendarConnect(options: CalendarLifecycleOptions): Promise<CalendarStatus>;
597
+ /** Ask the hosted connector for an immediate incremental/full recovery sync. */
598
+ declare function calendarResync(options: CalendarLifecycleOptions): Promise<CalendarStatus>;
599
+ /** Stop watches and delete this connection's encrypted platform token/cursors
600
+ * without purging the mirror or revoking the shared Google project grant. */
601
+ declare function calendarDisconnect(options: CalendarLifecycleOptions): Promise<CalendarStatus>;
527
602
 
528
603
  interface RunResult {
529
604
  code: number;
@@ -548,6 +623,41 @@ interface ClerkAuthConfig {
548
623
  audience?: string;
549
624
  mode?: "client" | "full";
550
625
  }
626
+ /** Read-only event selection applied by the hosted Google calendar connector. */
627
+ interface GoogleCalendarMatchConfig {
628
+ /** Mirror only events whose summary begins with this value. */
629
+ summaryPrefix?: string;
630
+ /** Require the connected identity to organize the event. Defaults to true. */
631
+ organizerSelf?: boolean;
632
+ /** Require at least one attendee. Defaults to true. */
633
+ requireAttendees?: boolean;
634
+ }
635
+ /** Google Calendar intent. OAuth credentials never belong in this config. */
636
+ interface GoogleCalendarConfig {
637
+ /** One through ten provider calendar ids to mirror in each odla environment. */
638
+ calendars: Record<OdlaEnvName, string[]>;
639
+ /** Public Google Appointment Schedule URL for static embed/link mode. */
640
+ bookingPageUrl?: Record<OdlaEnvName, string | null>;
641
+ match?: GoogleCalendarMatchConfig;
642
+ /** Store normalized attendee addresses, or only SHA-256 match keys. */
643
+ attendeePolicy?: "full" | "hashed";
644
+ }
645
+ interface CalendarConfig {
646
+ google: GoogleCalendarConfig;
647
+ }
648
+ /** Canonical, environment-specific config sent to the calendar controller. */
649
+ interface CalendarServiceConfig extends Record<string, unknown> {
650
+ provider: "google";
651
+ access: "read";
652
+ /** Deduplicated provider ids; the hosted connector accepts at most ten. */
653
+ calendars: string[];
654
+ match: {
655
+ organizerSelf: boolean;
656
+ requireAttendees: boolean;
657
+ summaryPrefix?: string;
658
+ };
659
+ attendeePolicy: "full" | "hashed";
660
+ }
551
661
  /** Authored `odla.config.mjs` contract consumed by doctor and provision. */
552
662
  interface OdlaProjectConfig {
553
663
  platformUrl?: string;
@@ -576,6 +686,7 @@ interface OdlaProjectConfig {
576
686
  /** Vault secret name. Defaults to @odla-ai/ai's provider default. */
577
687
  secretName?: string;
578
688
  };
689
+ calendar?: CalendarConfig;
579
690
  o11y?: {
580
691
  /** This service's name in the collector. Defaults to the app id. */
581
692
  service?: string;
@@ -640,6 +751,10 @@ interface ProvisionOptions {
640
751
  interactive?: boolean;
641
752
  /** Test/embedding override for opening the approval URL. Defaults to the OS browser opener. */
642
753
  openApprovalUrl?: (url: string) => Promise<void>;
754
+ /** Test/embedding override for calendar connection polling. */
755
+ calendarPollWait?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
756
+ /** Maximum calendar consent + initial-sync wait. Defaults to ten minutes. */
757
+ calendarPollTimeoutMs?: number;
643
758
  /** Explicit consent for a non-dry-run plan containing `prod` or `production`. */
644
759
  yes?: boolean;
645
760
  fetch?: typeof fetch;
@@ -663,10 +778,86 @@ interface ProvisionPlan {
663
778
  interface SmokeOptions {
664
779
  configPath: string;
665
780
  env?: string;
781
+ /** Developer-token override used only for owner-visible calendar health. */
782
+ token?: string;
783
+ open?: boolean;
784
+ interactive?: boolean;
785
+ openApprovalUrl?: (url: string) => Promise<void>;
666
786
  fetch?: typeof fetch;
667
787
  stdout?: Pick<typeof console, "log" | "error">;
668
788
  }
669
789
 
790
+ declare const GOOGLE_CALENDAR_READ_SCOPE = "https://www.googleapis.com/auth/calendar.events.readonly";
791
+ /** Resolve the checked-in calendar block into one controller-safe env payload. */
792
+ declare function calendarServiceConfig(cfg: LoadedProjectConfig, env: string): CalendarServiceConfig;
793
+ /** Public Appointment Schedule URL to apply separately from connector config. */
794
+ declare function calendarBookingPageUrl(cfg: LoadedProjectConfig, env: string): string | null | undefined;
795
+
796
+ interface DoctorOptions {
797
+ configPath: string;
798
+ stdout?: Pick<typeof console, "log" | "error">;
799
+ }
800
+ /**
801
+ * Validate and summarize an odla project config entirely offline — no network
802
+ * calls and no file writes. Loads `configPath`, builds the provision plan, and
803
+ * resolves the schema and rules (synthesizing deny-all rules from the schema
804
+ * when `db.rules` is omitted and `db.defaultRules` isn't `false`), then prints a
805
+ * summary line for the app, envs, services, schema entity count, rules namespace
806
+ * count, and AI provider.
807
+ *
808
+ * It then collects warnings for the mistakes provision can't fix silently: a
809
+ * schema with no entities, a rules namespace with no matching entity, the `ai`
810
+ * service enabled without an `ai.provider`, `auth.clerk` entries that reference
811
+ * an unset env var (`$FOO`), an `ai.keyEnv` that isn't set (provider key upload
812
+ * will be skipped), and — when `o11y` is enabled — any env whose local
813
+ * credentials lack an ingest token. It also folds in the shared rule linter,
814
+ * git-tracked secret-shaped files, and wrangler config warnings. Prints `ok`
815
+ * when clean, otherwise the warning list. Purely advisory: never throws on
816
+ * warnings and never mutates anything.
817
+ *
818
+ * @param options.configPath Path to the `odla.config.mjs` to inspect.
819
+ * @param options.stdout Optional console-like sink (defaults to `console`).
820
+ */
821
+ declare function doctor(options: DoctorOptions): Promise<void>;
822
+
823
+ interface InitOptions {
824
+ rootDir?: string;
825
+ configPath?: string;
826
+ appId: string;
827
+ name: string;
828
+ envs?: string[];
829
+ services?: string[];
830
+ aiProvider?: string;
831
+ force?: boolean;
832
+ stdout?: Pick<typeof console, "log" | "error">;
833
+ }
834
+ /**
835
+ * Scaffold a new odla project into `rootDir` (default: cwd). Writes an
836
+ * `odla.config.mjs` (default name, overridable via `configPath`) plus starter
837
+ * `src/odla/schema.mjs` (a single `notes` entity) and `src/odla/rules.mjs`
838
+ * (deny-all), and creates the `src/odla` and `.odla` directories. Also runs
839
+ * `ensureGitignore` so the local token/credentials paths are ignored.
840
+ *
841
+ * Fully local and synchronous — no network calls. The config file is only
842
+ * written when it doesn't already exist unless `force` is set (otherwise it
843
+ * throws); the schema/rules files are written only when missing (never
844
+ * overwritten, even with `force`). `appId` must match `^[a-z0-9][a-z0-9-]*$` or
845
+ * it throws. Defaults: `envs` → `["dev"]`, `services` → `["db","ai"]`,
846
+ * `aiProvider` → `"anthropic"` (which also picks the `keyEnv`, e.g.
847
+ * `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`/`GOOGLE_API_KEY`).
848
+ *
849
+ * @param options.appId Slug for the app (lowercase alphanumerics and hyphens).
850
+ * @param options.name Human-readable app name embedded in the config.
851
+ * @param options.rootDir Project root (defaults to `process.cwd()`).
852
+ * @param options.configPath Config filename relative to root (default `odla.config.mjs`).
853
+ * @param options.envs Environment names (default `["dev"]`; production is an explicit opt-in).
854
+ * @param options.services Enabled services (default `["db","ai"]`).
855
+ * @param options.aiProvider AI provider slug (default `"anthropic"`).
856
+ * @param options.force Overwrite an existing config file instead of throwing.
857
+ * @param options.stdout Optional console-like sink (defaults to `console`).
858
+ */
859
+ declare function initProject(options: InitOptions): void;
860
+
670
861
  /**
671
862
  * Provision an app end-to-end from its config — the primary deploy path, and
672
863
  * (apart from key rotation) idempotent, so re-running is safe. Obtains a
@@ -727,6 +918,47 @@ interface SecretsPushOptions {
727
918
  */
728
919
  declare function secretsPush(options: SecretsPushOptions): Promise<void>;
729
920
 
921
+ /**
922
+ * Inputs for one write-only tenant-vault transfer (`secrets set` /
923
+ * `secrets set-clerk-key`). Values may come only from a named environment
924
+ * variable or stdin, so a producer command can be piped straight into the
925
+ * vault without the secret ever being displayed or stored on disk.
926
+ */
927
+ interface SecretsSetOptions {
928
+ configPath: string;
929
+ env: string;
930
+ /** Vault secret name (`secrets set` only; `$`-prefixed names are reserved). */
931
+ name?: string;
932
+ fromEnv?: string;
933
+ stdin?: boolean;
934
+ /** Explicit consent for a prod-named env (and for sk_live_ into a non-prod env). */
935
+ yes?: boolean;
936
+ /** Developer token override; defaults to $ODLA_DEV_TOKEN, the cached token, or a device handshake. */
937
+ token?: string;
938
+ open?: boolean;
939
+ interactive?: boolean;
940
+ openApprovalUrl?: (url: string) => Promise<void>;
941
+ fetch?: typeof fetch;
942
+ readStdin?: () => Promise<string>;
943
+ stdout?: Pick<typeof console, "log" | "error">;
944
+ }
945
+ /**
946
+ * Store one named secret in the env's tenant vault (the same write-only slot
947
+ * Studio's Secrets panel fills), e.g. `clerk_webhook_secret` for synced auth.
948
+ * The value is encrypted at rest and can be read back only by that tenant's
949
+ * app API key — never by this CLI, Studio, or the developer token.
950
+ */
951
+ declare function secretsSet(options: SecretsSetOptions): Promise<void>;
952
+ /**
953
+ * Store the app's Clerk secret key under the reserved `$clerk_secret` slot so
954
+ * odla-db can resolve the app's users (invites, member lookups). Reserved
955
+ * secrets are never readable by app keys; the dedicated endpoint is the only
956
+ * way to set one. Guards against instance/env mismatches: an sk_test_ key
957
+ * never belongs in a prod-named env, and an sk_live_ key entering a non-prod
958
+ * env requires `--yes` (live users would sync into a non-prod tenant).
959
+ */
960
+ declare function secretsSetClerkKey(options: SecretsSetOptions): Promise<void>;
961
+
730
962
  /** Built-in security coverage profile selected for a hosted reasoning run. */
731
963
  type HostedSecurityProfile = "odla" | "cloudflare-app" | "generic";
732
964
  /** Exact attribution and authorization need supplied to a lazy token callback. */
@@ -782,223 +1014,15 @@ interface HostedSecurityResult {
782
1014
  */
783
1015
  declare function runHostedSecurity(options: RunHostedSecurityOptions): Promise<HostedSecurityResult>;
784
1016
 
785
- /** GitHub repository connection persisted by odla.ai. GitHub credentials and
786
- * installation identifiers are deliberately absent from this client view. */
787
- interface GitHubSecuritySource {
788
- sourceId: string;
789
- repository: string;
790
- installationAccount: string;
791
- defaultBranch: string;
792
- env: string;
793
- status: "active" | "disabled" | "deleted";
794
- connectedAt: string;
795
- }
796
- /** One browser-mediated GitHub App installation attempt. */
797
- interface GitHubConnectionAttempt {
798
- attemptId: string;
799
- installUrl: string;
800
- expiresAt: string;
801
- }
802
- /** Poll-safe state for a GitHub App installation attempt. */
803
- interface GitHubConnectionStatus {
804
- attemptId: string;
805
- status: "pending" | "connected" | "expired" | "failed";
806
- connectionId?: string;
807
- sourceId?: string;
808
- repository?: string;
809
- installationAccount?: string;
810
- failureCode?: string;
811
- }
812
- /** Model identity selected by platform policy. No provider credential is
813
- * returned to the CLI. */
814
- interface HostedSecurityRoute {
815
- provider: string;
816
- model: string;
817
- policyVersion?: number;
818
- }
819
- /** One purpose-specific System AI route disclosed before a hosted review. */
820
- interface HostedSecurityPlanRoute extends HostedSecurityRoute {
821
- purpose: "security.discovery" | "security.validation";
822
- policyVersion: number;
823
- enabled: boolean;
824
- credentialReady: boolean;
825
- maxCallsPerRun: number;
826
- maxInputBytes: number;
827
- maxOutputTokens: number;
828
- }
829
- /** Owner-readable, versioned disclosure preflight. Provider/model identities
830
- * and bounds are non-secret; the full plan digest binds subsequent consent so
831
- * an admin edit or processing-contract release cannot silently change it. */
832
- interface HostedSecurityPlan {
833
- env: string;
834
- /** Versioned disclosure and processing contract covered by `planDigest`. */
835
- consentContract: "odla.hosted-security-consent.v1";
836
- /** Versioned private report projection covered by `planDigest`. */
837
- reportProjection: "odla.hosted-security-report.v1";
838
- /** Best-effort credential-pattern redaction contract covered by `planDigest`. */
839
- redactionContract: "odla.best-effort-credential-pattern-redaction.v1";
840
- /** Server-owned prompt bundle covered by `planDigest`. */
841
- promptBundle: string;
842
- /** Digest binding this exact disclosure plan to a later enqueue request. */
843
- planDigest: `sha256:${string}`;
844
- ready: boolean;
845
- independent: boolean;
846
- sourceDisclosure: "redacted";
847
- targetExecution: false;
848
- reportRetentionDays: number;
849
- routes: {
850
- discovery: HostedSecurityPlanRoute;
851
- validation: HostedSecurityPlanRoute;
852
- };
853
- }
854
- /** Server-authored preview of the exact source/ref/profile execution choice. */
855
- interface HostedSecurityIntent {
856
- executionContract: "odla.hosted-security-execution-consent.v1";
857
- executionDigest: `sha256:${string}`;
858
- appId: string;
859
- env: string;
860
- sourceId: string;
861
- repository: string;
862
- defaultBranch: string;
863
- /** Explicit requested ref; omitted requests are canonicalized to the source's current default branch. */
864
- requestedRef: string;
865
- profile: "odla" | "cloudflare-app" | "generic";
866
- sourceDisclosure: "redacted";
867
- planDigest: `sha256:${string}`;
868
- }
869
- /** Atomic server response used to review both the processing plan and execution intent. */
870
- interface HostedSecurityIntentResponse {
871
- plan: HostedSecurityPlan;
872
- intent: HostedSecurityIntent;
873
- }
874
- /** Lifecycle state reported for a hosted repository security job. */
875
- type HostedSecurityJobStatus = "queued" | "acquiring" | "analyzing" | "completed" | "failed" | "cancelled";
876
- /** Finding totals summarized on hosted job metadata. */
877
- interface HostedSecurityCounts {
878
- confirmed: number;
879
- needsReproduction: number;
880
- candidates: number;
881
- }
882
- /** Coverage-cell totals summarized on hosted job metadata. */
883
- interface HostedSecurityCoverage {
884
- completeCells?: number;
885
- totalCells?: number;
886
- shallowCells?: number;
887
- blockedCells?: number;
888
- unscheduledCells?: number;
889
- budgetExhaustedCells?: number;
890
- }
891
- /** Bounded job metadata returned by odla.ai; it has no archive, file-body, or
892
- * explicit source-excerpt field. */
893
- interface HostedSecurityJob {
894
- jobId: string;
895
- env: string;
896
- sourceId: string;
897
- repository: string;
898
- requestedRef?: string;
899
- commitSha?: string;
900
- status: HostedSecurityJobStatus;
901
- runId?: string;
902
- profile: string;
903
- routes?: {
904
- discovery: HostedSecurityRoute;
905
- validation: HostedSecurityRoute;
906
- };
907
- sourceDisclosure: "redacted";
908
- planDigest: `sha256:${string}`;
909
- executionContract: "odla.hosted-security-execution-consent.v1";
910
- executionDigest: `sha256:${string}`;
911
- retentionDays: number;
912
- snapshotDigest?: `sha256:${string}`;
913
- fileCount?: number;
914
- inputBytes?: number;
915
- reportDigest?: `sha256:${string}`;
916
- coverageStatus?: "not_run" | "complete" | "incomplete";
917
- coverage?: HostedSecurityCoverage;
918
- counts?: HostedSecurityCounts;
919
- errorCode?: string;
920
- createdAt: string;
921
- updatedAt: string;
922
- startedAt?: string;
923
- completedAt?: string;
924
- }
925
- /** One normalized, bounded finding in a hosted security report. */
926
- interface HostedSecurityReportFinding {
927
- fingerprint: string;
928
- kind: string;
929
- attackClass: string;
930
- title: string;
931
- summary: string;
932
- impact: string;
933
- remediation?: string;
934
- validationRationale?: string;
935
- validationStatus?: string;
936
- confidence: "low" | "medium" | "high";
937
- severity: "critical" | "high" | "medium" | "low" | "informational";
938
- disposition: string;
939
- locations: Array<{
940
- path: string;
941
- line: number;
942
- column?: number;
943
- }>;
944
- }
945
- /** Bounded report projection with normalized model-derived prose and paths.
946
- * Its schema has no dedicated raw-source, archive, source-excerpt, context
947
- * packet, raw-provider-response, credential, execution-trace, or reproduction
948
- * output field. Model prose may reveal or echo source semantics, so the report
949
- * must remain private. */
950
- interface HostedSecurityReport {
951
- schemaVersion: 1;
952
- jobId: string;
953
- runId: string;
954
- repository: string;
955
- revision: string;
956
- snapshotDigest: string;
957
- reportDigest: string;
958
- startedAt: string;
959
- completedAt: string;
960
- mode: "passive" | "autonomous";
961
- coverageStatus: "not_run" | "complete" | "incomplete";
962
- findingsReturned: number;
963
- findingsTotal: number;
964
- coverageReturned: number;
965
- coverageTotal: number;
966
- metrics: HostedSecurityCounts & {
967
- rejected: number;
968
- suppressed: number;
969
- coverageCells: number;
970
- shallowCells: number;
971
- blockedCells: number;
972
- unscheduledCells: number;
973
- budgetExhaustedCells: number;
974
- };
975
- provenance: {
976
- harnessVersion: string;
977
- promptBundle: string;
978
- profile: {
979
- id: string;
980
- version: string;
981
- };
982
- independentValidation: boolean;
983
- discovery?: {
984
- provider?: string;
985
- model?: string;
986
- };
987
- validation?: {
988
- provider?: string;
989
- model?: string;
990
- };
991
- };
992
- coverage: Array<{
993
- area: string;
994
- attackClass: string;
995
- attempts: number;
996
- candidateCount: number;
997
- state: string;
998
- }>;
999
- limitations: string[];
1000
- findings: HostedSecurityReportFinding[];
1001
- }
1017
+ /**
1018
+ * The hosted-security client surface. The WIRE types live in @odla-ai/security/hosted — one
1019
+ * definition shared with the dashboard and the server that produces them. Re-exported here so
1020
+ * this module stays the single import path for the rest of the CLI.
1021
+ *
1022
+ * What remains below is what is genuinely the CLI's own: transport controls (platform origin,
1023
+ * token, fetch, AbortSignal) that no other client shares.
1024
+ */
1025
+
1002
1026
  /** Common authenticated transport controls for hosted-security requests. */
1003
1027
  interface HostedSecurityRequestOptions {
1004
1028
  platform: string;
@@ -1193,5 +1217,5 @@ declare function installSkill(options?: SkillInstallOptions): SkillInstallResult
1193
1217
  */
1194
1218
  declare function smoke(options: SmokeOptions): Promise<void>;
1195
1219
 
1196
- export { AGENT_HARNESSES, type AdminAiOptions, type AgentHarness, type AgentHarnessSelector, CAPABILITIES, type CliDependencies, type ConnectGitHubSecuritySourceOptions, type DisconnectGitHubSecuritySourceOptions, type FollowHostedSecurityJobOptions, type GetHostedSecurityIntentOptions, type GetHostedSecurityJobOptions, type GitHubConnectionAttempt, type GitHubConnectionStatus, type GitHubSecuritySource, type HarnessInstallResult, type HostedSecurityCounts, type HostedSecurityCoverage, type HostedSecurityIntent, type HostedSecurityIntentResponse, type HostedSecurityJob, type HostedSecurityJobStatus, type HostedSecurityPlan, type HostedSecurityPlanRoute, type HostedSecurityProfile, type HostedSecurityReport, type HostedSecurityReportFinding, type HostedSecurityResult, type HostedSecurityRoute, type HostedSecurityTokenRequest, type ListGitHubSecuritySourcesOptions, type ListHostedSecurityJobsOptions, type LoadedProjectConfig, type LocalCredentials, type OdlaProjectConfig, type ProvisionOptions, type ProvisionPlan, type RunHostedSecurityOptions, SYSTEM_AI_PURPOSES, type ScopedPlatformTokenOptions, type SecretsPushOptions, type SkillInstallOptions, type SkillInstallResult, type SmokeOptions, type StartHostedSecurityJobOptions, type SystemAiPurpose, adminAi, connectGitHubSecuritySource, disconnectGitHubSecuritySource, doctor, followHostedSecurityJob, getHostedSecurityIntent, getHostedSecurityJob, getHostedSecurityPlan, getHostedSecurityReport, getScopedPlatformToken, inferGitHubRepository, initProject, installSkill, isTerminalHostedSecurityStatus, listGitHubSecuritySources, listHostedSecurityJobs, printCapabilities, provision, redactSecrets, repositoryFromGitRemote, runCli, runHostedSecurity, secretsPush, smoke, startHostedSecurityJob };
1220
+ export { AGENT_HARNESSES, type AdminAiOptions, type AgentHarness, type AgentHarnessSelector, type AvailableGoogleCalendar, CAPABILITIES, type CalendarConfig, type CalendarConnectionState, type CalendarLifecycleOptions, type CalendarServiceConfig, type CalendarStatus, type CliDependencies, type ConnectGitHubSecuritySourceOptions, type DisconnectGitHubSecuritySourceOptions, type FollowHostedSecurityJobOptions, GOOGLE_CALENDAR_READ_SCOPE, type GetHostedSecurityIntentOptions, type GetHostedSecurityJobOptions, type GoogleCalendarConfig, type GoogleCalendarMatchConfig, type HarnessInstallResult, type HostedSecurityProfile, type HostedSecurityResult, type HostedSecurityTokenRequest, type ListGitHubSecuritySourcesOptions, type ListHostedSecurityJobsOptions, type LoadedProjectConfig, type LocalCredentials, type OdlaProjectConfig, type ProvisionOptions, type ProvisionPlan, type RunHostedSecurityOptions, SYSTEM_AI_PURPOSES, type ScopedPlatformTokenOptions, type SecretsPushOptions, type SecretsSetOptions, type SkillInstallOptions, type SkillInstallResult, type SmokeOptions, type StartHostedSecurityJobOptions, type SystemAiPurpose, adminAi, calendarBookingPageUrl, calendarCalendars, calendarConnect, calendarDisconnect, calendarResync, calendarServiceConfig, calendarStatus, connectGitHubSecuritySource, disconnectGitHubSecuritySource, doctor, followHostedSecurityJob, getHostedSecurityIntent, getHostedSecurityJob, getHostedSecurityPlan, getHostedSecurityReport, getScopedPlatformToken, inferGitHubRepository, initProject, installSkill, isTerminalHostedSecurityStatus, listGitHubSecuritySources, listHostedSecurityJobs, printCapabilities, provision, redactSecrets, repositoryFromGitRemote, runCli, runHostedSecurity, secretsPush, secretsSet, secretsSetClerkKey, smoke, startHostedSecurityJob };
1197
1221
  ```