@odla-ai/cli 0.9.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.
@@ -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
 
@@ -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
@@ -48,6 +51,9 @@ State which path you're taking and what you'll build in one line; get a nod.
48
51
  4. Never `git add -A` without reading `git status` first. Never run
49
52
  `provision --rotate-keys` or `provision --rotate-o11y-token` unless the human
50
53
  explicitly asks.
54
+ 5. Google OAuth is a distinct human checkpoint. Never request, paste, print, or
55
+ store a Google authorization code/refresh token. Open only the state-bound
56
+ platform/Google consent URL returned by odla; calendar actions remain unavailable in the read-only slice.
51
57
 
52
58
  ## The flow
53
59
 
@@ -63,11 +69,15 @@ guessing which platform/credential steps need manual work. Then:
63
69
  `withObservability`. The CLI will refuse secret delivery until the Wrangler
64
70
  target exists.
65
71
  3. **provision** ⏸ —
66
- `npx @odla-ai/cli provision --write-dev-vars --push-secrets`. A device code
67
- 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,
68
76
  enables services, issues or reuses configured credentials (db key + o11y
69
77
  ingest token when enabled), pushes schema + rules, writes `.dev.vars`, and
70
- transfers Worker secrets through Wrangler stdin.
78
+ transfers Worker secrets through Wrangler stdin. With calendar enabled it
79
+ then prints a second, server-issued Google URL; the human grants read-only consent
80
+ and the CLI follows initial sync.
71
81
  4. **run** — `npx wrangler dev` (auto-loads `.dev.vars`); verify locally.
72
82
  5. **security** — run the passive `@odla-ai/security` odla profile; inspect
73
83
  every lead and keep critical candidate gating enabled. If the human approves
@@ -88,6 +98,10 @@ guessing which platform/credential steps need manual work. Then:
88
98
 
89
99
  `npx @odla-ai/cli doctor` is an offline config check anytime;
90
100
  `npx @odla-ai/cli smoke --env dev` verifies a live deployment.
101
+ Use `calendar status --json` for safe connection/sync state and `calendar
102
+ calendars --json` to discover ids after consent; update checked-in config and
103
+ re-provision. Resync production only with `--yes`; disconnect always requires
104
+ `--yes` and leaves retained rows potentially stale.
91
105
 
92
106
  The CLI owns deterministic platform work from `odla.config.mjs`: service
93
107
  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
@@ -30,11 +37,13 @@ service source or online documentation is not setup context.
30
37
  ## 3. Provision ⏸ device-code approval
31
38
 
32
39
  ```
33
- 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
34
41
  ```
35
42
 
36
- Prints a short device code and a link. The human opens https://odla.ai/studio, signs
37
- 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:
38
47
  creates the app, enables its services, issues or reuses the db key (and the
39
48
  o11y ingest token when o11y is enabled), pushes schema + rules, writes
40
49
  `.dev.vars`, and transfers configured Worker secrets through Wrangler stdin.
@@ -43,6 +52,13 @@ Local credential files are `0600` and gitignored. Verify with
43
52
  anything unset. `--push-secrets` preflights the Wrangler config and login before
44
53
  issuing or rotating a shown-once credential.
45
54
 
55
+ Calendar adds a second ⏸ checkpoint after the odla device approval: provision
56
+ prints/opens a state-bound Google URL issued by the platform and waits while the human grants
57
+ `calendar.events.readonly` and the hosted connector performs initial sync. The
58
+ CLI never receives the Google callback code or tokens. Once connected, run
59
+ `npx @odla-ai/cli calendar calendars --env dev --json`, refine the checked-in
60
+ calendar ids if needed, re-provision, and verify `calendar status --json`.
61
+
46
62
  The CLI stops at the source boundary: it verifies but does not invent your
47
63
  application semantics. Do not use Studio to mint a routine o11y token. Manual
48
64
  Studio rotation is recovery-only;
@@ -61,6 +77,11 @@ means the namespace has no rule yet — add one in `src/odla/rules.mjs`
61
77
  (deliberately; e.g. `{ notes: { view: "auth.signedIn", create: "auth.signedIn" } }`),
62
78
  re-provision to push it, and retry.
63
79
 
80
+ Calendar uses the same `ODLA_ENDPOINT`, `ODLA_TENANT`, and server-only
81
+ `ODLA_API_KEY`; it adds no Worker secret. Keep `initCalendar` in trusted Worker
82
+ code. Browser code uses db subscriptions under explicit rules or pure
83
+ `@odla-ai/calendar/client` helpers, never the admin key.
84
+
64
85
  ## 5. Security preflight
65
86
 
66
87
  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
@@ -37,6 +38,9 @@ installed skill at `../odla/SKILL.md` instead.
37
38
  3. Never print, paste, or commit a secret. Never `cat` .dev.vars,
38
39
  .odla/credentials.local.json, or .odla/dev-token.json. Read
39
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.
40
44
  4. Never `git add -A` without reading `git status` first.
41
45
  5. Never widen a db rule to silence a 403 — default-deny is the design.
42
46
  Any rules change is a human checkpoint.
@@ -45,12 +49,16 @@ installed skill at `../odla/SKILL.md` instead.
45
49
  approved o11y rotation with `--push-secrets` so the Worker is updated in the
46
50
  same run. If the final Wrangler transfer fails, use the CLI's printed
47
51
  non-rotating `secrets push` retry; never rotate again just to retry delivery.
52
+ 7. Existing calendar embeds are public links, not proof of a booking. Preserve
53
+ them via `bookingPageUrl`; never claim reconciliation until the read-only
54
+ mirror is connected and the app correlates `$bookings` deliberately. Google
55
+ OAuth tokens never enter the repo, CLI, or chat.
48
56
 
49
57
  ## Phase state machine
50
58
 
51
59
  Phases run strictly in order; each has a verification gate:
52
60
 
53
- P0 preflight -> P1 static-on-cloudflare -> P2 database -> P3 login
61
+ P0 preflight -> P1 static-on-cloudflare -> P2 database -> P2b calendar (optional) -> P3 login
54
62
  -> P4 ai (optional) -> P5 prod + DNS cutover
55
63
 
56
64
  `MIGRATION.md` at the user's repo root is the durable state: create it in
@@ -100,6 +108,7 @@ Read the current phase's file when you enter it — not before:
100
108
  - references/phase-0-preflight.md
101
109
  - references/phase-1-static.md
102
110
  - references/phase-2-db.md
111
+ - references/phase-2b-calendar.md (optional: preserve an embed and add authoritative read-only reconciliation)
103
112
  - references/phase-3-auth.md
104
113
  - references/phase-3b-user-sync.md (optional: mirror Clerk users into $users)
105
114
  - references/phase-4-ai.md
@@ -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.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`.
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
@@ -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,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