@odla-ai/cli 0.17.1 → 0.18.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/cli",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
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",
@@ -9,6 +9,7 @@ description: >
9
9
  runbookOrder:
10
10
  - references/build.md
11
11
  - references/sdks.md
12
+ - references/pm.md
12
13
  - references/co-owners.md
13
14
  ---
14
15
 
@@ -41,6 +42,13 @@ State which path you're taking and what you'll build in one line; get a nod.
41
42
  A fresh odla device request also needs the existing account email via
42
43
  `--email` or `ODLA_USER_EMAIL`; email is a non-secret identifier. Never ask
43
44
  for an odla password, Clerk token, or browser session.
45
+ **That is the human's odla ACCOUNT email — never infer it from `git config
46
+ user.email`, `gh`, or a commit author.** A git identity is frequently a
47
+ different address (a `+alias`, a `users.noreply.github.com`), and the
48
+ registry matches the exact string. An address that is not a registered
49
+ account does NOT error: it mints an indistinguishable decoy, so the CLI
50
+ prints a normal approval URL that can never be approved by anyone. Ask the
51
+ human, or reuse the address already in `.odla/handshake.local.json`.
44
52
  2. **Sandbox before live.** Every app has two databases on production odla.ai:
45
53
  a sandbox (env `dev`, tenant `<appId>--dev`) and a live one (env `prod`,
46
54
  tenant `<appId>`). "prod" names the app's live DATABASE, never a different
@@ -129,6 +137,95 @@ guessing which platform/credential steps need manual work. Then:
129
137
  show the human, then `npx @odla-ai/cli provision --yes --push-secrets` and
130
138
  `npx wrangler deploy`.
131
139
 
140
+ ## Track the work in PM, as you go
141
+
142
+ odla has a project manager for agents — conformance goals, a kanban board, a
143
+ decision log, and bugs, shared with the human and with every other agent working
144
+ on the app. It is platform-side: nothing to install or provision. Start a session
145
+ by reading it, and keep it current while you build:
146
+
147
+ ```cmd
148
+ npx @odla-ai/cli pm task list --app <appId> --column doing
149
+ ```
150
+
151
+ Record a **decision** the moment you make one (with what it rules out), file a
152
+ **bug** the moment you notice one, and move a **task** when the work moves. A
153
+ decision you don't record is one the next agent re-litigates. Full command set,
154
+ conventions, and the read-first session opener: `references/pm.md`.
155
+
156
+ ## Operational runbooks live in the database, not in this bundle
157
+
158
+ This bundle covers **setup**, pinned to the CLI version you installed. Operational
159
+ procedures — releasing, backups, database and code-runtime operations — are stored
160
+ in odla and change without a CLI release. Read one at the moment you follow it:
161
+
162
+ ```cmd
163
+ npx @odla-ai/cli runbook list
164
+ ```
165
+
166
+ ```cmd
167
+ npx @odla-ai/cli runbook get release
168
+ ```
169
+
170
+ `runbook get` prints raw markdown, so pipe it or read it directly. Do not follow a
171
+ multi-step platform procedure from memory, and never scrape odla.ai for one — the
172
+ CLI reads the same content authenticated and current.
173
+
174
+ Answering a question rather than following a procedure? `ask` returns a written,
175
+ cited answer; `search` returns the **sections** behind it, each with its source
176
+ and the command to pull the rest:
177
+
178
+ ```cmd
179
+ npx @odla-ai/cli runbook ask "how do I roll back a bad publish?"
180
+ ```
181
+
182
+ ```cmd
183
+ npx @odla-ai/cli runbook search "roll back a bad publish"
184
+ ```
185
+
186
+ Cite what you quote — `release#rolling-back-a-bad-publish (v3)` — so the reader
187
+ can check it. An agent holding the PM skill has the same reach as tools:
188
+ `search_runbooks` (question in, cited sections out), `read_runbook` (whole
189
+ runbook, or one `section`), and `amend_runbook` for this project's own.
190
+
191
+ **A runbook is half the answer.** It carries the procedure — the order, the
192
+ gates, the commands. What an export takes, returns, and guarantees is the JSDoc
193
+ on that symbol: shipped in the installed package's `dist/*.d.ts` and rendered at
194
+ `https://odla.ai/docs/packages/<pkg>`. When a step calls an odla API, take the
195
+ step from the runbook and the arguments from the JSDoc. Never fill in a
196
+ signature, a default, or a return shape from memory — you do not have the
197
+ installed version in front of you, and the runbook is not claiming to describe it.
198
+
199
+ ### After you change something, ask what you invalidated
200
+
201
+ ```cmd
202
+ npx @odla-ai/cli runbook impact
203
+ ```
204
+
205
+ It diffs your working tree against `origin/main` (`--base <ref>` for anything
206
+ else, uncommitted work included), works out which surfaces moved — the package,
207
+ the exports whose declaration or **JSDoc** changed, the console area — and names
208
+ the runbooks whose steps describe them, with the command to read each one.
209
+
210
+ Run it after touching an exported API, a doc comment, a package version, or a
211
+ Studio surface. Editing a JSDoc block is exactly the moment a procedure that
212
+ quotes that API can go stale, and nothing else will catch it: a runbook lives in
213
+ the database, so no build, test, or CI gate in the repository can see it drift
214
+ from your code. Reread what it names and fix any step your change made wrong,
215
+ in the same piece of work.
216
+
217
+ Found a wrong step? Fix an app runbook directly. For a platform runbook,
218
+ `runbook edit` requests an admin-approved capability — a plain handshake token is
219
+ never admin, and `npx @odla-ai/cli whoami` tells you what you hold.
220
+
221
+ ⏸ That request prints an approval block with a URL on its own line. **Relay that
222
+ URL to the human verbatim, immediately, before you wait on anything.** You cannot
223
+ approve it yourself — the grant is issued to you, not by you — and the command
224
+ blocks until a signed-in admin approves it. The browser launch is best-effort and
225
+ often shows no tab, so the printed URL is the only thing the human can rely on.
226
+ If approval is not available, comment on the runbook instead so an admin can fix
227
+ it later.
228
+
132
229
  `npx @odla-ai/cli doctor` is an offline config check anytime;
133
230
  `npx @odla-ai/cli smoke --env dev` verifies a live deployment.
134
231
  Use `calendar status --json` for safe connection state (`bookable`, booking
@@ -163,6 +260,9 @@ code, paste a publishable key, run a command) — and wait for a nod.
163
260
  exported TypeScript declarations/JSDoc (resolve entry points through its
164
261
  `package.json` `exports`). Rendered references are also public at
165
262
  `https://odla.ai/docs/packages/<pkg>`.
263
+ - `references/pm.md` — the project manager for agents: goals, board, decisions,
264
+ bugs. What to file where, the `pm` command set, and how to open and close a
265
+ session against it.
166
266
  - `references/co-owners.md` — sharing one app's db and tooling across a team:
167
267
  `app owners add/list/remove`, and how each co-owner self-provisions their own
168
268
  credentials (dev and the shared prod database) without any secret handoff.
@@ -0,0 +1,109 @@
1
+ # PM — the project's memory, for agents
2
+
3
+ odla ships a project manager built for the agents doing the work:
4
+ **conformance goals**, a kanban **board**, a **decision** log, and **bugs**. It is
5
+ not a status report for a human to read later — it is your working memory across
6
+ sessions, and the place another agent (or the human) picks up what you learned.
7
+
8
+ Use it. A decision you don't record is one the next agent re-litigates; a bug you
9
+ only mention in chat is a bug nobody fixes.
10
+
11
+ ## What it is, mechanically
12
+
13
+ - PM data lives on the **platform**, in odla's own shared store — *not* in the
14
+ app's database. There is nothing to install, schema, or provision, and no
15
+ entry in `odla.config.mjs`.
16
+ - Every item is tagged with an **appId**, and access rides **app co-ownership**:
17
+ you see and write PM items for exactly the apps you co-own
18
+ (`references/co-owners.md`). Co-own three apps and one `pm bug list` spans all
19
+ three.
20
+ - Auth is the same developer device grant the rest of the CLI uses. The first
21
+ `pm` command in a fresh checkout may print an approval code — relay it to the
22
+ human verbatim, same as `provision`.
23
+ - The human's view of the same data is **https://odla.ai/studio/pm** (and each
24
+ app's **Project** tab in Studio): the board is drag-and-drop there, and every
25
+ section filters by project.
26
+
27
+ ## The four things, and when each one is right
28
+
29
+ | You have | File it as | Because |
30
+ | --- | --- | --- |
31
+ | an outcome that must be true, plus the test that proves it | **goal** | it carries `--proof`; `pm goal done` means the proof passed, not "I think it works" |
32
+ | a specific unit of work | **task** | it has a board column, so the human can see what is moving |
33
+ | a choice that closes off alternatives | **decision** | the log is full-text searchable, so the *next* agent finds the why |
34
+ | something that is broken | **bug** | it has a severity and survives the session that noticed it |
35
+
36
+ Do not file a task per keystroke. One task per thing a human would recognise as a
37
+ piece of work; one decision per choice you would otherwise bury in a commit
38
+ message; one bug per defect.
39
+
40
+ ## The commands
41
+
42
+ Entity is `goal | task | decision | bug`; every command takes `--json` for
43
+ machine-readable output.
44
+
45
+ ```cmd
46
+ npx @odla-ai/cli pm bug add --app <appId> --title "Checkout 500s on retry" --severity high --desc "…"
47
+ ```
48
+
49
+ ```cmd
50
+ npx @odla-ai/cli pm bug list --app <appId> --status open
51
+ ```
52
+
53
+ ```cmd
54
+ npx @odla-ai/cli pm task set <id> --column doing
55
+ ```
56
+
57
+ ```cmd
58
+ npx @odla-ai/cli pm decision list --q "stripe"
59
+ ```
60
+
61
+ - `add` needs `--app <appId>` and `--title`; it takes the entity's own fields
62
+ (`--proof`, `--target` for goals; `--column`, `--goal`, `--assignee`, `--due`
63
+ for tasks; `--body` for decisions; `--severity`, `--desc` for bugs).
64
+ - `list` filters with `--status`, `--severity`, `--column`, `--assignee`,
65
+ `--goal`, full-text `--q`, and pages with `--limit` / `--offset`. **With no
66
+ `--app` it spans every project you co-own** — that is the cross-project view,
67
+ and it is usually the one you want when you start a session.
68
+ - `get <id>` prints one item; `set <id> --<field> <value>` patches it
69
+ (`--no-<field>` clears an optional field); `done <id>` is the shortcut for the
70
+ terminal state (goal → met, task → done, decision → accepted, bug → fixed).
71
+ - `comment <id> --body "…"` and `comments <id>` are the item's discussion thread.
72
+ Status and column changes post there automatically, so the thread is the audit
73
+ trail — the human sees what you did without asking.
74
+ - `rm <id>` deletes. Prefer `done` or a status change; deletion loses the thread.
75
+
76
+ ## Working with it
77
+
78
+ **At the start of a session**, read before you write:
79
+
80
+ ```cmd
81
+ npx @odla-ai/cli pm task list --app <appId> --column doing
82
+ ```
83
+
84
+ then `pm bug list --app <appId> --status open` and
85
+ `pm decision list --app <appId> --limit 10`. That is the state of the project in
86
+ three commands — including anything a *different* agent left behind.
87
+
88
+ **While you build:**
89
+
90
+ - Move the card when the work moves — `pm task set <id> --column doing` when you
91
+ start, `pm task done <id>` when it is really done (tests green, not "written").
92
+ - Record a decision the moment you make it, with what it rules out:
93
+ `pm decision add --app <appId> --title "Bun over tsx for the worker build"
94
+ --body "…"`. This is the single highest-value thing you can do here.
95
+ - File a bug the moment you notice it, even mid-task, even if it is not yours.
96
+ `--severity critical|high|medium|low`. Then keep going — the bug is captured,
97
+ so it does not have to derail what you are doing.
98
+ - A goal is how you say what "done" means before you start:
99
+ `pm goal add --app <appId> --title "Checkout survives a retry storm" --proof
100
+ "test/checkout-retry.test.ts"`. Mark it met only when that proof passes.
101
+
102
+ **Never** put a secret, a token, or a credential in a title, body, description,
103
+ or comment. PM items are shared with every co-owner of the app.
104
+
105
+ ## Checkpoints
106
+
107
+ PM writes are not destructive and need no approval — file freely. Deleting an
108
+ item (`pm … rm`) and closing something you did not open are the two calls to
109
+ put to the human first.
@@ -12,6 +12,7 @@ default-deny `src/odla/rules.mjs`. Isomorphic (browser + Worker).
12
12
 
13
13
  ```ts
14
14
  // browser client — token from your IdP (Clerk):
15
+ import { OdlaProvider, useQuery, useTransact } from "@odla-ai/db/preact";
15
16
  const db = init({ appId, endpoint, getToken });
16
17
  const { data } = useQuery({ notes: { $: { order: { createdAt: "asc" } } } });
17
18
  transact(db.tx.notes[crypto.randomUUID()].update({ text, createdAt: Date.now() }));
@@ -20,7 +21,7 @@ transact(db.tx.notes[crypto.randomUUID()].update({ text, createdAt: Date.now() }
20
21
  Worker/admin side: `init({ appId: tenantId, adminToken: env.ODLA_API_KEY, endpoint })`
21
22
  — the admin key bypasses rules, so a worker-mediated backend needs none.
22
23
 
23
- ## @odla-ai/auth-clerk — sign-in (Clerk; runs on Preact or React)
24
+ ## @odla-ai/auth-clerk — native Preact sign-in (Clerk)
24
25
 
25
26
  ```tsx
26
27
  <ClerkGate publishableKey={pk} appearance={clerkAppearanceFromTokens()}>
@@ -158,10 +159,12 @@ injected isolated executor and never falls back to the host shell.
158
159
  - **@odla-ai/chapter** — membership sites from one `defineChapter()` config:
159
160
  generates the odla-db schema + deny-all rules + group seed + provisioning
160
161
  integration, and ships the worker (join/apply, Stripe membership, Google
161
- booking, member area, exactly-once lifecycle email, CRM projection) plus a UI
162
- kit. Composable: `chapterWorker({ chapter, routes })` runs host routes before
163
- the built-ins and shares its auth context, and `./ui/member` is react-only
164
- (no Clerk SDK) while `./ui/admin` is the gated console. **If a migration target
162
+ booking, member area, exactly-once lifecycle email, CRM projection, and
163
+ leader follower record delivery) plus a UI kit. Composable:
164
+ `chapterWorker({ chapter, routes })` runs host routes before the built-ins and
165
+ shares its auth context; `<ChapterAdmin chapter={chapter}/>` derives the
166
+ familiar brand/section shell and uses host-independent `?tab=` routing;
167
+ `./ui/member` is Preact-only (no Clerk SDK). **If a migration target
165
168
  is a membership site, start here instead of hand-authoring schema** — see
166
169
  `odla-migrate/references/phase-2-chapter.md`.
167
170
  - **@odla-ai/ui** — design system: CSS tokens, five themes, component styles,
@@ -42,7 +42,10 @@ installed skill at `../odla/SKILL.md` instead.
42
42
  references/secrets-map.md BEFORE any command that touches a credential.
43
43
  A fresh device request requires the existing odla account email via
44
44
  `--email` or `ODLA_USER_EMAIL`; email is a non-secret identifier. Never ask
45
- for an odla password, Clerk token, or browser session.
45
+ for an odla password, Clerk token, or browser session. That is the human's
46
+ odla ACCOUNT email — never infer it from `git config user.email`, `gh`, or a
47
+ commit author; a wrong address silently mints an unapprovable request. See
48
+ references/troubleshooting.md.
46
49
  4. Never `git add -A` without reading `git status` first.
47
50
  5. Never widen a db rule to silence a 403 — default-deny is the design.
48
51
  Any rules change is a human checkpoint.
@@ -36,8 +36,14 @@ hand-author only the namespaces it does not.
36
36
  hands each the same context (`ctx.verifyUser`, `ctx.makeDb`, `ctx.roleFor`,
37
37
  `ctx.isAdmin`), so you never verify a JWT twice. Do NOT hand routes over to
38
38
  chapter in the same change as the framework swap.
39
- 5. **Override rather than inherit wherever local behavior was a decision.**
40
- 6. `npx @odla-ai/cli doctor` `provision --dry-run` (show the human) → provision.
39
+ 5. **Adopt the host-independent console shell.** Import
40
+ `@odla-ai/ui/themes/salt/app.css` once, then render
41
+ `<ChapterAdmin chapter={chapter}/>` at the existing `/admin/` mount. The
42
+ config supplies brand tokens and the standard sections. Navigation uses
43
+ `?tab=` by default, so do not add `/admin/*` SPA rewrites to an ordinary
44
+ static site. Legacy path links remain readable.
45
+ 6. **Override rather than inherit wherever local behavior was a decision.**
46
+ 7. `npx @odla-ai/cli doctor` → `provision --dry-run` (show the human) → provision.
41
47
 
42
48
  The same parity-before-deletion pattern works for UI: render the packaged
43
49
  component beside yours, assert byte-identical markup, then delete yours and keep
@@ -64,18 +70,23 @@ These bite silently; no smoke test catches them.
64
70
  invitation**. Swapping in chapter's route while expecting server-side create
65
71
  loses it silently unless you set `account: "create"` — the applicant finds out
66
72
  when they cannot sign in.
67
- - **CRM projection points.** chapter projects on application submit, not on
68
- booking or webhook status change. Mirroring pipeline stage into the CRM means
69
- keeping those routes.
73
+ - **CRM authority.** `applications` remains authoritative for member lifecycle;
74
+ `crm_record` mirrors identity, stage, billing, and Clerk linkage. Leader
75
+ follower delivery intentionally copies allowlisted record fields only, not
76
+ another site's pipeline/account/billing authority.
70
77
  - **Route names.** chapter serves `/api/config`, `/api/join-config`. Alias legacy
71
78
  names in ~4 lines as host routes rather than rewriting pages.
79
+ - **Follower record types.** A follower rejects a pushed business/deal if its own
80
+ CRM does not declare that type and fields. Add the type to the follower config
81
+ and expose it through the standard console before enabling the target in the
82
+ leader.
72
83
 
73
84
  ## Install notes
74
85
 
75
86
  - **A worker-only adoption does not need `@odla-ai/auth-clerk`.** The worker
76
87
  verifies Clerk JWTs with `jose` via `ctx.verifyUser`. Install auth-clerk only
77
88
  for `@odla-ai/chapter/ui/admin` (the Clerk-gated console) or the themed sign-in
78
- components; `@odla-ai/chapter/ui/member` needs nothing but react.
89
+ components; `@odla-ai/chapter/ui/member` needs only Preact.
79
90
  - **`--legacy-peer-deps` is a diagnostic, not a setting.** It suppresses exactly
80
91
  the peer error that tells you a pair is unsupported. If an install errors, read
81
92
  it. A site that left the flag on ran an unsupported ui/crm pair for a day with
@@ -108,10 +108,10 @@ this app's own Worker endpoints and may import pure helpers from
108
108
  7. Frontend. Two supported shapes:
109
109
  - **`@odla-ai/ui` SlotPicker** — renders the slots response directly
110
110
  (`{ slots, timezone, onSelect }`). On a non-framework static site use a
111
- Preact island: a small entry that mounts `<SlotPicker>` into a div,
112
- bundled with esbuild as an IIFE with `react`/`react-dom` aliased to
113
- `preact/compat`, committed as a static asset next to the site's other
114
- assets.
111
+ native Preact island: a small entry that imports `render` from `preact`,
112
+ mounts `<SlotPicker>` into an empty div, and bundles with
113
+ `jsxImportSource: "preact"` as an IIFE committed next to the site's other
114
+ assets. Do not install React or add compatibility aliases.
115
115
  - **Plain fetch + buttons** — `fetch("/api/booking/slots")`, render times
116
116
  with `formatBookingRange` from `@odla-ai/calendar/client`, POST the
117
117
  chosen slot.
@@ -52,10 +52,10 @@ authoritative for commands and flags.
52
52
  `{"email": "{{user.primary_email_address}}"}`
53
53
  4. Gate at least one `/api/*` route on a verified user; return 401
54
54
  otherwise.
55
- 5. Add Clerk sign-in to the site pages. For a React/Preact site, use
55
+ 5. Add Clerk sign-in to the site pages. For a Preact site, use
56
56
  `@odla-ai/auth-clerk`: wrap the app in `<ClerkGate publishableKey>`
57
57
  (key from public-config) and drop in `<SignIn />`. It runs on vanilla
58
- clerk-js (no clerk-react), loads clerk-js as a lazy chunk, and themes
58
+ clerk-js (no React wrapper), loads clerk-js as a lazy chunk, and themes
59
59
  to odla-ui via `clerkAppearanceFromTokens()`. Otherwise drive ClerkJS
60
60
  directly with the publishable key from public-config, or use Clerk's
61
61
  hosted pages. For a **vanilla/static site**, load Clerk's browser bundle
@@ -46,6 +46,29 @@ appearing), wait for them to approve, then re-run the same command; it
46
46
  resumes the same code and collects the token. Do not loop unattended
47
47
  retries; each handshake lives ~10 minutes.
48
48
 
49
+ ## The approval URL says no request matches, or is never approvable
50
+
51
+ Symptom: the human opens the printed `/studio?code=XXXX-XXXX` link and Studio
52
+ says "No request for your account matches …", or they approve nothing because
53
+ no request card appears — while the CLI polls until the handshake expires.
54
+
55
+ Cause: `--email` / `ODLA_USER_EMAIL` was not the human's odla **account**
56
+ email. The registry matches the exact normalized string (`trim` + `lowercase`
57
+ only), so a `+alias`, a `users.noreply.github.com` address, or a second
58
+ personal address is a different identity. An address that resolves to no
59
+ account is **not** an error — to keep anyone from discovering which emails are
60
+ registered, the request is recorded as an inert decoy bound to no owner, which
61
+ no one can preview or approve. Every surface therefore looks normal: the CLI
62
+ prints a URL, polls, and times out.
63
+
64
+ Fix: confirm the address with the human and re-run. **Never derive it from
65
+ `git config user.email`, `gh`, or a commit author** — that is the usual source
66
+ of this, because a git identity is routinely a different address from the odla
67
+ account. If `.odla/handshake.local.json` exists, the address it records is
68
+ known-good; reuse that. Repeated failures with the same address mean it is not
69
+ registered — the human must add it to their account (or use the registered
70
+ one), and no amount of retrying will help.
71
+
49
72
  ## `smoke` fails: missing credentials
50
73
 
51
74
  Cause: `.odla/credentials.local.json` absent, for a different app id,
@@ -95,3 +95,17 @@ suggest one (metric + threshold + window).
95
95
  - **Say what you couldn't see.** Arbitrary custom attributes live in the trace
96
96
  payload, not the group-by dimensions; if the cohort is defined by a custom
97
97
  field, note that you inferred it from individual traces, not an aggregate.
98
+
99
+ ## Land the finding
100
+
101
+ A triage that ends in chat is lost by the next session. File what you found as a
102
+ bug against the app, with the cohort in the description and the trace id in a
103
+ comment:
104
+
105
+ ```cmd
106
+ npx @odla-ai/cli pm bug add --app <appId> --title "POST /pay 503s under retry" --severity high --desc "…"
107
+ ```
108
+
109
+ Severity follows the blast radius, not your confidence. The conventions and the
110
+ rest of the `pm` command set are in `.agents/skills/odla/references/pm.md`
111
+ (installed alongside this skill).