octwin-cli 0.8.6 → 0.8.8

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/CHANGELOG.md CHANGED
@@ -5,6 +5,80 @@ Format: [Keep a Changelog](https://keepachangelog.com/) — newest first, bucket
5
5
  **Added · Changed · Deprecated · Removed · Fixed · Security**. The platform-wide view lives in the
6
6
  repo root [`CHANGELOG.md`](../../CHANGELOG.md); this file is the CLI-only cut that ships with the package.
7
7
 
8
+ ## [0.8.8] - 2026-09-06
9
+
10
+ ### Added
11
+ - **`octwin login` can sign you in through a browser — no token is pasted anywhere.** Run it with
12
+ no `--token` and it prints an approval link:
13
+
14
+ ```
15
+ Open this link to approve: https://app.octwin.ai/cli-login?code=WDJB-MJHT
16
+ Code shown on that page: WDJB-MJHT
17
+ ```
18
+
19
+ A workspace admin opens it, checks which machine is asking, and clicks Approve; the CLI collects a
20
+ 30-day token by itself and saves it exactly where `--token` would have. `octwin login --token oct_…`
21
+ is unchanged, and CI should still skip login entirely and set `PACK_PLATFORM_URL` + `PACK_TOKEN`.
22
+
23
+ **The wait is bounded and resumable, because this CLI is mostly driven by an agent.** An agent's
24
+ shell call is killed after about two minutes, and a wait that outlives its caller is worse than a
25
+ short one — the process dies mid-handshake and the code the human is still typing belongs to
26
+ nothing. So `login` waits 90 seconds, then exits **75** (`EX_TEMPFAIL`) with the handshake kept in
27
+ `~/.octwin/pending-login.json`; running it again resumes the **same** code, which the platform
28
+ keeps alive for ten minutes. `0` still means signed in and `1` still means failed, so an agent
29
+ branches on the exit code instead of parsing prose. `--wait <seconds>` overrides the 90 (`--wait 0`
30
+ prints the code and returns at once), and `--json` prints the same facts as an object.
31
+
32
+ **Approve only a link you opened because you ran the command.** The link carries the code
33
+ (RFC 8628 `verification_uri_complete`), so the click *is* the consent: approving a link somebody
34
+ sent you would connect their machine to your workspace. The console says so on the page. The code
35
+ is printed here too, and shown there, so you can always tell one request from another.
36
+
37
+ The pending handshake gets its own file rather than a new key in `credentials.json`: every value
38
+ in that file is a token, and its one reserved key (`default_url`) is collision-free only because a
39
+ platform url always contains `://`. A second value shape there would end that argument.
40
+
41
+ - **A 401 now points at `octwin login`, not at the console.** With a 30-day browser token, renewing is
42
+ one command; the old hint sent people to mint a replacement by hand. Mint a token yourself if you
43
+ want a different lifetime — the console offers 30 / 90 days / a year / never.
44
+
45
+ ## [0.8.7] - 2026-09-06
46
+
47
+ ### Added
48
+ - **`validate` now reads a map's KEYS, not only its values.** The declaration walker reported four
49
+ things — an unknown key, a missing `required` key, a wrong scalar type, a value outside a closed
50
+ `enum` — and every one of them is about a VALUE. But a map's keys carry meaning in several
51
+ declarations (`roles.yaml` grant resources and verbs, `xrm.yaml` entity names), and those keys had
52
+ no check at all. Rule 5 replays `propertyNames` — both the `enum` and the `pattern` form.
53
+
54
+ This is why publishing the RBAC vocabulary was worth doing. A pack author wrote `case:` where the
55
+ grant key is `record.case`, passed offline `validate` clean, and learned the truth one bad guess at
56
+ a time from `--remote` round-trips. They gave up on custom RBAC over it. The check now says
57
+ `not a valid key here — must be one of "record.case", …` before a deploy is attempted.
58
+
59
+ It stays inside the file's false-positive rule because it is a **faithful replay**: the same `enum`
60
+ membership and the same JS regex engine Zod itself runs, over a pattern the platform generated. A
61
+ pattern this engine cannot compile is treated like a combinator — walk away, do not guess.
62
+
63
+ Verified 2026-09-06 against the shipped binary: across all 23 marketplace packs rule 5 reports
64
+ **nothing**, with 10 of the 12 pulled declaration schemas carrying `propertyNames` (so the silence
65
+ is a result, not an unexercised branch). A deliberately broken key is caught on both branches — a
66
+ role named `Regional-Agent` against `^[a-z][a-z0-9_]*$`, and a grant key `case:` against an enum
67
+ vocabulary — each naming the file, the path and the rule it broke.
68
+
69
+ ### Fixed
70
+ - **`deploy` had stopped printing its seed counts entirely.** `printDeploySuccess` read
71
+ `r.summary.records` / `.updated` / `.images` / `.rules` / `.failed`, but the deploy route's
72
+ `summary` is a run-log **string** (`"clinic v1.2.0 — {…}"`), not an object. So every field was
73
+ `undefined`, `parts` stayed empty, and the `if (parts.length)` guard turned *"I am reading the
74
+ wrong field"* into *"there was nothing to say"* — the block printed nothing and looked like a
75
+ deploy that simply seeded no rows. The counts have been on `r.seeded`, keyed by seed kind, and
76
+ `printSeedCounts` already renders them (filtering zeros, which is what makes a partial seed
77
+ legible). The dead block is gone and `printSeedCounts` is called instead.
78
+
79
+ Same shape as the bug 0.8.6 fixed in `validate`: *no data* and *no problem* rendering identically.
80
+ Two independent places in this CLI reached it within one week.
81
+
8
82
  ## [0.8.6] - 2026-09-04
9
83
 
10
84
  ### Changed
package/README.md CHANGED
@@ -46,9 +46,10 @@ git init && git add -A && git commit -m "init pack"
46
46
  # 2. Author it — edit manifest.yaml, flows/tools/main.flow.yaml (+ its locale),
47
47
  # and prompts/identity.md. Everything is pure YAML.
48
48
 
49
- # 3. Point it at your platform — one command, no config file
50
- # (Octwin console your workspace API tokens Generate)
51
- octwin login --url https://your-octwin.example.com --token oct_…
49
+ # 3. Point it at your platform — one command, no config file.
50
+ # It prints a link; approve it in the console and it saves a 30-day token
51
+ # by itself. (Already have a token? add --token oct_…)
52
+ octwin login --url https://your-octwin.example.com
52
53
 
53
54
  # 4. Validate → deploy → confirm it's live
54
55
  octwin validate
@@ -65,7 +66,7 @@ octwin status # "✓ live and current" once it's warm
65
66
  | --- | --- |
66
67
  | `octwin init <dir>` | Scaffold a new pure-YAML pack into `<dir>` (writes a starter `manifest.yaml`, flow, prompt, `.gitignore` and `README.md` — pack content only). Options: `--id`, `--description`, `--display-name`. |
67
68
  | `octwin validate` | Check the pack locally (structure + pure-YAML rules). `--remote` runs **the same validation the platform runs to load your pack** — every declaration file (`xrm.yaml`, `scheduling.yaml`, `worklist.yaml`, `roles.yaml`, `automation.yaml`, `integrations.yaml`, `surveys.yaml`, `taps.yaml`, `commands.yaml`, `journeys/`, `messages.<lang>.yaml`), the cross-file checks between them, and the **flow lint** — conventions the schema can't express, like an `assign:` whose value is a quoted literal or a `$t()` key with no namespace. These need template expansion and the platform's own schemas, which only the server has, so a local ✓ does not cover them. A `--remote` ✓ and a successful deploy are now the same answer by construction, not by convention. |
68
- | `octwin login` | Save a deploy token for a platform URL **and make that URL the default target** (both stored in `~/.octwin/credentials.json`). `--url`, `--token`. |
69
+ | `octwin login` | Sign in to a platform URL **and make it the default target** (both stored in `~/.octwin/credentials.json`). With no `--token` it prints a link to approve in the console, then collects a 30-day token itself — nothing is pasted. The wait is bounded and resumable: run it again to keep waiting on the same request. `--url`, `--token`, `--wait <seconds>`, `--json`. |
69
70
  | `octwin whoami` | Verify the saved/passed token is valid for a tenant. `--url`, `--tenant`. |
70
71
  | `octwin deploy` | Upload + install the pack onto your tenant's project. `--seed` also runs the pack's demo seed. Says **nothing** about the public marketplace unless asked: `--request-listing` submits the pack for operator review (needs `listing.public: true` in the manifest too), `--withdraw-listing` retracts. Reports the **listing verdict** whenever one exists. |
71
72
  | `octwin status` | Report what the platform has live for this pack — installed version, the **content sha** the instance loaded vs. the one the catalog holds (a redeploy of the *same* version changes it), its flows, and whether it is live on the public marketplace. |
@@ -164,10 +165,12 @@ Three things worth knowing when you read the output:
164
165
  holds pack content and nothing else, so the same repo deploys from any machine:
165
166
 
166
167
  ```bash
167
- octwin login --url https://your-octwin.example.com --token oct_…
168
+ octwin login --url https://your-octwin.example.com # approve in a browser
169
+ octwin login --url https://your-octwin.example.com --token oct_… # or bring your own token
168
170
  ```
169
171
 
170
- That stores the token *and* makes the URL your default target, in `~/.octwin/credentials.json`:
172
+ Either way it stores the token *and* makes the URL your default target, in
173
+ `~/.octwin/credentials.json`:
171
174
 
172
175
  ```jsonc
173
176
  {
@@ -176,6 +179,9 @@ That stores the token *and* makes the URL your default target, in `~/.octwin/cre
176
179
  }
177
180
  ```
178
181
 
182
+ A browser sign-in in progress is held separately, in `~/.octwin/pending-login.json`, and is deleted
183
+ as soon as the token arrives — the credentials file holds tokens only.
184
+
179
185
  Tenant and project need no setting at all — the **token carries its own tenant**, plus an optional
180
186
  project pin. `--tenant` / `--project` exist only as overrides (a multi-workspace human, or an
181
187
  unpinned token that must name a project).
@@ -195,9 +201,24 @@ variables (add `PACK_PROJECT` only if the token isn't pinned).
195
201
  ## Authentication
196
202
 
197
203
  You authenticate with a tenant-scoped **deploy token** (prefixed `oct_…`) — not a password and not
198
- an operator token. Generate it in the Octwin console (**your workspace API tokens → Generate**).
199
- It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your tenant but cannot
200
- manage members, billing, or other tenants, and it is revocable at any time.
204
+ an operator token. It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your
205
+ tenant but cannot manage members, billing, or other tenants, and it is revocable at any time. A
206
+ token can never mint another token, which is why approving a browser sign-in needs a *human*
207
+ workspace admin.
208
+
209
+ Two ways to get one:
210
+
211
+ - **`octwin login` with no `--token`** prints an approval link. A workspace admin opens it, sees
212
+ which machine is asking, and clicks Approve; the CLI collects a 30-day token by polling. Nothing is
213
+ copied by hand, so no live credential passes through a chat window or an email. The wait is
214
+ bounded (90s) and **resumable** — run the command again to keep waiting on the same request, which
215
+ lives ten minutes. Exit code `75` means *still waiting*; `0` means signed in.
216
+ **Approve only a link you opened because you ran the command.** The link carries the code, so
217
+ clicking Approve on a link someone sent you would connect *their* machine to your workspace. The
218
+ code is printed too, and the page shows it, so you can always tell one request from another.
219
+ - **Generate one yourself** in the Octwin console (**your workspace → API tokens → Generate**) and
220
+ pass it with `--token`. This is the way to pick narrower scopes or a different expiry, and the
221
+ way to get a token for CI.
201
222
 
202
223
  Add the optional **`media:generate`** scope to let a `--seed` deploy AI-generate seed images
203
224
  (for a demo record field like `photo: "generate:<prompt>"`); without it, such fields are seeded as