@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.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  Project-neutral provisioning CLI for odla apps. It creates and validates an
9
9
  `odla.config.mjs`, then uses that config to register an app, enable services,
10
10
  push odla-db schema/rules, configure per-app BYOK AI, configure Clerk auth, record
11
- deployment links, provision o11y ingest credentials, and transfer local or
11
+ deployment links, connect read-only Google Calendar mirrors, provision o11y ingest credentials, and transfer local or
12
12
  deployed Worker secrets without printing them. It also gives platform admins a
13
13
  scoped System AI control surface and gives app owners a provider-key-free
14
14
  hosted security command.
@@ -29,13 +29,17 @@ For a project you keep working in, install it as a dev dependency so the
29
29
  shorter `npx odla-ai` form works and the version is pinned by your lockfile:
30
30
 
31
31
  ```bash
32
- npm view @odla-ai/cli@0.9.0 version
33
- npm i -D --save-exact @odla-ai/cli@0.9.0
32
+ npm view @odla-ai/cli@0.10.1 version
33
+ npm i -D --save-exact @odla-ai/cli@0.10.1
34
34
  ```
35
35
 
36
36
  ## Prerequisites
37
37
 
38
38
  - Node.js 20 or newer (`node --version`).
39
+ - An existing odla account that has signed in at least once. For any command
40
+ that may need a fresh device grant, pass `--email <account>` or set
41
+ `ODLA_USER_EMAIL`. This value identifies who may review the request; it is not
42
+ a password or session credential, and an agent must never ask for either.
39
43
  - odla apps usually deploy as Cloudflare Workers. If you have never used
40
44
  Cloudflare:
41
45
  1. Create a free account at <https://dash.cloudflare.com/sign-up>.
@@ -53,10 +57,15 @@ npm i -D --save-exact @odla-ai/cli@0.9.0
53
57
  npx odla-ai setup
54
58
  npx odla-ai init --app-id my-app --name "My App"
55
59
  npx odla-ai doctor
60
+ npx odla-ai calendar status --env dev
61
+ npx odla-ai calendar calendars --env dev
62
+ npx odla-ai calendar connect --env dev
63
+ npx odla-ai calendar resync --env dev
64
+ npx odla-ai calendar disconnect --env dev --yes
56
65
  npx odla-ai capabilities --json
57
66
  # install SDKs, write the Worker, and create wrangler.jsonc before secret push
58
67
  npx odla-ai provision --dry-run
59
- npx odla-ai provision --write-dev-vars --push-secrets
68
+ npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
60
69
  npx odla-ai smoke --env dev
61
70
  npx odla-ai secrets push --env dev
62
71
  npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
@@ -68,7 +77,7 @@ npx odla-ai security sources --env dev
68
77
  npx odla-ai security run --source <source-id> --ref main --env dev --plan-digest <digest-from-security-plan> --ack-redacted-source
69
78
  npx odla-ai security status <job-id>
70
79
  npx odla-ai security report <job-id>
71
- npx odla-ai admin ai show
80
+ npx odla-ai admin ai show --email owner@example.com
72
81
  npx odla-ai admin ai models
73
82
  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
74
83
  npx odla-ai admin ai credentials
@@ -96,21 +105,30 @@ When using `--push-secrets`, create the Worker and its Wrangler config first.
96
105
  The CLI checks that config and `wrangler whoami` before it issues or rotates a
97
106
  shown-once credential.
98
107
 
99
- 1. Gets an `odla_dev_...` token by device handshake, or reuses
100
- `ODLA_DEV_TOKEN` / `.odla/dev-token.json`.
108
+ 1. Gets an `odla_dev_...` token by email-bound device handshake, or reuses
109
+ `ODLA_DEV_TOKEN` / `.odla/dev-token.json`. A fresh handshake requires
110
+ `--email <account>` or `ODLA_USER_EMAIL`; the matching existing account must
111
+ sign in, review the exact code, and approve it. Opening the URL alone does
112
+ not claim the request.
101
113
  In an interactive terminal it opens the approval page in your browser when
102
114
  the code is displayed. Pass `--open` to force browser launch from a
103
115
  non-interactive shell, or `--no-open` to suppress it. Browser launch is
104
116
  best-effort; the printed URL and code always remain the fallback.
105
117
  2. Creates the platform app if needed.
106
- 3. Enables configured services in every configured environment.
118
+ 3. Enables configured services in every configured environment. Calendar
119
+ config is normalized per env and fixed to Google read-only access.
107
120
  4. Mints or reuses each env's configured service credentials: an odla-db app
108
121
  key for `db` and an ingest token for `o11y`.
109
122
  5. Pushes the configured schema and rules.
110
123
  6. Configures platform AI and stores provider keys in the tenant vault when the
111
124
  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
125
+ 7. For calendar, reads owner-visible connection status and, when needed, asks
126
+ the platform for a state-bound Google authorization URL and opens only the
127
+ exact Google OAuth endpoint (or a same-platform interstitial). The human completes consent there;
128
+ provision follows the exact attempt through initial sync. OAuth codes and
129
+ refresh tokens never enter the CLI, repo, chat, or app.
130
+ 8. Writes `.odla/credentials.local.json` with mode `0600`.
131
+ 9. With `--write-dev-vars`, writes the local Worker values to `.dev.vars`; with
114
132
  `--push-secrets`, transfers the configured service secrets to the matching
115
133
  deployed Worker environment over Wrangler stdin.
116
134
 
@@ -127,12 +145,74 @@ The CLI preflights Wrangler first and persists the replacement before pushing;
127
145
  if that final push still fails, follow the printed `secrets push --env ...`
128
146
  retry. Do not run the rotation flag again.
129
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
+
130
168
  `smoke` verifies a provisioned environment from local credentials. It fetches
131
169
  the platform public config, checks the configured AI provider, fetches the live
132
170
  odla-db schema with the tenant key, compares expected schema entities, and runs
133
171
  a count aggregate against the first entity. It fails early with a clear message
134
172
  when provisioning has not written `.odla/credentials.local.json`.
135
173
 
174
+ ### Read-only Google Calendar
175
+
176
+ Calendar is a platform-custodied connector, not a Google credential stored by
177
+ the application. Enable both `db` and `calendar`, configure calendar ids per
178
+ environment, then run normal provision. The first run has two distinct human
179
+ checkpoints: the odla device code establishes app-owner authority, and the
180
+ subsequent server-issued Google page grants `calendar.events.readonly` consent.
181
+
182
+ For parity with a static Google Appointment Schedule embed/link, set the public
183
+ `bookingPageUrl` per environment. Provision applies it through the owner-only
184
+ calendar settings route; it is public configuration, not a credential.
185
+
186
+ `calendar status` returns the safe connection/sync projection; add `--json` for
187
+ agent automation. Once connected, `calendar calendars` lists ids visible to the
188
+ Google identity so the checked-in selection can be refined. `calendar connect`
189
+ applies the checked-in booking-page setting and starts consent when the
190
+ connection is absent, failed, disconnected, or degraded (so an agent can
191
+ repair revoked credentials); a healthy connection is reused. A `needs_sync`
192
+ connection already has provider credentials, so connect/provision resyncs it
193
+ without requesting Google consent again; an existing authorization or initial
194
+ sync is awaited instead of creating a duplicate attempt.
195
+ `calendar resync` asks the hosted connector to sync immediately; `calendar
196
+ disconnect --yes` stops watches and deletes this app/environment connection's
197
+ encrypted platform token and cursors without purging retained mirror rows,
198
+ which may therefore remain readable but stale. It does not revoke the shared
199
+ user-to-Google-OAuth-project grant, because that could invalidate other odla
200
+ connections for the same user. A future explicitly global revoke command would
201
+ need cross-connection accounting and warning. Production resync requires
202
+ `--yes`, as does a direct production connect. None of these commands accepts a
203
+ Google authorization code, client secret, access token, or refresh token.
204
+
205
+ The SDK uses the existing server-side db values (`ODLA_ENDPOINT`,
206
+ `ODLA_TENANT`, `ODLA_API_KEY`), so calendar adds no Worker secret. Never expose
207
+ `ODLA_API_KEY` to a browser. Trusted Worker code can initialize
208
+ `@odla-ai/calendar`; browser code reads `$bookings` through `@odla-ai/db/client`
209
+ under explicit rules or imports pure helpers from `@odla-ai/calendar/client`.
210
+
211
+ These commands operate the current read/embed reconciliation slice. The CLI has
212
+ no native slot-picker or create/reschedule/cancel command, and exposes no
213
+ rolling-window/age-out, quota-backoff, retention/purge, or provider-derived
214
+ mutation-id control. Public health remains aggregate rather than per calendar.
215
+
136
216
  The bundled build/migration skills add a passive pre-ship gate with
137
217
  `@odla-ai/security`: after the exact-version availability check, run
138
218
  `npm i -D --save-exact @odla-ai/security@0.2.2`, then
@@ -283,7 +363,8 @@ approval), and prod-named envs require `--yes`, matching `secrets push`.
283
363
 
284
364
  If a change is deterministic from `odla.config.mjs`, the CLI owns it: app and
285
365
  service enablement, credential issuance and local persistence, `.dev.vars`,
286
- schema/rules/auth/AI configuration, and Wrangler secret transfer. The coding
366
+ schema/rules/auth/AI/calendar configuration, owner-safe consent orchestration,
367
+ and Wrangler secret transfer. The coding
287
368
  agent owns application semantics: installing `@odla-ai/o11y`, wrapping the
288
369
  Worker with `withObservability`, and choosing useful spans, metrics, errors, and
289
370
  LLM-usage records. The human owns the device approval, production consent, and
@@ -335,7 +416,7 @@ export default {
335
416
  dbEndpoint: process.env.ODLA_ENDPOINT ?? process.env.ODLA_DB_ENDPOINT ?? "https://db.odla.ai",
336
417
  app: { id: "my-app", name: "My App" },
337
418
  envs: ["dev"], // add "prod" deliberately; provisioning it requires --yes
338
- services: ["db", "ai", "o11y"],
419
+ services: ["db", "ai", "o11y", "calendar"],
339
420
  db: {
340
421
  schema: "./src/odla/schema.mjs",
341
422
  rules: "./src/odla/rules.mjs",
@@ -346,6 +427,21 @@ export default {
346
427
  provider: process.env.ODLA_AI_PROVIDER ?? "anthropic",
347
428
  keyEnv: "ANTHROPIC_API_KEY",
348
429
  },
430
+ calendar: {
431
+ google: {
432
+ calendars: { dev: ["primary"] },
433
+ bookingPageUrl: {
434
+ dev: "https://calendar.google.com/calendar/appointments/schedules/…",
435
+ },
436
+ match: {
437
+ summaryPrefix: "Intro", // optional
438
+ organizerSelf: true, // default true
439
+ requireAttendees: true, // default true
440
+ },
441
+ attendeePolicy: "full", // or "hashed"
442
+ // This CLI release always requests calendar.events.readonly.
443
+ },
444
+ },
349
445
  auth: {
350
446
  // Publishable key (public). Created + pulled by the Clerk CLI (`npx clerk`);
351
447
  // See the installed odla-migrate phase 3. Inline `pk_…` or "$ENV_VAR".
package/REQUIREMENTS.md CHANGED
@@ -25,11 +25,27 @@ Agnacl, but none should mention or special-case Agnacl.
25
25
 
26
26
  - Provisioning must default to device-handshake auth, never a platform admin
27
27
  secret in chat or config.
28
- - The approval code must be printed in a copyable URL.
28
+ - A fresh handshake must require the existing account email via `--email` or
29
+ `ODLA_USER_EMAIL`. Email is an identity hint, never a password or bearer
30
+ credential; agents must not request either from the human.
31
+ - Unknown and never-signed-in accounts must not yield a claimable request, but
32
+ the public response must not disclose account existence.
33
+ - The approval code must be printed in a copyable, server-supplied verification
34
+ URL. Opening it must not claim the request; the matching signed-in user must
35
+ explicitly review that exact code before approval controls appear.
36
+ - Only one claimed pending or approved-but-uncollected request may be active per
37
+ user. Unclaimed issued codes must not occupy that slot.
29
38
  - Interactive provisioning should launch the browser to the approval page when
30
39
  possible. `--open` should force launch; `--no-open` should suppress it.
31
40
  - Cached developer tokens must be mode `0600`, gitignored, and reused until
32
41
  expiry.
42
+ - Minted developer tokens must have a metadata-only inventory (owner, label,
43
+ scopes, created/expires/last-used/revoked state), immediate owner revocation,
44
+ and global admin incident response. Plaintext is shown only once and never
45
+ returned by inventory.
46
+ - Handshake attempts must retain bounded metadata-only attribution using a
47
+ keyed requester fingerprint rather than a raw IP, with independent issue and
48
+ poll rate limits. Agents and developer tokens must not read this admin trail.
33
49
 
34
50
  ## Safe Provisioning
35
51
 
@@ -44,6 +60,20 @@ Agnacl, but none should mention or special-case Agnacl.
44
60
  configured custom in-repository paths, must be mode `0600` where applicable
45
61
  and gitignored.
46
62
  - O11y-only projects must not create or require an odla-db key.
63
+ - Calendar projects must require db, apply one normalized config per env, and
64
+ use the existing db app key rather than introducing an application-held
65
+ Google credential.
66
+ - Google consent must be a separate, state-bound platform checkpoint. The CLI may
67
+ print/open and poll an opaque attempt, but must never receive an OAuth code,
68
+ access token, or refresh token.
69
+ - Read-only calendar intent must request only
70
+ `calendar.events.readonly`; scope elevation belongs in a later explicit flow.
71
+ - Calendar connect/status/discovery/resync/disconnect must be resumable and
72
+ machine-readable. Disconnect must be explicit and disclose that retained
73
+ mirror rows can remain stale.
74
+ - A public per-env `bookingPageUrl` must be validated and applied independently
75
+ of connector config so static Appointment Schedule embeds need no Google/app
76
+ credential in browser code.
47
77
  - Schema pushes must be explicit and visible in dry-run.
48
78
  - If rules are omitted, schema-derived deny-all rules are the safe default.
49
79