@odla-ai/cli 0.4.0 → 0.5.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/README.md +104 -30
- package/REQUIREMENTS.md +18 -3
- package/dist/bin.cjs +780 -276
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-UWT7C6VT.js → chunk-7WZIZCGA.js} +780 -273
- package/dist/chunk-7WZIZCGA.js.map +1 -0
- package/dist/index.cjs +786 -276
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -21
- package/dist/index.d.ts +205 -21
- package/dist/index.js +7 -1
- package/llms.txt +311 -53
- package/package.json +14 -6
- package/skills/odla/SKILL.md +39 -12
- package/skills/odla/references/build.md +52 -17
- package/skills/odla/references/sdks.md +33 -3
- package/skills/odla-migrate/SKILL.md +38 -13
- package/skills/odla-migrate/references/phase-0-preflight.md +0 -2
- package/skills/odla-migrate/references/phase-1-static.md +2 -4
- package/skills/odla-migrate/references/phase-2-db.md +25 -17
- package/skills/odla-migrate/references/phase-3-auth.md +106 -20
- package/skills/odla-migrate/references/phase-3b-user-sync.md +57 -0
- package/skills/odla-migrate/references/phase-4-ai.md +6 -5
- package/skills/odla-migrate/references/phase-5-cutover.md +43 -16
- package/skills/odla-migrate/references/secrets-map.md +21 -11
- package/skills/odla-migrate/references/troubleshooting.md +27 -13
- package/skills/odla-o11y-debug/SKILL.md +93 -0
- package/dist/chunk-UWT7C6VT.js.map +0 -1
package/skills/odla/SKILL.md
CHANGED
|
@@ -6,6 +6,9 @@ description: >
|
|
|
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.
|
|
9
|
+
runbookOrder:
|
|
10
|
+
- references/build.md
|
|
11
|
+
- references/sdks.md
|
|
9
12
|
---
|
|
10
13
|
|
|
11
14
|
# odla
|
|
@@ -34,32 +37,56 @@ State which path you're taking and what you'll build in one line; get a nod.
|
|
|
34
37
|
`.odla/credentials.local.json`, or `.odla/dev-token.json` — use `ls -l` to
|
|
35
38
|
confirm they exist (and are `0600`). The only value a human ever pastes is a
|
|
36
39
|
Clerk **publishable** key (`pk_…`) — public by design.
|
|
37
|
-
2. **Dev before prod.** Keep `envs: ["dev"]`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
2. **Dev before prod.** Keep `envs: ["dev"]` and verify the tenant
|
|
41
|
+
(`<appId>--dev`) before any write or deploy. Add `prod` only at the explicit
|
|
42
|
+
production checkpoint. The first prod
|
|
43
|
+
`provision --dry-run` review, `provision --yes --push-secrets`, and deploy
|
|
44
|
+
are human checkpoints.
|
|
40
45
|
3. **Rules are default-deny.** A namespace with no rule is invisible; a write
|
|
41
46
|
with no rule is rejected. Never widen a rule just to clear a 403 — edit
|
|
42
47
|
`src/odla/rules.mjs` deliberately, and flag rule changes to the human.
|
|
43
48
|
4. Never `git add -A` without reading `git status` first. Never run
|
|
44
|
-
`provision --rotate-keys` unless the human
|
|
49
|
+
`provision --rotate-keys` or `provision --rotate-o11y-token` unless the human
|
|
50
|
+
explicitly asks.
|
|
45
51
|
|
|
46
52
|
## The flow
|
|
47
53
|
|
|
48
54
|
You reached this skill because the human ran `npx @odla-ai/cli setup`. Then:
|
|
49
55
|
|
|
56
|
+
First run `npx @odla-ai/cli capabilities --json`; use that contract instead of
|
|
57
|
+
guessing which platform/credential steps need manual work. Then:
|
|
58
|
+
|
|
50
59
|
1. **init** — `npx @odla-ai/cli init --app-id <id> --name "<Name>"` scaffolds
|
|
51
60
|
`odla.config.mjs`, `src/odla/schema.mjs`, `src/odla/rules.mjs` (deny-all).
|
|
52
|
-
2. **
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
3. **
|
|
61
|
+
2. **build the Worker shell** — install the SDKs, write the app, and create its
|
|
62
|
+
Wrangler config (`references/sdks.md`). With o11y, add `nodejs_compat` and
|
|
63
|
+
`withObservability`. The CLI will refuse secret delivery until the Wrangler
|
|
64
|
+
target exists.
|
|
65
|
+
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,
|
|
68
|
+
enables services, issues or reuses configured credentials (db key + o11y
|
|
69
|
+
ingest token when enabled), pushes schema + rules, writes `.dev.vars`, and
|
|
70
|
+
transfers Worker secrets through Wrangler stdin.
|
|
57
71
|
4. **run** — `npx wrangler dev` (auto-loads `.dev.vars`); verify locally.
|
|
58
|
-
5. **
|
|
72
|
+
5. **security** — run the passive `@odla-ai/security` odla profile;
|
|
73
|
+
inspect every lead and keep critical candidate gating enabled.
|
|
74
|
+
6. **ship** ⏸ — after adding `prod`, run `npx @odla-ai/cli provision --dry-run`,
|
|
75
|
+
show the human, then `npx @odla-ai/cli provision --yes --push-secrets` and
|
|
59
76
|
`npx wrangler deploy`.
|
|
60
77
|
|
|
61
|
-
`npx odla-ai doctor` is an offline config check anytime;
|
|
62
|
-
verifies a live deployment.
|
|
78
|
+
`npx @odla-ai/cli doctor` is an offline config check anytime;
|
|
79
|
+
`npx @odla-ai/cli smoke --env dev` verifies a live deployment.
|
|
80
|
+
|
|
81
|
+
The CLI owns deterministic platform work from `odla.config.mjs`: service
|
|
82
|
+
enablement, credential issuance/storage, `.dev.vars`, and Wrangler secret
|
|
83
|
+
transfer. You own source changes — install `@odla-ai/o11y`, add
|
|
84
|
+
`withObservability`, create the Wrangler config, and choose application-specific
|
|
85
|
+
signals. The human owns device approval, production consent, and explicit
|
|
86
|
+
destructive rotation. If an
|
|
87
|
+
o11y token must be replaced, use
|
|
88
|
+
`provision --rotate-o11y-token --push-secrets` so the cached and deployed values
|
|
89
|
+
move together; Studio rotation is manual recovery only.
|
|
63
90
|
|
|
64
91
|
## Checkpoint protocol
|
|
65
92
|
|
|
@@ -15,24 +15,40 @@ 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
|
-
## 2.
|
|
18
|
+
## 2. Build the Worker shell
|
|
19
|
+
|
|
20
|
+
`npm i` only the SDKs you need and use the local `references/sdks.md` as the
|
|
21
|
+
integration map. Create the Worker entrypoint and `wrangler.jsonc` before any
|
|
22
|
+
deployed-secret transfer. With o11y, install `@odla-ai/o11y`, add
|
|
23
|
+
`"nodejs_compat"` to `compatibility_flags`, and wrap the entrypoint with
|
|
24
|
+
`withObservability`.
|
|
25
|
+
|
|
26
|
+
Each installed package's `node_modules/@odla-ai/<pkg>/llms.txt` is the full API
|
|
27
|
+
contract. The npm artifacts contain everything required for this flow; private
|
|
28
|
+
service source or online documentation is not setup context.
|
|
29
|
+
|
|
30
|
+
## 3. Provision ⏸ device-code approval
|
|
19
31
|
|
|
20
32
|
```
|
|
21
|
-
npx odla-ai provision --write-dev-vars
|
|
33
|
+
npx @odla-ai/cli provision --write-dev-vars --push-secrets
|
|
22
34
|
```
|
|
23
35
|
|
|
24
|
-
Prints a short device code and a link. ⏸ The human opens https://odla.ai, signs
|
|
36
|
+
Prints a short device code and a link. ⏸ The human opens https://odla.ai/studio, signs
|
|
25
37
|
in, and approves the code — no secret passes through the chat. Provision then:
|
|
26
|
-
creates the app, enables its services,
|
|
27
|
-
token when o11y is enabled), pushes schema + rules,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
creates the app, enables its services, issues or reuses the db key (and the
|
|
39
|
+
o11y ingest token when o11y is enabled), pushes schema + rules, writes
|
|
40
|
+
`.dev.vars`, and transfers configured Worker secrets through Wrangler stdin.
|
|
41
|
+
Local credential files are `0600` and gitignored. Verify with
|
|
42
|
+
`npx @odla-ai/cli doctor` — it prints the app, envs, services, and flags
|
|
43
|
+
anything unset. `--push-secrets` preflights the Wrangler config and login before
|
|
44
|
+
issuing or rotating a shown-once credential.
|
|
45
|
+
|
|
46
|
+
The CLI stops at the source boundary: it verifies but does not invent your
|
|
47
|
+
application semantics. Do not use Studio to mint a routine o11y token. Manual
|
|
48
|
+
Studio rotation is recovery-only;
|
|
49
|
+
normal replacement is
|
|
50
|
+
`npx @odla-ai/cli provision --rotate-o11y-token --push-secrets` after explicit
|
|
51
|
+
human approval.
|
|
36
52
|
|
|
37
53
|
## 4. Run locally
|
|
38
54
|
|
|
@@ -45,14 +61,33 @@ means the namespace has no rule yet — add one in `src/odla/rules.mjs`
|
|
|
45
61
|
(deliberately; e.g. `{ notes: { view: "auth.signedIn", create: "auth.signedIn" } }`),
|
|
46
62
|
re-provision to push it, and retry.
|
|
47
63
|
|
|
48
|
-
## 5.
|
|
64
|
+
## 5. Security preflight
|
|
65
|
+
|
|
66
|
+
Install the passive harness and scan before any production secret or deploy:
|
|
67
|
+
|
|
68
|
+
Before installing the exact release, first verify
|
|
69
|
+
`npm view @odla-ai/security@0.1.0 version`. An exact-version `E404` means the
|
|
70
|
+
release is unavailable and blocks this preflight; it is not a clean scan and
|
|
71
|
+
does not prove the package name is absent.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
npm i -D --save-exact @odla-ai/security@0.1.0
|
|
75
|
+
npx odla-security scan . --profile odla --out .odla/security/pre-ship --fail-on high --fail-on-candidates critical
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Read `.odla/security/pre-ship/REPORT.md`. The CLI makes no model calls and does
|
|
79
|
+
not execute target code. A `candidate` is a review lead, not a confirmed
|
|
80
|
+
vulnerability; do not suppress one merely to clear the gate. Baseline entries
|
|
81
|
+
need a fingerprint, concrete reason, owner, and expiry.
|
|
82
|
+
|
|
83
|
+
## 6. Ship ⏸ human checkpoint
|
|
49
84
|
|
|
50
85
|
Add `"prod"` to `envs`, provision again (prod tenant = the bare `appId`), then:
|
|
51
86
|
|
|
52
87
|
```
|
|
53
|
-
npx odla-ai
|
|
88
|
+
npx @odla-ai/cli provision --yes --push-secrets # prod mutation + configured Worker secrets
|
|
54
89
|
npx wrangler deploy
|
|
55
90
|
```
|
|
56
91
|
|
|
57
|
-
Verify with `npx odla-ai smoke --env prod`. Point env vars at the service
|
|
58
|
-
domains, never `*.workers.dev` (Workers can't fetch same-account workers.dev).
|
|
92
|
+
Verify with `npx @odla-ai/cli smoke --env prod`. Point env vars at the service
|
|
93
|
+
custom domains, never `*.workers.dev` (Workers can't fetch same-account workers.dev).
|
|
@@ -49,9 +49,39 @@ count("http.requests", 1, { "http.route": path }); // metrics
|
|
|
49
49
|
recordError(err, { route, code }); // structured errors
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
When `o11y` is in `services`, run
|
|
53
|
+
`npx @odla-ai/cli provision --write-dev-vars --push-secrets`. The CLI enables
|
|
54
|
+
the service, issues or reuses its token, persists it locally, writes
|
|
55
|
+
`.dev.vars`, and transfers `ODLA_O11Y_TOKEN` to the Worker over Wrangler stdin.
|
|
56
|
+
With a token present, the SDK defaults `ODLA_O11Y_ENDPOINT` to
|
|
57
|
+
`https://o11y.odla.ai` and the service label to
|
|
58
|
+
`ODLA_O11Y_SERVICE ?? ODLA_APP_ID`; both remain overrides. First-party hosting
|
|
59
|
+
binds `ODLA_O11Y_COLLECTOR` instead (no public endpoint/token).
|
|
60
|
+
|
|
61
|
+
Source instrumentation remains your job: install the package, wrap with
|
|
62
|
+
`withObservability`, and select useful signals. Never rotate through Studio as
|
|
63
|
+
routine setup. On explicit human request, use
|
|
64
|
+
`provision --rotate-o11y-token --push-secrets` so local and deployed values stay
|
|
65
|
+
in sync.
|
|
66
|
+
|
|
67
|
+
## @odla-ai/security — pre-ship vulnerability harness
|
|
68
|
+
|
|
69
|
+
Run the passive CLI in local/CI development; do not import it into the running
|
|
70
|
+
Worker:
|
|
71
|
+
|
|
72
|
+
Before installing the exact release, require
|
|
73
|
+
`npm view @odla-ai/security@0.1.0 version` to succeed before installing it. An
|
|
74
|
+
exact-version `E404` means the release is unavailable, not that the preflight
|
|
75
|
+
passed, and does not prove the package name is absent.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
npm i -D --save-exact @odla-ai/security@0.1.0
|
|
79
|
+
npx odla-security scan . --profile odla --out .odla/security/pre-ship --fail-on high --fail-on-candidates critical
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Deterministic rules emit leads. Autonomous recon/hunt/validation is an optional
|
|
83
|
+
library flow with separate models, persistent state, and an explicitly injected
|
|
84
|
+
isolated executor; it never falls back to the host shell.
|
|
55
85
|
|
|
56
86
|
## Others
|
|
57
87
|
|
|
@@ -5,6 +5,16 @@ description: >
|
|
|
5
5
|
phases, then add a database, 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
|
+
runbookOrder:
|
|
9
|
+
- references/phase-0-preflight.md
|
|
10
|
+
- references/phase-1-static.md
|
|
11
|
+
- references/phase-2-db.md
|
|
12
|
+
- references/phase-3-auth.md
|
|
13
|
+
- references/phase-3b-user-sync.md
|
|
14
|
+
- references/phase-4-ai.md
|
|
15
|
+
- references/phase-5-cutover.md
|
|
16
|
+
- references/troubleshooting.md
|
|
17
|
+
- references/secrets-map.md
|
|
8
18
|
---
|
|
9
19
|
|
|
10
20
|
# odla-migrate
|
|
@@ -15,8 +25,8 @@ handshake approval, DNS). You do everything else.
|
|
|
15
25
|
|
|
16
26
|
## When NOT to use this skill
|
|
17
27
|
|
|
18
|
-
Greenfield odla apps (no existing site to migrate): follow the
|
|
19
|
-
|
|
28
|
+
Greenfield odla apps (no existing site to migrate): read and follow the sibling
|
|
29
|
+
installed skill at `../odla/SKILL.md` instead.
|
|
20
30
|
|
|
21
31
|
## Non-negotiable rules
|
|
22
32
|
|
|
@@ -30,7 +40,11 @@ agent flow" in https://odla.ai/llms.txt instead.
|
|
|
30
40
|
4. Never `git add -A` without reading `git status` first.
|
|
31
41
|
5. Never widen a db rule to silence a 403 — default-deny is the design.
|
|
32
42
|
Any rules change is a human checkpoint.
|
|
33
|
-
6. Never run `provision --rotate-keys`
|
|
43
|
+
6. Never run `provision --rotate-keys` or
|
|
44
|
+
`provision --rotate-o11y-token` unless the human explicitly asks. Pair any
|
|
45
|
+
approved o11y rotation with `--push-secrets` so the Worker is updated in the
|
|
46
|
+
same run. If the final Wrangler transfer fails, use the CLI's printed
|
|
47
|
+
non-rotating `secrets push` retry; never rotate again just to retry delivery.
|
|
34
48
|
|
|
35
49
|
## Phase state machine
|
|
36
50
|
|
|
@@ -42,7 +56,14 @@ Phases run strictly in order; each has a verification gate:
|
|
|
42
56
|
`MIGRATION.md` at the user's repo root is the durable state: create it in
|
|
43
57
|
Phase 0, update it at every gate (phase, what changed, what was verified).
|
|
44
58
|
In a fresh session, read MIGRATION.md first and resume from the recorded
|
|
45
|
-
phase
|
|
59
|
+
phase; `npx @odla-ai/cli doctor` confirms the current config state.
|
|
60
|
+
|
|
61
|
+
## Auth model (one line)
|
|
62
|
+
|
|
63
|
+
Clerk is the **source of truth** for users; odla-db keeps a mirror in the
|
|
64
|
+
app's reserved `$users` namespace (Phase 3 ships login; Phase 3b enables the
|
|
65
|
+
mirror). That per-app `$users` is distinct from odla.ai's own operator
|
|
66
|
+
access list — don't conflate them.
|
|
46
67
|
|
|
47
68
|
## Checkpoint protocol (every phase boundary)
|
|
48
69
|
|
|
@@ -52,18 +73,21 @@ phase. If the CLI offers `odla-ai status`, run it to confirm.
|
|
|
52
73
|
approve, value to paste, command to run themselves).
|
|
53
74
|
3. Wait for explicit approval before entering the next phase.
|
|
54
75
|
|
|
55
|
-
At the very first checkpoint, have the human sign in at https://odla.ai and
|
|
76
|
+
At the very first checkpoint, have the human sign in at https://odla.ai/studio and
|
|
56
77
|
open Docs → "Moving your site to odla" — it explains their side of the whole
|
|
57
78
|
journey in plain language. They need an odla account for the migration anyway,
|
|
58
79
|
so this is a natural first step.
|
|
59
80
|
|
|
60
81
|
## Verification tools
|
|
61
82
|
|
|
62
|
-
- `npx odla-ai
|
|
83
|
+
- `npx @odla-ai/cli capabilities --json` — authoritative automation boundary;
|
|
84
|
+
run once when orienting so source edits and human checkpoints are not confused
|
|
85
|
+
with CLI-owned platform/credential work.
|
|
86
|
+
- `npx @odla-ai/cli doctor` — offline config/schema/rules validation; run
|
|
63
87
|
after any config edit.
|
|
64
|
-
- `npx odla-ai provision --dry-run` — the plan, zero network/file I/O;
|
|
88
|
+
- `npx @odla-ai/cli provision --dry-run` — the plan, zero network/file I/O;
|
|
65
89
|
show it to the human before the first real provision.
|
|
66
|
-
- `npx odla-ai smoke --env dev` — live, read-only: public-config, live
|
|
90
|
+
- `npx @odla-ai/cli smoke --env dev` — live, read-only: public-config, live
|
|
67
91
|
schema diff, a count aggregate. Run after every provision.
|
|
68
92
|
- `wrangler dev` + curl — exercise routes locally before deploying.
|
|
69
93
|
- Parity curls — compare the old and new site on representative paths
|
|
@@ -77,6 +101,7 @@ Read the current phase's file when you enter it — not before:
|
|
|
77
101
|
- references/phase-1-static.md
|
|
78
102
|
- references/phase-2-db.md
|
|
79
103
|
- references/phase-3-auth.md
|
|
104
|
+
- references/phase-3b-user-sync.md (optional: mirror Clerk users into $users)
|
|
80
105
|
- references/phase-4-ai.md
|
|
81
106
|
- references/phase-5-cutover.md
|
|
82
107
|
|
|
@@ -84,8 +109,8 @@ On any failure, read references/troubleshooting.md before improvising.
|
|
|
84
109
|
|
|
85
110
|
## Context bootstrap
|
|
86
111
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`node_modules/@odla-ai/*/llms.txt` over training memory for
|
|
91
|
-
|
|
112
|
+
This installed skill and its `references/` directory are the complete migration
|
|
113
|
+
runbook; do not fetch a website or private source repository to reconstruct it.
|
|
114
|
+
Read only the current phase file as directed above. After `npm install`, prefer
|
|
115
|
+
`node_modules/@odla-ai/*/llms.txt` over training memory for package APIs — the
|
|
116
|
+
installed artifact is the version this project actually uses.
|
|
@@ -16,8 +16,6 @@ Human obligation: none.
|
|
|
16
16
|
`.github/workflows/*` using deploy-pages.
|
|
17
17
|
- Dynamic wishes: forms posting to third parties, `mailto:` contact,
|
|
18
18
|
localStorage used as a database, TODOs mentioning login/db.
|
|
19
|
-
- If your installed CLI has `npx odla-ai inspect`, run it instead of
|
|
20
|
-
hand-checking; it also does the secret scan below.
|
|
21
19
|
2. Ensure the build outputs to a DEDICATED directory (`dist/`, `_site/`,
|
|
22
20
|
`build/`). If the site is served from the repo root, restructure so a
|
|
23
21
|
build step (even a copy script) produces a clean output dir first.
|
|
@@ -10,9 +10,7 @@ in this session).
|
|
|
10
10
|
## Steps
|
|
11
11
|
|
|
12
12
|
1. `npm i -D wrangler`
|
|
13
|
-
2.
|
|
14
|
-
Otherwise write `wrangler.jsonc` by hand, modeled on
|
|
15
|
-
`examples/demo-app/wrangler.jsonc` in the odla-ai monorepo:
|
|
13
|
+
2. Write `wrangler.jsonc` by hand using this documented shape:
|
|
16
14
|
- `name`: kebab-case app id; `main`: `src/worker.ts`
|
|
17
15
|
- `compatibility_date` (today), `compatibility_flags: ["nodejs_compat"]`
|
|
18
16
|
- `assets: { "directory": "<buildDir>", "binding": "ASSETS",
|
|
@@ -45,4 +43,4 @@ Rollback: delete the dev worker in the Cloudflare dashboard. Pages was
|
|
|
45
43
|
never touched.
|
|
46
44
|
|
|
47
45
|
Done when: parity recorded and the human approves Phase 2 (their next
|
|
48
|
-
obligation: sign in at https://odla.ai and approve a handshake code).
|
|
46
|
+
obligation: sign in at https://odla.ai/studio and approve a handshake code).
|
|
@@ -4,15 +4,22 @@ 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 and approve the handshake
|
|
7
|
+
Human obligation: sign in at https://odla.ai/studio and approve the handshake
|
|
8
8
|
code when the provision run prints it (it also opens the approval page
|
|
9
9
|
in the browser in interactive terminals).
|
|
10
10
|
|
|
11
11
|
## Steps
|
|
12
12
|
|
|
13
|
-
1. `npm
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
1. Require `npm view @odla-ai/cli@0.5.0 version` to succeed, then run
|
|
14
|
+
`npm i -D --save-exact @odla-ai/cli@0.5.0` and `npm i @odla-ai/db`.
|
|
15
|
+
2. `npx @odla-ai/cli init --app-id <id> --name "<Name>" --env dev --services db`
|
|
16
|
+
Review `odla.config.mjs`. Keep `envs: ["dev"]` — prod is Phase 5. Set
|
|
17
|
+
`links.dev` to the URL the Phase 1 `wrangler deploy` **actually printed** —
|
|
18
|
+
copy it from that output and `curl` it (200) before pasting. NEVER predict or
|
|
19
|
+
remember a workers.dev URL; the worker name and account subdomain must match
|
|
20
|
+
exactly or Studio links to a dead URL. Provision records it so Studio and
|
|
21
|
+
public-config show where the app runs. (`links.prod` is set the same way at
|
|
22
|
+
Phase 5, from the prod deploy's printed URL.)
|
|
16
23
|
3. STOP before touching schema: read "Porting relational code" in
|
|
17
24
|
`node_modules/@odla-ai/db/llms.txt`. The traps are silent: entity ids
|
|
18
25
|
are not attrs (mirror an id attr), there is no NULL (omit on write,
|
|
@@ -23,16 +30,15 @@ in the browser in interactive terminals).
|
|
|
23
30
|
generated deny-all `src/odla/rules.mjs`: the worker mediates all
|
|
24
31
|
access with its app key (which bypasses rules); browsers get nothing
|
|
25
32
|
directly. Loosening a rule is a human checkpoint.
|
|
26
|
-
5. `npx odla-ai doctor` until clean.
|
|
27
|
-
6. `npx odla-ai provision --dry-run` — show the plan to the human.
|
|
28
|
-
7. `npx odla-ai provision --write-dev-vars` — handshake,
|
|
29
|
-
registration, dev db key, schema + rules push; writes
|
|
30
|
-
`.odla/credentials.local.json` (0600) and `.dev.vars
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`npx odla-ai secrets push --env dev`
|
|
34
|
-
|
|
35
|
-
manual fallback: references/secrets-map.md).
|
|
33
|
+
5. `npx @odla-ai/cli doctor` until clean.
|
|
34
|
+
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
|
+
app registration, dev db key, schema + rules push; writes
|
|
37
|
+
`.odla/credentials.local.json` (0600) and `.dev.vars`, then pipes configured
|
|
38
|
+
Worker secrets to Wrangler over stdin. Both local files are gitignored by
|
|
39
|
+
init — confirm with `git status`. Use
|
|
40
|
+
`npx @odla-ai/cli secrets push --env dev` only to retry the secret-transfer
|
|
41
|
+
portion (details and manual fallback: references/secrets-map.md).
|
|
36
42
|
9. Add to `wrangler.jsonc` `env.dev.vars`: `ODLA_ENDPOINT`
|
|
37
43
|
("https://db.odla.ai"), `ODLA_TENANT` ("<appId>--dev"),
|
|
38
44
|
`ODLA_PLATFORM` ("https://odla.ai"), `ODLA_APP_ID`, `ODLA_ENV`
|
|
@@ -40,15 +46,17 @@ in the browser in interactive terminals).
|
|
|
40
46
|
Phase 5. These are vars, not secrets.
|
|
41
47
|
10. Add `/api/*` routes before the assets fall-through, using
|
|
42
48
|
`init({ appId: env.ODLA_TENANT, adminToken: env.ODLA_API_KEY,
|
|
43
|
-
endpoint: env.ODLA_ENDPOINT })` from `@odla-ai/db`.
|
|
44
|
-
`
|
|
49
|
+
endpoint: env.ODLA_ENDPOINT })` from `@odla-ai/db`. Read the installed
|
|
50
|
+
package's `node_modules/@odla-ai/db/llms.txt` for query, transaction, and
|
|
51
|
+
error contracts.
|
|
45
52
|
11. `wrangler dev` + curl each route; then `npm run deploy:app:dev` and
|
|
46
53
|
curl the workers.dev URL.
|
|
47
54
|
|
|
48
55
|
## Verification checklist
|
|
49
56
|
|
|
50
|
-
- [ ] `npx odla-ai smoke --env dev` passes (public-config, schema, aggregate)
|
|
57
|
+
- [ ] `npx @odla-ai/cli smoke --env dev` passes (public-config, schema, aggregate)
|
|
51
58
|
- [ ] Routes work locally and on the deployed dev worker
|
|
59
|
+
- [ ] `links.dev` set — public-config carries the URL and Studio shows it
|
|
52
60
|
- [ ] `git status` shows no credential files staged
|
|
53
61
|
- [ ] MIGRATION.md updated with tenant id + route list
|
|
54
62
|
|
|
@@ -3,31 +3,48 @@
|
|
|
3
3
|
Goal: Clerk sign-in on the site, the worker verifying session JWTs
|
|
4
4
|
itself, at least one route gated. Dev only.
|
|
5
5
|
|
|
6
|
-
Human obligation:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Human obligation: **run `npx clerk auth login`** — a one-time browser
|
|
7
|
+
OAuth into their Clerk account. This is the ONE interactive step and the
|
|
8
|
+
whole reason to surface it early: the agent cannot authenticate as the
|
|
9
|
+
user. After login, credentials are stored locally and the agent drives
|
|
10
|
+
the rest (`apps create`, `link`, `env pull`, `config patch`) from Bash.
|
|
11
|
+
There is no `pk_` to hand-paste — the CLI pulls it. (Never handle `sk_…`
|
|
12
|
+
or `whsec_…` in client mode; `env pull` writes an `sk_` into a gitignored
|
|
13
|
+
`.env.local` that stays local and unused.)
|
|
14
|
+
|
|
15
|
+
Use the Clerk CLI (`npx clerk`) to create the instance rather than the
|
|
16
|
+
dashboard. Do NOT assume an app exists: a stale publishable key can linger
|
|
17
|
+
on the registry's app_auth record from a deleted/recreated app —
|
|
18
|
+
`clerk apps list` is the source of truth for what's real.
|
|
10
19
|
|
|
11
20
|
## Steps
|
|
12
21
|
|
|
13
|
-
1.
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`
|
|
22
|
+
1. Provision the Clerk app with the CLI (after the human's `clerk auth
|
|
23
|
+
login`):
|
|
24
|
+
- `npx clerk apps list` — confirm what exists (catch stale/leftover
|
|
25
|
+
instances); create if absent.
|
|
26
|
+
- `npx clerk apps create "<App Name>"` — creates the app + dev
|
|
27
|
+
instance (`pk_test_…`).
|
|
28
|
+
- `npx clerk link` (or `--app <id>`), then `npx clerk env pull` — pull
|
|
29
|
+
the dev `pk_test_…` into `.env.local`. Take only the publishable key.
|
|
30
|
+
2. Add the key to `odla.config.mjs`: `auth: { clerk: { dev: "<pk_test_…>" } }`
|
|
31
|
+
(inline is fine — it's public), then `npx @odla-ai/cli provision`
|
|
32
|
+
(idempotent) — calls setAuth for the dev env; the issuer is derived
|
|
33
|
+
from the key. **provision OVERWRITES any stale key** on public-config.
|
|
34
|
+
The worker fetches auth config from public-config at runtime, so a key
|
|
35
|
+
change propagates without a redeploy.
|
|
36
|
+
3. `npm i jose`. In the worker, use this documented verification pattern:
|
|
23
37
|
- fetch `<ODLA_PLATFORM>/registry/apps/<ODLA_APP_ID>/public-config?env=<ODLA_ENV>`,
|
|
24
38
|
cache ~5 min per isolate
|
|
25
39
|
- `createRemoteJWKSet(new URL(issuer + "/.well-known/jwks.json"))`,
|
|
26
40
|
cached per issuer
|
|
27
41
|
- `jwtVerify(token, jwks, { issuer })` on the `Authorization: Bearer`
|
|
28
42
|
header; pass `{ sub, email }` to routes
|
|
29
|
-
- ensure the Clerk session token includes an email claim
|
|
30
|
-
|
|
43
|
+
- ensure the Clerk session token includes an email claim if routes
|
|
44
|
+
need email: try `npx clerk config schema --keys session` then
|
|
45
|
+
`npx clerk config patch --json '{…}'`; else set it in the dashboard
|
|
46
|
+
(Sessions → customize session token) to
|
|
47
|
+
`{"email": "{{user.primary_email_address}}"}`
|
|
31
48
|
4. Gate at least one `/api/*` route on a verified user; return 401
|
|
32
49
|
otherwise.
|
|
33
50
|
5. Add Clerk sign-in to the site pages. For a React/Preact site, use
|
|
@@ -36,17 +53,86 @@ they will ever paste into chat. Never ask for `sk_…` or `whsec_…`.
|
|
|
36
53
|
clerk-js (no clerk-react), loads clerk-js as a lazy chunk, and themes
|
|
37
54
|
to odla-ui via `clerkAppearanceFromTokens()`. Otherwise drive ClerkJS
|
|
38
55
|
directly with the publishable key from public-config, or use Clerk's
|
|
39
|
-
hosted pages.
|
|
56
|
+
hosted pages. For a **vanilla/static site**, load Clerk's browser bundle
|
|
57
|
+
with the key on the tag — it hot-loads `window.Clerk` as a ready
|
|
58
|
+
*instance* (not a constructor): `<script src="https://<frontend-api>/
|
|
59
|
+
npm/@clerk/clerk-js@5/dist/clerk.browser.js" data-clerk-publishable-key=
|
|
60
|
+
"<pk>">` then `await window.Clerk.load()`. The `<frontend-api>` is the
|
|
61
|
+
host encoded in the pk. Then mount `Clerk.mountSignIn(el, { appearance })`
|
|
62
|
+
and attach `Clerk.session.getToken()` as the `Bearer` on `/api/*` calls.
|
|
40
63
|
6. Deploy dev (`npm run deploy:app:dev`).
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
Clerk is the **source of truth** for identity; odla-db keeps a mirror in
|
|
66
|
+
`$users`. Shipping login (this phase) is enough to gate routes. Mirroring
|
|
67
|
+
users into `$users` (svix webhook, "full" mode) is a **separate, optional
|
|
68
|
+
next step — see phase-3b-user-sync.md** — not needed to ship login.
|
|
69
|
+
|
|
70
|
+
## Shape the instance with the Clerk CLI (optional, for invite-only sites)
|
|
71
|
+
|
|
72
|
+
The same `clerk` CLI configures the instance as code (per app, from any
|
|
73
|
+
directory with `--app <id>`) — no dashboard clicking. For a members-only
|
|
74
|
+
site you typically want email-only + no social SSO + an allowlist:
|
|
75
|
+
|
|
76
|
+
- Inspect: `npx clerk config schema --keys session auth_email auth_password
|
|
77
|
+
auth_access_control` ; pull current with `npx clerk config pull`.
|
|
78
|
+
- Apply a partial patch (dry-run first with `--dry-run`):
|
|
79
|
+
`npx clerk config patch --json '{"auth_password":{"enabled":false},
|
|
80
|
+
"connection_oauth_google":{"enabled":false},
|
|
81
|
+
"auth_access_control":{"allowlist_enabled":true,
|
|
82
|
+
"allowlist_blocklist_enforced_on_sign_in":true}}'`
|
|
83
|
+
(email-code sign-in stays on via `auth_email`). A committed
|
|
84
|
+
`clerk-config.json` + `clerk config patch --file` keeps it reproducible and
|
|
85
|
+
reusable across the dev and prod instances.
|
|
86
|
+
- Invite-gate sign-ups by adding allowed emails to the allowlist:
|
|
87
|
+
`npx clerk api /allowlist_identifiers -X POST -d
|
|
88
|
+
'{"identifier":"<email>","notify":false}'` (per app with `--app <id>`).
|
|
89
|
+
- The email session-token claim (Phase 3 step 3) is just another patch:
|
|
90
|
+
`clerk config patch --json '{"session":{"claims":{"email":
|
|
91
|
+
"{{user.primary_email_address}}"}}}'`.
|
|
92
|
+
|
|
93
|
+
## Match the site's existing look and feel (do NOT invent a style)
|
|
94
|
+
|
|
95
|
+
Every visible element you add — the sign-in gate, any nav/top bar, an
|
|
96
|
+
admin console, cross-links — MUST look like it was always part of the
|
|
97
|
+
site. A bolted-on or default-styled element is a defect even if it
|
|
98
|
+
works. Before writing any markup:
|
|
99
|
+
|
|
100
|
+
1. **Read the site's design system first.** Find its stylesheet(s) and
|
|
101
|
+
shared components (header/nav/footer). Note the exact tokens: fonts
|
|
102
|
+
(heading vs body), color palette, logo/wordmark markup, spacing,
|
|
103
|
+
button styles, radii. Reuse them verbatim — same classes, or copy the
|
|
104
|
+
exact values. Never approximate; "close enough" reads as wrong (a
|
|
105
|
+
32px logo next to the real 36px one, the accent colour off by a shade,
|
|
106
|
+
a heading in the body font — all instantly visible).
|
|
107
|
+
2. **Reuse existing components.** If the site has a `<site-header>`/nav
|
|
108
|
+
component, extend or replicate it exactly rather than inventing a new
|
|
109
|
+
bar. New surfaces (admin, member area) should share ONE bar with the
|
|
110
|
+
rest of the site — identical logo size, wordmark size, padding, max
|
|
111
|
+
width.
|
|
112
|
+
3. **Theme third-party widgets to the brand.** A vendor's default chrome
|
|
113
|
+
(Clerk's card + purple user-button avatar, Stripe's default inputs)
|
|
114
|
+
is off-brand by definition. Use its theming API — Clerk `appearance`:
|
|
115
|
+
strip the card to blend into a host card, set `colorPrimary`/fonts/
|
|
116
|
+
radii to the site tokens, hide redundant headers, `overflow:visible`
|
|
117
|
+
so it can't clip its own labels. Don't ship a raw vendor widget, and
|
|
118
|
+
don't show an off-brand account avatar when a plain "Sign out" fits.
|
|
119
|
+
4. **Bump the cache-bust version** (`?v=`) on any shared asset you change
|
|
120
|
+
(stylesheet, header script), on every page that embeds it — otherwise
|
|
121
|
+
cached visitors keep the old copy and "your fix didn't deploy".
|
|
122
|
+
5. **You cannot see the render — so never claim a visual match.** State
|
|
123
|
+
what you changed and ask the human to confirm; expect a round or two
|
|
124
|
+
of "the logo's too small / it clips / that colour is wrong" and treat
|
|
125
|
+
that as the normal loop, not something to argue with.
|
|
44
126
|
|
|
45
127
|
## Verification checklist
|
|
46
128
|
|
|
47
129
|
- [ ] Unauthenticated curl to the gated route → 401
|
|
48
130
|
- [ ] Signed-in browser session reaches the gated route
|
|
49
|
-
- [ ] `npx odla-ai smoke --env dev` still passes
|
|
131
|
+
- [ ] `npx @odla-ai/cli smoke --env dev` still passes
|
|
132
|
+
- [ ] Every added UI element matches the site's existing look (fonts,
|
|
133
|
+
palette, logo, spacing) — human-confirmed, not self-asserted
|
|
134
|
+
- [ ] Post-login redirect lands on a real URL (mind trailing-slash /
|
|
135
|
+
directory-index routing), not a 404
|
|
50
136
|
- [ ] MIGRATION.md updated (gated routes, Clerk instance name)
|
|
51
137
|
|
|
52
138
|
Rollback: remove the auth layer / ungate the route; nothing outside dev
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Phase 3b — Mirror Clerk users into odla-db (`$users`)
|
|
2
|
+
|
|
3
|
+
Optional, and staged: ship login first (Phase 3), then turn this on. **Clerk is
|
|
4
|
+
the source of truth for identity.** odla-db keeps a *mirror* of your users in the
|
|
5
|
+
reserved `$users` namespace so rules and app data can reference them. You do not
|
|
6
|
+
build the mirror yourself — the platform does it. This phase enables it.
|
|
7
|
+
|
|
8
|
+
Goal: signed-in users appear as rows in the app's `$users`, kept in sync with
|
|
9
|
+
Clerk (create/update/delete). Dev first, then prod.
|
|
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.
|
|
14
|
+
|
|
15
|
+
## How `$users` fills — two paths
|
|
16
|
+
|
|
17
|
+
1. **Browser talks to odla-db directly with its Clerk session token.** The
|
|
18
|
+
platform verifies the JWT and auto-creates a basic `$users` row (`id` = the
|
|
19
|
+
Clerk `sub`). No setup. This only happens for *user-token* access.
|
|
20
|
+
2. **The Clerk webhook (full profile).** Clerk pushes `user.created/updated/
|
|
21
|
+
deleted` to odla-db, which upserts the full profile (`email`, `name`,
|
|
22
|
+
`imageUrl`) and tombstones on delete. Works for **any** app.
|
|
23
|
+
|
|
24
|
+
**If your app is worker-mediated** (the worker holds the app's admin/scoped key
|
|
25
|
+
and calls odla-db itself — the Phase-2/3 pattern), that scoped credential is
|
|
26
|
+
**not projected into `$users`**. So the webhook (path 2) is how you get `$users`.
|
|
27
|
+
Enable it here.
|
|
28
|
+
|
|
29
|
+
## Enable the webhook
|
|
30
|
+
|
|
31
|
+
1. **Create the endpoint in Clerk** (per instance — dev instance → dev tenant;
|
|
32
|
+
prod instance → prod tenant). URL:
|
|
33
|
+
`https://db.odla.ai/webhooks/clerk/<ODLA_TENANT>`
|
|
34
|
+
(the odla tenant, e.g. `<appId>--dev` in dev, `<appId>` in prod). Subscribe to
|
|
35
|
+
`user.created`, `user.updated`, `user.deleted`. Clerk returns a signing secret
|
|
36
|
+
`whsec_…`. Prefer the Clerk CLI if it exposes webhook creation; otherwise the
|
|
37
|
+
Clerk dashboard → Webhooks → Add Endpoint. (`whsec_` is a real secret — never
|
|
38
|
+
print/commit it; it is the only thing you paste, and it goes into the vault,
|
|
39
|
+
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`).
|
|
43
|
+
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.
|
|
46
|
+
|
|
47
|
+
## Verify
|
|
48
|
+
|
|
49
|
+
- Sign in (or edit the user in Clerk) → a `$users` row appears (check Studio, or
|
|
50
|
+
a worker route that runs `db.query({ $users: {} })`).
|
|
51
|
+
- Delete the Clerk user → the row is tombstoned, not hard-deleted.
|
|
52
|
+
- Rules can now gate on it: `$users` is platform-managed (a browser may view its
|
|
53
|
+
own row but cannot write `$users`). Your app's own tables (members, roles,
|
|
54
|
+
status) layer on top, keyed by the Clerk `sub` or `email`.
|
|
55
|
+
|
|
56
|
+
Done when: a real sign-in produces a `$users` row in the dev tenant. Repeat the
|
|
57
|
+
endpoint + secret for the prod instance/tenant at Phase 5.
|