octwin-cli 0.6.0 → 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.
- package/CHANGELOG.md +540 -498
- package/README.md +1 -1
- package/dist/index.js +455 -265
- package/dist/lib/net-hint.js +57 -0
- package/dist/lib/render-check.js +44 -6
- package/package.json +37 -37
- package/templates/starter/manifest.yaml +10 -0
package/dist/index.js
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* octwin projects [--archived] # the --project slugs this token can name
|
|
16
16
|
* octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
|
|
17
17
|
* octwin pull <packId> [--dir <out>] [--version v] [--force] # write a DEPLOYED pack's source back to disk
|
|
18
|
-
* octwin status [
|
|
18
|
+
* octwin status [<packId>] # did my deploy land? which version is live?
|
|
19
19
|
* octwin records [entity] [id] # inspect the pack's XRM data (records:read token)
|
|
20
20
|
* octwin work [recordId] [--queues] # inspect the work inbox (worked records) — list / one item + timeline
|
|
21
21
|
* octwin logs [conversationId] [--as h] [--json] # list conversations / show one's timeline
|
|
22
22
|
* octwin chat "msg" [--as h] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn via the web channel (+ send media)
|
|
23
23
|
* octwin chat --script <file> [--as h] # drive a WHOLE conversation, one turn per line (the reliable way to test a flow)
|
|
24
|
-
* octwin media generate "<prompt>" [--out <file.png>] [--
|
|
24
|
+
* octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image → MEDIA- handle (media:generate scope)
|
|
25
25
|
* octwin agents [packId::agentId] [--prompt] # effective model/memory + which layer won; --prompt = the resolved system prompt
|
|
26
26
|
* octwin orders [reference_id] # the orders a conversation produced — money breakdown + payment state (orders:read)
|
|
27
27
|
* octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion, any pipelined entity
|
|
@@ -62,6 +62,7 @@ import { validatePackBundle, describePackNameProblem, asPackName } from './lib/v
|
|
|
62
62
|
import { loadAllowedRenderKeys, findRenderKeyViolations, describeRenderFinding } from './lib/render-check.js';
|
|
63
63
|
import { loadPrimitiveArgSpecs, findArgViolations, describeArgFinding } from './lib/args-check.js';
|
|
64
64
|
import { yamlLineOf } from './lib/yaml-pos.js';
|
|
65
|
+
import { localhostFamilyHint } from './lib/net-hint.js';
|
|
65
66
|
import { loadBuiltinNames, findBuiltinViolations, describeBuiltinFinding } from './lib/builtin-check.js';
|
|
66
67
|
import { loadTemplateSpecs, findTemplateViolations, describeTemplateFinding } from './lib/template-check.js';
|
|
67
68
|
import { loadSystemEntities, findEntityViolations, describeEntityFinding } from './lib/entity-check.js';
|
|
@@ -164,7 +165,8 @@ async function fetchOrDie(url, init, what) {
|
|
|
164
165
|
return await fetch(url, init);
|
|
165
166
|
}
|
|
166
167
|
catch (err) {
|
|
167
|
-
|
|
168
|
+
const hint = localhostFamilyHint(url, err);
|
|
169
|
+
die(`${what} — platform unreachable at ${url} (${err?.message ?? err})${hint ? `\n ${hint}` : ''}`);
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
/** One consistent explanation for auth failures on admin reads. A 401 can also
|
|
@@ -618,6 +620,54 @@ async function notifyIfKbStale(flags) {
|
|
|
618
620
|
}
|
|
619
621
|
catch { /* a KB check must never break the CLI */ }
|
|
620
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Nudge (to stderr) when the platform has memos this workspace has not read — a reply to
|
|
625
|
+
* a `octwin feedback` report, or a notice we published (new capability, deprecation,
|
|
626
|
+
* breaking change).
|
|
627
|
+
*
|
|
628
|
+
* The sibling of `notifyIfKbStale`, and the same three decisions apply for the same
|
|
629
|
+
* reasons: it rides commands that ALREADY hit the platform so it costs one tiny GET on
|
|
630
|
+
* top of networked work; it never throws, because observing must not break a command;
|
|
631
|
+
* and it is **deliberately not TTY-gated**, because the primary reader of this CLI is an
|
|
632
|
+
* authoring agent whose output is piped — the one reader that cannot notice an unread
|
|
633
|
+
* reply on its own would otherwise be the only one never told.
|
|
634
|
+
*
|
|
635
|
+
* WHY THIS EXISTS AT ALL. `octwin feedback` was one-way. Measured 2026-08-18: four
|
|
636
|
+
* reports sat unread for nine days, and one author hardcoded English across 16 flows to
|
|
637
|
+
* work around a bug that had been fixed two days earlier, because nothing could tell
|
|
638
|
+
* them. A nudge that does not name the command to run does not motivate an agent, so the
|
|
639
|
+
* line always ends in `octwin memos`.
|
|
640
|
+
*
|
|
641
|
+
* Unlike the KB check there is NO local state to compare: read state is per-tenant on the
|
|
642
|
+
* platform, so an agent on a fresh machine or in a fresh container still learns about an
|
|
643
|
+
* unread memo. That is the whole reason it is not a local marker file.
|
|
644
|
+
*/
|
|
645
|
+
async function notifyIfMemosWaiting(flags) {
|
|
646
|
+
try {
|
|
647
|
+
const t = resolveTargetOrNull(flags);
|
|
648
|
+
if (!t)
|
|
649
|
+
return;
|
|
650
|
+
const { status, json } = await apiGet(`${t.url}/api/self/p/memos?meta=1`, t);
|
|
651
|
+
// Silent on ANY failure — an older platform has no such route, and a nudge is never
|
|
652
|
+
// worth a diagnostic of its own. Unlike the KB poll there is no scope to explain: the
|
|
653
|
+
// route is gated on tenant access precisely so every token can answer it.
|
|
654
|
+
if (status !== 200 || !json || typeof json !== 'object')
|
|
655
|
+
return;
|
|
656
|
+
const unread = Number(json.unread ?? 0);
|
|
657
|
+
if (!Number.isFinite(unread) || unread <= 0)
|
|
658
|
+
return;
|
|
659
|
+
const actionable = Number(json.unread_actionable ?? 0);
|
|
660
|
+
const what = unread === 1 ? '1 memo' : `${unread} memos`;
|
|
661
|
+
const tail = actionable > 0 ? ` (${actionable} needing action)` : '';
|
|
662
|
+
console.error(`\n✉ ${what} from the platform${tail} — read them: octwin memos`);
|
|
663
|
+
if (actionable > 0) {
|
|
664
|
+
// Said separately, because the whole point of the severity axis is that an agent
|
|
665
|
+
// mid-build should stop and read rather than finish first.
|
|
666
|
+
console.error(' One or more may change what you are building — read before continuing.');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch { /* a memo check must never break the CLI */ }
|
|
670
|
+
}
|
|
621
671
|
/** Which commands already made a platform call, so the trailing KB-drift poll
|
|
622
672
|
* rides on existing network work (never on offline `validate` / `init`;
|
|
623
673
|
* `platform-kb` refreshes the reference itself, so it needs no nudge). */
|
|
@@ -630,6 +680,9 @@ function commandTouchesPlatform(command, flags) {
|
|
|
630
680
|
case 'chat':
|
|
631
681
|
case 'media':
|
|
632
682
|
case 'pull':
|
|
683
|
+
// `memos` is networked but needs NO memo nudge — it just read them. It still gets the
|
|
684
|
+
// KB drift check, which is a different question.
|
|
685
|
+
case 'memos': return true;
|
|
633
686
|
case 'records':
|
|
634
687
|
case 'work':
|
|
635
688
|
case 'logs':
|
|
@@ -748,7 +801,7 @@ async function cmdValidate(flags) {
|
|
|
748
801
|
const withLines = (fs) => fs.map(f => ({ ...f, line: files[f.file] ? yamlLineOf(files[f.file], f.path) : null }));
|
|
749
802
|
const render = loadAllowedRenderKeys(packDir);
|
|
750
803
|
if (render.keys) {
|
|
751
|
-
const findings = withLines(yamlDocs().flatMap(([p, doc]) => findRenderKeyViolations(doc, p, render.keys)));
|
|
804
|
+
const findings = withLines(yamlDocs().flatMap(([p, doc]) => findRenderKeyViolations(doc, p, render.keys, render.nested)));
|
|
752
805
|
if (findings.length) {
|
|
753
806
|
console.error(`✗ ${findings.length} render-intent field error${findings.length === 1 ? '' : 's'}:`);
|
|
754
807
|
for (const f of findings)
|
|
@@ -896,10 +949,16 @@ async function cmdValidate(flags) {
|
|
|
896
949
|
const t = resolveTarget(flags);
|
|
897
950
|
const { url } = t;
|
|
898
951
|
console.log(`→ Validating against ${targetLabel(t)} @ ${url} …`);
|
|
952
|
+
// `--strict-primitives` (opt-in): the server additionally type-checks LITERAL
|
|
953
|
+
// `args:` values against each primitive's per-key input schema. Expression
|
|
954
|
+
// strings are always exempt — this judges only plain YAML scalars.
|
|
899
955
|
const res = await fetchOrDie(`${url}/api/self/p/packs/validate`, {
|
|
900
956
|
method: 'POST',
|
|
901
957
|
headers: { 'content-type': 'application/json', ...authHeaders(t) },
|
|
902
|
-
body: JSON.stringify({
|
|
958
|
+
body: JSON.stringify({
|
|
959
|
+
files, blobs,
|
|
960
|
+
...(flags['strict-primitives'] === true ? { strict_primitives: true } : {}),
|
|
961
|
+
}),
|
|
903
962
|
}, 'remote validate');
|
|
904
963
|
const text = await res.text();
|
|
905
964
|
let json;
|
|
@@ -1257,13 +1316,13 @@ async function cmdSeed(flags) {
|
|
|
1257
1316
|
const { terminal: final, stepErrors } = await readDeployProgress(res.body);
|
|
1258
1317
|
if (!final || final.stage === 'error')
|
|
1259
1318
|
die(`seed failed${final?.message ? `: ${final.message}` : ' (stream ended early)'}`);
|
|
1260
|
-
console.log(`
|
|
1319
|
+
console.log(`
|
|
1261
1320
|
✓ ${final.message ?? 'seed complete'}`);
|
|
1262
1321
|
printSeedCounts(final.result?.seeded);
|
|
1263
1322
|
if (stepErrors.length) {
|
|
1264
1323
|
// A kind failed but the rest ran — the reconcile softens each step. Say which,
|
|
1265
1324
|
// and exit non-zero so a scripted `seed && chat` doesn't read as clean.
|
|
1266
|
-
console.error(`
|
|
1325
|
+
console.error(`
|
|
1267
1326
|
⚠ ${stepErrors.length} step${stepErrors.length === 1 ? '' : 's'} failed — data may be incomplete:`);
|
|
1268
1327
|
for (const e of stepErrors)
|
|
1269
1328
|
console.error(` • ${e}`);
|
|
@@ -1345,6 +1404,33 @@ async function cmdDeploy(flags) {
|
|
|
1345
1404
|
}
|
|
1346
1405
|
printDeploySuccess(id, version, t, json);
|
|
1347
1406
|
}
|
|
1407
|
+
/**
|
|
1408
|
+
* The QUALIFIED pack id (`<owner>.<name>`) for a manifest's bare name.
|
|
1409
|
+
*
|
|
1410
|
+
* A manifest declares a bare name and cannot legally declare anything else — the platform
|
|
1411
|
+
* and this CLI both reject a `.` in it, because the owner segment is attached at publish
|
|
1412
|
+
* time from the authenticated publisher. Every route that takes a pack id in its PATH
|
|
1413
|
+
* requires the qualified form, so a command holding only a manifest has to ask who it is.
|
|
1414
|
+
*
|
|
1415
|
+
* `whoami`'s `tenant_slug` IS the owner segment. Without this, `octwin status` sent the bare
|
|
1416
|
+
* name, `asPackId` rejected it, and the route's 404 was reported as "not installed" — so the
|
|
1417
|
+
* command was structurally broken for every pack from the day ids gained owners
|
|
1418
|
+
* (2026-08-01) until this was fixed, while the KB still taught it as step 4 of the loop.
|
|
1419
|
+
*/
|
|
1420
|
+
async function qualifiedPackId(t, bareName) {
|
|
1421
|
+
if (bareName.includes('.'))
|
|
1422
|
+
return bareName; // already qualified (explicit override)
|
|
1423
|
+
const res = await fetchOrDie(`${t.url}/api/self/t/whoami`, { headers: authHeaders(t) }, 'workspace lookup');
|
|
1424
|
+
if (!res.ok) {
|
|
1425
|
+
printAuthHint(res.status, t.url);
|
|
1426
|
+
die(`could not resolve your workspace to qualify the pack id (HTTP ${res.status}) — pass the full '<owner>.${bareName}' instead`);
|
|
1427
|
+
}
|
|
1428
|
+
const j = await res.json();
|
|
1429
|
+
if (typeof j.tenant_slug !== 'string' || !j.tenant_slug) {
|
|
1430
|
+
die(`the platform did not report a workspace slug — pass the full '<owner>.${bareName}' instead`);
|
|
1431
|
+
}
|
|
1432
|
+
return `${j.tenant_slug}.${bareName}`;
|
|
1433
|
+
}
|
|
1348
1434
|
async function cmdStatus(flags) {
|
|
1349
1435
|
const packDir = resolve(flags.dir ?? '.');
|
|
1350
1436
|
const t = resolveTarget(flags);
|
|
@@ -1355,8 +1441,11 @@ async function cmdStatus(flags) {
|
|
|
1355
1441
|
const doc = parseYaml(readFileSync(manifestPath, 'utf8'));
|
|
1356
1442
|
if (typeof doc?.id !== 'string')
|
|
1357
1443
|
die('manifest.yaml must declare a string `id`');
|
|
1358
|
-
const
|
|
1444
|
+
const bare = doc.id;
|
|
1359
1445
|
const localVersion = typeof doc?.version === 'string' ? doc.version : '?';
|
|
1446
|
+
// An explicit `octwin status <packId>` wins, so an author who already knows the qualified
|
|
1447
|
+
// id (from `octwin agents` / `octwin projects`) can skip the lookup.
|
|
1448
|
+
const id = await qualifiedPackId(t, flags._[1] ?? bare);
|
|
1360
1449
|
console.log(`→ Checking ${id}@${localVersion} on ${targetLabel(t)} @ ${url} …`);
|
|
1361
1450
|
const res = await fetchOrDie(`${url}/api/self/p/packs/${id}/runtime`, {
|
|
1362
1451
|
headers: authHeaders(t),
|
|
@@ -1370,8 +1459,16 @@ async function cmdStatus(flags) {
|
|
|
1370
1459
|
json = text;
|
|
1371
1460
|
}
|
|
1372
1461
|
if (!res.ok) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1462
|
+
// A 404 here means one of four different things, and collapsing them into "not
|
|
1463
|
+
// installed" sent one author chasing a deploy that had already succeeded. The route
|
|
1464
|
+
// says which; relay it and only add the deploy hint to the case it fits.
|
|
1465
|
+
if (res.status === 404) {
|
|
1466
|
+
const why = typeof json === 'object' && json !== null && typeof json.error === 'string' ? json.error : text;
|
|
1467
|
+
const notInstalled = /no active install|not installed/i.test(why);
|
|
1468
|
+
die(notInstalled
|
|
1469
|
+
? `${why}\n Run \`octwin deploy\` first.`
|
|
1470
|
+
: `status check failed (HTTP 404) — ${why}`);
|
|
1471
|
+
}
|
|
1375
1472
|
console.error(`✗ status check failed (HTTP ${res.status})`);
|
|
1376
1473
|
printAuthHint(res.status, url);
|
|
1377
1474
|
console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
|
|
@@ -2012,6 +2109,60 @@ async function cmdFeedback(flags) {
|
|
|
2012
2109
|
console.log(' ⓘ no local capability reference found, so the report carries no KB version.');
|
|
2013
2110
|
console.log(' Pull it before your next session: octwin platform-kb');
|
|
2014
2111
|
}
|
|
2112
|
+
console.log(' A reply arrives as a memo — this CLI will tell you when one is waiting.');
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* `octwin memos [--json] [--all]` — read what the platform has told you.
|
|
2116
|
+
*
|
|
2117
|
+
* The other half of `octwin feedback`. Two kinds arrive here: a REPLY to a report you
|
|
2118
|
+
* sent, and a NOTICE we published to every author (a new capability, a deprecation, a
|
|
2119
|
+
* breaking change). Both are markdown, printed in full — this is a channel for reading,
|
|
2120
|
+
* not a list to page through, so there is no `show <id>`.
|
|
2121
|
+
*
|
|
2122
|
+
* Reading ACKS: the default prints unread memos and marks them read, so the nudge stops.
|
|
2123
|
+
* `--all` re-reads history and acks nothing, which is also the escape hatch if a piped
|
|
2124
|
+
* `--json` consumer crashed halfway through.
|
|
2125
|
+
*/
|
|
2126
|
+
async function cmdMemos(flags) {
|
|
2127
|
+
const t = resolveTarget(flags);
|
|
2128
|
+
const all = flags.all === true;
|
|
2129
|
+
const { status, json } = await apiGet(`${t.url}/api/self/p/memos${all ? '?all=1' : ''}`, t);
|
|
2130
|
+
if (status !== 200) {
|
|
2131
|
+
die(`could not read memos (HTTP ${status})${errDetail(json)}${authFailureDetail(status, t.url)}`);
|
|
2132
|
+
}
|
|
2133
|
+
const rows = (json?.rows ?? []);
|
|
2134
|
+
if (flags.json === true) {
|
|
2135
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (rows.length === 0) {
|
|
2139
|
+
console.log(all ? 'No memos yet.' : 'No unread memos. (History: octwin memos --all)');
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
const mark = (s) => s === 'breaking' ? '⛔ BREAKING' : s === 'action_required' ? '⚠ ACTION' : 'ⓘ';
|
|
2143
|
+
for (const m of rows) {
|
|
2144
|
+
const when = String(m.published_at ?? '').slice(0, 10);
|
|
2145
|
+
const about = m.pack_id ? ` · ${m.pack_id}` : '';
|
|
2146
|
+
const seen = m.read_at ? ' · (already read)' : '';
|
|
2147
|
+
console.log(`\n${'─'.repeat(72)}`);
|
|
2148
|
+
console.log(`${mark(m.severity)} ${m.title}`);
|
|
2149
|
+
console.log(`${m.kind === 'reply' ? 'reply to your report' : 'platform notice'} · ${when}${about}${seen}`);
|
|
2150
|
+
console.log(`${'─'.repeat(72)}\n`);
|
|
2151
|
+
console.log(m.body_md.trimEnd());
|
|
2152
|
+
}
|
|
2153
|
+
console.log();
|
|
2154
|
+
// Ack only what we just showed as unread, and only on the default path — `--all`
|
|
2155
|
+
// deliberately leaves read state alone so history can be re-read any number of times.
|
|
2156
|
+
if (!all) {
|
|
2157
|
+
const ids = rows.filter(m => !m.read_at).map(m => m.id);
|
|
2158
|
+
if (ids.length > 0) {
|
|
2159
|
+
const res = await apiSend('POST', `${t.url}/api/self/p/memos/read`, { ids }, t);
|
|
2160
|
+
// A failed ack is not a failed command — the author HAS read them. Say so rather
|
|
2161
|
+
// than dying after printing everything, and the nudge simply fires again.
|
|
2162
|
+
if (res.status !== 200)
|
|
2163
|
+
console.error(`ⓘ could not mark them read (HTTP ${res.status}) — they will be offered again.`);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2015
2166
|
}
|
|
2016
2167
|
/** `octwin logs [conversationId] [--as <handle>] [--json]` — list conversations
|
|
2017
2168
|
* or show one's event timeline (full text + the renders each turn produced). */
|
|
@@ -2028,7 +2179,11 @@ async function cmdLogs(flags) {
|
|
|
2028
2179
|
const { status, json } = await apiGet(`${base}/conversations?limit=50`, t);
|
|
2029
2180
|
if (status !== 200)
|
|
2030
2181
|
die(`could not read conversations (HTTP ${status})${errDetail(json)}${authFailureDetail(status, url)}`);
|
|
2031
|
-
|
|
2182
|
+
// `rows` — the shared `makePage` envelope every other list command here reads. This one
|
|
2183
|
+
// read `json.conversations`, a noun the route stopped emitting on 2026-08-09, so `convs`
|
|
2184
|
+
// was ALWAYS empty and the command reported "No conversations yet" seconds after a chat
|
|
2185
|
+
// that had plainly worked. Two pack authors filed it as a replica-lag bug; nothing lagged.
|
|
2186
|
+
let convs = (json?.rows ?? []);
|
|
2032
2187
|
if (asHandle)
|
|
2033
2188
|
convs = convs.filter((c) => c.contact?.channel_contact_handle === asHandle);
|
|
2034
2189
|
if (convs.length === 0) {
|
|
@@ -2426,29 +2581,32 @@ async function cmdChat(flags) {
|
|
|
2426
2581
|
}
|
|
2427
2582
|
console.log(`\n(same --as '${from}' continues this conversation — timeline: octwin logs --as ${from})`);
|
|
2428
2583
|
}
|
|
2429
|
-
/** `octwin media generate "<prompt>" [--out <file.png>] [--
|
|
2584
|
+
/** `octwin media generate "<prompt>" [--out <file.png>] [--json]`
|
|
2430
2585
|
* — AI-generate an image on the platform (needs a `media:generate`-scoped token),
|
|
2431
2586
|
* store it as a public asset, and return its `MEDIA-` handle + serve URL. `--out`
|
|
2432
2587
|
* downloads the bytes to a file (WhatsApp renders only `.png`/`.jpg`); the paired
|
|
2433
|
-
* `octwin chat --media <file|id>` feeds it into a running media-collect flow.
|
|
2588
|
+
* `octwin chat --media <file|id>` feeds it into a running media-collect flow.
|
|
2589
|
+
*
|
|
2590
|
+
* A `--size` flag was accepted until 2026-08-10. The platform never forwarded it
|
|
2591
|
+
* to the provider, and stored resolution is decided at ingest regardless, so it
|
|
2592
|
+
* only ever changed the (fabricated) width/height the command printed back. */
|
|
2434
2593
|
async function cmdMedia(flags) {
|
|
2435
2594
|
const sub = flags._[0];
|
|
2436
2595
|
if (sub !== 'generate')
|
|
2437
|
-
die('usage: octwin media generate "<prompt>" [--out <file.png>] [--
|
|
2596
|
+
die('usage: octwin media generate "<prompt>" [--out <file.png>] [--json]');
|
|
2438
2597
|
const t = resolveTarget(flags);
|
|
2439
2598
|
const { url } = t;
|
|
2440
2599
|
const prompt = flags._[1];
|
|
2441
2600
|
if (!prompt)
|
|
2442
|
-
die('usage: octwin media generate "<prompt>" [--out <file.png>] [--
|
|
2601
|
+
die('usage: octwin media generate "<prompt>" [--out <file.png>] [--json]');
|
|
2443
2602
|
const asJson = flags.json === true;
|
|
2444
|
-
const size = typeof flags.size === 'string' ? flags.size : undefined;
|
|
2445
2603
|
const out = typeof flags.out === 'string' ? flags.out : undefined;
|
|
2446
2604
|
if (!asJson)
|
|
2447
2605
|
console.log(`→ Generating an image on ${targetLabel(t)} @ ${url} …`);
|
|
2448
2606
|
const res = await fetchOrDie(`${url}/api/self/p/media/generate`, {
|
|
2449
2607
|
method: 'POST',
|
|
2450
2608
|
headers: { 'content-type': 'application/json', ...authHeaders(t) },
|
|
2451
|
-
body: JSON.stringify({ prompt
|
|
2609
|
+
body: JSON.stringify({ prompt }),
|
|
2452
2610
|
}, 'media generate');
|
|
2453
2611
|
const text = await res.text();
|
|
2454
2612
|
if (!res.ok) {
|
|
@@ -2477,10 +2635,10 @@ async function cmdMedia(flags) {
|
|
|
2477
2635
|
}
|
|
2478
2636
|
}
|
|
2479
2637
|
if (asJson) {
|
|
2480
|
-
console.log(JSON.stringify({ media_id: r.media_id, url: absUrl, mime: r.mime,
|
|
2638
|
+
console.log(JSON.stringify({ media_id: r.media_id, url: absUrl, mime: r.mime, bytes: r.bytes }));
|
|
2481
2639
|
return;
|
|
2482
2640
|
}
|
|
2483
|
-
console.log(`✓ Generated ${r.media_ref} (${r.
|
|
2641
|
+
console.log(`✓ Generated ${r.media_ref} (${r.mime}, ${r.bytes} bytes)`);
|
|
2484
2642
|
console.log(` id: ${r.media_id}`);
|
|
2485
2643
|
console.log(` url: ${absUrl}`);
|
|
2486
2644
|
if (out)
|
|
@@ -3514,7 +3672,7 @@ async function cmdSchedulingWrite(flags) {
|
|
|
3514
3672
|
console.log(JSON.stringify(json, null, 2));
|
|
3515
3673
|
return;
|
|
3516
3674
|
}
|
|
3517
|
-
if (json?.
|
|
3675
|
+
if (json?.has_data === false) {
|
|
3518
3676
|
console.log('This pack declares no scheduling.');
|
|
3519
3677
|
return;
|
|
3520
3678
|
}
|
|
@@ -3589,7 +3747,7 @@ async function cmdSchedulingWrite(flags) {
|
|
|
3589
3747
|
const { status, json } = await apiSend('POST', `${base}/availability/${path}`, body, t);
|
|
3590
3748
|
if (status !== 201 && status !== 200)
|
|
3591
3749
|
writeFail(`add the ${noun}`, status, json, url);
|
|
3592
|
-
if (json?.
|
|
3750
|
+
if (json?.has_data === false)
|
|
3593
3751
|
die('this pack declares no scheduling');
|
|
3594
3752
|
const created = json?.rule ?? json?.exception ?? {};
|
|
3595
3753
|
console.log(`✓ ${noun[0].toUpperCase()}${noun.slice(1)} added — ${created.id ?? '(no id returned)'}`);
|
|
@@ -3610,30 +3768,42 @@ async function cmdScheduling(flags) {
|
|
|
3610
3768
|
if (flags.slots === true)
|
|
3611
3769
|
die('usage: octwin scheduling --slots <resourceRecordId> (the record id of a bookable resource)');
|
|
3612
3770
|
if (resourceId) {
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3771
|
+
// The range is `from`/`to`; `--days` is kept as the ergonomic flag and converted
|
|
3772
|
+
// here, since a CLI user thinks in "the next 7 days".
|
|
3773
|
+
const from = typeof flags.from === 'string' ? flags.from : new Date().toISOString().slice(0, 10);
|
|
3774
|
+
const days = typeof flags.days === 'string' ? Math.max(Number(flags.days) || 1, 1) : 7;
|
|
3775
|
+
const to = new Date(Date.parse(`${from}T00:00:00Z`) + (days - 1) * 86_400_000).toISOString().slice(0, 10);
|
|
3776
|
+
const q = new URLSearchParams({ include_booked: '1', from, to }); // full occupancy, as the operator preview does
|
|
3777
|
+
q.append('resource_id', resourceId);
|
|
3618
3778
|
if (!asJson)
|
|
3619
3779
|
console.log(`→ Computing slots for resource ${resourceId} in ${targetLabel(t)} …`);
|
|
3620
|
-
const { status, json } = await apiGet(`${base}/
|
|
3621
|
-
if (status === 404)
|
|
3622
|
-
die(`resource '${resourceId}' not found (pass an XRM record id — \`octwin records <entity>\` lists them)`);
|
|
3780
|
+
const { status, json } = await apiGet(`${base}/slots?${q.toString()}`, t);
|
|
3623
3781
|
if (status === 400)
|
|
3624
|
-
die(`
|
|
3782
|
+
die(`could not compute slots${errDetail(json)}`);
|
|
3625
3783
|
if (status !== 200)
|
|
3626
3784
|
die(`could not compute slots (HTTP ${status})${errDetail(json)}${authFailureDetail(status, url)}`);
|
|
3627
3785
|
if (asJson) {
|
|
3628
3786
|
console.log(JSON.stringify(json, null, 2));
|
|
3629
3787
|
return;
|
|
3630
3788
|
}
|
|
3631
|
-
if (json?.
|
|
3789
|
+
if (json?.has_data === false) {
|
|
3632
3790
|
console.log('This pack declares no `scheduling.yaml` — nothing to schedule.');
|
|
3633
3791
|
return;
|
|
3634
3792
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3793
|
+
// A resource the server could not use comes back named, with a reason, rather
|
|
3794
|
+
// than as an HTTP status — one call may carry several resources.
|
|
3795
|
+
const bad = (json?.unresolved ?? []).find((u) => u.resource_id === resourceId);
|
|
3796
|
+
if (bad?.reason === 'not_found') {
|
|
3797
|
+
die(`resource '${resourceId}' not found (pass an XRM record id — \`octwin records <entity>\` lists them)`);
|
|
3798
|
+
}
|
|
3799
|
+
if (bad?.reason === 'not_bookable')
|
|
3800
|
+
die(`that record isn't a bookable resource`);
|
|
3801
|
+
const res = (json?.resources ?? [])[0];
|
|
3802
|
+
const slots = (res?.slots ?? []);
|
|
3803
|
+
console.log(`Slots for ${resourceId} (timezone ${res?.timezone ?? '?'} — from ${res?.timezone_source ?? '?'}): ${slots.length}`);
|
|
3804
|
+
if (res?.timezone_source === 'pack_default' && json?.pack_timezone_declared === false) {
|
|
3805
|
+
console.log(' ⚠ The pack declares no `timezone:`, so these are UTC — a clock nobody chose.');
|
|
3806
|
+
}
|
|
3637
3807
|
if (slots.length === 0)
|
|
3638
3808
|
console.log(' (none — no availability rules cover this window; `octwin deploy --seed` seeds the demo rules)');
|
|
3639
3809
|
for (const s of slots) {
|
|
@@ -3650,7 +3820,7 @@ async function cmdScheduling(flags) {
|
|
|
3650
3820
|
console.log(JSON.stringify(json, null, 2));
|
|
3651
3821
|
return;
|
|
3652
3822
|
}
|
|
3653
|
-
if (json?.
|
|
3823
|
+
if (json?.has_data === false) {
|
|
3654
3824
|
console.log('This pack declares no `scheduling.yaml` — nothing to schedule.');
|
|
3655
3825
|
return;
|
|
3656
3826
|
}
|
|
@@ -3662,260 +3832,275 @@ async function cmdScheduling(flags) {
|
|
|
3662
3832
|
console.log('\nSlots for one resource: octwin scheduling --slots <resourceRecordId> (ids: octwin records <entity>)');
|
|
3663
3833
|
}
|
|
3664
3834
|
function help() {
|
|
3665
|
-
console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
|
|
3666
|
-
|
|
3667
|
-
octwin --version # print the CLI version (+ any upgrade notice)
|
|
3668
|
-
octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
|
|
3669
|
-
octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
|
|
3670
|
-
octwin login --url <platformUrl> --token oct_… # a deploy token from the console
|
|
3671
|
-
octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
|
|
3672
|
-
octwin projects [--archived] [--json] # the --project slugs this token can name
|
|
3673
|
-
octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
|
|
3674
|
-
octwin status [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
|
|
3675
|
-
octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
|
|
3676
|
-
octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
|
|
3677
|
-
octwin work [recordId] [--queues] [--json] # inspect the work inbox (worked records) + timelines
|
|
3678
|
-
octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
|
|
3679
|
-
octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
|
|
3680
|
-
octwin media generate "<prompt>" [--out <file.png>] [--
|
|
3681
|
-
octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
|
|
3682
|
-
octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
|
|
3683
|
-
octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
|
|
3684
|
-
octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
|
|
3685
|
-
octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
|
|
3686
|
-
octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
|
|
3687
|
-
octwin test [--dir .] # = validate --remote (the full platform check)
|
|
3688
|
-
octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
octwin records
|
|
3693
|
-
octwin
|
|
3694
|
-
octwin work
|
|
3695
|
-
octwin
|
|
3696
|
-
octwin
|
|
3697
|
-
octwin
|
|
3698
|
-
octwin
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3835
|
+
console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
|
|
3836
|
+
|
|
3837
|
+
octwin --version # print the CLI version (+ any upgrade notice)
|
|
3838
|
+
octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
|
|
3839
|
+
octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
|
|
3840
|
+
octwin login --url <platformUrl> --token oct_… # a deploy token from the console
|
|
3841
|
+
octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
|
|
3842
|
+
octwin projects [--archived] [--json] # the --project slugs this token can name
|
|
3843
|
+
octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
|
|
3844
|
+
octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
|
|
3845
|
+
octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
|
|
3846
|
+
octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
|
|
3847
|
+
octwin work [recordId] [--queues] [--json] # inspect the work inbox (worked records) + timelines
|
|
3848
|
+
octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
|
|
3849
|
+
octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
|
|
3850
|
+
octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image → MEDIA- handle (needs media:generate scope)
|
|
3851
|
+
octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
|
|
3852
|
+
octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
|
|
3853
|
+
octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
|
|
3854
|
+
octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
|
|
3855
|
+
octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
|
|
3856
|
+
octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
|
|
3857
|
+
octwin test [--dir .] # = validate --remote (the full platform check)
|
|
3858
|
+
octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
|
|
3859
|
+
octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
|
|
3860
|
+
|
|
3861
|
+
Writes — exercise the state your pack creates (each needs the matching :write scope):
|
|
3862
|
+
octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
|
|
3863
|
+
octwin records tasks | task complete <taskId> [--outcome done|cancelled]
|
|
3864
|
+
octwin work assign <id> --to user:<uuid>|none | note <id> "…" | stage <id> --to <stage>
|
|
3865
|
+
octwin work decide <id> --action <a> [--param k=v] [--dry-run] # --dry-run previews, commits nothing
|
|
3866
|
+
octwin orders transition <ref> --to <status> | refund <ref> --force
|
|
3867
|
+
octwin catalog availability <sku> --to "in stock" | stock <sku> [--set-on-hand n]
|
|
3868
|
+
octwin scheduling rules --resource <id> | rule add|rm | exception add|rm
|
|
3869
|
+
octwin agents set <packId::agentId> [--model m] [--enable-tool t] [--disable-tool t]
|
|
3870
|
+
|
|
3871
|
+
Multi-turn: the platform keeps ONE open conversation per --as handle — consecutive
|
|
3872
|
+
\`octwin chat --as <h>\` calls continue the same conversation; press a rendered
|
|
3873
|
+
button/row with \`--tap "<tap-id>"\` (chat prints every tap id).
|
|
3874
|
+
Get a deploy token: console → your workspace → Settings → API tokens → Generate (tick records:read to inspect data).
|
|
3875
|
+
octwin platform-kb pull → writes the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
|
|
3876
|
+
Config (deploy): flags > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login (\`octwin login\` sets the default target).
|
|
3706
3877
|
Per-command usage: octwin <command> --help`);
|
|
3707
3878
|
}
|
|
3708
3879
|
/** Per-subcommand usage — printed for `octwin <cmd> --help|-h` BEFORE any
|
|
3709
3880
|
* network/auth work (a --help that 401s is worse than no help at all). */
|
|
3710
3881
|
const COMMAND_HELP = {
|
|
3711
|
-
init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
|
|
3882
|
+
init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
|
|
3712
3883
|
Scaffold a pure-YAML starter pack into <dir>.`,
|
|
3713
|
-
validate: `octwin validate [--dir .] [--remote] [--require-kb]
|
|
3714
|
-
Offline structural check, plus two checks driven by the pulled capability
|
|
3715
|
-
reference (render-intent fields, primitive arguments). Those two SKIP when the
|
|
3716
|
-
reference is missing — the run says so, and --require-kb turns the skip into a
|
|
3717
|
-
failure for CI. --remote additionally runs the platform's FULL manifest +
|
|
3718
|
-
flow-DSL validation and its flow lint (all errors at once) — same check as deploy
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3884
|
+
validate: `octwin validate [--dir .] [--remote] [--require-kb] [--strict-primitives]
|
|
3885
|
+
Offline structural check, plus two checks driven by the pulled capability
|
|
3886
|
+
reference (render-intent fields, primitive arguments). Those two SKIP when the
|
|
3887
|
+
reference is missing — the run says so, and --require-kb turns the skip into a
|
|
3888
|
+
failure for CI. --remote additionally runs the platform's FULL manifest +
|
|
3889
|
+
flow-DSL validation and its flow lint (all errors at once) — same check as deploy.
|
|
3890
|
+
--strict-primitives (with --remote) additionally type-checks LITERAL args:
|
|
3891
|
+
values against each primitive's declared input schema; expression strings
|
|
3892
|
+
('$found.id', '{$t(…)}') are always exempt.`,
|
|
3893
|
+
login: `octwin login --url <platformUrl> --token oct_…
|
|
3894
|
+
Save a deploy token (console → Settings → API tokens) for that platform url,
|
|
3895
|
+
make that url the DEFAULT deploy target for every later command, and echo the
|
|
3722
3896
|
workspace + project pin + scopes the token reaches.`,
|
|
3723
|
-
whoami: `octwin whoami [--url <url>] [--tenant <slug>]
|
|
3897
|
+
whoami: `octwin whoami [--url <url>] [--tenant <slug>]
|
|
3724
3898
|
Verify the resolved token authenticates against the tenant.`,
|
|
3725
|
-
projects: `octwin projects [--archived] [--json]
|
|
3726
|
-
List the workspace's projects — the slugs every --project flag takes, with the
|
|
3727
|
-
plan's project cap. --archived includes archived ones. A pack:deploy token
|
|
3728
|
-
reaches this (it names a project in every other command).
|
|
3729
|
-
|
|
3730
|
-
octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
|
|
3731
|
-
Create a project. The URL slug is derived from the name unless --slug pins one.
|
|
3732
|
-
--pack installs an ALREADY-published pack; the usual next step is instead
|
|
3733
|
-
\`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
|
|
3734
|
-
|
|
3735
|
-
octwin projects rm <slug> [--yes]
|
|
3736
|
-
HARD delete — the project and everything cascading from it (conversations,
|
|
3737
|
-
contacts, records, installs). No undo, and not the same as archiving.
|
|
3738
|
-
WITHOUT --yes it only previews what would be destroyed, so the dry run is the
|
|
3739
|
-
default. Together these make a disposable end-to-end environment:
|
|
3740
|
-
octwin projects create "Scratch" && octwin deploy --project scratch --seed
|
|
3741
|
-
octwin chat "hi" --project scratch
|
|
3742
|
-
octwin projects rm scratch --yes
|
|
3899
|
+
projects: `octwin projects [--archived] [--json]
|
|
3900
|
+
List the workspace's projects — the slugs every --project flag takes, with the
|
|
3901
|
+
plan's project cap. --archived includes archived ones. A pack:deploy token
|
|
3902
|
+
reaches this (it names a project in every other command).
|
|
3903
|
+
|
|
3904
|
+
octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
|
|
3905
|
+
Create a project. The URL slug is derived from the name unless --slug pins one.
|
|
3906
|
+
--pack installs an ALREADY-published pack; the usual next step is instead
|
|
3907
|
+
\`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
|
|
3908
|
+
|
|
3909
|
+
octwin projects rm <slug> [--yes]
|
|
3910
|
+
HARD delete — the project and everything cascading from it (conversations,
|
|
3911
|
+
contacts, records, installs). No undo, and not the same as archiving.
|
|
3912
|
+
WITHOUT --yes it only previews what would be destroyed, so the dry run is the
|
|
3913
|
+
default. Together these make a disposable end-to-end environment:
|
|
3914
|
+
octwin projects create "Scratch" && octwin deploy --project scratch --seed
|
|
3915
|
+
octwin chat "hi" --project scratch
|
|
3916
|
+
octwin projects rm scratch --yes
|
|
3743
3917
|
Both verbs need the \`projects:write\` scope — a pack:deploy token does NOT confer it.`,
|
|
3744
|
-
deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
|
|
3745
|
-
Upload the pack bundle, validate server-side, install onto the project.
|
|
3918
|
+
deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
|
|
3919
|
+
Upload the pack bundle, validate server-side, install onto the project.
|
|
3746
3920
|
--seed additionally applies the pack's demo seed (streams progress).`,
|
|
3747
|
-
seed: `octwin seed [--pack <packId>]
|
|
3748
|
-
Apply the pack's demo/reference data to the project it is installed on, without
|
|
3749
|
-
redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
|
|
3750
|
-
and the demo operator topology. Reports what each kind produced.
|
|
3751
|
-
Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
|
|
3752
|
-
than regenerated, so a second pass costs nothing. --pack is only needed when a
|
|
3921
|
+
seed: `octwin seed [--pack <packId>]
|
|
3922
|
+
Apply the pack's demo/reference data to the project it is installed on, without
|
|
3923
|
+
redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
|
|
3924
|
+
and the demo operator topology. Reports what each kind produced.
|
|
3925
|
+
Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
|
|
3926
|
+
than regenerated, so a second pass costs nothing. --pack is only needed when a
|
|
3753
3927
|
project somehow runs more than one.`,
|
|
3754
|
-
status: `octwin status [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
|
|
3755
|
-
Show installed vs live version + the flow list for this pack
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
octwin records
|
|
3766
|
-
octwin records
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3928
|
+
status: `octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
|
|
3929
|
+
Show installed vs live version + the flow list for this pack.
|
|
3930
|
+
The pack id is read from manifest.yaml and QUALIFIED with your workspace slug
|
|
3931
|
+
(a manifest declares a bare name; the owner is attached when you publish). Pass
|
|
3932
|
+
<packId> explicitly to skip that lookup — \`octwin agents\` and \`octwin projects\`
|
|
3933
|
+
both print the qualified form.`,
|
|
3934
|
+
records: `octwin records [entity] [id] [--limit 50] [--offset n]
|
|
3935
|
+
Inspect the pack's XRM data. No args = list entities. Worked records (cases,
|
|
3936
|
+
tickets, anything routed to a queue) read best through \`octwin work\`.
|
|
3937
|
+
|
|
3938
|
+
WRITES (need \`records:write\`; every one is re-checked by RBAC on the record):
|
|
3939
|
+
octwin records create <entity> --set field=value [--set …] [--stage s] [--contact <id>]
|
|
3940
|
+
octwin records patch <recordId> --entity <entity> --set field=value
|
|
3941
|
+
octwin records stage <recordId> --to <stage> [--note "..."]
|
|
3942
|
+
octwin records note <recordId> "the note text"
|
|
3943
|
+
octwin records tasks # open follow-up tasks (\`tasks\` plan feature)
|
|
3944
|
+
octwin records task complete <taskId> [--outcome done|cancelled] [--note "..."]
|
|
3945
|
+
|
|
3946
|
+
--set coerces JSON scalars: \`--set rating=4.5\` sends a number, \`--set x=null\`
|
|
3947
|
+
sends null. Use --fields-json '{"a":{"b":1}}' for anything nested.
|
|
3948
|
+
\`patch\` needs --entity even though it has an id: the route resolves the field
|
|
3949
|
+
validator from it. A leading \`create/patch/stage/note/tasks/task\` is read as a
|
|
3772
3950
|
VERB — to list an entity actually named one of those, use \`--entity <name>\`.`,
|
|
3773
|
-
work: `octwin work [recordId] [--queues] [--limit 50] [--offset n] [--json]
|
|
3774
|
-
Inspect the work inbox — every entity the pack declares worked (cases, orders
|
|
3775
|
-
needing review, applications, …): the inbox, one item + its timeline
|
|
3776
|
-
(+ applicable actions), or --queues for queue keys + open counts.
|
|
3777
|
-
|
|
3778
|
-
WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
|
|
3779
|
-
octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
|
|
3780
|
-
octwin work note <recordId> "the note text"
|
|
3781
|
-
octwin work stage <recordId> --to <stage> [--note "..."]
|
|
3782
|
-
octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
|
|
3783
|
-
|
|
3784
|
-
\`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
|
|
3785
|
-
lists them with their params. --dry-run previews the customer-facing copy and the
|
|
3786
|
-
resulting stage WITHOUT committing (that route needs only \`work:read\`).
|
|
3951
|
+
work: `octwin work [recordId] [--queues] [--limit 50] [--offset n] [--json]
|
|
3952
|
+
Inspect the work inbox — every entity the pack declares worked (cases, orders
|
|
3953
|
+
needing review, applications, …): the inbox, one item + its timeline
|
|
3954
|
+
(+ applicable actions), or --queues for queue keys + open counts.
|
|
3955
|
+
|
|
3956
|
+
WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
|
|
3957
|
+
octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
|
|
3958
|
+
octwin work note <recordId> "the note text"
|
|
3959
|
+
octwin work stage <recordId> --to <stage> [--note "..."]
|
|
3960
|
+
octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
|
|
3961
|
+
|
|
3962
|
+
\`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
|
|
3963
|
+
lists them with their params. --dry-run previews the customer-facing copy and the
|
|
3964
|
+
resulting stage WITHOUT committing (that route needs only \`work:read\`).
|
|
3787
3965
|
\`stage\` is the XRM records verb (one transition spelling platform-wide).`,
|
|
3788
|
-
logs: `octwin logs [conversationId] [--as <handle>] [--json]
|
|
3789
|
-
No id = recent conversations (handle, status, last activity; --as filters).
|
|
3790
|
-
With id = the full event timeline including what each turn rendered.
|
|
3966
|
+
logs: `octwin logs [conversationId] [--as <handle>] [--json]
|
|
3967
|
+
No id = recent conversations (handle, status, last activity; --as filters).
|
|
3968
|
+
With id = the full event timeline including what each turn rendered.
|
|
3791
3969
|
--json = raw events (verbatim payloads).`,
|
|
3792
|
-
pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
|
|
3793
|
-
Write a DEPLOYED pack's source back to disk — the inverse of deploy.
|
|
3794
|
-
A pack pushed with 'octwin deploy' lives on the platform as an artifact the
|
|
3795
|
-
runtime serves but nothing hands back, so its only source copy is the machine
|
|
3796
|
-
that pushed it. Pull it, fix it, redeploy it.
|
|
3797
|
-
Defaults to the version installed on the target project; --version overrides.
|
|
3798
|
-
--dir defaults to ./<packId>; a non-empty dir needs --force.
|
|
3799
|
-
The pulled dir redeploys where it came from — the target is your saved login.
|
|
3970
|
+
pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
|
|
3971
|
+
Write a DEPLOYED pack's source back to disk — the inverse of deploy.
|
|
3972
|
+
A pack pushed with 'octwin deploy' lives on the platform as an artifact the
|
|
3973
|
+
runtime serves but nothing hands back, so its only source copy is the machine
|
|
3974
|
+
that pushed it. Pull it, fix it, redeploy it.
|
|
3975
|
+
Defaults to the version installed on the target project; --version overrides.
|
|
3976
|
+
--dir defaults to ./<packId>; a non-empty dir needs --force.
|
|
3977
|
+
The pulled dir redeploys where it came from — the target is your saved login.
|
|
3800
3978
|
You may pull a pack your tenant OWNS (deployed); an operator token pulls any.`,
|
|
3801
|
-
chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
|
|
3802
|
-
octwin chat --script <file> [--as <handle>] [--json]
|
|
3803
|
-
Drive ONE turn through the dev web channel and print every render with its
|
|
3804
|
-
tap ids. Same --as handle = same conversation (multi-turn works).
|
|
3805
|
-
--tap presses a rendered button/list row instead of sending text.
|
|
3806
|
-
--media uploads a local file (or a media id from 'media generate --json') as
|
|
3807
|
-
an image/document/audio inbound — any "message" rides as its caption; feeds a
|
|
3808
|
-
running media-collect flow (e.g. activate-app).
|
|
3809
|
-
--json dumps the raw SSE envelopes for the turn.
|
|
3810
|
-
|
|
3811
|
-
--script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
|
|
3812
|
-
process over one connection — waiting for each turn to settle before sending
|
|
3813
|
-
the next. Use this for any multi-step flow: chaining shell invocations races
|
|
3814
|
-
the agent loop, because a turn ends on a quiet gap that can arrive while the
|
|
3815
|
-
server is still working (the symptom is placeholder-filled fields or a second
|
|
3816
|
-
workflow run). Blank lines and # comments are skipped:
|
|
3817
|
-
|
|
3818
|
-
# book an appointment end to end
|
|
3819
|
-
احجز موعد
|
|
3820
|
-
tap:t:invoke:book-appointment:doctor_id=D1
|
|
3821
|
-
media:./licence.jpg | here is my licence
|
|
3979
|
+
chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
|
|
3980
|
+
octwin chat --script <file> [--as <handle>] [--json]
|
|
3981
|
+
Drive ONE turn through the dev web channel and print every render with its
|
|
3982
|
+
tap ids. Same --as handle = same conversation (multi-turn works).
|
|
3983
|
+
--tap presses a rendered button/list row instead of sending text.
|
|
3984
|
+
--media uploads a local file (or a media id from 'media generate --json') as
|
|
3985
|
+
an image/document/audio inbound — any "message" rides as its caption; feeds a
|
|
3986
|
+
running media-collect flow (e.g. activate-app).
|
|
3987
|
+
--json dumps the raw SSE envelopes for the turn.
|
|
3988
|
+
|
|
3989
|
+
--script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
|
|
3990
|
+
process over one connection — waiting for each turn to settle before sending
|
|
3991
|
+
the next. Use this for any multi-step flow: chaining shell invocations races
|
|
3992
|
+
the agent loop, because a turn ends on a quiet gap that can arrive while the
|
|
3993
|
+
server is still working (the symptom is placeholder-filled fields or a second
|
|
3994
|
+
workflow run). Blank lines and # comments are skipped:
|
|
3995
|
+
|
|
3996
|
+
# book an appointment end to end
|
|
3997
|
+
احجز موعد
|
|
3998
|
+
tap:t:invoke:book-appointment:doctor_id=D1
|
|
3999
|
+
media:./licence.jpg | here is my licence
|
|
3822
4000
|
tap:t:resume:book-appointment:run_id=R1;_ctl_approved=true`,
|
|
3823
|
-
media: `octwin media generate "<prompt>" [--out <file.png>] [--
|
|
3824
|
-
AI-generate an image (needs a media:generate-scoped token), store it as a
|
|
3825
|
-
public asset, and print its MEDIA- handle + serve URL. --out downloads the
|
|
3826
|
-
bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
|
|
3827
|
-
|
|
3828
|
-
agents: `octwin agents [packId::agentId] [--prompt] [--json]
|
|
3829
|
-
No args = the roster with each agent's EFFECTIVE model and which layer set it.
|
|
3830
|
-
With an agent = every governed setting (model / memory.last_messages /
|
|
3831
|
-
working_memory) plus the layer that won — an operator PLATFORM default can
|
|
3832
|
-
override what your manifest declares, and this is where you see that.
|
|
3833
|
-
--prompt = the exact system prompt the LLM sees for this project (pack
|
|
3834
|
-
instructions + platform protocol + any project overlay). Needs agents:read.
|
|
3835
|
-
The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
|
|
3836
|
-
|
|
3837
|
-
WRITES (need \`agents:write\`):
|
|
3838
|
-
octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
|
|
3839
|
-
[--enable-tool <toolId>] [--disable-tool <toolId>]
|
|
3840
|
-
|
|
3841
|
-
Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
|
|
3842
|
-
so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
|
|
4001
|
+
media: `octwin media generate "<prompt>" [--out <file.png>] [--json]
|
|
4002
|
+
AI-generate an image (needs a media:generate-scoped token), store it as a
|
|
4003
|
+
public asset, and print its MEDIA- handle + serve URL. --out downloads the
|
|
4004
|
+
bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
|
|
4005
|
+
bytes }. Pair with 'octwin chat --media' to drive media flows.`,
|
|
4006
|
+
agents: `octwin agents [packId::agentId] [--prompt] [--json]
|
|
4007
|
+
No args = the roster with each agent's EFFECTIVE model and which layer set it.
|
|
4008
|
+
With an agent = every governed setting (model / memory.last_messages /
|
|
4009
|
+
working_memory) plus the layer that won — an operator PLATFORM default can
|
|
4010
|
+
override what your manifest declares, and this is where you see that.
|
|
4011
|
+
--prompt = the exact system prompt the LLM sees for this project (pack
|
|
4012
|
+
instructions + platform protocol + any project overlay). Needs agents:read.
|
|
4013
|
+
The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
|
|
4014
|
+
|
|
4015
|
+
WRITES (need \`agents:write\`):
|
|
4016
|
+
octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
|
|
4017
|
+
[--enable-tool <toolId>] [--disable-tool <toolId>]
|
|
4018
|
+
|
|
4019
|
+
Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
|
|
4020
|
+
so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
|
|
3843
4021
|
ids refuses --model with a 403 — the platform default governs there.`,
|
|
3844
|
-
orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
|
|
3845
|
-
No args = the order list (#number, status/payment, total, contact). With a
|
|
3846
|
-
reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
|
|
3847
|
-
payment_ref, and the allowed status transitions. Needs orders:read + the
|
|
3848
|
-
\`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
|
|
3849
|
-
so \`pending\` on a gateway-less workspace is expected, not a bug.
|
|
3850
|
-
|
|
3851
|
-
WRITES (need \`orders:write\`):
|
|
3852
|
-
octwin orders transition <reference_id> --to <status>
|
|
3853
|
-
octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
|
|
3854
|
-
|
|
3855
|
-
Refund is irreversible and moves money, hence --force. The route answers 200 even
|
|
3856
|
-
when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
|
|
3857
|
-
on a refusal rather than reporting a refund that never happened. Only a payment in
|
|
4022
|
+
orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
|
|
4023
|
+
No args = the order list (#number, status/payment, total, contact). With a
|
|
4024
|
+
reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
|
|
4025
|
+
payment_ref, and the allowed status transitions. Needs orders:read + the
|
|
4026
|
+
\`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
|
|
4027
|
+
so \`pending\` on a gateway-less workspace is expected, not a bug.
|
|
4028
|
+
|
|
4029
|
+
WRITES (need \`orders:write\`):
|
|
4030
|
+
octwin orders transition <reference_id> --to <status>
|
|
4031
|
+
octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
|
|
4032
|
+
|
|
4033
|
+
Refund is irreversible and moves money, hence --force. The route answers 200 even
|
|
4034
|
+
when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
|
|
4035
|
+
on a refusal rather than reporting a refund that never happened. Only a payment in
|
|
3858
4036
|
\`captured\` state can be refunded; \`payment_status\` is never settable directly.`,
|
|
3859
|
-
analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
|
|
3860
|
-
No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
|
|
3861
|
-
With an entity = stage-by-stage conversion (default --funnel) over the last 30
|
|
3862
|
-
days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
|
|
4037
|
+
analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
|
|
4038
|
+
No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
|
|
4039
|
+
With an entity = stage-by-stage conversion (default --funnel) over the last 30
|
|
4040
|
+
days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
|
|
3863
4041
|
range-filtered). Needs records:read + a \`view\` grant on \`record.<entity>\`.`,
|
|
3864
|
-
catalog: `octwin catalog [--readiness] [--json]
|
|
3865
|
-
The commerce \`product\` records + price, availability, stock (null = not
|
|
3866
|
-
inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
|
|
3867
|
-
Graph checklist (LIVE Graph calls; needs a bound access token). Needs
|
|
3868
|
-
catalog:read + the \`catalog\` plan feature.
|
|
3869
|
-
|
|
3870
|
-
WRITES (need \`catalog:write\`):
|
|
3871
|
-
octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
|
|
3872
|
-
octwin catalog stock <retailerId> [--set-on-hand <n>]
|
|
3873
|
-
|
|
3874
|
-
\`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
|
|
3875
|
-
inventory-tracked (always sellable), which is different from 0. Lowering on_hand
|
|
3876
|
-
below the units already reserved for open carts is refused. Creating/deleting
|
|
4042
|
+
catalog: `octwin catalog [--readiness] [--json]
|
|
4043
|
+
The commerce \`product\` records + price, availability, stock (null = not
|
|
4044
|
+
inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
|
|
4045
|
+
Graph checklist (LIVE Graph calls; needs a bound access token). Needs
|
|
4046
|
+
catalog:read + the \`catalog\` plan feature.
|
|
4047
|
+
|
|
4048
|
+
WRITES (need \`catalog:write\`):
|
|
4049
|
+
octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
|
|
4050
|
+
octwin catalog stock <retailerId> [--set-on-hand <n>]
|
|
4051
|
+
|
|
4052
|
+
\`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
|
|
4053
|
+
inventory-tracked (always sellable), which is different from 0. Lowering on_hand
|
|
4054
|
+
below the units already reserved for open carts is refused. Creating/deleting
|
|
3877
4055
|
products and the Meta catalog binding/sync stay in the console.`,
|
|
3878
|
-
scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
|
|
3879
|
-
No args = the engine state (bookable resource types, upcoming slots, booked
|
|
3880
|
-
seats). --slots <recordId> computes the slots for one bookable resource
|
|
3881
|
-
(occupancy included; --days is clamped to 1-31 server-side) — the way to verify
|
|
3882
|
-
the availability rules a \`deploy --seed\` created. Needs scheduling:read.
|
|
3883
|
-
|
|
3884
|
-
RULES (list needs scheduling:read; add/rm need scheduling:write):
|
|
3885
|
-
octwin scheduling rules --resource <resourceRecordId>
|
|
3886
|
-
octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
|
|
3887
|
-
[--slot-minutes 30] [--capacity 1]
|
|
3888
|
-
octwin scheduling rule rm <ruleId>
|
|
3889
|
-
octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
|
|
3890
|
-
[--start 09:00 --end 13:00]
|
|
3891
|
-
octwin scheduling exception rm <exceptionId>
|
|
3892
|
-
|
|
3893
|
-
--dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
|
|
4056
|
+
scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
|
|
4057
|
+
No args = the engine state (bookable resource types, upcoming slots, booked
|
|
4058
|
+
seats). --slots <recordId> computes the slots for one bookable resource
|
|
4059
|
+
(occupancy included; --days is clamped to 1-31 server-side) — the way to verify
|
|
4060
|
+
the availability rules a \`deploy --seed\` created. Needs scheduling:read.
|
|
4061
|
+
|
|
4062
|
+
RULES (list needs scheduling:read; add/rm need scheduling:write):
|
|
4063
|
+
octwin scheduling rules --resource <resourceRecordId>
|
|
4064
|
+
octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
|
|
4065
|
+
[--slot-minutes 30] [--capacity 1]
|
|
4066
|
+
octwin scheduling rule rm <ruleId>
|
|
4067
|
+
octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
|
|
4068
|
+
[--start 09:00 --end 13:00]
|
|
4069
|
+
octwin scheduling exception rm <exceptionId>
|
|
4070
|
+
|
|
4071
|
+
--dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
|
|
3894
4072
|
\`--slots\` is how you check what a rule actually produces.`,
|
|
3895
|
-
'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
|
|
3896
|
-
Pull the platform capability reference (markdown + JSON catalogs) into
|
|
3897
|
-
.octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
|
|
3898
|
-
INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
|
|
3899
|
-
OUTLINE.md (every heading with its line number).
|
|
3900
|
-
|
|
3901
|
-
NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously.
|
|
3902
|
-
A token is used when you have one (it also works against older platforms).
|
|
3903
|
-
|
|
3904
|
-
--if-stale poll the platform's content_hash first and skip the download when
|
|
3905
|
-
nothing changed. Cheap enough to run at the start of every session.
|
|
3906
|
-
--check report only, write nothing. Exit 0 = current, 2 = stale or never
|
|
3907
|
-
pulled, 1 = could not tell (offline / refused). For scripts and
|
|
4073
|
+
'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
|
|
4074
|
+
Pull the platform capability reference (markdown + JSON catalogs) into
|
|
4075
|
+
.octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
|
|
4076
|
+
INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
|
|
4077
|
+
OUTLINE.md (every heading with its line number).
|
|
4078
|
+
|
|
4079
|
+
NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously.
|
|
4080
|
+
A token is used when you have one (it also works against older platforms).
|
|
4081
|
+
|
|
4082
|
+
--if-stale poll the platform's content_hash first and skip the download when
|
|
4083
|
+
nothing changed. Cheap enough to run at the start of every session.
|
|
4084
|
+
--check report only, write nothing. Exit 0 = current, 2 = stale or never
|
|
4085
|
+
pulled, 1 = could not tell (offline / refused). For scripts and
|
|
3908
4086
|
agent loops that want to branch without parsing prose.`,
|
|
3909
|
-
test: `octwin test [--dir .]
|
|
4087
|
+
test: `octwin test [--dir .]
|
|
3910
4088
|
Alias for \`octwin validate --remote\` — the full platform check.`,
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4089
|
+
memos: `octwin memos [--all] [--json]
|
|
4090
|
+
Read what the platform has told you: a REPLY to a report you sent with
|
|
4091
|
+
\`octwin feedback\`, or a NOTICE published to every author (a new capability,
|
|
4092
|
+
a deprecation, a breaking change). Bodies are printed in full.
|
|
4093
|
+
Reading marks them read, so the reminder stops. --all re-reads history and
|
|
4094
|
+
acks nothing. --json to branch on \`severity\`
|
|
4095
|
+
(info | action_required | breaking).`,
|
|
4096
|
+
feedback: `octwin feedback [--dir .]
|
|
4097
|
+
Submit this pack's FEEDBACK.md to the platform team.
|
|
4098
|
+
The octwin-pack skill writes that file in its last step — findings grouped by
|
|
4099
|
+
owner (A · CLI, B · Platform, C · Skill/KB). This delivers it instead of asking
|
|
4100
|
+
you to paste it into a chat.
|
|
4101
|
+
Attaches the pack id + version from manifest.yaml, this CLI's version, and the
|
|
4102
|
+
content_hash of the capability reference in .octwin/platform-kb/ — triage needs
|
|
4103
|
+
the last two to tell "the platform is wrong" from "that was already fixed" or
|
|
3919
4104
|
"you were reading a stale reference". Needs the \`pack:deploy\` scope.`,
|
|
3920
4105
|
};
|
|
3921
4106
|
async function main() {
|
|
@@ -3998,6 +4183,9 @@ async function main() {
|
|
|
3998
4183
|
case 'feedback':
|
|
3999
4184
|
await cmdFeedback(flags);
|
|
4000
4185
|
break;
|
|
4186
|
+
case 'memos':
|
|
4187
|
+
await cmdMemos(flags);
|
|
4188
|
+
break;
|
|
4001
4189
|
case 'test':
|
|
4002
4190
|
await cmdValidate({ ...flags, remote: true });
|
|
4003
4191
|
break; // A6: `test` = the full remote validate, not a validate-clone
|
|
@@ -4019,6 +4207,8 @@ async function main() {
|
|
|
4019
4207
|
// offline paths); CLI-upgrade always.
|
|
4020
4208
|
if (commandTouchesPlatform(command, flags))
|
|
4021
4209
|
await notifyIfKbStale(flags);
|
|
4210
|
+
if (commandTouchesPlatform(command, flags))
|
|
4211
|
+
await notifyIfMemosWaiting(flags);
|
|
4022
4212
|
await notifyIfOutdated();
|
|
4023
4213
|
}
|
|
4024
4214
|
main().catch((err) => die(err?.message ?? String(err)));
|