octwin-cli 0.6.1 → 0.7.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +540 -520
  2. package/dist/index.js +118 -0
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,520 +1,540 @@
1
- # Changelog — octwin-cli
2
-
3
- All notable changes to the `octwin` CLI (npm package [`octwin-cli`](https://www.npmjs.com/package/octwin-cli)).
4
- Format: [Keep a Changelog](https://keepachangelog.com/) — newest first, bucketed into
5
- **Added · Changed · Deprecated · Removed · Fixed · Security**. The platform-wide view lives in the
6
- repo root [`CHANGELOG.md`](../../CHANGELOG.md); this file is the CLI-only cut that ships with the package.
7
-
8
- ## [0.6.1] - 2026-08-18
9
-
10
- ### Fixed
11
- - **`octwin status` worked for no pack at all, and had not since 2026-08-01.** It sent the manifest's
12
- **bare** id (`clinic`) to a route that requires the qualified `<owner>.<name>`and a manifest
13
- cannot legally declare the qualified form, because the owner segment is attached at publish time
14
- from the authenticated publisher (both the platform and this CLI reject a `.` in it). So the route
15
- answered 404 for every pack, every time. It now resolves the owner from `whoami`'s `tenant_slug`,
16
- and takes an explicit `octwin status <packId>` when you already know it (from `octwin agents` or
17
- `octwin projects`, both of which print qualified ids). Reported by two pack authors, who lost time
18
- to a command that said their pack was missing seconds after a deploy the platform had accepted.
19
- - **A 404 from `octwin status` no longer always reads "not installed".** That one branch covered four
20
- different failures a malformed pack id, an unknown tenant, an unknown/pinned-elsewhere project,
21
- and the genuine no-active-install so the message named the *least* likely cause and sent an author
22
- chasing a deploy that had already succeeded. The route says which; the CLI now relays it, and adds
23
- the "run `octwin deploy` first" hint only to the case it fits.
24
- - **`octwin logs` always reported "No conversations"**, including immediately after an `octwin chat`
25
- that had plainly worked. It read `conversations` off the response; the platform moved that endpoint
26
- to the shared page envelope on 2026-08-09 and answers `rows`, which every other list command here
27
- already reads. Both authors filed this as replica lag or a cache gap — nothing lagged, the payload
28
- was simply never read. `octwin logs <conversationId>` was unaffected.
29
-
30
- ## [0.6.0] - 2026-08-09
31
-
32
- ### Changed
33
- - **`octwin cases` is now `octwin work`, and it works again.** The platform's work-inbox
34
- consolidation (2026-08-08) replaced the case-only surface with `/work` over every entity a pack
35
- declares worked, and renamed the scopes to `work:read`/`work:write` which left every `octwin
36
- cases` call a silent 404 in a published CLI. The command family is renamed to match the platform:
37
- `octwin work [recordId] [--queues]`, with writes `assign` / `note` / `decide` (the declared
38
- operator actions, `--dry-run` previews) and `stage <id> --to <s>` riding the one platform-wide
39
- stage verb (`POST …/xrm/records/:id/stage`, needs `records:write`). `transition` is gone with the
40
- route it called.
41
- - **`octwin orders transition` rides the same stage verb.** `POST …/orders/:ref/transition` was
42
- retired server-side in the same consolidation; the CLI now resolves the order's record id from the
43
- detail read and moves the stage through `…/xrm/records/:id/stage` (scope: `records:write`).
44
-
45
- ### Added
46
- - **A route guard so this cannot rot silently again**: `src/lib/cli-routes.test.ts` extracts every
47
- `/api/self/**` URL the CLI builds and asserts it (method included) against the platform's dumped
48
- route table the gap that let both breakages above ship.
49
-
50
- ## [0.5.1] - 2026-08-01
51
-
52
- ### Fixed
53
- - **`validate --remote` blamed the platform's version for a bad `--project`.** Every 404 from
54
- `POST /api/self/p/packs/validate` printed *"this platform has no /packs/validate endpoint yet
55
- (older version)"*. That was accurate when the route resolved nothing, but it now resolves the
56
- tenant and project **before** validating and an unknown project, or one outside a token's pin,
57
- answers 404 by design. So a typo in `--project` sent you looking for a version mismatch instead of
58
- at the flag. The two cases are now told apart by the response **body**, not the status: a missing
59
- route is Fastify's `error: 'Not Found'` and keeps the old wording, while the platform's own
60
- `project '<slug>' not found under tenant` is printed as-is, with the flags to check.
61
-
62
- ## [0.5.0] - 2026-08-01
63
-
64
- ### Added
65
- - **`octwin seed [--pack <packId>]`** — apply the pack's demo/reference data to the project it is
66
- installed on, without redeploying: `xrm.yaml` `demo:` records + scheduling availability, the
67
- commerce catalog, and the demo operator topology. Reports what **each kind** produced.
68
- Previously seeding was reachable only as `deploy --seed`, because the platform's seed endpoint was
69
- keyed on an install id, guarded platform-admin, and carried no tenant/project segments — so the
70
- `/api/self/**` surface could not reach it and a `pack:deploy` token never could. Re-seeding meant
71
- a full redeploy or asking an operator. Idempotent and cheap to re-run: records upsert, and
72
- existing media is REUSED rather than regenerated, so a second pass reports zero images.
73
-
74
- ## [0.4.0] - 2026-08-01
75
-
76
- ### Added
77
- - **`octwin projects create` / `octwin projects rm`** a **disposable end-to-end environment** from
78
- the CLI, which is what marketplace developers have been missing. `octwin deploy` has always needed
79
- a project that already existed and the CLI could only *list* them, so standing up a throwaway
80
- full deployment meant opening the console or asking an operator. Now:
81
- ```bash
82
- octwin projects create "Scratch" # → slug `scratch`
83
- octwin deploy --project scratch --seed # publish + install + demo data
84
- octwin chat "hi" --project scratch # talk to it (--script for a whole conversation)
85
- octwin projects rm scratch --yes # throw it away
86
- ```
87
- A throwaway environment is deliberately **not a special kind of thing** — it is an ordinary
88
- project in your own workspace, so it inherits your plan, entitlements, RBAC and teardown with no
89
- separate lifecycle. `create` derives the URL slug from the name (`--slug` pins one, `--pack`
90
- installs an already-published pack). `rm` is a HARD delete the project and everything cascading
91
- from it, no undo, not the same as archiving so **without `--yes` it only prints what would be
92
- destroyed**, making the dry run the default; the impact list is derived from `pg_constraint`, the
93
- same payload the console's confirm dialog renders. Both need `projects:write`, which a
94
- `pack:deploy` token does **not** confer — the CLI names that scope in the 403 hint.
95
-
96
- ## [0.3.0] - 2026-07-31
97
-
98
- ### Added
99
- - **`octwin projects`** the `--project <slug>` values your token can actually name, with the plan's
100
- project cap. Every project-scoped command takes that flag and nothing printed the list, so an
101
- author whose token was not pinned had to guess, and a wrong guess 404s identically to a project
102
- that exists but has no install. `--archived` includes archived projects; `--json` for raw.
103
- A `pack:deploy` token reaches it: the platform route was widened to accept `pack:deploy` alongside
104
- `projects:read`, because `pack:deploy` is `access: 'special'` and confers nothing else the
105
- command would have 403ed for exactly the person it exists for.
106
- - **`octwin validate --require-kb`** — turn a skipped KB-driven check into a failure, for CI.
107
-
108
- ### Fixed
109
- - **`octwin validate` printed with two of its three checks switched off.** The render-intent and
110
- primitive-argument checks read `<packDir>/.octwin/platform-kb/`, joined onto the pack directory
111
- EXACTLY so an author who pulled once at a repo root and then worked in `packs/<name>/` had both
112
- checks silently disabled, and the printed above them unconditionally while the per-check ✓s lived
113
- inside the `if`s. An entire batch of platform-backlog entries reached production through that hole.
114
- Three fixes: the KB is now found by **walking up** (one pull at a repo root covers every pack under
115
- it, `src/lib/kb-path.ts`); the readers report **three states** — absent, malformed, ok — so a
116
- half-written pull no longer reads as "you haven't pulled yet"; and a skip is **announced on the
117
- last line printed**, because a caveat above a ✓ does not reach a reader who stopped at the ✓.
118
- Warn-and-exit-0 stays the default (the KB is a gitignored cache and `platform-kb pull` needs
119
- `pack:deploy`, so a hard failure would break a fresh clone) the defect was the silence, not the skip.
120
- - **`octwin status` said "live and current" for a pack that could not receive a message.** Every field
121
- it printed was process-global: `registered` is true for a pack ANY project on that instance loaded.
122
- Dispatch is per-project and takes the oldest ACTIVE install, ignoring the rest. The route now
123
- filters `status = 'active'` (an archived install used to report live) and compares against the real
124
- dispatch target, so status can say plainly that another pack owns this project's messages.
125
- - **`octwin validate --remote --project does-not-exist` answered `{ ok: true }`** while `deploy` with
126
- the same flag 404ed. The platform route resolves tenant and project before validating now.
127
-
128
- ### Changed
129
- - **`octwin validate --remote` also runs the platform's flow LINT.** It used to run only at pack load,
130
- so a finding of severity `error` an `assign:` whose value is a quoted literal, a `$t()` key with
131
- no namespace passed validate and failed the deploy. Offline `validate` cannot do this: the lint
132
- needs a template-expanded `FlowDef`, which only the server builds.
133
- - **`record_aggregate` / `record_group` now REQUIRE a scope** (`contact_id` or `all: true`) a
134
- platform contract change the offline arg check replays from the KB. Re-pull after upgrading.
135
-
136
- ## [0.2.0] - 2026-07-30
137
-
138
- ### Fixed
139
- - **`records`, `cases`, `orders` and `catalog` printed a correct total and ZERO rows.** The platform
140
- collapsed its eight per-domain list envelopes into one `Page<T>` whose collection key is `rows`;
141
- the CLI still read `json.records` / `.cases` / `.orders` / `.products` behind a `?? []`, so the
142
- fallback turned a key rename into an empty list instead of an error. `octwin records doctor`
143
- reported *"5 record(s)"* and then *"(none mint a `records:read` token )"*, blaming the user's
144
- credentials. All five readers now go through one `readPage` helper
145
- (`src/lib/page.ts`) with a test pinned to the real envelope a hand-copied key name in an
146
- untested CLI is what shipped this, so the fix is the single reader, not five edits.
147
- - **`--flag=value` silently did nothing.** `--limit=5` became the key `limit=5` and the command ran
148
- with its default. Now parsed; `--set`/`--param` also accept repetition.
149
- - **`octwin catalog` always printed `Products: 0`** it counted the page, and the route's `total`
150
- had never been read.
151
-
152
- ### Added
153
- - **Write commands a pack author can now exercise the state their pack creates, not just read it.**
154
- Every one needs the matching `:write` scope; a leading verb (`create`/`note`/`transition`/…) is
155
- what distinguishes a write from the read that shares its command.
156
- - `records create|patch|stage|note`, `records tasks`, `records task complete`
157
- - `cases assign|note|transition|decide` — `decide --dry-run` previews the customer-facing copy and
158
- resulting status **without committing** (that route needs only `cases:read`)
159
- - `orders transition|refund` · `catalog availability|stock` · `agents set`
160
- - `scheduling rules|rule add|rule rm|exception add|exception rm`
161
- - **`octwin feedback`** submits the pack's `FEEDBACK.md` to the platform team, attaching the pack
162
- version, this CLI's version and the `content_hash` of the capability reference you pulled the two
163
- facts that separate a real platform gap from something already fixed or a stale KB. Replaces
164
- "hand the file to the platform team".
165
- - **`--offset` on every list**, and a line saying when a list was truncated and what to run next.
166
- `has_more` was on every response and no command surfaced it, so a partial list looked complete.
167
-
168
- ### Changed
169
- - `orders refund` **exits non-zero when the payment gateway refuses.** The route answers `200` with
170
- the verdict in `gateway`, so reporting the HTTP status would tell an operator money moved when it
171
- did not.
172
- - A 403 on a record or case write now says it may be an **RBAC grant gap** rather than a missing
173
- scope: the scope is checked on the token, then the verb is re-checked against that specific row.
174
-
175
- ## [0.1.21] - 2026-07-30
176
-
177
- > **Never published to npm.** Bumped in-repo, then superseded hours later by 0.2.0 — everything
178
- > below ships as part of that release. Kept as its own section because the changes are distinct.
179
-
180
- ### Added
181
- - **`deploy` and `status` report the marketplace-listing verdict.** A manifest carrying
182
- `listing.public: true` put the pack into an operator review queue, and the state existed only in
183
- the console so an author working from the CLI got no acknowledgement that the request had
184
- registered, and never saw a rejection note (which the platform *requires* precisely because it is
185
- their only feedback). Both commands now print it via `printPublicListing`, and stay silent for a
186
- pack that never asked. `status` also distinguishes *approved* from *actually public*: an approval
187
- pins the content sha it reviewed, so any edit returns the pack to the queue on its own.
188
- - **`octwin pull` is discoverable.** It was implemented and answered `--help`, but appeared in
189
- neither `octwin help` nor the README the one command that recovers a deployed pack's only source
190
- copy was invisible unless you already knew its name. Also added to the scope-requirement table, so
191
- a 403 names `pack:deploy` instead of printing the generic hint.
192
-
193
- ### Fixed
194
- - **The capability-reference drift check no longer fails silently on a narrow token.** The `?meta=1`
195
- poll needs `pack:deploy`, but it rides on every networked command — so an author inspecting data
196
- with a `records:read`-only token got no drift signal at all, and a stale reference is exactly what
197
- leads to inventing a primitive from memory. A 401/403 there now says so once.
198
- - **README: casework is declared in `worklist.yaml`, not a `cases.yaml`.** That grammar was retired;
199
- following the README produced a pack whose casework silently never loaded. `octwin media generate`
200
- was also missing from the command table.
201
- - **`records` no longer blames the plan for a route the plan does not gate.** `GET /xrm/entities`
202
- carries only the scope guard, so a 403 on the entity list wrongly cited the `records` plan feature.
203
-
204
- ### Removed
205
- - **Unreachable rename branches in `init`.** The agent-id and `main`-flow renames sat behind
206
- `--agent` / `--flow` options the CLI never parsed, against a template that ships no `main` flow
207
- both branches could never fire, and their patterns would have matched nothing if they had.
208
-
209
- ## [0.1.20] - 2026-07-29
210
-
211
- ### Added
212
- - **`octwin validate` catches primitive arguments the platform would drop — offline, no token.** A
213
- primitive takes the arguments it declares and silently ignored the rest, so an invented or
214
- misspelled `args:` key deployed clean, ran clean, and simply did nothing: `record_list args: {
215
- order: }` (the argument is `sort`) never ordered, and `booking_cancel args: { booking_record_id:
216
- }` meant the REQUIRED `record_id` never arrived, so cancelling just failed. Validate now replays
217
- the platform's contract from the pulled KB (`primitives/*.json` `inputSchema`) and names the bad
218
- argument, what the primitive takes, and any required argument that is missing. Scanning the 21
219
- shipped marketplace packs found **ten** such errors across five packs. Keys are checked, values are
220
- not an `args:` value is normally an expression string resolved at runtime.
221
- [`args-check.ts`](src/lib/args-check.ts).
222
- - Silently skipped when the KB has not been pulled yet, like the render-intent check beside it.
223
- - It cannot see inside a `use:` template body (expanding one is the platform's job) — `octwin
224
- validate --remote` covers that case, and now also reports unreachable `$bind.<path>` reads and
225
- `outputs:` port typos, which have no offline equivalent.
226
-
227
- ## [0.1.19] - 2026-07-29
228
-
229
- ### Added
230
- - **`octwin validate` catches render-intent fields the platform would drop — offline, no token.** Each
231
- render intent takes a fixed field set; a key outside it used to be swallowed at load and dropped at
232
- render, with no error anywhere just a card missing what the author wrote. Validate now replays the
233
- platform's own contract from the pulled KB (`render-intents/*.json` `allowed_keys`) and names the
234
- bad field, the intent, and what is allowed. Scanning the 21 shipped marketplace packs found two real
235
- instances in 223 intents: a `text_card` carrying `buttons` (the fallback's only escape button never
236
- rendered) and a `list_picker` carrying a non-existent `group_by` (a whole mapper step feeding a
237
- fabricated parameter). Silently skipped when the KB has not been pulled yet.
238
-
239
- ### Changed
240
- - **`octwin platform-kb pull` now explodes the reference and writes an `INDEX.md`.** A pull used to
241
- write ~787 KB across 34 flat files — about 225k tokens, more than the pack being authored — so
242
- finding one primitive meant reading a 122 KB catalog. Catalogs the platform describes as
243
- enumerable are now written **one file per entry** (`primitives/record_list.json`,
244
- `render-intents/carousel.json`, `declarations/xrm.json`, …), with `INDEX.md` mapping every doc and
245
- all 171 entries to its file with a one-line summary. Reading one primitive went from ~38k tokens to
246
- ~700. The layout comes from the platform (an `entries` descriptor on the bundle index) rather than
247
- being hardcoded here, so this CLI keeps working against any platform version and a platform that
248
- adds a catalog needs no CLI release. The pulled directory is cleared each pull, so a withdrawn
249
- capability cannot linger as a file the authoring agent reads as current — its *contents* are
250
- cleared rather than the directory itself, since on Windows a directory that is any shell's working
251
- directory cannot be removed.
252
-
253
- ## [0.1.18] - 2026-07-29
254
-
255
- ### Fixed
256
- - **The bundle collector and the platform's repo import disagreed about what a pack directory
257
- contains.** `collectBundleFiles` had its own rule and kept three things the operator's GitHub
258
- import drops: `*.ts`/`*.tsx`, `__snapshots__/` and `*.example`. Because the server rejects a
259
- `.ts` outright, the same directory imported cleanly from GitHub and failed to deploy from
260
- disk with `'…': executable code is not allowed` an error blaming a file that was never
261
- meant to ship (a snapshot test beside a flow, or the documented `xrm.yaml.example` template).
262
- The rule now lives in a vendored `lib/pack-source.ts` beside the vendored validator, and a
263
- parity test in the platform drives both copies over one table.
264
-
265
- ### Removed
266
- - **`pack.json` is gone the saved login IS the deploy target.** The file had eroded to a single
267
- field, `platform_url`, which `octwin login --url --token …` already states; `tenant`/`project`
268
- became optional overrides once deploy tokens started carrying their own tenant and project pin.
269
- So it was a second home for a fact `login` knew, and it cost real things: a machine-specific file
270
- committed into every pack repo, a four-link config-precedence chain to document, and an
271
- exclusion rule duplicated in the bundler and the server (which had already drifted apart).
272
- `octwin init` and `octwin pull` no longer write one, and nothing reads one. **A pack directory is
273
- now pack content and nothing else** — the same repo deploys from any machine, and a pulled pack
274
- redeploys with no flags.
275
-
276
- ### Changed
277
- - **`octwin login` now sets the default deploy target**, not just the token: it writes a
278
- `default_url` key alongside the token in `~/.octwin/credentials.json`. Every command resolves
279
- **flags env (`PACK_PLATFORM_URL`/`PACK_TENANT`/`PACK_PROJECT`/`PACK_TOKEN`) saved login**.
280
- An existing credentials file simply has no `default_url` until the next `octwin login` which is
281
- exactly what the "no platform url" error now tells you to run. CI is unaffected (it passes
282
- `PACK_PLATFORM_URL` + `PACK_TOKEN` as env and never had a `pack.json` that file is retired).
283
-
284
- ## [0.1.17] - 2026-07-27
285
-
286
- ### Added
287
- - **`octwin pull <packId>` write a DEPLOYED pack's source back to disk.** The inverse of
288
- `deploy`. A pack pushed with the CLI lived on the platform as an artifact the runtime served
289
- but nothing could hand back, so its only source copy was the machine that pushed it. Pull it,
290
- fix it, redeploy it. Writes a `pack.json` (retired in 0.1.18 the saved login is now the
291
- target) so the pulled directory redeploys where it came from; `--version` picks a version,
292
- `--force` overwrites a non-empty dir. You may pull a pack
293
- your tenant OWNS (an operator token pulls any) a pack you merely installed is not yours to
294
- read.
295
- - **`octwin chat --script <file>` — drive a whole conversation from one invocation.** One turn per
296
- line, in order, in a single process over a single SSE connection, waiting for each turn to settle
297
- before sending the next. Blank lines and `#` comments are skipped; `tap:<id>` presses a rendered
298
- row/button (the rest of the line is kept verbatim, since a tap id is itself colon-delimited),
299
- `media:<path>` uploads a file, and `media:<path> | caption` attaches one.
300
-
301
- This exists because **chaining invocations races the agent loop.** A turn ends on a quiet gap
302
- (`TURN_SETTLE_MS`), which can arrive while the server-side loop is still running so
303
- `chat A && chat B` lets B's inbound land mid-turn, and the agent fills required fields with
304
- placeholder text (`service_id: "service-id"`) or starts a second workflow run. Those look exactly
305
- like flow bugs and cost a field author three separate investigations. The one-turn-per-invocation
306
- rule is now stated in `chat --help` and the header usage as well.
307
-
308
- ### Changed
309
- - **`octwin deploy` reports the suspended runs it invalidated.** A redeploy rebuilds the pack's
310
- tools, and in-flight flow runs live in those tools so every suspended conversation is dropped.
311
- The deploy prints `ⓘ N suspended run(s) invalidated` when N > 0, instead of leaving the author to
312
- discover it as a `workflow_resume_stale` on the next tap.
313
-
314
- ## [0.1.16] - 2026-07-26
315
-
316
- ### Added
317
- - **A pack can ship committed images.** `jpg`/`jpeg`/`png`/`webp`/`gif`/`pdf` files in your pack
318
- directory now travel as a separate `blobs` half of the bundle (base64 on the wire, `bytea` in
319
- storage) instead of being rejected as "not an allowed pack file type". Declare each one under the
320
- manifest's `static_assets: [{ file, key }]` and reference it from `config:` with the
321
- `$pack_asset:<key>` sentinel the platform uploads it to the media system at install and the
322
- sentinel resolves to the served URL. `octwin validate` reports the image count alongside the file
323
- count.
324
- - **The size ceilings fail locally, before upload** 2 MB per file, 32 MB of binary per pack,
325
- mirroring the server. `svg` stays rejected: it is script-capable and these assets are served to
326
- browsers.
327
-
328
- ### Fixed
329
- - **Committed images were silently corrupted.** `collectBundleFiles` read *every* file with
330
- `readFileSync(full, 'utf8')`, so a JPEG went through a lossy UTF-8 decode and arrived mangled
331
- server-side a deploy that "succeeded" and rendered a broken image. Binary files now split off
332
- into `blobs` and keep their bytes.
333
- - **`octwin status` reported `loaded=(none)` for every pack, always.** It printed a `loaded_version`
334
- field that stopped existing when reload moved to content-sha keying, which also meant the
335
- version-drift warning underneath it could never fire. It now prints the **content sha** the
336
- instance has loaded and the one the catalog holds (with the artifact's `origin`), and warns off the
337
- platform's own `up_to_date` flag. The sha is the more useful fact anyway: re-publishing the *same*
338
- version changes it, which is exactly the author's inner loop. A pack that is live and current but
339
- **withdrawn** from the catalog now says so, rather than reporting a clean bill of health.
340
-
341
- ## [0.1.15] - 2026-07-25
342
-
343
- ### Added
344
- - **`octwin agents [packId::agentId] [--prompt]`see what your agent actually runs as.** The platform
345
- gained *governed* agent settings: an operator platform default can override the `default_model`
346
- (and history window / working-memory toggle) your manifest declares, and that override is invisible
347
- from the pack source. The command prints each governed setting's **effective** value, **which layer
348
- won** (project override → platform default → pack manifest), and warns explicitly when your pack's
349
- declared value is *not* in force. `--prompt` prints the exact system prompt the LLM sees for this
350
- project (pack instructions + platform protocol + any project overlay).
351
- - **`octwin orders [reference_id]` the commerce a conversation produced.** The list (number,
352
- status/payment, total, contact) or one order with its line items, the
353
- subtotal/tax/shipping/discount/total breakdown, `payment_ref`, and the allowed transitions. On a
354
- `pending`/`none` payment it explains *why that's expected*: the forward payment lifecycle is
355
- **webhook-owned** (not patchable), and the default gateway-less `manual` driver makes
356
- `payment_request` take its `empty` port so the flow should confirm pay-on-delivery, not error.
357
- - **`octwin analytics [entity]` stage conversion for any pipelined entity.** The funnel engine that
358
- powered journeys is now generic, so any entity declared with a `pipeline:` charts conversion +
359
- drop-off (`--overview` / `--milestones` / `--trends` / `--cost`, and `--stage <id>` for the records
360
- currently at a stage). An empty result names **both** causes the platform folds into one response —
361
- no `pipeline:`, or no `view` grant on `record.<entity>`.
362
- - **`octwin catalog [--readiness]`** — commerce products with price, availability and stock
363
- (`untracked` when the SKU isn't inventory-tracked), plus the WhatsApp catalog binding.
364
- `--readiness` runs the Meta Graph checklist with each failing item's suggested fix.
365
- - **`octwin scheduling [--slots <resourceRecordId>]`** the engine state (bookable resource types,
366
- upcoming slots, booked seats) or the computed slots for one resource. This is how you verify the
367
- availability rules a `deploy --seed` created, which 0.1.14 could create but not read back.
368
-
369
- ### Changed
370
- - **Auth failures now name the scope the command needs.** Every 401/403 appends the required scope
371
- (and the plan feature, where the route is entitlement-gated) from one client-side mirror of the
372
- platform's scope registry replacing the per-command hardcoded strings, several of which named no
373
- scope at all. It also spells out that `pack:deploy` / `media:generate` are direct-grant only, so a
374
- `tenant:admin` preset token does **not** confer them (the most common "but my token is admin" dead end).
375
-
376
- ## [0.1.14] - 2026-07-23
377
-
378
- ### Fixed
379
- - **A failed demo seed no longer reports a clean `✓`.** `deploy` softens non-fatal
380
- install steps (e.g. a demo-seed row) to warning frames, but the summary still printed
381
- `✓ Deployed` and exited 0 so a deploy that seeded ZERO records read as success (the
382
- false-✓ trap). `deploy` now collects those step errors, prints `⚠ Deployed with N
383
- warning(s) data may be incomplete` with each message, and exits non-zero so CI /
384
- a `deploy && chat` chain catches it.
385
-
386
- ### Added
387
- - **`login` echoes what the token reaches.** After saving, `octwin login` calls
388
- `/api/self/t/whoami` and prints the workspace, project pin, and scopes — so a fresh
389
- token self-identifies without a second `octwin whoami` (best-effort; a network failure
390
- never fails the save).
391
- - **`validate --remote` surfaces warnings + the data-store trap.** The remote validate now
392
- renders advisory `⚠` warnings (which don't block deploy) and, for a pack declaring
393
- `required_adapters: [data-store]`, reports the missing-data-store error the deploy would
394
- have 409'd on plus a warning that an `xrm.yaml` pack usually shouldn't declare that
395
- adapter at all. It also now catches (offline) a list-form `entities:` block and a
396
- `localized: true` field seeded with a bare string — traps that previously surfaced only
397
- mid-seed at deploy.
398
-
399
- ## [0.1.13] - 2026-07-22
400
-
401
- ### Added
402
- - **A headless media loop produce + send.** Two paired additions close the coverage hole where any
403
- media-collect flow (e.g. `activate-app`'s registration / ID uploads) stalled at the upload prompt
404
- because the CLI could neither *make* nor *send* an image:
405
- - `octwin media generate "<prompt>" [--out <file.png>] [--size 1024x1024] [--json]` — AI-generates an
406
- image on the platform (needs a `media:generate`-scoped token), stores it as a public asset, and prints
407
- its `MEDIA-` handle + serve URL. `--out` downloads the bytes to a file (WhatsApp renders only
408
- `.png`/`.jpg`); `--json` emits `{ media_id, url, mime, width, height, bytes }`.
409
- - `octwin chat --media <file|media-id>` uploads a local file (or a media id from
410
- `media generate --json`) as an image/document/audio inbound; any accompanying `"message"` rides as
411
- its caption. The platform's media pipeline folds the upload into a running collect, so media flows
412
- are now fully drivable headlessly.
413
-
414
- ### Changed
415
- - **The KB-drift nudge now names what changed.** With the platform serving per-entry content hashes, the
416
- post-command nudge appends a `(N changed · M added · K removed)` summary instead of a bare hash pair.
417
- - **`octwin platform-kb` prints a changelog on pull.** Instead of only a doc/catalog count, the pull now
418
- diffs the fresh index against your last pull and lists exactly which docs/catalogs were added (`+`),
419
- changed (`~`), or removed (`-`) — so a replaced schema shape is visible, not silent.
420
-
421
- ## [0.1.12] - 2026-07-22
422
-
423
- ### Fixed
424
- - **The KB-drift nudge now fires after `chat` too.** `chat` — the command a debugging session runs most —
425
- was missing from the networked-command list, so a chat-heavy session never noticed the platform's
426
- capability reference had moved. The nudge also now shows the concrete drift
427
- (`old-hash new-hash`) so you can see it's real, not a heuristic.
428
-
429
- ### Changed
430
- - **Every networked command announces what it's doing before it does it.** `chat` prints
431
- `→ Connecting to <tenant>/<project> as '<handle>' …` before opening the stream and
432
- `… delivered waiting for the reply (up to Ns)` after the send; `status` / `whoami` /
433
- `platform-kb` / `records` / `logs` / `cases` each print a one-line `→ …` header naming the
434
- action and target before the first network call — no more silent seconds followed by a result
435
- (or a hang with no clue what was being attempted). JSON modes (`--json`) stay clean for piping.
436
-
437
- ## [0.1.11] - 2026-07-21
438
-
439
- ### Fixed
440
- - **Auth-failure hint on every command, not just the inspect family** (author-feedback A7). `validate`,
441
- `deploy`, `status`, and the single-record read now print the scope/transient explanation on a 401/403
442
- (the shared `authFailureHint`), instead of a bare `HTTP 401` that left it ambiguous whether the token
443
- lacked a scope or the platform hiccupped.
444
-
445
- ## [0.1.10] - 2026-07-21
446
-
447
- The capability reference is now **pulled, never bundled** the authoring skill dropped its committed KB
448
- snapshot, so `octwin platform-kb pull` is the single source of truth, and the CLI watches it for drift.
449
-
450
- ### Added
451
- - **KB-drift nudge** after any command that already talks to the platform (`deploy` / `status` /
452
- `test` / `validate --remote` / `records` / `cases` / `logs` / `whoami`), the CLI does one tiny
453
- `octwin-platform-kb?meta=1` GET and prints a one-line "the platform capability reference changed —
454
- `octwin platform-kb pull`" when the platform's `content_hash` no longer matches your last pull.
455
- Fail-silent + TTY-only + no-op until you've pulled once the sibling of the existing CLI-upgrade notice.
456
-
457
- ### Changed
458
- - **`octwin platform-kb pull`** now records the reference's `content_hash` in `.octwin/platform-kb/index.json`
459
- (the fingerprint the drift check compares against).
460
-
461
- The conversation-debugging release driven by the second author-feedback round (xpeng-egypt),
462
- which found the author→validate→deploy loop solid but multi-turn debugging impossible from the CLI.
463
-
464
- ### Added
465
- - **`octwin chat --tap "<tap-id>"`** — press a rendered button / list row (sends the web channel's
466
- `type:'interactive'` inbound). Paired with full render printing, any tap-driven flow is now drivable headlessly.
467
- - **Full render printing** — every reply prints its complete interactive content: list sections/rows,
468
- buttons, and carousel cards, each with its **tap id**; header/footer/list-button text included.
469
- - **`--json` on `chat` / `logs` / `cases`** — raw SSE envelopes (chat) or raw event/case payloads.
470
- - **`octwin cases [caseId] [--queues]`** — inspect casework (support tickets): the inbox, one case +
471
- timeline + applicable decisions, or queue keys with open counts. Casework packs previously had zero
472
- data-inspection path (`records case` was a dead end).
473
- - **Per-subcommand `--help`/`-h`** intercepted before any network or auth work (previously
474
- `octwin logs --help` made a network call and could die with a 401).
475
- - Multi-message turns: `chat` now collects **all** of a turn's renders (quiet-gap detection) instead
476
- of cutting at the first one.
477
-
478
- ### Changed
479
- - **`octwin logs`**: the conversation list shows contact handle + last activity and filters with
480
- `--as <handle>`; the timeline prints **full** event text (no more 240-char truncation) plus the
481
- renders each turn actually sent; footer points at `--json` / `mastra-messages` for verbatim payloads.
482
- - **Network failures name the target URL** (`platform unreachable at <url>`), and 401s explain
483
- invalid-token vs one-off transient instead of a bare status code.
484
- - `octwin records` on an unknown entity now surfaces the server's error body, and for `case`/`cases`
485
- points at `octwin cases`.
486
-
487
- ### Fixed
488
- - **Multi-turn chat actually works.** Two bugs made consecutive `octwin chat` calls look like "every
489
- call starts fresh": (1) the idempotency `local_id` was a **constant** per handle, so the platform's
490
- 5-minute inbound dedup silently dropped the second message a fresh id is now minted per call;
491
- (2) a fresh SSE connect **replays** recent history and the CLI printed the first render frame — i.e.
492
- the *previous* turn's greeting as the reply; the CLI now drains the replay and only accepts frames
493
- newer than the connect boundary. The platform always kept one open conversation per `--as` handle —
494
- the same handle genuinely continues the same conversation now.
495
-
496
- ## [0.1.8] - 2026-07-21
497
- - Global-first install guidance (`npm i -g octwin-cli`); upgrade notice suppressed under `npx`.
498
-
499
- ## [0.1.7] - 2026-07-21
500
- - `--version`/`-v` flag; daily fail-silent, TTY-only "newer version available" notice.
501
-
502
- ## [0.1.6] - 2026-07-21
503
- - `octwin init` scaffolds a home-hub menu (`home` list_picker + example `browse` tool) instead of a bare greeting.
504
-
505
- ## [0.1.5] - 2026-07-20
506
- - `octwin validate --remote` (the platform's FULL manifest + flow-DSL check, all errors at once);
507
- headless debugging commands `octwin records` / `octwin logs` / `octwin chat`; `octwin test` became
508
- an alias for `validate --remote`. First author-feedback round.
509
-
510
- ## [0.1.4] - 2026-07-20
511
- - Authoring reliability + DX fixes from the first end-to-end external-pack test.
512
-
513
- ## [0.1.3] - 2026-07-20
514
- - `octwin platform-kb pull` pull the platform capability reference for the authoring skill.
515
-
516
- ## [0.1.2] - 2026-07-20
517
- - Proper npm README.
518
-
519
- ## [0.1.1] - 2026-07-20
520
- - Initial npm publish: `init` / `validate` / `login` / `whoami` / `deploy` / `status`, `oct_` deploy tokens.
1
+ # Changelog — octwin-cli
2
+
3
+ All notable changes to the `octwin` CLI (npm package [`octwin-cli`](https://www.npmjs.com/package/octwin-cli)).
4
+ Format: [Keep a Changelog](https://keepachangelog.com/) — newest first, bucketed into
5
+ **Added · Changed · Deprecated · Removed · Fixed · Security**. The platform-wide view lives in the
6
+ repo root [`CHANGELOG.md`](../../CHANGELOG.md); this file is the CLI-only cut that ships with the package.
7
+
8
+ ## [0.7.0] - 2026-08-18
9
+
10
+ ### Added
11
+ - **`octwin memos` the channel answers now.** `octwin feedback` was one-way: an author reported and
12
+ heard nothing, ever. Two kinds arrive here, both printed in fulla **reply** to a report you sent,
13
+ and a **notice** published to every author (a new capability, a deprecation, a breaking change) —
14
+ each carrying `info` / `action_required` / `breaking`. Reading marks them read; `--all` re-reads
15
+ history and acks nothing (also the escape hatch if a piped `--json` consumer died halfway).
16
+ - **Every networked command now says when a memo is waiting**, one line to stderr, always naming the
17
+ command to run. It rides work that was already networked, never throws, and is **deliberately not
18
+ TTY-gated** the same reasoning as the KB drift nudge, whose docblock says it plainly: the primary
19
+ reader of this CLI is an authoring agent whose output is piped, and gating on `isTTY` means the one
20
+ reader that cannot notice on its own is the only one never told. Unlike that check there is no local
21
+ marker file: read state is per-workspace on the platform, so an agent in a fresh container still
22
+ learns about an unread reply.
23
+
24
+ Why it matters, measured: in August 2026 four reports sat unread for nine days, and one author
25
+ replaced every `$t()` call with hardcoded English across all 16 flows of a production pack to work
26
+ around a bug that had been fixed two days earlier. Nothing could tell them.
27
+
28
+ ## [0.6.1] - 2026-08-18
29
+
30
+ ### Fixed
31
+ - **`octwin status` worked for no pack at all, and had not since 2026-08-01.** It sent the manifest's
32
+ **bare** id (`clinic`) to a route that requires the qualified `<owner>.<name>` — and a manifest
33
+ cannot legally declare the qualified form, because the owner segment is attached at publish time
34
+ from the authenticated publisher (both the platform and this CLI reject a `.` in it). So the route
35
+ answered 404 for every pack, every time. It now resolves the owner from `whoami`'s `tenant_slug`,
36
+ and takes an explicit `octwin status <packId>` when you already know it (from `octwin agents` or
37
+ `octwin projects`, both of which print qualified ids). Reported by two pack authors, who lost time
38
+ to a command that said their pack was missing seconds after a deploy the platform had accepted.
39
+ - **A 404 from `octwin status` no longer always reads "not installed".** That one branch covered four
40
+ different failures — a malformed pack id, an unknown tenant, an unknown/pinned-elsewhere project,
41
+ and the genuine no-active-install so the message named the *least* likely cause and sent an author
42
+ chasing a deploy that had already succeeded. The route says which; the CLI now relays it, and adds
43
+ the "run `octwin deploy` first" hint only to the case it fits.
44
+ - **`octwin logs` always reported "No conversations"**, including immediately after an `octwin chat`
45
+ that had plainly worked. It read `conversations` off the response; the platform moved that endpoint
46
+ to the shared page envelope on 2026-08-09 and answers `rows`, which every other list command here
47
+ already reads. Both authors filed this as replica lag or a cache gap — nothing lagged, the payload
48
+ was simply never read. `octwin logs <conversationId>` was unaffected.
49
+
50
+ ## [0.6.0] - 2026-08-09
51
+
52
+ ### Changed
53
+ - **`octwin cases` is now `octwin work`, and it works again.** The platform's work-inbox
54
+ consolidation (2026-08-08) replaced the case-only surface with `/work` over every entity a pack
55
+ declares worked, and renamed the scopes to `work:read`/`work:write` which left every `octwin
56
+ cases` call a silent 404 in a published CLI. The command family is renamed to match the platform:
57
+ `octwin work [recordId] [--queues]`, with writes `assign` / `note` / `decide` (the declared
58
+ operator actions, `--dry-run` previews) and `stage <id> --to <s>` riding the one platform-wide
59
+ stage verb (`POST …/xrm/records/:id/stage`, needs `records:write`). `transition` is gone with the
60
+ route it called.
61
+ - **`octwin orders transition` rides the same stage verb.** `POST …/orders/:ref/transition` was
62
+ retired server-side in the same consolidation; the CLI now resolves the order's record id from the
63
+ detail read and moves the stage through `…/xrm/records/:id/stage` (scope: `records:write`).
64
+
65
+ ### Added
66
+ - **A route guard so this cannot rot silently again**: `src/lib/cli-routes.test.ts` extracts every
67
+ `/api/self/**` URL the CLI builds and asserts it (method included) against the platform's dumped
68
+ route table the gap that let both breakages above ship.
69
+
70
+ ## [0.5.1] - 2026-08-01
71
+
72
+ ### Fixed
73
+ - **`validate --remote` blamed the platform's version for a bad `--project`.** Every 404 from
74
+ `POST /api/self/p/packs/validate` printed *"this platform has no /packs/validate endpoint yet
75
+ (older version)"*. That was accurate when the route resolved nothing, but it now resolves the
76
+ tenant and project **before** validating — and an unknown project, or one outside a token's pin,
77
+ answers 404 by design. So a typo in `--project` sent you looking for a version mismatch instead of
78
+ at the flag. The two cases are now told apart by the response **body**, not the status: a missing
79
+ route is Fastify's `error: 'Not Found'` and keeps the old wording, while the platform's own
80
+ `project '<slug>' not found under tenant` is printed as-is, with the flags to check.
81
+
82
+ ## [0.5.0] - 2026-08-01
83
+
84
+ ### Added
85
+ - **`octwin seed [--pack <packId>]`** apply the pack's demo/reference data to the project it is
86
+ installed on, without redeploying: `xrm.yaml` `demo:` records + scheduling availability, the
87
+ commerce catalog, and the demo operator topology. Reports what **each kind** produced.
88
+ Previously seeding was reachable only as `deploy --seed`, because the platform's seed endpoint was
89
+ keyed on an install id, guarded platform-admin, and carried no tenant/project segments so the
90
+ `/api/self/**` surface could not reach it and a `pack:deploy` token never could. Re-seeding meant
91
+ a full redeploy or asking an operator. Idempotent and cheap to re-run: records upsert, and
92
+ existing media is REUSED rather than regenerated, so a second pass reports zero images.
93
+
94
+ ## [0.4.0] - 2026-08-01
95
+
96
+ ### Added
97
+ - **`octwin projects create` / `octwin projects rm`** — a **disposable end-to-end environment** from
98
+ the CLI, which is what marketplace developers have been missing. `octwin deploy` has always needed
99
+ a project that already existed and the CLI could only *list* them, so standing up a throwaway
100
+ full deployment meant opening the console or asking an operator. Now:
101
+ ```bash
102
+ octwin projects create "Scratch" # slug `scratch`
103
+ octwin deploy --project scratch --seed # publish + install + demo data
104
+ octwin chat "hi" --project scratch # talk to it (--script for a whole conversation)
105
+ octwin projects rm scratch --yes # throw it away
106
+ ```
107
+ A throwaway environment is deliberately **not a special kind of thing** — it is an ordinary
108
+ project in your own workspace, so it inherits your plan, entitlements, RBAC and teardown with no
109
+ separate lifecycle. `create` derives the URL slug from the name (`--slug` pins one, `--pack`
110
+ installs an already-published pack). `rm` is a HARD delete — the project and everything cascading
111
+ from it, no undo, not the same as archiving so **without `--yes` it only prints what would be
112
+ destroyed**, making the dry run the default; the impact list is derived from `pg_constraint`, the
113
+ same payload the console's confirm dialog renders. Both need `projects:write`, which a
114
+ `pack:deploy` token does **not** confer the CLI names that scope in the 403 hint.
115
+
116
+ ## [0.3.0] - 2026-07-31
117
+
118
+ ### Added
119
+ - **`octwin projects`** the `--project <slug>` values your token can actually name, with the plan's
120
+ project cap. Every project-scoped command takes that flag and nothing printed the list, so an
121
+ author whose token was not pinned had to guess, and a wrong guess 404s identically to a project
122
+ that exists but has no install. `--archived` includes archived projects; `--json` for raw.
123
+ A `pack:deploy` token reaches it: the platform route was widened to accept `pack:deploy` alongside
124
+ `projects:read`, because `pack:deploy` is `access: 'special'` and confers nothing else the
125
+ command would have 403ed for exactly the person it exists for.
126
+ - **`octwin validate --require-kb`** turn a skipped KB-driven check into a failure, for CI.
127
+
128
+ ### Fixed
129
+ - **`octwin validate` printed with two of its three checks switched off.** The render-intent and
130
+ primitive-argument checks read `<packDir>/.octwin/platform-kb/`, joined onto the pack directory
131
+ EXACTLYso an author who pulled once at a repo root and then worked in `packs/<name>/` had both
132
+ checks silently disabled, and the printed above them unconditionally while the per-check ✓s lived
133
+ inside the `if`s. An entire batch of platform-backlog entries reached production through that hole.
134
+ Three fixes: the KB is now found by **walking up** (one pull at a repo root covers every pack under
135
+ it, `src/lib/kb-path.ts`); the readers report **three states** — absent, malformed, ok — so a
136
+ half-written pull no longer reads as "you haven't pulled yet"; and a skip is **announced on the
137
+ last line printed**, because a caveat above a ✓ does not reach a reader who stopped at the ✓.
138
+ Warn-and-exit-0 stays the default (the KB is a gitignored cache and `platform-kb pull` needs
139
+ `pack:deploy`, so a hard failure would break a fresh clone) the defect was the silence, not the skip.
140
+ - **`octwin status` said "live and current" for a pack that could not receive a message.** Every field
141
+ it printed was process-global: `registered` is true for a pack ANY project on that instance loaded.
142
+ Dispatch is per-project and takes the oldest ACTIVE install, ignoring the rest. The route now
143
+ filters `status = 'active'` (an archived install used to report live) and compares against the real
144
+ dispatch target, so status can say plainly that another pack owns this project's messages.
145
+ - **`octwin validate --remote --project does-not-exist` answered `{ ok: true }`** while `deploy` with
146
+ the same flag 404ed. The platform route resolves tenant and project before validating now.
147
+
148
+ ### Changed
149
+ - **`octwin validate --remote` also runs the platform's flow LINT.** It used to run only at pack load,
150
+ so a finding of severity `error` — an `assign:` whose value is a quoted literal, a `$t()` key with
151
+ no namespace — passed validate and failed the deploy. Offline `validate` cannot do this: the lint
152
+ needs a template-expanded `FlowDef`, which only the server builds.
153
+ - **`record_aggregate` / `record_group` now REQUIRE a scope** (`contact_id` or `all: true`) a
154
+ platform contract change the offline arg check replays from the KB. Re-pull after upgrading.
155
+
156
+ ## [0.2.0] - 2026-07-30
157
+
158
+ ### Fixed
159
+ - **`records`, `cases`, `orders` and `catalog` printed a correct total and ZERO rows.** The platform
160
+ collapsed its eight per-domain list envelopes into one `Page<T>` whose collection key is `rows`;
161
+ the CLI still read `json.records` / `.cases` / `.orders` / `.products` behind a `?? []`, so the
162
+ fallback turned a key rename into an empty list instead of an error. `octwin records doctor`
163
+ reported *"5 record(s)"* and then *"(none mint a `records:read` token …)"*, blaming the user's
164
+ credentials. All five readers now go through one `readPage` helper
165
+ (`src/lib/page.ts`) with a test pinned to the real envelope a hand-copied key name in an
166
+ untested CLI is what shipped this, so the fix is the single reader, not five edits.
167
+ - **`--flag=value` silently did nothing.** `--limit=5` became the key `limit=5` and the command ran
168
+ with its default. Now parsed; `--set`/`--param` also accept repetition.
169
+ - **`octwin catalog` always printed `Products: 0`** it counted the page, and the route's `total`
170
+ had never been read.
171
+
172
+ ### Added
173
+ - **Write commands a pack author can now exercise the state their pack creates, not just read it.**
174
+ Every one needs the matching `:write` scope; a leading verb (`create`/`note`/`transition`/…) is
175
+ what distinguishes a write from the read that shares its command.
176
+ - `records create|patch|stage|note`, `records tasks`, `records task complete`
177
+ - `cases assign|note|transition|decide` `decide --dry-run` previews the customer-facing copy and
178
+ resulting status **without committing** (that route needs only `cases:read`)
179
+ - `orders transition|refund` · `catalog availability|stock` · `agents set`
180
+ - `scheduling rules|rule add|rule rm|exception add|exception rm`
181
+ - **`octwin feedback`** submits the pack's `FEEDBACK.md` to the platform team, attaching the pack
182
+ version, this CLI's version and the `content_hash` of the capability reference you pulled the two
183
+ facts that separate a real platform gap from something already fixed or a stale KB. Replaces
184
+ "hand the file to the platform team".
185
+ - **`--offset` on every list**, and a line saying when a list was truncated and what to run next.
186
+ `has_more` was on every response and no command surfaced it, so a partial list looked complete.
187
+
188
+ ### Changed
189
+ - `orders refund` **exits non-zero when the payment gateway refuses.** The route answers `200` with
190
+ the verdict in `gateway`, so reporting the HTTP status would tell an operator money moved when it
191
+ did not.
192
+ - A 403 on a record or case write now says it may be an **RBAC grant gap** rather than a missing
193
+ scope: the scope is checked on the token, then the verb is re-checked against that specific row.
194
+
195
+ ## [0.1.21] - 2026-07-30
196
+
197
+ > **Never published to npm.** Bumped in-repo, then superseded hours later by 0.2.0 everything
198
+ > below ships as part of that release. Kept as its own section because the changes are distinct.
199
+
200
+ ### Added
201
+ - **`deploy` and `status` report the marketplace-listing verdict.** A manifest carrying
202
+ `listing.public: true` put the pack into an operator review queue, and the state existed only in
203
+ the console — so an author working from the CLI got no acknowledgement that the request had
204
+ registered, and never saw a rejection note (which the platform *requires* precisely because it is
205
+ their only feedback). Both commands now print it via `printPublicListing`, and stay silent for a
206
+ pack that never asked. `status` also distinguishes *approved* from *actually public*: an approval
207
+ pins the content sha it reviewed, so any edit returns the pack to the queue on its own.
208
+ - **`octwin pull` is discoverable.** It was implemented and answered `--help`, but appeared in
209
+ neither `octwin help` nor the README — the one command that recovers a deployed pack's only source
210
+ copy was invisible unless you already knew its name. Also added to the scope-requirement table, so
211
+ a 403 names `pack:deploy` instead of printing the generic hint.
212
+
213
+ ### Fixed
214
+ - **The capability-reference drift check no longer fails silently on a narrow token.** The `?meta=1`
215
+ poll needs `pack:deploy`, but it rides on every networked command so an author inspecting data
216
+ with a `records:read`-only token got no drift signal at all, and a stale reference is exactly what
217
+ leads to inventing a primitive from memory. A 401/403 there now says so once.
218
+ - **README: casework is declared in `worklist.yaml`, not a `cases.yaml`.** That grammar was retired;
219
+ following the README produced a pack whose casework silently never loaded. `octwin media generate`
220
+ was also missing from the command table.
221
+ - **`records` no longer blames the plan for a route the plan does not gate.** `GET /xrm/entities`
222
+ carries only the scope guard, so a 403 on the entity list wrongly cited the `records` plan feature.
223
+
224
+ ### Removed
225
+ - **Unreachable rename branches in `init`.** The agent-id and `main`-flow renames sat behind
226
+ `--agent` / `--flow` options the CLI never parsed, against a template that ships no `main` flow —
227
+ both branches could never fire, and their patterns would have matched nothing if they had.
228
+
229
+ ## [0.1.20] - 2026-07-29
230
+
231
+ ### Added
232
+ - **`octwin validate` catches primitive arguments the platform would drop offline, no token.** A
233
+ primitive takes the arguments it declares and silently ignored the rest, so an invented or
234
+ misspelled `args:` key deployed clean, ran clean, and simply did nothing: `record_list args: {
235
+ order: }` (the argument is `sort`) never ordered, and `booking_cancel args: { booking_record_id:
236
+ }` meant the REQUIRED `record_id` never arrived, so cancelling just failed. Validate now replays
237
+ the platform's contract from the pulled KB (`primitives/*.json` `inputSchema`) and names the bad
238
+ argument, what the primitive takes, and any required argument that is missing. Scanning the 21
239
+ shipped marketplace packs found **ten** such errors across five packs. Keys are checked, values are
240
+ not an `args:` value is normally an expression string resolved at runtime.
241
+ [`args-check.ts`](src/lib/args-check.ts).
242
+ - Silently skipped when the KB has not been pulled yet, like the render-intent check beside it.
243
+ - It cannot see inside a `use:` template body (expanding one is the platform's job) — `octwin
244
+ validate --remote` covers that case, and now also reports unreachable `$bind.<path>` reads and
245
+ `outputs:` port typos, which have no offline equivalent.
246
+
247
+ ## [0.1.19] - 2026-07-29
248
+
249
+ ### Added
250
+ - **`octwin validate` catches render-intent fields the platform would drop offline, no token.** Each
251
+ render intent takes a fixed field set; a key outside it used to be swallowed at load and dropped at
252
+ render, with no error anywhere — just a card missing what the author wrote. Validate now replays the
253
+ platform's own contract from the pulled KB (`render-intents/*.json` → `allowed_keys`) and names the
254
+ bad field, the intent, and what is allowed. Scanning the 21 shipped marketplace packs found two real
255
+ instances in 223 intents: a `text_card` carrying `buttons` (the fallback's only escape button never
256
+ rendered) and a `list_picker` carrying a non-existent `group_by` (a whole mapper step feeding a
257
+ fabricated parameter). Silently skipped when the KB has not been pulled yet.
258
+
259
+ ### Changed
260
+ - **`octwin platform-kb pull` now explodes the reference and writes an `INDEX.md`.** A pull used to
261
+ write ~787 KB across 34 flat files about 225k tokens, more than the pack being authored — so
262
+ finding one primitive meant reading a 122 KB catalog. Catalogs the platform describes as
263
+ enumerable are now written **one file per entry** (`primitives/record_list.json`,
264
+ `render-intents/carousel.json`, `declarations/xrm.json`, …), with `INDEX.md` mapping every doc and
265
+ all 171 entries to its file with a one-line summary. Reading one primitive went from ~38k tokens to
266
+ ~700. The layout comes from the platform (an `entries` descriptor on the bundle index) rather than
267
+ being hardcoded here, so this CLI keeps working against any platform version and a platform that
268
+ adds a catalog needs no CLI release. The pulled directory is cleared each pull, so a withdrawn
269
+ capability cannot linger as a file the authoring agent reads as current its *contents* are
270
+ cleared rather than the directory itself, since on Windows a directory that is any shell's working
271
+ directory cannot be removed.
272
+
273
+ ## [0.1.18] - 2026-07-29
274
+
275
+ ### Fixed
276
+ - **The bundle collector and the platform's repo import disagreed about what a pack directory
277
+ contains.** `collectBundleFiles` had its own rule and kept three things the operator's GitHub
278
+ import drops: `*.ts`/`*.tsx`, `__snapshots__/` and `*.example`. Because the server rejects a
279
+ `.ts` outright, the same directory imported cleanly from GitHub and failed to deploy from
280
+ disk with `'…': executable code is not allowed` an error blaming a file that was never
281
+ meant to ship (a snapshot test beside a flow, or the documented `xrm.yaml.example` template).
282
+ The rule now lives in a vendored `lib/pack-source.ts` beside the vendored validator, and a
283
+ parity test in the platform drives both copies over one table.
284
+
285
+ ### Removed
286
+ - **`pack.json` is gone — the saved login IS the deploy target.** The file had eroded to a single
287
+ field, `platform_url`, which `octwin login --url --token …` already states; `tenant`/`project`
288
+ became optional overrides once deploy tokens started carrying their own tenant and project pin.
289
+ So it was a second home for a fact `login` knew, and it cost real things: a machine-specific file
290
+ committed into every pack repo, a four-link config-precedence chain to document, and an
291
+ exclusion rule duplicated in the bundler and the server (which had already drifted apart).
292
+ `octwin init` and `octwin pull` no longer write one, and nothing reads one. **A pack directory is
293
+ now pack content and nothing else** the same repo deploys from any machine, and a pulled pack
294
+ redeploys with no flags.
295
+
296
+ ### Changed
297
+ - **`octwin login` now sets the default deploy target**, not just the token: it writes a
298
+ `default_url` key alongside the token in `~/.octwin/credentials.json`. Every command resolves
299
+ **flags env (`PACK_PLATFORM_URL`/`PACK_TENANT`/`PACK_PROJECT`/`PACK_TOKEN`) saved login**.
300
+ An existing credentials file simply has no `default_url` until the next `octwin login` — which is
301
+ exactly what the "no platform url" error now tells you to run. CI is unaffected (it passes
302
+ `PACK_PLATFORM_URL` + `PACK_TOKEN` as env and never had a `pack.json`that file is retired).
303
+
304
+ ## [0.1.17] - 2026-07-27
305
+
306
+ ### Added
307
+ - **`octwin pull <packId>` — write a DEPLOYED pack's source back to disk.** The inverse of
308
+ `deploy`. A pack pushed with the CLI lived on the platform as an artifact the runtime served
309
+ but nothing could hand back, so its only source copy was the machine that pushed it. Pull it,
310
+ fix it, redeploy it. Writes a `pack.json` (retired in 0.1.18the saved login is now the
311
+ target) so the pulled directory redeploys where it came from; `--version` picks a version,
312
+ `--force` overwrites a non-empty dir. You may pull a pack
313
+ your tenant OWNS (an operator token pulls any) — a pack you merely installed is not yours to
314
+ read.
315
+ - **`octwin chat --script <file>` — drive a whole conversation from one invocation.** One turn per
316
+ line, in order, in a single process over a single SSE connection, waiting for each turn to settle
317
+ before sending the next. Blank lines and `#` comments are skipped; `tap:<id>` presses a rendered
318
+ row/button (the rest of the line is kept verbatim, since a tap id is itself colon-delimited),
319
+ `media:<path>` uploads a file, and `media:<path> | caption` attaches one.
320
+
321
+ This exists because **chaining invocations races the agent loop.** A turn ends on a quiet gap
322
+ (`TURN_SETTLE_MS`), which can arrive while the server-side loop is still running so
323
+ `chat A && chat B` lets B's inbound land mid-turn, and the agent fills required fields with
324
+ placeholder text (`service_id: "service-id"`) or starts a second workflow run. Those look exactly
325
+ like flow bugs and cost a field author three separate investigations. The one-turn-per-invocation
326
+ rule is now stated in `chat --help` and the header usage as well.
327
+
328
+ ### Changed
329
+ - **`octwin deploy` reports the suspended runs it invalidated.** A redeploy rebuilds the pack's
330
+ tools, and in-flight flow runs live in those tools so every suspended conversation is dropped.
331
+ The deploy prints `ⓘ N suspended run(s) invalidated` when N > 0, instead of leaving the author to
332
+ discover it as a `workflow_resume_stale` on the next tap.
333
+
334
+ ## [0.1.16] - 2026-07-26
335
+
336
+ ### Added
337
+ - **A pack can ship committed images.** `jpg`/`jpeg`/`png`/`webp`/`gif`/`pdf` files in your pack
338
+ directory now travel as a separate `blobs` half of the bundle (base64 on the wire, `bytea` in
339
+ storage) instead of being rejected as "not an allowed pack file type". Declare each one under the
340
+ manifest's `static_assets: [{ file, key }]` and reference it from `config:` with the
341
+ `$pack_asset:<key>` sentinel the platform uploads it to the media system at install and the
342
+ sentinel resolves to the served URL. `octwin validate` reports the image count alongside the file
343
+ count.
344
+ - **The size ceilings fail locally, before upload** 2 MB per file, 32 MB of binary per pack,
345
+ mirroring the server. `svg` stays rejected: it is script-capable and these assets are served to
346
+ browsers.
347
+
348
+ ### Fixed
349
+ - **Committed images were silently corrupted.** `collectBundleFiles` read *every* file with
350
+ `readFileSync(full, 'utf8')`, so a JPEG went through a lossy UTF-8 decode and arrived mangled
351
+ server-side a deploy that "succeeded" and rendered a broken image. Binary files now split off
352
+ into `blobs` and keep their bytes.
353
+ - **`octwin status` reported `loaded=(none)` for every pack, always.** It printed a `loaded_version`
354
+ field that stopped existing when reload moved to content-sha keying, which also meant the
355
+ version-drift warning underneath it could never fire. It now prints the **content sha** the
356
+ instance has loaded and the one the catalog holds (with the artifact's `origin`), and warns off the
357
+ platform's own `up_to_date` flag. The sha is the more useful fact anyway: re-publishing the *same*
358
+ version changes it, which is exactly the author's inner loop. A pack that is live and current but
359
+ **withdrawn** from the catalog now says so, rather than reporting a clean bill of health.
360
+
361
+ ## [0.1.15] - 2026-07-25
362
+
363
+ ### Added
364
+ - **`octwin agents [packId::agentId] [--prompt]` see what your agent actually runs as.** The platform
365
+ gained *governed* agent settings: an operator platform default can override the `default_model`
366
+ (and history window / working-memory toggle) your manifest declares, and that override is invisible
367
+ from the pack source. The command prints each governed setting's **effective** value, **which layer
368
+ won** (project override → platform default → pack manifest), and warns explicitly when your pack's
369
+ declared value is *not* in force. `--prompt` prints the exact system prompt the LLM sees for this
370
+ project (pack instructions + platform protocol + any project overlay).
371
+ - **`octwin orders [reference_id]` the commerce a conversation produced.** The list (number,
372
+ status/payment, total, contact) or one order with its line items, the
373
+ subtotal/tax/shipping/discount/total breakdown, `payment_ref`, and the allowed transitions. On a
374
+ `pending`/`none` payment it explains *why that's expected*: the forward payment lifecycle is
375
+ **webhook-owned** (not patchable), and the default gateway-less `manual` driver makes
376
+ `payment_request` take its `empty` port — so the flow should confirm pay-on-delivery, not error.
377
+ - **`octwin analytics [entity]` — stage conversion for any pipelined entity.** The funnel engine that
378
+ powered journeys is now generic, so any entity declared with a `pipeline:` charts conversion +
379
+ drop-off (`--overview` / `--milestones` / `--trends` / `--cost`, and `--stage <id>` for the records
380
+ currently at a stage). An empty result names **both** causes the platform folds into one response —
381
+ no `pipeline:`, or no `view` grant on `record.<entity>`.
382
+ - **`octwin catalog [--readiness]`** commerce products with price, availability and stock
383
+ (`untracked` when the SKU isn't inventory-tracked), plus the WhatsApp catalog binding.
384
+ `--readiness` runs the Meta Graph checklist with each failing item's suggested fix.
385
+ - **`octwin scheduling [--slots <resourceRecordId>]`** — the engine state (bookable resource types,
386
+ upcoming slots, booked seats) or the computed slots for one resource. This is how you verify the
387
+ availability rules a `deploy --seed` created, which 0.1.14 could create but not read back.
388
+
389
+ ### Changed
390
+ - **Auth failures now name the scope the command needs.** Every 401/403 appends the required scope
391
+ (and the plan feature, where the route is entitlement-gated) from one client-side mirror of the
392
+ platform's scope registry replacing the per-command hardcoded strings, several of which named no
393
+ scope at all. It also spells out that `pack:deploy` / `media:generate` are direct-grant only, so a
394
+ `tenant:admin` preset token does **not** confer them (the most common "but my token is admin" dead end).
395
+
396
+ ## [0.1.14] - 2026-07-23
397
+
398
+ ### Fixed
399
+ - **A failed demo seed no longer reports a clean `✓`.** `deploy` softens non-fatal
400
+ install steps (e.g. a demo-seed row) to warning frames, but the summary still printed
401
+ `✓ Deployed` and exited 0 — so a deploy that seeded ZERO records read as success (the
402
+ false-✓ trap). `deploy` now collects those step errors, prints `⚠ Deployed with N
403
+ warning(s) data may be incomplete` with each message, and exits non-zero so CI /
404
+ a `deploy && chat` chain catches it.
405
+
406
+ ### Added
407
+ - **`login` echoes what the token reaches.** After saving, `octwin login` calls
408
+ `/api/self/t/whoami` and prints the workspace, project pin, and scopes so a fresh
409
+ token self-identifies without a second `octwin whoami` (best-effort; a network failure
410
+ never fails the save).
411
+ - **`validate --remote` surfaces warnings + the data-store trap.** The remote validate now
412
+ renders advisory `⚠` warnings (which don't block deploy) and, for a pack declaring
413
+ `required_adapters: [data-store]`, reports the missing-data-store error the deploy would
414
+ have 409'd on — plus a warning that an `xrm.yaml` pack usually shouldn't declare that
415
+ adapter at all. It also now catches (offline) a list-form `entities:` block and a
416
+ `localized: true` field seeded with a bare string traps that previously surfaced only
417
+ mid-seed at deploy.
418
+
419
+ ## [0.1.13] - 2026-07-22
420
+
421
+ ### Added
422
+ - **A headless media loop — produce + send.** Two paired additions close the coverage hole where any
423
+ media-collect flow (e.g. `activate-app`'s registration / ID uploads) stalled at the upload prompt
424
+ because the CLI could neither *make* nor *send* an image:
425
+ - `octwin media generate "<prompt>" [--out <file.png>] [--size 1024x1024] [--json]` AI-generates an
426
+ image on the platform (needs a `media:generate`-scoped token), stores it as a public asset, and prints
427
+ its `MEDIA-` handle + serve URL. `--out` downloads the bytes to a file (WhatsApp renders only
428
+ `.png`/`.jpg`); `--json` emits `{ media_id, url, mime, width, height, bytes }`.
429
+ - `octwin chat --media <file|media-id>` — uploads a local file (or a media id from
430
+ `media generate --json`) as an image/document/audio inbound; any accompanying `"message"` rides as
431
+ its caption. The platform's media pipeline folds the upload into a running collect, so media flows
432
+ are now fully drivable headlessly.
433
+
434
+ ### Changed
435
+ - **The KB-drift nudge now names what changed.** With the platform serving per-entry content hashes, the
436
+ post-command nudge appends a `(N changed · M added · K removed)` summary instead of a bare hash pair.
437
+ - **`octwin platform-kb` prints a changelog on pull.** Instead of only a doc/catalog count, the pull now
438
+ diffs the fresh index against your last pull and lists exactly which docs/catalogs were added (`+`),
439
+ changed (`~`), or removed (`-`) — so a replaced schema shape is visible, not silent.
440
+
441
+ ## [0.1.12] - 2026-07-22
442
+
443
+ ### Fixed
444
+ - **The KB-drift nudge now fires after `chat` too.** `chat` — the command a debugging session runs most —
445
+ was missing from the networked-command list, so a chat-heavy session never noticed the platform's
446
+ capability reference had moved. The nudge also now shows the concrete drift
447
+ (`old-hash new-hash`) so you can see it's real, not a heuristic.
448
+
449
+ ### Changed
450
+ - **Every networked command announces what it's doing before it does it.** `chat` prints
451
+ `→ Connecting to <tenant>/<project> as '<handle>' …` before opening the stream and
452
+ `… delivered waiting for the reply (up to Ns)` after the send; `status` / `whoami` /
453
+ `platform-kb` / `records` / `logs` / `cases` each print a one-line `→ …` header naming the
454
+ action and target before the first network call — no more silent seconds followed by a result
455
+ (or a hang with no clue what was being attempted). JSON modes (`--json`) stay clean for piping.
456
+
457
+ ## [0.1.11] - 2026-07-21
458
+
459
+ ### Fixed
460
+ - **Auth-failure hint on every command, not just the inspect family** (author-feedback A7). `validate`,
461
+ `deploy`, `status`, and the single-record read now print the scope/transient explanation on a 401/403
462
+ (the shared `authFailureHint`), instead of a bare `HTTP 401` that left it ambiguous whether the token
463
+ lacked a scope or the platform hiccupped.
464
+
465
+ ## [0.1.10] - 2026-07-21
466
+
467
+ The capability reference is now **pulled, never bundled** — the authoring skill dropped its committed KB
468
+ snapshot, so `octwin platform-kb pull` is the single source of truth, and the CLI watches it for drift.
469
+
470
+ ### Added
471
+ - **KB-drift nudge** after any command that already talks to the platform (`deploy` / `status` /
472
+ `test` / `validate --remote` / `records` / `cases` / `logs` / `whoami`), the CLI does one tiny
473
+ `octwin-platform-kb?meta=1` GET and prints a one-line "the platform capability reference changed —
474
+ `octwin platform-kb pull`" when the platform's `content_hash` no longer matches your last pull.
475
+ Fail-silent + TTY-only + no-op until you've pulled once — the sibling of the existing CLI-upgrade notice.
476
+
477
+ ### Changed
478
+ - **`octwin platform-kb pull`** now records the reference's `content_hash` in `.octwin/platform-kb/index.json`
479
+ (the fingerprint the drift check compares against).
480
+
481
+ The conversation-debugging release driven by the second author-feedback round (xpeng-egypt),
482
+ which found the author→validate→deploy loop solid but multi-turn debugging impossible from the CLI.
483
+
484
+ ### Added
485
+ - **`octwin chat --tap "<tap-id>"`** — press a rendered button / list row (sends the web channel's
486
+ `type:'interactive'` inbound). Paired with full render printing, any tap-driven flow is now drivable headlessly.
487
+ - **Full render printing** — every reply prints its complete interactive content: list sections/rows,
488
+ buttons, and carousel cards, each with its **tap id**; header/footer/list-button text included.
489
+ - **`--json` on `chat` / `logs` / `cases`** raw SSE envelopes (chat) or raw event/case payloads.
490
+ - **`octwin cases [caseId] [--queues]`** inspect casework (support tickets): the inbox, one case +
491
+ timeline + applicable decisions, or queue keys with open counts. Casework packs previously had zero
492
+ data-inspection path (`records case` was a dead end).
493
+ - **Per-subcommand `--help`/`-h`** intercepted before any network or auth work (previously
494
+ `octwin logs --help` made a network call and could die with a 401).
495
+ - Multi-message turns: `chat` now collects **all** of a turn's renders (quiet-gap detection) instead
496
+ of cutting at the first one.
497
+
498
+ ### Changed
499
+ - **`octwin logs`**: the conversation list shows contact handle + last activity and filters with
500
+ `--as <handle>`; the timeline prints **full** event text (no more 240-char truncation) plus the
501
+ renders each turn actually sent; footer points at `--json` / `mastra-messages` for verbatim payloads.
502
+ - **Network failures name the target URL** (`platform unreachable at <url>`), and 401s explain
503
+ invalid-token vs one-off transient instead of a bare status code.
504
+ - `octwin records` on an unknown entity now surfaces the server's error body, and for `case`/`cases`
505
+ points at `octwin cases`.
506
+
507
+ ### Fixed
508
+ - **Multi-turn chat actually works.** Two bugs made consecutive `octwin chat` calls look like "every
509
+ call starts fresh": (1) the idempotency `local_id` was a **constant** per handle, so the platform's
510
+ 5-minute inbound dedup silently dropped the second message — a fresh id is now minted per call;
511
+ (2) a fresh SSE connect **replays** recent history and the CLI printed the first render frame — i.e.
512
+ the *previous* turn's greeting — as the reply; the CLI now drains the replay and only accepts frames
513
+ newer than the connect boundary. The platform always kept one open conversation per `--as` handle —
514
+ the same handle genuinely continues the same conversation now.
515
+
516
+ ## [0.1.8] - 2026-07-21
517
+ - Global-first install guidance (`npm i -g octwin-cli`); upgrade notice suppressed under `npx`.
518
+
519
+ ## [0.1.7] - 2026-07-21
520
+ - `--version`/`-v` flag; daily fail-silent, TTY-only "newer version available" notice.
521
+
522
+ ## [0.1.6] - 2026-07-21
523
+ - `octwin init` scaffolds a home-hub menu (`home` list_picker + example `browse` tool) instead of a bare greeting.
524
+
525
+ ## [0.1.5] - 2026-07-20
526
+ - `octwin validate --remote` (the platform's FULL manifest + flow-DSL check, all errors at once);
527
+ headless debugging commands `octwin records` / `octwin logs` / `octwin chat`; `octwin test` became
528
+ an alias for `validate --remote`. First author-feedback round.
529
+
530
+ ## [0.1.4] - 2026-07-20
531
+ - Authoring reliability + DX fixes from the first end-to-end external-pack test.
532
+
533
+ ## [0.1.3] - 2026-07-20
534
+ - `octwin platform-kb pull` — pull the platform capability reference for the authoring skill.
535
+
536
+ ## [0.1.2] - 2026-07-20
537
+ - Proper npm README.
538
+
539
+ ## [0.1.1] - 2026-07-20
540
+ - Initial npm publish: `init` / `validate` / `login` / `whoami` / `deploy` / `status`, `oct_` deploy tokens.