@odla-ai/cli 0.38.3 → 0.40.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/dist/index.js CHANGED
@@ -56,7 +56,7 @@ import {
56
56
  startHostedSecurityJob,
57
57
  surfacePaths,
58
58
  validateInvocation
59
- } from "./chunk-WJAN5CZ2.js";
59
+ } from "./chunk-2IHE5U3M.js";
60
60
  import {
61
61
  exitCodeFor,
62
62
  redactSecrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/cli",
3
- "version": "0.38.3",
3
+ "version": "0.40.0",
4
4
  "description": "Agent-operable CLI for odla provisioning, calendar consent and connection lifecycle, System AI administration, Worker secrets, security jobs, and smoke checks.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://odla.ai/docs/packages/cli",
@@ -61,7 +61,7 @@
61
61
  "@odla-ai/ai": "0.15.0",
62
62
  "@odla-ai/apps": "0.12.0",
63
63
  "@odla-ai/brand": "0.6.0",
64
- "@odla-ai/db": "0.11.0",
64
+ "@odla-ai/db": "0.12.0",
65
65
  "@odla-ai/security": "0.3.3"
66
66
  },
67
67
  "devDependencies": {
@@ -36,9 +36,12 @@ State which path you're taking and what you'll build in one line; get a nod.
36
36
 
37
37
  ## Non-negotiable rules
38
38
 
39
- 1. **Never print, paste, or commit a secret.** Never `cat` `.dev.vars`,
40
- `.odla/credentials.local.json`, or `.odla/dev-token.json` — use `ls -l` to
41
- confirm they exist (and are `0600`). The only value a human ever pastes is a
39
+ 1. **Never print, paste, or commit a secret.** Never `cat` `.dev.vars` or
40
+ anything under `~/.odla` (`device.json`, `session.json`,
41
+ `apps/<appId>/credentials.json`, `admin-token.local.json`) use `ls -l` to
42
+ confirm they exist (and are `0600`). Credentials live per machine, under
43
+ `~/.odla`, not per checkout; a repository copy from an older CLI is moved
44
+ there automatically on first run. The only value a human ever pastes is a
42
45
  Clerk **publishable** key (`pk_…`) — public by design.
43
46
  A fresh odla device request also needs the existing account email via
44
47
  `--email` or `ODLA_USER_EMAIL`; email is a non-secret identifier. Never ask
@@ -49,9 +52,9 @@ State which path you're taking and what you'll build in one line; get a nod.
49
52
  registry matches the exact string. An address that is not a registered
50
53
  account does NOT error: it mints an indistinguishable decoy, so the CLI
51
54
  prints a normal approval URL that can never be approved by anyone. Ask the
52
- human, or reuse the same-audience account identity already recorded with a
53
- collected developer token. Current clients never persist pending device
54
- codes in `.odla/handshake.local.json`.
55
+ human once. The CLI records the account an approval actually succeeded for
56
+ in `~/.odla/identity.json`, so later commands on this machine need no
57
+ `--email` at all. Current clients never persist pending device codes.
55
58
  2. **Sandbox before live.** Every app has two databases on production odla.ai:
56
59
  a sandbox (env `dev`, tenant `<appId>--dev`) and a live one (env `prod`,
57
60
  tenant `<appId>`). "prod" names the app's live DATABASE, never a different
@@ -71,10 +74,43 @@ State which path you're taking and what you'll build in one line; get a nod.
71
74
  platform/Google consent URL returned by odla; booking runs server-side
72
75
  through the SDK with the app's existing key, never from a browser.
73
76
 
74
- ## Agent handshake protocol
77
+ ## Enrol the machine once. Then stop asking.
75
78
 
76
- Use the current published CLI and suppress browser auto-launch. Start the exact
77
- authorization as one foreground tool process:
79
+ **This is step zero, and it is one command.** Run it before anything else that
80
+ touches odla:
81
+
82
+ ```bash
83
+ npx --yes @odla-ai/cli@latest device enroll --no-open --wait 600
84
+ ```
85
+
86
+ One browser approval, and no flags to remember. After it, every worktree on
87
+ this machine mints its own short-lived credentials with nobody's attention, for
88
+ every app the approver owns — including apps created later — carrying every
89
+ capability that approval was allowed to carry. Narrowing is deliberate:
90
+ `--app <id>` or `--capability <c>`, and the CLI says what that gave up. The deadline is a **gap, not a clock**: each use rolls it
91
+ forward, so continuous work never interrupts anyone.
92
+
93
+ A platform administrator can do the same across all of odla:
94
+
95
+ ```bash
96
+ npx --yes @odla-ai/cli@latest device enroll --platform-wide --device-ttl 6w --no-open --wait 600
97
+ ```
98
+
99
+ Check what this machine already holds before asking anyone for anything:
100
+
101
+ ```bash
102
+ npx --yes @odla-ai/cli@latest whoami # what it holds, and when it lapses
103
+ ```
104
+
105
+ Do **not** ask for capabilities one at a time, and do not re-enrol per worktree.
106
+ Both were the old shape and both mean interrupting a human who is usually not
107
+ there. If a command still starts a handshake, the CLI prints why and prints the
108
+ enrollment line that ends it — run that instead of approving repeatedly.
109
+
110
+ ## The approval itself
111
+
112
+ Whichever command needs it, the handshake protocol is the same. Start it as one
113
+ foreground tool process with browser auto-launch suppressed:
78
114
 
79
115
  ```bash
80
116
  npx --yes @odla-ai/cli@latest auth login --app <appId> --email <odla-account> --no-open --wait 600
@@ -88,8 +124,12 @@ the OS `open` command, use browser control, curl a handshake endpoint, create a
88
124
  shell polling/wait loop, detach the process, or start a substitute handshake.
89
125
  The device code exists only in the running CLI. If it exits 75, the old request
90
126
  cannot be collected; start one fresh foreground invocation and surface only its
91
- new URL. Apply the same `--no-open --wait 600` flags when another command, such
92
- as first provision, must request a different reviewed capability.
127
+ new URL.
128
+
129
+ Enrollment is the only human decision the CLI can obtain. Revoking a machine,
130
+ purging an app, transferring ownership, and rotating credentials still need a
131
+ signed-in human in Studio — no machine credential can do them, however wide its
132
+ approval was.
93
133
 
94
134
  ## Tooling sources
95
135
 
@@ -1,132 +1,115 @@
1
- # Reconnect an agent across worktrees and projects
1
+ # Keep one identity across worktrees, projects, and days
2
2
 
3
- Use this procedure when an agent must keep the same attributable identity while
4
- its short-lived device credential expires, or when it operates outside the
5
- checkout that first obtained the credential.
3
+ Use this when an agent must keep the same attributable identity while working in
4
+ more than one checkout, or across the expiry of a short-lived credential.
6
5
 
7
- ## Preconditions
6
+ ## The short version
8
7
 
9
- - The human approving the request owns or co-owns every selected project.
10
- - The handshake contains one deliberate handle suffix, such as `customer-app`
11
- or `release-reviewer`. The Registry places it under the approving human's
12
- verified namespace (for example, `@cory/customer-app`). The CLI derives this
13
- suffix deterministically from the exact project; SDK callers may explicitly
14
- request a different stable `agentHandle` for a cross-project agent.
15
- - No token, credential file, or browser session is copied between worktrees,
16
- machines, people, or chat messages.
8
+ Enrol the machine once. Nothing else on this page is required for the ordinary
9
+ case:
17
10
 
18
- ## 1. Save non-secret context outside the checkout
11
+ ```cmd
12
+ npx --yes @odla-ai/cli@latest device enroll --no-open --wait 600
13
+ ```
14
+
15
+ One human approval binds THIS machine. From then on every worktree on it mints
16
+ its own short-lived sessions with no browser, for every app the approver owns —
17
+ including apps created afterwards — carrying every capability that approval was
18
+ allowed to carry. The credential's deadline measures **silence**: each use rolls
19
+ it forward, so an agent that keeps working never has to interrupt anyone.
19
20
 
20
21
  ```cmd
21
- npx --yes @odla-ai/cli@latest context save delivery --platform https://odla.ai --app <appId> --env prod
22
- npx --yes @odla-ai/cli@latest context show --context delivery --json
22
+ npx --yes @odla-ai/cli@latest whoami # what this machine holds, and when it lapses
23
+ npx --yes @odla-ai/cli@latest device list # every machine you have enrolled
23
24
  ```
24
25
 
25
- The context stores only platform/app/environment metadata. Its private token
26
- cache lives under `~/.odla/profiles/delivery/`, independently of any repository
27
- or linked worktree. Select it explicitly with `--context delivery` or
28
- `ODLA_CONTEXT=delivery`; there is no ambient current context.
26
+ ## Where state lives
29
27
 
30
- ## 2. Establish the durable principal
28
+ Per machine, under `~/.odla`, never per checkout:
31
29
 
32
- Run the intended command with the named context. If no valid cached credential
33
- exists, the CLI starts the normal email-bound device handshake and prints the
34
- exact Studio approval URL.
30
+ | file | what it is |
31
+ |---|---|
32
+ | `device.json` | the durable enrollment secret. Never printed, never copied |
33
+ | `session.json` | the short-lived session minted from it, reused until it lapses |
34
+ | `identity.json` | the odla account an approval succeeded for — so no command has to ask again |
35
+ | `apps/<appId>/dev-token.json` | that app's cached developer credential |
36
+ | `apps/<appId>/credentials.json` | that app's local service credentials |
37
+ | `admin-token.local.json` | scoped capability grants, one file for the machine |
38
+ | `pm-context.json` | the PM project selected by `pm project use` |
39
+ | `profiles/<name>/` | the same, isolated per named operator context |
35
40
 
36
- ```cmd
37
- npx --yes @odla-ai/cli@latest whoami --context delivery --email <account-email> --json
38
- ```
41
+ A repository `.odla/` cache written by an older CLI is moved here on first run
42
+ and the old copy removed; the CLI says so on stderr. `.dev.vars` stays in the
43
+ checkout — it is a Worker's file, not a credential of yours.
44
+
45
+ `ODLA_HOME` relocates the whole store. The per-file `ODLA_DEVICE_CREDENTIAL`,
46
+ `ODLA_DEV_TOKEN_FILE`, `ODLA_ADMIN_TOKEN_FILE`, and `ODLA_CONTEXT_FILE`
47
+ overrides still win where they are read.
48
+
49
+ ## Preconditions for the approval
50
+
51
+ - The human approving owns or co-owns everything the request covers. `--all-apps`
52
+ is not platform-wide authority: the registry resolves it against that human's
53
+ live owned apps at every request, so an app transferred away stops being
54
+ reachable and one created tomorrow is already covered.
55
+ - `--email` is the signed-in **odla account**, never git or GitHub identity. An
56
+ address that is not a registered account does not error — it mints an
57
+ indistinguishable decoy whose approval URL nobody can approve. Ask the human,
58
+ or let `~/.odla/identity.json` answer.
59
+ - No token, credential file, or browser session is ever copied between
60
+ worktrees, machines, people, or chat messages.
39
61
 
40
- In Studio, review the exact code, requested handle, projects, and capabilities.
41
- The request is immutable: approval cannot edit its identity or grants. The
42
- handle—not the request label or local context name—is the durable agent identity
43
- key. Record the full verified handle in the agent handoff; never record the
44
- token. A normal CLI handshake derives the same handle from the configured
45
- project in every checkout and worktree.
62
+ ## Named contexts, for operators outside a checkout
46
63
 
47
- After approval and collection, verify the selected identity and context:
64
+ A named context isolates scope metadata and credential caches useful when one
65
+ machine acts for more than one platform or account, not for ordinary worktrees:
48
66
 
49
67
  ```cmd
68
+ npx --yes @odla-ai/cli@latest context save delivery --platform https://odla.ai --app <appId> --env prod
50
69
  npx --yes @odla-ai/cli@latest whoami --context delivery --json
51
- npx --yes @odla-ai/cli@latest pm next --context delivery --json
52
70
  ```
53
71
 
54
- If the agent request omits the handle, Registry deliberately creates a new
55
- principal for that handshake. An untrusted request label can therefore never
56
- select an existing agent.
57
-
58
- ## 3. Reconnect after expiry
59
-
60
- Re-run the command with the same named context and project. The cache keeps the
61
- approved account identifier and starts a fresh handshake when the token is
62
- expired. During human review:
63
-
64
- 1. verify the immutable request contains the expected existing handle suffix;
65
- 2. verify every exact requested project should receive access now;
66
- 3. review optional capabilities again; and
67
- 4. approve the request unchanged, or decline it with corrective feedback.
68
-
69
- Registry binds the new credential to the existing managed-agent principal and
70
- stores the human's reviewed exact-project set without activating it yet. The
71
- existing principal's authoritative name and purpose are retained; reusing its
72
- handle is not a profile-edit operation. Approval alone leaves the current
73
- credential and its project grants usable. When the replacement plaintext is
74
- successfully collected, Registry atomically clears that plaintext, retires
75
- older collected ordinary-handshake credentials, and replaces the principal's
76
- manager-issued project grants with the reviewed set. Newly selected projects
77
- are unavailable before collection, and omitted projects are revoked during the
78
- switch. If collection never succeeds, the working credential and grants are not
79
- stranded by the approval.
80
-
81
- Verify `whoami` reports the same principal/handle before resuming work. Ordinary
82
- reviewed project access includes `pm.execute` but not `pm.plan`: re-read `pm
83
- next` and claim the current Ready task revision, while leaving Ready approval to
84
- a human owner or explicit planner. Identity continuity alone does not preserve
85
- a stale claim or imply authority outside the newly reviewed grants.
86
-
87
- ## 4. Review remaining credentials
88
-
89
- Successful collection automatically retires the preceding collected ordinary
90
- handshake credential for that principal. Verify the old row in Studio's
91
- agent-credential inventory has a `system:handshake-reconnect:<handshake-id>`
92
- revocation reason. Do not delete a local cache as a substitute for this
93
- server-side evidence.
94
-
95
- The rotation set deliberately excludes manually minted credentials and
96
- separately scoped handshakes: those may represent another deliberate runtime,
97
- not a stale copy of this context. Review and revoke them explicitly when they
98
- are no longer intended.
99
-
100
- Project grants are still time-bounded. Collection makes the newly reviewed
101
- selection authoritative for the principal: selected projects receive new
102
- revisions and every live manager-issued project omitted from that selection is
103
- revoked. There is not yet a non-interactive `auth rotate` command or OS
104
- credential-store integration. The named context plus stable handle solves
105
- worktree/project portability and principal continuity, but it is not ambient or
106
- permanent authority.
72
+ It stores platform/app/environment metadata only, never credentials, under
73
+ `~/.odla/profiles/delivery/`. Select it explicitly with `--context delivery` or
74
+ `ODLA_CONTEXT`; there is no ambient current context.
75
+
76
+ ## When a human is asked again
77
+
78
+ Three cases, and only three:
79
+
80
+ 1. **The machine has never been enrolled.** Run the enrollment above.
81
+ 2. **The machine lapsed.** It went unused for longer than its window. This is
82
+ the one deliberate interruption in the design the platform's guaranteed
83
+ moment to tell the human what changed since they last looked. Re-enrol.
84
+ 3. **The command needs something the approval did not carry.** A platform scope
85
+ on an owner's enrollment, most often. The CLI says which scope and prints the
86
+ enrollment line that would carry it; an administrator's approval is required
87
+ for `--platform-wide`.
88
+
89
+ Anything else asking repeatedly is a defect file it with `odla-ai bug report`
90
+ rather than approving on a loop.
91
+
92
+ ## What enrollment still cannot do
93
+
94
+ Revoking a machine, permanently deleting an app, transferring ownership, and
95
+ rotating credentials require a signed-in human in Studio. No machine credential
96
+ performs them however wide its approval was, and no flag widens that.
97
+
98
+ Ordinary reviewed access includes `pm.execute` but not `pm.plan`: re-read `pm
99
+ next` and claim the current Ready task revision, leaving Ready approval to a
100
+ human owner or explicit planner.
107
101
 
108
102
  ## Recovery and audit evidence
109
103
 
110
- - `handshake_pending`: approve the existing code and rerun the same command; do
111
- not create a substitute request.
112
- - Wrong handle requested: decline it and correct the project/SDK request before
113
- starting a fresh handshake. If already approved, revoke the new credential in
114
- Studio; do not rename or merge principals by editing local metadata.
115
- - Missing project on first provision: approve the exact id. Collection records
116
- a one-time reservation plus the explicit `app.manage` provision capability;
117
- the credential creates only that app, Registry binds the grant to its new
118
- incarnation, and provision may continue through non-lifecycle configuration.
119
- Never pre-create a substitute or widen to all projects. Ownership and
120
- lifecycle operations still require the direct human session.
121
- - Approval returns 404: treat it as a Registry routing regression, not a missing
122
- project outcome. Preserve the code and pending state, report the incident,
123
- and retry the same request after Registry is healthy.
124
- - Project ownership/lifecycle changed after approval: collection expires the
125
- unusable replacement while preserving the current credential and grant set.
126
- Restore the intended project state, then start and review a fresh handshake.
127
- - Lost local cache: save/select the same non-secret context, complete a new
128
- handshake with the same handle, verify `whoami`, then revoke the lost
129
- credential from Studio.
130
-
131
- Audit evidence is the stable principal id/handle, the new credential id, and the
132
- monotonic project-grant revision shown by Registry—not possession of a token.
104
+ - **`handshake_pending`**: approve the existing code and rerun the same command;
105
+ never create a substitute request.
106
+ - **Exit 75**: the wait cap elapsed with the handshake still approvable. Rerun
107
+ it; the old code cannot be collected and a new one is issued.
108
+ - **A revoked or lapsed machine**: `whoami` says so. Re-enrol; do not delete a
109
+ local file as a substitute for revoking server-side.
110
+ - **Lost `~/.odla`**: re-enrol, verify `whoami`, then revoke the old machine in
111
+ Studio — the credential it holds is still live until you do.
112
+
113
+ Audit evidence is the stable principal id and handle, the device id shown by
114
+ `device list`, and the grant revision Registry reports — never possession of a
115
+ token.