octwin-cli 0.1.10 → 0.1.12
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 +107 -83
- package/dist/index.js +30 -4
- package/package.json +1 -1
- package/templates/starter/manifest.yaml +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,83 +1,107 @@
|
|
|
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.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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.12] - 2026-07-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **The KB-drift nudge now fires after `chat` too.** `chat` — the command a debugging session runs most —
|
|
12
|
+
was missing from the networked-command list, so a chat-heavy session never noticed the platform's
|
|
13
|
+
capability reference had moved. The nudge also now shows the concrete drift
|
|
14
|
+
(`old-hash → new-hash`) so you can see it's real, not a heuristic.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Every networked command announces what it's doing before it does it.** `chat` prints
|
|
18
|
+
`→ Connecting to <tenant>/<project> as '<handle>' …` before opening the stream and
|
|
19
|
+
`… delivered — waiting for the reply (up to Ns)` after the send; `status` / `whoami` /
|
|
20
|
+
`platform-kb` / `records` / `logs` / `cases` each print a one-line `→ …` header naming the
|
|
21
|
+
action and target before the first network call — no more silent seconds followed by a result
|
|
22
|
+
(or a hang with no clue what was being attempted). JSON modes (`--json`) stay clean for piping.
|
|
23
|
+
|
|
24
|
+
## [0.1.11] - 2026-07-21
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Auth-failure hint on every command, not just the inspect family** (author-feedback A7). `validate`,
|
|
28
|
+
`deploy`, `status`, and the single-record read now print the scope/transient explanation on a 401/403
|
|
29
|
+
(the shared `authFailureHint`), instead of a bare `HTTP 401` that left it ambiguous whether the token
|
|
30
|
+
lacked a scope or the platform hiccupped.
|
|
31
|
+
|
|
32
|
+
## [0.1.10] - 2026-07-21
|
|
33
|
+
|
|
34
|
+
The capability reference is now **pulled, never bundled** — the authoring skill dropped its committed KB
|
|
35
|
+
snapshot, so `octwin platform-kb pull` is the single source of truth, and the CLI watches it for drift.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- **KB-drift nudge** — after any command that already talks to the platform (`deploy` / `status` /
|
|
39
|
+
`test` / `validate --remote` / `records` / `cases` / `logs` / `whoami`), the CLI does one tiny
|
|
40
|
+
`octwin-platform-kb?meta=1` GET and prints a one-line "the platform capability reference changed —
|
|
41
|
+
`octwin platform-kb pull`" when the platform's `content_hash` no longer matches your last pull.
|
|
42
|
+
Fail-silent + TTY-only + no-op until you've pulled once — the sibling of the existing CLI-upgrade notice.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- **`octwin platform-kb pull`** now records the reference's `content_hash` in `.octwin/platform-kb/index.json`
|
|
46
|
+
(the fingerprint the drift check compares against).
|
|
47
|
+
|
|
48
|
+
The conversation-debugging release — driven by the second author-feedback round (xpeng-egypt),
|
|
49
|
+
which found the author→validate→deploy loop solid but multi-turn debugging impossible from the CLI.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- **`octwin chat --tap "<tap-id>"`** — press a rendered button / list row (sends the web channel's
|
|
53
|
+
`type:'interactive'` inbound). Paired with full render printing, any tap-driven flow is now drivable headlessly.
|
|
54
|
+
- **Full render printing** — every reply prints its complete interactive content: list sections/rows,
|
|
55
|
+
buttons, and carousel cards, each with its **tap id**; header/footer/list-button text included.
|
|
56
|
+
- **`--json` on `chat` / `logs` / `cases`** — raw SSE envelopes (chat) or raw event/case payloads.
|
|
57
|
+
- **`octwin cases [caseId] [--queues]`** — inspect casework (support tickets): the inbox, one case +
|
|
58
|
+
timeline + applicable decisions, or queue keys with open counts. Casework packs previously had zero
|
|
59
|
+
data-inspection path (`records case` was a dead end).
|
|
60
|
+
- **Per-subcommand `--help`/`-h`** — intercepted before any network or auth work (previously
|
|
61
|
+
`octwin logs --help` made a network call and could die with a 401).
|
|
62
|
+
- Multi-message turns: `chat` now collects **all** of a turn's renders (quiet-gap detection) instead
|
|
63
|
+
of cutting at the first one.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
- **`octwin logs`**: the conversation list shows contact handle + last activity and filters with
|
|
67
|
+
`--as <handle>`; the timeline prints **full** event text (no more 240-char truncation) plus the
|
|
68
|
+
renders each turn actually sent; footer points at `--json` / `mastra-messages` for verbatim payloads.
|
|
69
|
+
- **Network failures name the target URL** (`platform unreachable at <url>`), and 401s explain
|
|
70
|
+
invalid-token vs one-off transient instead of a bare status code.
|
|
71
|
+
- `octwin records` on an unknown entity now surfaces the server's error body, and for `case`/`cases`
|
|
72
|
+
points at `octwin cases`.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- **Multi-turn chat actually works.** Two bugs made consecutive `octwin chat` calls look like "every
|
|
76
|
+
call starts fresh": (1) the idempotency `local_id` was a **constant** per handle, so the platform's
|
|
77
|
+
5-minute inbound dedup silently dropped the second message — a fresh id is now minted per call;
|
|
78
|
+
(2) a fresh SSE connect **replays** recent history and the CLI printed the first render frame — i.e.
|
|
79
|
+
the *previous* turn's greeting — as the reply; the CLI now drains the replay and only accepts frames
|
|
80
|
+
newer than the connect boundary. The platform always kept one open conversation per `--as` handle —
|
|
81
|
+
the same handle genuinely continues the same conversation now.
|
|
82
|
+
|
|
83
|
+
## [0.1.8] - 2026-07-21
|
|
84
|
+
- Global-first install guidance (`npm i -g octwin-cli`); upgrade notice suppressed under `npx`.
|
|
85
|
+
|
|
86
|
+
## [0.1.7] - 2026-07-21
|
|
87
|
+
- `--version`/`-v` flag; daily fail-silent, TTY-only "newer version available" notice.
|
|
88
|
+
|
|
89
|
+
## [0.1.6] - 2026-07-21
|
|
90
|
+
- `octwin init` scaffolds a home-hub menu (`home` list_picker + example `browse` tool) instead of a bare greeting.
|
|
91
|
+
|
|
92
|
+
## [0.1.5] - 2026-07-20
|
|
93
|
+
- `octwin validate --remote` (the platform's FULL manifest + flow-DSL check, all errors at once);
|
|
94
|
+
headless debugging commands `octwin records` / `octwin logs` / `octwin chat`; `octwin test` became
|
|
95
|
+
an alias for `validate --remote`. First author-feedback round.
|
|
96
|
+
|
|
97
|
+
## [0.1.4] - 2026-07-20
|
|
98
|
+
- Authoring reliability + DX fixes from the first end-to-end external-pack test.
|
|
99
|
+
|
|
100
|
+
## [0.1.3] - 2026-07-20
|
|
101
|
+
- `octwin platform-kb pull` — pull the platform capability reference for the authoring skill.
|
|
102
|
+
|
|
103
|
+
## [0.1.2] - 2026-07-20
|
|
104
|
+
- Proper npm README.
|
|
105
|
+
|
|
106
|
+
## [0.1.1] - 2026-07-20
|
|
107
|
+
- Initial npm publish: `init` / `validate` / `login` / `whoami` / `deploy` / `status`, `oct_` deploy tokens.
|
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)
|
|
@@ -275,20 +282,22 @@ async function notifyIfKbStale(flags) {
|
|
|
275
282
|
return;
|
|
276
283
|
const meta = await res.json();
|
|
277
284
|
if (meta.content_hash && meta.content_hash !== localHash) {
|
|
278
|
-
console.error(
|
|
279
|
-
console.error(' Refresh it: octwin platform-kb
|
|
285
|
+
console.error(`\n⬆ the platform capability reference changed since you last pulled it (${localHash} → ${meta.content_hash}).`);
|
|
286
|
+
console.error(' Refresh it: octwin platform-kb');
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
catch { /* a KB check must never break the CLI */ }
|
|
283
290
|
}
|
|
284
|
-
/** Which commands already made
|
|
285
|
-
*
|
|
291
|
+
/** Which commands already made a platform call, so the trailing KB-drift poll
|
|
292
|
+
* rides on existing network work (never on offline `validate` / `init`;
|
|
293
|
+
* `platform-kb` refreshes the reference itself, so it needs no nudge). */
|
|
286
294
|
function commandTouchesPlatform(command, flags) {
|
|
287
295
|
switch (command) {
|
|
288
296
|
case 'validate': return flags.remote === true; // offline validate stays offline
|
|
289
297
|
case 'deploy':
|
|
290
298
|
case 'status':
|
|
291
299
|
case 'test':
|
|
300
|
+
case 'chat':
|
|
292
301
|
case 'records':
|
|
293
302
|
case 'cases':
|
|
294
303
|
case 'logs':
|
|
@@ -379,6 +388,7 @@ async function cmdValidate(flags) {
|
|
|
379
388
|
if (res.status === 404)
|
|
380
389
|
die('this platform has no /packs/validate endpoint yet (older version) — deploy runs the full check');
|
|
381
390
|
console.error(`✗ remote validate failed (HTTP ${res.status})`);
|
|
391
|
+
printAuthHint(res.status, url);
|
|
382
392
|
console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
|
|
383
393
|
process.exit(1);
|
|
384
394
|
}
|
|
@@ -440,6 +450,7 @@ function resolveTargetOrNull(flags, packDir) {
|
|
|
440
450
|
async function cmdWhoami(flags) {
|
|
441
451
|
const packDir = resolve(flags.dir ?? '.');
|
|
442
452
|
const { url, tenant, token } = resolveTarget(flags, packDir);
|
|
453
|
+
console.log(`→ Checking the saved token against '${tenant}' @ ${url} …`);
|
|
443
454
|
const res = await fetchOrDie(`${url}/api/admin/tenants/${tenant}/packs`, { headers: { authorization: `Bearer ${token}` } }, 'token check');
|
|
444
455
|
if (res.ok) {
|
|
445
456
|
console.log(`✓ Token valid for tenant '${tenant}' at ${url} (${token.startsWith('oct_') ? 'deploy token' : 'session token'})`);
|
|
@@ -545,6 +556,7 @@ async function cmdDeploy(flags) {
|
|
|
545
556
|
}
|
|
546
557
|
if (!res.ok) {
|
|
547
558
|
console.error(`✗ deploy failed (HTTP ${res.status})`);
|
|
559
|
+
printAuthHint(res.status, url);
|
|
548
560
|
console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
|
|
549
561
|
process.exit(1);
|
|
550
562
|
}
|
|
@@ -561,6 +573,7 @@ async function cmdStatus(flags) {
|
|
|
561
573
|
die('manifest.yaml must declare a string `id`');
|
|
562
574
|
const id = doc.id;
|
|
563
575
|
const localVersion = typeof doc?.version === 'string' ? doc.version : '?';
|
|
576
|
+
console.log(`→ Checking ${id}@${localVersion} on ${tenant}/${project} @ ${url} …`);
|
|
564
577
|
const res = await fetchOrDie(`${url}/api/admin/tenants/${tenant}/projects/${project}/packs/${id}/runtime`, {
|
|
565
578
|
headers: { authorization: `Bearer ${token}` },
|
|
566
579
|
}, 'status check');
|
|
@@ -576,6 +589,7 @@ async function cmdStatus(flags) {
|
|
|
576
589
|
if (res.status === 404)
|
|
577
590
|
die(`'${id}' is not installed on ${tenant}/${project} yet — run \`octwin deploy\` first`);
|
|
578
591
|
console.error(`✗ status check failed (HTTP ${res.status})`);
|
|
592
|
+
printAuthHint(res.status, url);
|
|
579
593
|
console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
|
|
580
594
|
process.exit(1);
|
|
581
595
|
}
|
|
@@ -599,6 +613,7 @@ async function cmdStatus(flags) {
|
|
|
599
613
|
async function cmdPlatformKb(flags) {
|
|
600
614
|
const packDir = resolve(flags.dir ?? '.');
|
|
601
615
|
const { url, tenant, token } = resolveTarget(flags, packDir);
|
|
616
|
+
console.log(`→ Pulling the platform capability reference from '${tenant}' @ ${url} …`);
|
|
602
617
|
const res = await fetchOrDie(`${url}/api/admin/tenants/${tenant}/octwin-platform-kb`, {
|
|
603
618
|
headers: { authorization: `Bearer ${token}` },
|
|
604
619
|
}, 'platform-kb pull');
|
|
@@ -664,6 +679,7 @@ async function cmdRecords(flags) {
|
|
|
664
679
|
const base = `${url}/api/admin/tenants/${tenant}/projects/${project}`;
|
|
665
680
|
const entity = flags._[0];
|
|
666
681
|
const recordId = flags._[1];
|
|
682
|
+
console.log(`→ Reading ${recordId ? `${entity} record ${recordId}` : entity ? `${entity} records` : 'the entity catalog'} from ${tenant}/${project} …`);
|
|
667
683
|
if (!entity) {
|
|
668
684
|
const { status, json } = await apiGet(`${base}/xrm/entities`, token);
|
|
669
685
|
if (status !== 200)
|
|
@@ -709,6 +725,8 @@ async function cmdRecords(flags) {
|
|
|
709
725
|
die('forbidden — mint a token with the `records:read` scope');
|
|
710
726
|
if (status === 404)
|
|
711
727
|
die(`record '${recordId}' not found`);
|
|
728
|
+
if (status === 401)
|
|
729
|
+
die(`could not read record — ${authFailureHint(401, url)}`);
|
|
712
730
|
if (status !== 200)
|
|
713
731
|
die(`could not read record (HTTP ${status})`);
|
|
714
732
|
console.log(JSON.stringify(json?.record ?? json, null, 2));
|
|
@@ -722,6 +740,8 @@ async function cmdLogs(flags) {
|
|
|
722
740
|
const convId = flags._[0];
|
|
723
741
|
const asJson = flags.json === true;
|
|
724
742
|
const asHandle = typeof flags.as === 'string' ? flags.as : undefined;
|
|
743
|
+
if (!asJson)
|
|
744
|
+
console.log(`→ Reading ${convId ? `conversation ${convId}` : 'recent conversations'} from ${tenant}/${project} …`);
|
|
725
745
|
if (!convId) {
|
|
726
746
|
const { status, json } = await apiGet(`${base}/conversations?limit=50`, token);
|
|
727
747
|
if (status !== 200)
|
|
@@ -916,6 +936,8 @@ async function cmdChat(flags) {
|
|
|
916
936
|
die('usage: octwin chat "your message" [--as <handle>] [--tap <tap-id>] [--json]');
|
|
917
937
|
// Fresh idempotency key per call (see the command doc above).
|
|
918
938
|
const localId = `cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
939
|
+
if (!asJson)
|
|
940
|
+
console.log(`→ Connecting to ${tenant}/${project} as '${from}' …`);
|
|
919
941
|
const evRes = await fetchOrDie(`${url}/api/web/events/${tenant}/${project}/${encodeURIComponent(from)}`, { headers: { accept: 'text/event-stream' } }, 'open chat stream');
|
|
920
942
|
if (!evRes.ok || !evRes.body)
|
|
921
943
|
die(`could not open chat stream (HTTP ${evRes.status})`);
|
|
@@ -949,6 +971,8 @@ async function cmdChat(flags) {
|
|
|
949
971
|
await cancel();
|
|
950
972
|
die(`send rejected (HTTP ${postRes.status}): ${await postRes.text()}`);
|
|
951
973
|
}
|
|
974
|
+
if (!asJson)
|
|
975
|
+
console.log(` … delivered — waiting for the reply (up to ${Math.round(REPLY_TIMEOUT_MS / 1000)}s)`);
|
|
952
976
|
// Phase 3 — collect THIS turn's renders (id > boundary). A turn can send
|
|
953
977
|
// several messages, so keep reading until a quiet gap after the last render.
|
|
954
978
|
const deadline = Date.now() + REPLY_TIMEOUT_MS;
|
|
@@ -995,6 +1019,8 @@ async function cmdCases(flags) {
|
|
|
995
1019
|
const base = `${url}/api/admin/tenants/${tenant}/projects/${project}`;
|
|
996
1020
|
const caseId = flags._[0];
|
|
997
1021
|
const asJson = flags.json === true;
|
|
1022
|
+
if (!asJson)
|
|
1023
|
+
console.log(`→ Reading ${flags.queues === true ? 'case queues' : caseId ? `case ${caseId}` : 'the case inbox'} from ${tenant}/${project} …`);
|
|
998
1024
|
const caseFail = (what, status, json) => {
|
|
999
1025
|
if (status === 403)
|
|
1000
1026
|
die(`forbidden — casework needs the 'cases' plan feature on this tenant, and a role whose grants reach the queue`);
|
package/package.json
CHANGED
|
@@ -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
|