@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@odla-ai/cli",
3
- "version": "0.8.0",
4
- "description": "Agent-operable CLI for odla provisioning, System AI administration, commit-pinned GitHub security jobs, Worker secrets, and smoke checks.",
3
+ "version": "0.10.0",
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",
7
7
  "repository": {
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@odla-ai/ai": "^0.3.3",
63
- "@odla-ai/apps": "^0.7.1",
63
+ "@odla-ai/apps": "^0.8.0",
64
64
  "@odla-ai/db": "^0.6.2",
65
65
  "@odla-ai/security": "^0.2.2"
66
66
  },
@@ -2,7 +2,7 @@
2
2
  name: odla
3
3
  description: >
4
4
  Build a new app on odla — a realtime database (odla-db) plus AI, Clerk auth,
5
- and observability on Cloudflare, provisioned by @odla-ai/cli. Use when the user
5
+ read-only Google Calendar mirrors, and observability on Cloudflare, provisioned by @odla-ai/cli. Use when the user
6
6
  wants to create/scaffold an odla app, add odla-db/ai/auth/o11y to a repo, or
7
7
  "get started with odla". For moving an existing static or GitHub Pages site to
8
8
  odla, use the odla-migrate skill instead.
@@ -15,7 +15,7 @@ runbookOrder:
15
15
 
16
16
  You drive setup and building on **odla** — an agent-operable app platform on
17
17
  Cloudflare: a realtime graph database (**odla-db**), multi-provider **AI**,
18
- **Clerk** auth, and OpenTelemetry **observability**, all provisioned by the
18
+ **Clerk** auth, read-only **Google Calendar** mirrors, and OpenTelemetry **observability**, all provisioned by the
19
19
  `@odla-ai/cli`. The human approves a couple of browser-only checkpoints; you do
20
20
  everything else.
21
21
 
@@ -48,6 +48,9 @@ State which path you're taking and what you'll build in one line; get a nod.
48
48
  4. Never `git add -A` without reading `git status` first. Never run
49
49
  `provision --rotate-keys` or `provision --rotate-o11y-token` unless the human
50
50
  explicitly asks.
51
+ 5. Google OAuth is a distinct human checkpoint. Never request, paste, print, or
52
+ store a Google authorization code/refresh token. Open only the state-bound
53
+ platform/Google consent URL returned by odla; calendar actions remain unavailable in the read-only slice.
51
54
 
52
55
  ## The flow
53
56
 
@@ -67,7 +70,9 @@ guessing which platform/credential steps need manual work. Then:
67
70
  prints; the human approves it at https://odla.ai/studio. It creates the app,
68
71
  enables services, issues or reuses configured credentials (db key + o11y
69
72
  ingest token when enabled), pushes schema + rules, writes `.dev.vars`, and
70
- transfers Worker secrets through Wrangler stdin.
73
+ transfers Worker secrets through Wrangler stdin. With calendar enabled it
74
+ then prints a second, server-issued Google URL; the human grants read-only consent
75
+ and the CLI follows initial sync.
71
76
  4. **run** — `npx wrangler dev` (auto-loads `.dev.vars`); verify locally.
72
77
  5. **security** — run the passive `@odla-ai/security` odla profile; inspect
73
78
  every lead and keep critical candidate gating enabled. If the human approves
@@ -88,6 +93,10 @@ guessing which platform/credential steps need manual work. Then:
88
93
 
89
94
  `npx @odla-ai/cli doctor` is an offline config check anytime;
90
95
  `npx @odla-ai/cli smoke --env dev` verifies a live deployment.
96
+ Use `calendar status --json` for safe connection/sync state and `calendar
97
+ calendars --json` to discover ids after consent; update checked-in config and
98
+ re-provision. Resync production only with `--yes`; disconnect always requires
99
+ `--yes` and leaves retained rows potentially stale.
91
100
 
92
101
  The CLI owns deterministic platform work from `odla.config.mjs`: service
93
102
  enablement, credential issuance/storage, `.dev.vars`, and Wrangler secret
@@ -15,6 +15,13 @@ deny-all rules from the schema. To turn on auth/observability, add `"o11y"` to
15
15
  `services` and an `auth.clerk` block in `odla.config.mjs` (init leaves it
16
16
  commented).
17
17
 
18
+ For a Google booking mirror, include both `"db"` and `"calendar"`. Configure
19
+ `calendar.google.calendars` for every env (start with `"primary"`), optional
20
+ match filters, and `attendeePolicy: "full" | "hashed"`. This release is
21
+ read-only; do not add OAuth credentials or action scopes to config.
22
+ For static Appointment Schedule parity, add the public per-env
23
+ `bookingPageUrl`; it is link/embed configuration, never a credential.
24
+
18
25
  ## 2. Build the Worker shell
19
26
 
20
27
  `npm i` only the SDKs you need and use the local `references/sdks.md` as the
@@ -43,6 +50,13 @@ Local credential files are `0600` and gitignored. Verify with
43
50
  anything unset. `--push-secrets` preflights the Wrangler config and login before
44
51
  issuing or rotating a shown-once credential.
45
52
 
53
+ Calendar adds a second ⏸ checkpoint after the odla device approval: provision
54
+ prints/opens a state-bound Google URL issued by the platform and waits while the human grants
55
+ `calendar.events.readonly` and the hosted connector performs initial sync. The
56
+ CLI never receives the Google callback code or tokens. Once connected, run
57
+ `npx @odla-ai/cli calendar calendars --env dev --json`, refine the checked-in
58
+ calendar ids if needed, re-provision, and verify `calendar status --json`.
59
+
46
60
  The CLI stops at the source boundary: it verifies but does not invent your
47
61
  application semantics. Do not use Studio to mint a routine o11y token. Manual
48
62
  Studio rotation is recovery-only;
@@ -61,6 +75,11 @@ means the namespace has no rule yet — add one in `src/odla/rules.mjs`
61
75
  (deliberately; e.g. `{ notes: { view: "auth.signedIn", create: "auth.signedIn" } }`),
62
76
  re-provision to push it, and retry.
63
77
 
78
+ Calendar uses the same `ODLA_ENDPOINT`, `ODLA_TENANT`, and server-only
79
+ `ODLA_API_KEY`; it adds no Worker secret. Keep `initCalendar` in trusted Worker
80
+ code. Browser code uses db subscriptions under explicit rules or pure
81
+ `@odla-ai/calendar/client` helpers, never the admin key.
82
+
64
83
  ## 5. Security preflight
65
84
 
66
85
  Install the passive harness and scan before any production secret or deploy:
@@ -31,6 +31,35 @@ Worker/admin side: `init({ appId: tenantId, adminToken: env.ODLA_API_KEY, endpoi
31
31
  The publishable key is public; `provision` stores it (`setAuth`). Rules evaluate
32
32
  the signed-in user's JWT as `auth` — e.g. `auth.signedIn`, `auth.email`.
33
33
 
34
+ ## @odla-ai/calendar — read-only Google booking mirror
35
+
36
+ Calendar requires `services: ["db", "calendar"]` plus a `calendar.google`
37
+ block. Normal provision opens a second, state-bound Google checkpoint issued by the platform for the
38
+ human to grant `calendar.events.readonly`; Google tokens stay in the hosted
39
+ connector and never become app/CLI secrets.
40
+
41
+ ```ts
42
+ // Trusted Worker only — the admin key must never enter browser code.
43
+ const calendar = initCalendar({
44
+ appId: env.ODLA_TENANT,
45
+ adminToken: env.ODLA_API_KEY,
46
+ endpoint: env.ODLA_ENDPOINT,
47
+ });
48
+ const next = await calendar.bookings.next("member@example.com");
49
+ ```
50
+
51
+ Browser code may query `$bookings` with `@odla-ai/db/client` only under an
52
+ explicit view rule, and may import pure formatting helpers from
53
+ `@odla-ai/calendar/client`. Do not import `initCalendar` or expose
54
+ `ODLA_API_KEY` in a browser. Provider actions are not part of the read-only
55
+ slice; a `calendar_not_connected`/501 action response is not a reason to widen
56
+ credentials or request a Google token.
57
+
58
+ After consent, use `odla-ai calendar calendars --env dev --json` to discover
59
+ selectable ids, edit the checked-in list, and optionally set a public per-env
60
+ `bookingPageUrl` for Appointment Schedule embed/link mode. Re-provision, then verify with
61
+ `calendar status --json` and `smoke`.
62
+
34
63
  ## @odla-ai/ai — inference (Claude / GPT / Gemini)
35
64
 
36
65
  ```ts
@@ -2,13 +2,14 @@
2
2
  name: odla-migrate
3
3
  description: >
4
4
  Migrate a static site (e.g. GitHub Pages) to odla on Cloudflare in safe
5
- phases, then add a database, Clerk login, and AI. Use when the user wants to
5
+ phases, then add a database, optional read-only calendar mirror, Clerk login, and AI. Use when the user wants to
6
6
  move a static or GitHub Pages site to Cloudflare/odla, or to add a backend,
7
7
  database, login/auth, or AI features to a static site via odla.
8
8
  runbookOrder:
9
9
  - references/phase-0-preflight.md
10
10
  - references/phase-1-static.md
11
11
  - references/phase-2-db.md
12
+ - references/phase-2b-calendar.md
12
13
  - references/phase-3-auth.md
13
14
  - references/phase-3b-user-sync.md
14
15
  - references/phase-4-ai.md
@@ -45,12 +46,16 @@ installed skill at `../odla/SKILL.md` instead.
45
46
  approved o11y rotation with `--push-secrets` so the Worker is updated in the
46
47
  same run. If the final Wrangler transfer fails, use the CLI's printed
47
48
  non-rotating `secrets push` retry; never rotate again just to retry delivery.
49
+ 7. Existing calendar embeds are public links, not proof of a booking. Preserve
50
+ them via `bookingPageUrl`; never claim reconciliation until the read-only
51
+ mirror is connected and the app correlates `$bookings` deliberately. Google
52
+ OAuth tokens never enter the repo, CLI, or chat.
48
53
 
49
54
  ## Phase state machine
50
55
 
51
56
  Phases run strictly in order; each has a verification gate:
52
57
 
53
- P0 preflight -> P1 static-on-cloudflare -> P2 database -> P3 login
58
+ P0 preflight -> P1 static-on-cloudflare -> P2 database -> P2b calendar (optional) -> P3 login
54
59
  -> P4 ai (optional) -> P5 prod + DNS cutover
55
60
 
56
61
  `MIGRATION.md` at the user's repo root is the durable state: create it in
@@ -100,6 +105,7 @@ Read the current phase's file when you enter it — not before:
100
105
  - references/phase-0-preflight.md
101
106
  - references/phase-1-static.md
102
107
  - references/phase-2-db.md
108
+ - references/phase-2b-calendar.md (optional: preserve an embed and add authoritative read-only reconciliation)
103
109
  - references/phase-3-auth.md
104
110
  - references/phase-3b-user-sync.md (optional: mirror Clerk users into $users)
105
111
  - references/phase-4-ai.md
@@ -10,8 +10,8 @@ in the browser in interactive terminals).
10
10
 
11
11
  ## Steps
12
12
 
13
- 1. Require `npm view @odla-ai/cli@0.8.0 version` to succeed, then run
14
- `npm i -D --save-exact @odla-ai/cli@0.8.0` and `npm i @odla-ai/db`.
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`.
15
15
  2. `npx @odla-ai/cli init --app-id <id> --name "<Name>" --env dev --services db`
16
16
  Review `odla.config.mjs`. Keep `envs: ["dev"]` — prod is Phase 5. Set
17
17
  `links.dev` to the URL the Phase 1 `wrangler deploy` **actually printed** —
@@ -0,0 +1,42 @@
1
+ # Phase 2b — Google Calendar (optional, read-only)
2
+
3
+ Use this phase only when the existing site has a Google Appointment Schedule
4
+ link/embed or locally reported meeting state. Keep the old booking experience
5
+ working while adding an authoritative read-only mirror.
6
+
7
+ Human obligations: review attendee-data retention and complete the second,
8
+ server-issued Google consent page. They never paste an OAuth code or token.
9
+
10
+ 1. Inventory the old behavior. Distinguish a public appointment-page link from
11
+ actual event create/reschedule/cancel APIs. Do not promise more than exists.
12
+ 2. Require `npm view @odla-ai/calendar@0.1.0 version` to succeed, then install
13
+ `npm i --save-exact @odla-ai/calendar@0.1.0` as a runtime dependency. An
14
+ exact-version `E404` blocks this phase; do not substitute a git checkout or
15
+ another version. Add `"calendar"` beside
16
+ `"db"` in services and configure:
17
+ - `calendar.google.calendars.dev` (start with `"primary"`);
18
+ - optional summary/organizer/attendee match filters;
19
+ - deliberate `attendeePolicy: "full" | "hashed"`;
20
+ - the old public Appointment Schedule URL as `bookingPageUrl.dev` when
21
+ preserving the embed/link. It is public configuration, not a secret.
22
+ 3. Run `doctor`, then `provision --dry-run`. Show the human the calendars,
23
+ attendee policy, public booking page, and read-only scope.
24
+ 4. Run normal dev provision. After odla device approval, pause again while the
25
+ human grants Google `calendar.events.readonly`; the CLI follows initial sync.
26
+ 5. Run `calendar calendars --env dev --json`, refine the checked-in ids, and
27
+ re-provision. Verify `calendar status --env dev --json` and `smoke --env dev`.
28
+ 6. Put `initCalendar` only in trusted Worker code. Browser code may keep using
29
+ the public `bookingPageUrl`, query `$bookings` through explicit db rules, or
30
+ import pure helpers from `@odla-ai/calendar/client`. Never expose
31
+ `ODLA_API_KEY`.
32
+ 7. Replace self-reported meeting state only after an explicit, tested
33
+ application-to-booking correlation exists. A matching attendee email may be
34
+ sufficient for some apps; do not silently regress later workflow statuses.
35
+
36
+ This slice cannot create, reschedule, or cancel Google events. A static picker
37
+ continues to be Google's sealed UI; odla supplies configuration, mirror reads,
38
+ and reconciliation.
39
+
40
+ Gate: the old embed/link still works, status is healthy, the selected calendars
41
+ match config, `$bookings` smoke succeeds, no Google credential exists in local
42
+ files, and `MIGRATION.md` records the disclosure/correlation decision.
@@ -8,9 +8,10 @@ build the mirror yourself — the platform does it. This phase enables it.
8
8
  Goal: signed-in users appear as rows in the app's `$users`, kept in sync with
9
9
  Clerk (create/update/delete). Dev first, then prod.
10
10
 
11
- Human obligation: create a Clerk webhook endpoint and paste its signing secret
12
- (`whsec_…`) into Studio (write-only). That plus the Phase-3 `clerk auth login`
13
- are the only human Clerk steps.
11
+ Human obligation: create a Clerk webhook endpoint and get its signing secret
12
+ (`whsec_…`) into the tenant vault piped through `secrets set` when a command
13
+ can print it, or pasted into Studio (both write-only). That plus the Phase-3
14
+ `clerk auth login` are the only human Clerk steps.
14
15
 
15
16
  ## How `$users` fills — two paths
16
17
 
@@ -37,12 +38,18 @@ Enable it here.
37
38
  Clerk dashboard → Webhooks → Add Endpoint. (`whsec_` is a real secret — never
38
39
  print/commit it; it is the only thing you paste, and it goes into the vault,
39
40
  not chat.)
40
- 2. **Store the secret in the tenant vault** as `clerk_webhook_secret`, write-only,
41
- via Studio's secret UI (`odla.ai/studio` the app the env → secrets). The server
42
- decrypts it only to verify inbound events (svix HMAC over `id.timestamp.body`).
41
+ 2. **Store the secret in the tenant vault** as `clerk_webhook_secret`, write-only.
42
+ If any command can print the `whsec_` (Clerk CLI, Svix API), pipe it without
43
+ it ever being displayed:
44
+ `<command that prints whsec_…> | npx @odla-ai/cli secrets set clerk_webhook_secret --env dev --stdin`.
45
+ Otherwise a human pastes it into Studio's secret UI (`odla.ai/studio` → the
46
+ app → the env → secrets) — same write-only slot. The server decrypts it only
47
+ to verify inbound events (svix HMAC over `id.timestamp.body`).
43
48
  3. **(Optional) `sk_…`** — the webhook payload already carries email/name/image,
44
- so the basic mirror needs only `whsec_`. Add the Clerk backend key `sk_…` to
45
- the vault only if you later need to resolve data the webhook doesn't send.
49
+ so the basic mirror needs only `whsec_`. Add the Clerk backend key only if
50
+ you later need to resolve data the webhook doesn't send (invites, member
51
+ lookups): `npx @odla-ai/cli secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY`
52
+ stores it in the reserved `$clerk_secret` slot, write-only.
46
53
 
47
54
  ## Verify
48
55
 
@@ -5,8 +5,8 @@
5
5
  | Value | Lives in | Handling |
6
6
  |---|---|---|
7
7
  | Clerk publishable key (`pk_test_`/`pk_live_`) | registry, via provision → setAuth | public by design; the one value a human may paste into chat; fine inline in odla.config.mjs |
8
- | Clerk webhook secret (`whsec_…`) | tenant vault (`clerk_webhook_secret`) | only for auth mode "full"; entered in Studio, never wrangler, never chat |
9
- | Clerk secret key (`sk_test_`/`sk_live_`) | not used in this journey | never ask for it |
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
+ | 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
11
  | `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
12
  | `odla_dev_…` (developer token) | `.odla/dev-token.json` (0600) | ~24h lifetime, provision-time only; never deployed |
@@ -42,6 +42,20 @@ Manual fallback (identical mechanics, if the CLI is unavailable):
42
42
  (For prod, use `c.envs.prod.dbKey` and drop `--env` — the top-level
43
43
  wrangler env is prod.)
44
44
 
45
+ ## Moving a tenant-vault secret (never through the transcript)
46
+
47
+ `secrets set` fills the same write-only vault slot as Studio's Secrets panel,
48
+ reading the value only from stdin or a named env var — never argv — so a
49
+ producer command pipes straight into the vault:
50
+
51
+ <command that prints the value> | npx @odla-ai/cli secrets set clerk_webhook_secret --env dev --stdin
52
+ npx @odla-ai/cli secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY
53
+
54
+ The value cannot be read back by the CLI, Studio, or the developer token —
55
+ only by that tenant's app API key (reserved `$` secrets not even then). Studio
56
+ remains the fallback when no producer command exists (a human copies from the
57
+ Clerk dashboard between two browser tabs). Prod-named envs require `--yes`.
58
+
45
59
  ## Standing rules
46
60
 
47
61
  - Never `cat`, `head`, `grep -v`, or otherwise display `.dev.vars`,