octwin-cli 0.1.9 → 0.1.11

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 ADDED
@@ -0,0 +1,91 @@
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.1.11] - 2026-07-21
9
+
10
+ ### Fixed
11
+ - **Auth-failure hint on every command, not just the inspect family** (author-feedback A7). `validate`,
12
+ `deploy`, `status`, and the single-record read now print the scope/transient explanation on a 401/403
13
+ (the shared `authFailureHint`), instead of a bare `HTTP 401` that left it ambiguous whether the token
14
+ lacked a scope or the platform hiccupped.
15
+
16
+ ## [0.1.10] - 2026-07-21
17
+
18
+ The capability reference is now **pulled, never bundled** — the authoring skill dropped its committed KB
19
+ snapshot, so `octwin platform-kb pull` is the single source of truth, and the CLI watches it for drift.
20
+
21
+ ### Added
22
+ - **KB-drift nudge** — after any command that already talks to the platform (`deploy` / `status` /
23
+ `test` / `validate --remote` / `records` / `cases` / `logs` / `whoami`), the CLI does one tiny
24
+ `octwin-platform-kb?meta=1` GET and prints a one-line "the platform capability reference changed —
25
+ `octwin platform-kb pull`" when the platform's `content_hash` no longer matches your last pull.
26
+ Fail-silent + TTY-only + no-op until you've pulled once — the sibling of the existing CLI-upgrade notice.
27
+
28
+ ### Changed
29
+ - **`octwin platform-kb pull`** now records the reference's `content_hash` in `.octwin/platform-kb/index.json`
30
+ (the fingerprint the drift check compares against).
31
+
32
+ The conversation-debugging release — driven by the second author-feedback round (xpeng-egypt),
33
+ which found the author→validate→deploy loop solid but multi-turn debugging impossible from the CLI.
34
+
35
+ ### Added
36
+ - **`octwin chat --tap "<tap-id>"`** — press a rendered button / list row (sends the web channel's
37
+ `type:'interactive'` inbound). Paired with full render printing, any tap-driven flow is now drivable headlessly.
38
+ - **Full render printing** — every reply prints its complete interactive content: list sections/rows,
39
+ buttons, and carousel cards, each with its **tap id**; header/footer/list-button text included.
40
+ - **`--json` on `chat` / `logs` / `cases`** — raw SSE envelopes (chat) or raw event/case payloads.
41
+ - **`octwin cases [caseId] [--queues]`** — inspect casework (support tickets): the inbox, one case +
42
+ timeline + applicable decisions, or queue keys with open counts. Casework packs previously had zero
43
+ data-inspection path (`records case` was a dead end).
44
+ - **Per-subcommand `--help`/`-h`** — intercepted before any network or auth work (previously
45
+ `octwin logs --help` made a network call and could die with a 401).
46
+ - Multi-message turns: `chat` now collects **all** of a turn's renders (quiet-gap detection) instead
47
+ of cutting at the first one.
48
+
49
+ ### Changed
50
+ - **`octwin logs`**: the conversation list shows contact handle + last activity and filters with
51
+ `--as <handle>`; the timeline prints **full** event text (no more 240-char truncation) plus the
52
+ renders each turn actually sent; footer points at `--json` / `mastra-messages` for verbatim payloads.
53
+ - **Network failures name the target URL** (`platform unreachable at <url>`), and 401s explain
54
+ invalid-token vs one-off transient instead of a bare status code.
55
+ - `octwin records` on an unknown entity now surfaces the server's error body, and for `case`/`cases`
56
+ points at `octwin cases`.
57
+
58
+ ### Fixed
59
+ - **Multi-turn chat actually works.** Two bugs made consecutive `octwin chat` calls look like "every
60
+ call starts fresh": (1) the idempotency `local_id` was a **constant** per handle, so the platform's
61
+ 5-minute inbound dedup silently dropped the second message — a fresh id is now minted per call;
62
+ (2) a fresh SSE connect **replays** recent history and the CLI printed the first render frame — i.e.
63
+ the *previous* turn's greeting — as the reply; the CLI now drains the replay and only accepts frames
64
+ newer than the connect boundary. The platform always kept one open conversation per `--as` handle —
65
+ the same handle genuinely continues the same conversation now.
66
+
67
+ ## [0.1.8] - 2026-07-21
68
+ - Global-first install guidance (`npm i -g octwin-cli`); upgrade notice suppressed under `npx`.
69
+
70
+ ## [0.1.7] - 2026-07-21
71
+ - `--version`/`-v` flag; daily fail-silent, TTY-only "newer version available" notice.
72
+
73
+ ## [0.1.6] - 2026-07-21
74
+ - `octwin init` scaffolds a home-hub menu (`home` list_picker + example `browse` tool) instead of a bare greeting.
75
+
76
+ ## [0.1.5] - 2026-07-20
77
+ - `octwin validate --remote` (the platform's FULL manifest + flow-DSL check, all errors at once);
78
+ headless debugging commands `octwin records` / `octwin logs` / `octwin chat`; `octwin test` became
79
+ an alias for `validate --remote`. First author-feedback round.
80
+
81
+ ## [0.1.4] - 2026-07-20
82
+ - Authoring reliability + DX fixes from the first end-to-end external-pack test.
83
+
84
+ ## [0.1.3] - 2026-07-20
85
+ - `octwin platform-kb pull` — pull the platform capability reference for the authoring skill.
86
+
87
+ ## [0.1.2] - 2026-07-20
88
+ - Proper npm README.
89
+
90
+ ## [0.1.1] - 2026-07-20
91
+ - Initial npm publish: `init` / `validate` / `login` / `whoami` / `deploy` / `status`, `oct_` deploy tokens.
package/README.md CHANGED
@@ -143,7 +143,8 @@ needs **no database of its own**.
143
143
  ## Links
144
144
 
145
145
  - **npm:** <https://www.npmjs.com/package/octwin-cli>
146
- - **Command help:** `octwin help`
146
+ - **Command help:** `octwin help` (each subcommand also answers `--help`)
147
+ - **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
147
148
 
148
149
  ## License
149
150
 
package/dist/index.js CHANGED
@@ -98,6 +98,13 @@ function authFailureHint(status, url) {
98
98
  ? `the token was rejected — invalid / expired / revoked. If it JUST worked, this can be a one-off platform hiccup: retry once before re-logging in (octwin login --url ${url} --token oct_…)`
99
99
  : `the token is valid but not authorized here (missing scope, plan feature, or role)`;
100
100
  }
101
+ /** Print the auth-scope hint below an HTTP-failure line when it's a 401/403 —
102
+ * so every command explains a token problem, not just the inspect family
103
+ * (author-feedback A7). No-op for other statuses. */
104
+ function printAuthHint(status, url) {
105
+ if (status === 401 || status === 403)
106
+ console.error(` → ${authFailureHint(status, url)}`);
107
+ }
101
108
  /** Pretty-print a JSON error body (or raw text) for an HTTP failure line. */
102
109
  function errDetail(json) {
103
110
  if (json == null)
@@ -236,6 +243,66 @@ async function notifyIfOutdated() {
236
243
  }
237
244
  catch { /* a version check must never break the CLI */ }
238
245
  }
246
+ // ── platform-KB drift check (observe the pulled reference, TTY-only, fail-silent) ──
247
+ /** The `content_hash` of a previously-pulled KB in `<packDir>/.octwin/platform-kb/`,
248
+ * or null if nothing has been pulled yet. */
249
+ function readLocalKbHash(packDir) {
250
+ try {
251
+ const idx = JSON.parse(readFileSync(join(packDir, '.octwin', 'platform-kb', 'index.json'), 'utf8'));
252
+ return typeof idx.content_hash === 'string' ? idx.content_hash : null;
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }
258
+ /** Nudge (to stderr) when the platform's capability KB has changed since the last
259
+ * `octwin platform-kb pull`. The sibling of `notifyIfOutdated`, for the KB instead
260
+ * of the CLI: run only after commands that already hit the platform, so this adds
261
+ * a single tiny `?meta=1` GET on top of work that was networked anyway. Never
262
+ * throws — observing must never break a command. No-op until the author has pulled
263
+ * once (nothing to compare) or if the target/token can't be resolved. */
264
+ async function notifyIfKbStale(flags) {
265
+ if (!process.stdout.isTTY)
266
+ return;
267
+ try {
268
+ const packDir = resolve(flags.dir ?? '.');
269
+ const localHash = readLocalKbHash(packDir);
270
+ if (!localHash)
271
+ return; // never pulled → the skill already says to pull
272
+ const t = resolveTargetOrNull(flags, packDir);
273
+ if (!t)
274
+ return;
275
+ const ctrl = new AbortController();
276
+ const timer = setTimeout(() => ctrl.abort(), 2_000);
277
+ const res = await fetch(`${t.url}/api/admin/tenants/${t.tenant}/octwin-platform-kb?meta=1`, {
278
+ headers: { authorization: `Bearer ${t.token}` }, signal: ctrl.signal,
279
+ });
280
+ clearTimeout(timer);
281
+ if (!res.ok)
282
+ return;
283
+ const meta = await res.json();
284
+ if (meta.content_hash && meta.content_hash !== localHash) {
285
+ console.error('\n⬆ the platform capability reference changed since you last pulled it.');
286
+ console.error(' Refresh it: octwin platform-kb pull');
287
+ }
288
+ }
289
+ catch { /* a KB check must never break the CLI */ }
290
+ }
291
+ /** Which commands already made an authed platform call, so the trailing KB-drift
292
+ * poll rides on existing network work (never on offline `validate` / `init`). */
293
+ function commandTouchesPlatform(command, flags) {
294
+ switch (command) {
295
+ case 'validate': return flags.remote === true; // offline validate stays offline
296
+ case 'deploy':
297
+ case 'status':
298
+ case 'test':
299
+ case 'records':
300
+ case 'cases':
301
+ case 'logs':
302
+ case 'whoami': return true;
303
+ default: return false;
304
+ }
305
+ }
239
306
  // ── commands ────────────────────────────────────────────────────────────────
240
307
  function cmdInit(flags) {
241
308
  const target = flags._[0] ?? die('usage: octwin init <dir> [--id my-pack]');
@@ -319,6 +386,7 @@ async function cmdValidate(flags) {
319
386
  if (res.status === 404)
320
387
  die('this platform has no /packs/validate endpoint yet (older version) — deploy runs the full check');
321
388
  console.error(`✗ remote validate failed (HTTP ${res.status})`);
389
+ printAuthHint(res.status, url);
322
390
  console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
323
391
  process.exit(1);
324
392
  }
@@ -364,6 +432,19 @@ function resolveTarget(flags, packDir) {
364
432
  die('no token — generate a deploy token in the console (Settings → API tokens), then `octwin login --url <url> --token oct_…` or pass --token');
365
433
  return { url, tenant, project, token };
366
434
  }
435
+ /** Non-fatal `resolveTarget`: returns null (never dies) when any of url/tenant/token
436
+ * is missing. Used by the fail-silent KB-staleness observer, which must never
437
+ * interrupt a command over a config gap. */
438
+ function resolveTargetOrNull(flags, packDir) {
439
+ const cfg = readPackConfig(packDir);
440
+ const url = (flags.url ?? process.env.PACK_PLATFORM_URL ?? cfg.platform_url ?? '').replace(/\/$/, '');
441
+ const tenant = flags.tenant ?? process.env.PACK_TENANT ?? cfg.tenant ?? '';
442
+ const project = flags.project ?? process.env.PACK_PROJECT ?? cfg.project ?? 'main';
443
+ const token = flags.token ?? process.env.PACK_TOKEN ?? readCreds()[url] ?? '';
444
+ if (!url || !tenant || !token)
445
+ return null;
446
+ return { url, tenant, project, token };
447
+ }
367
448
  async function cmdWhoami(flags) {
368
449
  const packDir = resolve(flags.dir ?? '.');
369
450
  const { url, tenant, token } = resolveTarget(flags, packDir);
@@ -472,6 +553,7 @@ async function cmdDeploy(flags) {
472
553
  }
473
554
  if (!res.ok) {
474
555
  console.error(`✗ deploy failed (HTTP ${res.status})`);
556
+ printAuthHint(res.status, url);
475
557
  console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
476
558
  process.exit(1);
477
559
  }
@@ -503,6 +585,7 @@ async function cmdStatus(flags) {
503
585
  if (res.status === 404)
504
586
  die(`'${id}' is not installed on ${tenant}/${project} yet — run \`octwin deploy\` first`);
505
587
  console.error(`✗ status check failed (HTTP ${res.status})`);
588
+ printAuthHint(res.status, url);
506
589
  console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
507
590
  process.exit(1);
508
591
  }
@@ -561,7 +644,9 @@ async function cmdPlatformKb(flags) {
561
644
  writeFileSync(join(outDir, `${key}.json`), JSON.stringify(val, null, 2) + '\n', 'utf8');
562
645
  jsonCount++;
563
646
  }
564
- writeFileSync(join(outDir, 'index.json'), JSON.stringify({ version: bundle.version, generated_at: bundle.generated_at, index: bundle.index }, null, 2) + '\n', 'utf8');
647
+ // Persist `content_hash` too the staleness observer (`notifyIfKbStale`) reads
648
+ // it back and compares against the platform's current hash to nudge a re-pull.
649
+ writeFileSync(join(outDir, 'index.json'), JSON.stringify({ version: bundle.version, content_hash: bundle.content_hash, generated_at: bundle.generated_at, index: bundle.index }, null, 2) + '\n', 'utf8');
565
650
  console.log(`✓ Pulled the Octwin platform KB → ${outDir}`);
566
651
  console.log(` ${mdCount} markdown docs + ${jsonCount} JSON catalogs (reference version ${bundle.version ?? '?'})`);
567
652
  console.log(' The octwin-pack authoring skill reads these as the source of truth for what the platform supports.');
@@ -634,6 +719,8 @@ async function cmdRecords(flags) {
634
719
  die('forbidden — mint a token with the `records:read` scope');
635
720
  if (status === 404)
636
721
  die(`record '${recordId}' not found`);
722
+ if (status === 401)
723
+ die(`could not read record — ${authFailureHint(401, url)}`);
637
724
  if (status !== 200)
638
725
  die(`could not read record (HTTP ${status})`);
639
726
  console.log(JSON.stringify(json?.record ?? json, null, 2));
@@ -1114,6 +1201,11 @@ async function main() {
1114
1201
  break;
1115
1202
  default: die(`unknown command '${command}' — run \`octwin help\``);
1116
1203
  }
1117
- await notifyIfOutdated(); // trailing, fail-silent, TTY-only "newer version available" notice
1204
+ // Trailing, fail-silent, TTY-only notices. KB-drift only after commands that
1205
+ // already hit the platform (so it's one extra tiny GET, never a new call on
1206
+ // offline paths); CLI-upgrade always.
1207
+ if (commandTouchesPlatform(command, flags))
1208
+ await notifyIfKbStale(flags);
1209
+ await notifyIfOutdated();
1118
1210
  }
1119
1211
  main().catch((err) => die(err?.message ?? String(err)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "dist",
11
11
  "templates",
12
12
  "README.md",
13
+ "CHANGELOG.md",
13
14
  "LICENSE"
14
15
  ],
15
16
  "engines": {
@@ -41,3 +41,14 @@ agents:
41
41
  # relative; `.md` files may use built-in placeholders like {{pack.id}}.
42
42
  instructions:
43
43
  - file: prompts/identity.md
44
+ # No working_memory block — deliberate, and you should keep it that way.
45
+ # Declaring one makes the platform add Mastra's updateWorkingMemory tool +
46
+ # a "call it every turn" instruction: an extra LLM generation on most turns,
47
+ # and small models (like the lite default above) parrot the
48
+ # <working_memory_data> envelope straight into user-visible replies. Session
49
+ # identity (name/lang/phone) already reaches the model via the platform's
50
+ # first-turn [ctx: …] header, and tools read it from the request context —
51
+ # so you gain nothing here. Leave it off unless you're on a large model and
52
+ # have a concrete slot schema to maintain.
53
+ default_memory:
54
+ lastMessages: 10 # size of the recalled message window