octwin-cli 0.8.4 → 0.8.6

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/dist/index.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * octwin pull <packId> [--dir <out>] [--version v] [--force] # write a DEPLOYED pack's source back to disk
19
19
  * octwin status [<packId>] # did my deploy land? which version is live?
20
20
  * octwin records [entity] [id] # inspect the pack's XRM data (records:read token)
21
- * octwin work [recordId] [--queues] # inspect the work inbox (worked records) — list / one item + timeline
21
+ * octwin work [recordId] [--queues] [--unrouted] # inspect the work inbox (worked records) — list / one item + timeline
22
22
  * octwin logs [conversationId] [--as h] [--json] # list conversations / show one's timeline
23
23
  * octwin chat "msg" [--as h] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn via the web channel (+ send media)
24
24
  * octwin chat --script <file> [--as h] # drive a WHOLE conversation, one turn per line (the reliable way to test a flow)
@@ -73,7 +73,8 @@ import { loadBuiltinNames, findBuiltinViolations, describeBuiltinFinding } from
73
73
  import { loadTemplateSpecs, findTemplateViolations, describeTemplateFinding } from './lib/template-check.js';
74
74
  import { loadSystemEntities, findEntityViolations, describeEntityFinding } from './lib/entity-check.js';
75
75
  import { loadDeclarationSpecs, findDeclarationViolations, describeDeclarationFinding } from './lib/declaration-check.js';
76
- import { describeKbLookup, findPlatformKbDir } from './lib/kb-path.js';
76
+ import { findPlatformKbDir } from './lib/kb-path.js';
77
+ import { reportChecks } from './lib/outcome.js';
77
78
  import { classifyPackPath, isSkippedDir } from './lib/pack-source.js';
78
79
  import { readPage, morePageHint } from './lib/page.js';
79
80
  import { kbOneLiner, buildKbIndexMarkdown, buildKbOutlineMarkdown, } from './lib/kb-index.js';
@@ -976,158 +977,161 @@ async function cmdValidate(flags) {
976
977
  const yamlDocs = () => parsed;
977
978
  // Checks that need the pulled KB. All of them DEGRADE when it is absent — the KB
978
979
  // is a gitignored cache wiped by every pull, so failing hard would break a fresh
979
- // clone before the author could act. But a skip is ANNOUNCED, and remembered:
980
- // the ✓ used to print above these blocks unconditionally while the per-check ✓s
981
- // lived inside the `if`s, so a KB-less run read as "one check, passed". An entire
982
- // backlog batch reached production that way. The defect is the silence, not the skip.
980
+ // clone before the author could act. But a skip is ANNOUNCED, and remembered.
983
981
  //
984
- // Reasons are COLLECTED rather than printed inline. When the KB is missing, every
985
- // check skips for the identical reason, and six copies of one sentence is how a
986
- // reader learns to scroll past the block which is the same failure as not
987
- // printing it. One line, naming all six.
988
- const skipped = [];
989
- const skipReasons = new Map();
990
- const noteSkip = (label, lookup) => {
991
- skipped.push(label);
992
- // Group by the lookup's IDENTITY, then let `describeKbLookup` phrase the one
993
- // line at print time — so the wording stays in the module that owns it and
994
- // cannot drift into a doubled "SKIPPED — SKIPPED —".
995
- const key = lookup.state === 'ok' ? 'ok'
996
- : `${lookup.state}|${'dir' in lookup ? lookup.dir : ''}|${'reason' in lookup ? lookup.reason : ''}`;
997
- const bucket = skipReasons.get(key) ?? { lookup, labels: [] };
998
- bucket.labels.push(label);
999
- skipReasons.set(key, bucket);
1000
- };
982
+ // Each check RETURNS an `Outcome` and prints nothing; `reportChecks` renders all of
983
+ // them and groups the skips by reason. That is the whole point: a check that returns
984
+ // nothing cannot reach the `passed` branch, so the false-`✓` this file printed twice
985
+ // before once for six checks at a time, once over an unparseable file — is no longer
986
+ // a mistake anyone can make. See [`lib/outcome.ts`](./lib/outcome.ts).
1001
987
  /** Stamp each finding with its source line — the walkers carry the node
1002
988
  * path; `files` holds the raw text the locator needs (E-08). */
1003
989
  const withLines = (fs) => fs.map(f => ({ ...f, line: files[f.file] ? yamlLineOf(files[f.file], f.path) : null }));
1004
- const render = loadAllowedRenderKeys(packDir);
1005
- if (render.keys) {
1006
- const findings = withLines(yamlDocs().flatMap(([p, doc]) => findRenderKeyViolations(doc, p, render.keys, render.nested)));
1007
- if (findings.length) {
1008
- console.error(`✗ ${findings.length} render-intent field error${findings.length === 1 ? '' : 's'}:`);
1009
- for (const f of findings)
1010
- console.error(` ✗ ${describeRenderFinding(f)}`);
1011
- die('fix these before deploying the platform rejects them at load, and before that they rendered as nothing');
1012
- }
1013
- console.log('✓ render intents use only fields the platform renders');
1014
- }
1015
- else {
1016
- noteSkip('render-intent fields', render.lookup);
1017
- }
1018
- // Primitive `args:` keys, same source and same contract. Cannot see inside a
1019
- // `use:` template body (expansion is the platform's job); `--remote` covers that.
1020
- const args = loadPrimitiveArgSpecs(packDir);
1021
- if (args.specs) {
1022
- const findings = withLines(yamlDocs().flatMap(([p, doc]) => findArgViolations(doc, p, args.specs)));
1023
- if (findings.length) {
1024
- console.error(`✗ ${findings.length} primitive-argument error${findings.length === 1 ? '' : 's'}:`);
1025
- for (const f of findings)
1026
- console.error(` ✗ ${describeArgFinding(f)}`);
1027
- die('fix these before deploying — an undeclared argument is dropped with no error at runtime');
1028
- }
1029
- console.log('✓ primitive arguments match their declared inputs');
1030
- }
1031
- else {
1032
- noteSkip('primitive arguments', args.lookup);
1033
- }
1034
- // Expression builtinsthe function set is CLOSED and generated from the
1035
- // runtime, so an invented `$fn(` is checkable here and nowhere else offline.
1036
- const builtins = loadBuiltinNames(packDir);
1037
- if (builtins.names) {
1038
- const findings = withLines(yamlDocs().flatMap(([p, doc]) => findBuiltinViolations(doc, p, builtins.names)));
1039
- if (findings.length) {
1040
- console.error(`✗ ${findings.length} unknown expression function${findings.length === 1 ? '' : 's'}:`);
1041
- for (const f of findings)
1042
- console.error(` ✗ ${describeBuiltinFinding(f)}`);
1043
- die('fix these before deploying — the evaluator cannot resolve them, and it fails mid-conversation');
1044
- }
1045
- console.log('✓ every $function() in an expression exists');
1046
- }
1047
- else {
1048
- noteSkip('expression functions', builtins.lookup);
1049
- }
1050
- // `use:` templates. A pack's OWN templates shadow the platform's, so they are
1051
- // named here and skipped — this check has no schema for them.
1052
- const templates = loadTemplateSpecs(packDir);
1053
- if (templates.specs) {
1054
- // A pack template is `templates/<name>.template.yaml` the `.template`
1055
- // segment is part of the convention the expander scans for, NOT part of the
1056
- // name a `use:` writes. Capturing it would leave every pack that shadows a
1057
- // platform template (kaiian shadows `field_prompt_render`) reported as using
1058
- // one that does not exist.
1059
- const packTemplates = new Set(Object.keys(files)
1060
- .map(p => /^templates\/(.+)\.template\.ya?ml$/i.exec(p.replace(/\\/g, '/'))?.[1])
1061
- .filter((n) => !!n));
1062
- const findings = yamlDocs().flatMap(([p, doc]) => findTemplateViolations(doc, p, templates.specs, packTemplates));
1063
- if (findings.length) {
1064
- console.error(`✗ ${findings.length} template error${findings.length === 1 ? '' : 's'}:`);
1065
- for (const f of findings)
1066
- console.error(` ✗ ${describeTemplateFinding(f)}`);
1067
- die('fix these before deploying — a template param that does not exist arrives as undefined, and renders as a blank');
1068
- }
1069
- console.log('✓ `use:` templates and their params exist');
1070
- }
1071
- else {
1072
- noteSkip('`use:` templates', templates.lookup);
1073
- }
1074
- // Reserved XRM entity keys — a boot error, which means the pack deploys clean
1075
- // and then fails to load on the first inbound message.
1076
- const system = loadSystemEntities(packDir);
1077
- if (system.entities) {
1078
- const findings = yamlDocs()
1079
- .filter(([p]) => /(^|[/\\])xrm\.ya?ml$/i.test(p))
1080
- .flatMap(([p, doc]) => findEntityViolations(doc, p, system.entities, system.rules));
1081
- if (findings.length) {
1082
- console.error(`✗ ${findings.length} reserved-entity error${findings.length === 1 ? '' : 's'}:`);
1083
- for (const f of findings)
1084
- console.error(` ✗ ${describeEntityFinding(f)}`);
1085
- die('fix these before deploying these fail at BOOT, after a deploy that reported success');
1086
- }
1087
- // Name the narrower promise when the catalog-level rules are absent (a KB
1088
- // pulled before they were published) — a ✓ that reads wider than what ran is
1089
- // the failure this file's skip contract exists to prevent.
1090
- console.log(system.rules
1091
- ? '✓ no entity collides with a reserved platform key, and no extension overrides a platform-owned one'
1092
- : '✓ no entity collides with a reserved platform key (re-pull for the `contact`/extension-override rules)');
1093
- }
1094
- else {
1095
- noteSkip('reserved entity keys', system.lookup);
1096
- }
1097
- // The declaration files themselves, against the published JSON Schemas.
1098
- // Deliberately narrow (see declaration-check.ts)it walks away from anything
1099
- // it cannot read rather than guessing.
1100
- const decls = loadDeclarationSpecs(packDir);
1101
- if (decls.specs) {
1102
- const findings = yamlDocs().flatMap(([p, doc]) => {
1103
- const base = p.replace(/\\/g, '/').split('/').pop() ?? p;
1104
- const spec = decls.specs.get(base);
1105
- // Only a file at the PACK ROOT is a declaration — `flows/tools/xrm.yaml`
1106
- // would be a flow that happens to share a name.
1107
- if (!spec || p.replace(/\\/g, '/').includes('/'))
1108
- return [];
1109
- return findDeclarationViolations(doc, spec).map(f => ({ ...f, file: p }));
1110
- });
1111
- if (findings.length) {
1112
- console.error(`✗ ${findings.length} declaration error${findings.length === 1 ? '' : 's'}:`);
1113
- for (const f of findings)
1114
- console.error(` ✗ ${describeDeclarationFinding(f)}`);
1115
- die('fix these before deploying a declaration file is parsed strictly, and an unknown key is rejected');
1116
- }
1117
- // Deliberately narrow wording. Most of `xrm.yaml`'s field shapes are a Zod
1118
- // union, which renders as `anyOf` and which this check walks away from by
1119
- // design — so "matches its schema" would be a promise it does not keep, and
1120
- // an over-claimed is how an author stops reading `--remote` output.
1121
- console.log('✓ declaration files carry no unknown or missing keys (unions are left to --remote)');
1122
- }
1123
- else {
1124
- noteSkip('declaration schemas', decls.lookup);
1125
- }
1126
- // One per distinct reason, naming every check it cost.
1127
- for (const { lookup, labels } of skipReasons.values()) {
1128
- const what = labels.length === 1 ? labels[0] : `${labels.length} checks (${labels.join(', ')})`;
1129
- console.log(`⚠ ${describeKbLookup(lookup, what)}`);
1130
- }
990
+ const checks = [
991
+ {
992
+ label: 'render-intent fields',
993
+ run: () => {
994
+ const render = loadAllowedRenderKeys(packDir);
995
+ if (!render.keys)
996
+ return { kind: 'not-run', lookup: render.lookup };
997
+ const findings = withLines(yamlDocs().flatMap(([p, doc]) => findRenderKeyViolations(doc, p, render.keys, render.nested)));
998
+ return findings.length
999
+ ? {
1000
+ kind: 'findings', noun: 'render-intent field error',
1001
+ lines: findings.map(describeRenderFinding),
1002
+ hint: 'fix these before deploying — the platform rejects them at load, and before that they rendered as nothing',
1003
+ }
1004
+ : { kind: 'passed', line: 'render intents use only fields the platform renders' };
1005
+ },
1006
+ },
1007
+ {
1008
+ // Primitive `args:` keys, same source and same contract. Cannot see inside a
1009
+ // `use:` template body (expansion is the platform's job); `--remote` covers that.
1010
+ label: 'primitive arguments',
1011
+ run: () => {
1012
+ const args = loadPrimitiveArgSpecs(packDir);
1013
+ if (!args.specs)
1014
+ return { kind: 'not-run', lookup: args.lookup };
1015
+ const findings = withLines(yamlDocs().flatMap(([p, doc]) => findArgViolations(doc, p, args.specs)));
1016
+ return findings.length
1017
+ ? {
1018
+ kind: 'findings', noun: 'primitive-argument error',
1019
+ lines: findings.map(describeArgFinding),
1020
+ hint: 'fix these before deploying an undeclared argument is dropped with no error at runtime',
1021
+ }
1022
+ : { kind: 'passed', line: 'primitive arguments match their declared inputs' };
1023
+ },
1024
+ },
1025
+ {
1026
+ // Expression builtins the function set is CLOSED and generated from the
1027
+ // runtime, so an invented `$fn(` is checkable here and nowhere else offline.
1028
+ label: 'expression functions',
1029
+ run: () => {
1030
+ const builtins = loadBuiltinNames(packDir);
1031
+ if (!builtins.names)
1032
+ return { kind: 'not-run', lookup: builtins.lookup };
1033
+ const findings = withLines(yamlDocs().flatMap(([p, doc]) => findBuiltinViolations(doc, p, builtins.names)));
1034
+ return findings.length
1035
+ ? {
1036
+ kind: 'findings', noun: 'unknown expression function',
1037
+ lines: findings.map(describeBuiltinFinding),
1038
+ hint: 'fix these before deploying — the evaluator cannot resolve them, and it fails mid-conversation',
1039
+ }
1040
+ : { kind: 'passed', line: 'every $function() in an expression exists' };
1041
+ },
1042
+ },
1043
+ {
1044
+ // `use:` templates. A pack's OWN templates shadow the platform's, so they are
1045
+ // named here and skipped — this check has no schema for them.
1046
+ label: '`use:` templates',
1047
+ run: () => {
1048
+ const templates = loadTemplateSpecs(packDir);
1049
+ if (!templates.specs)
1050
+ return { kind: 'not-run', lookup: templates.lookup };
1051
+ // A pack template is `templates/<name>.template.yaml` — the `.template`
1052
+ // segment is part of the convention the expander scans for, NOT part of the
1053
+ // name a `use:` writes. Capturing it would leave every pack that shadows a
1054
+ // platform template (kaiian shadows `field_prompt_render`) reported as using
1055
+ // one that does not exist.
1056
+ const packTemplates = new Set(Object.keys(files)
1057
+ .map(p => /^templates\/(.+)\.template\.ya?ml$/i.exec(p.replace(/\\/g, '/'))?.[1])
1058
+ .filter((n) => !!n));
1059
+ const findings = yamlDocs().flatMap(([p, doc]) => findTemplateViolations(doc, p, templates.specs, packTemplates));
1060
+ return findings.length
1061
+ ? {
1062
+ kind: 'findings', noun: 'template error',
1063
+ lines: findings.map(describeTemplateFinding),
1064
+ hint: 'fix these before deploying — a template param that does not exist arrives as undefined, and renders as a blank',
1065
+ }
1066
+ : { kind: 'passed', line: '`use:` templates and their params exist' };
1067
+ },
1068
+ },
1069
+ {
1070
+ // Reserved XRM entity keys — a boot error, which means the pack deploys clean
1071
+ // and then fails to load on the first inbound message.
1072
+ label: 'reserved entity keys',
1073
+ run: () => {
1074
+ const system = loadSystemEntities(packDir);
1075
+ if (!system.entities)
1076
+ return { kind: 'not-run', lookup: system.lookup };
1077
+ const findings = yamlDocs()
1078
+ .filter(([p]) => /(^|[/\\])xrm\.ya?ml$/i.test(p))
1079
+ .flatMap(([p, doc]) => findEntityViolations(doc, p, system.entities, system.rules));
1080
+ return findings.length
1081
+ ? {
1082
+ kind: 'findings', noun: 'reserved-entity error',
1083
+ lines: findings.map(describeEntityFinding),
1084
+ hint: 'fix these before deployingthese fail at BOOT, after a deploy that reported success',
1085
+ }
1086
+ // Name the narrower promise when the catalog-level rules are absent (a KB
1087
+ // pulled before they were published) — a ✓ that reads wider than what ran is
1088
+ // the failure this file's skip contract exists to prevent, and it is why
1089
+ // `passed` carries its own line instead of the printer inventing one.
1090
+ : {
1091
+ kind: 'passed',
1092
+ line: system.rules
1093
+ ? 'no entity collides with a reserved platform key, and no extension overrides a platform-owned one'
1094
+ : 'no entity collides with a reserved platform key (re-pull for the `contact`/extension-override rules)',
1095
+ };
1096
+ },
1097
+ },
1098
+ {
1099
+ // The declaration files themselves, against the published JSON Schemas.
1100
+ // Deliberately narrow (see declaration-check.ts) — it walks away from anything
1101
+ // it cannot read rather than guessing.
1102
+ label: 'declaration schemas',
1103
+ run: () => {
1104
+ const decls = loadDeclarationSpecs(packDir);
1105
+ if (!decls.specs)
1106
+ return { kind: 'not-run', lookup: decls.lookup };
1107
+ const findings = yamlDocs().flatMap(([p, doc]) => {
1108
+ const base = p.replace(/\\/g, '/').split('/').pop() ?? p;
1109
+ const spec = decls.specs.get(base);
1110
+ // Only a file at the PACK ROOT is a declaration `flows/tools/xrm.yaml`
1111
+ // would be a flow that happens to share a name.
1112
+ if (!spec || p.replace(/\\/g, '/').includes('/'))
1113
+ return [];
1114
+ return findDeclarationViolations(doc, spec).map(f => ({ ...f, file: p }));
1115
+ });
1116
+ return findings.length
1117
+ ? {
1118
+ kind: 'findings', noun: 'declaration error',
1119
+ lines: findings.map(describeDeclarationFinding),
1120
+ hint: 'fix these before deploying — a declaration file is parsed strictly, and an unknown key is rejected',
1121
+ }
1122
+ // Deliberately narrow wording. Most of `xrm.yaml`'s field shapes are a Zod
1123
+ // union, which renders as `anyOf` and which this check walks away from by
1124
+ // design — so "matches its schema" would be a promise it does not keep, and
1125
+ // an over-claimed ✓ is how an author stops reading `--remote` output.
1126
+ : { kind: 'passed', line: 'declaration files carry no unknown or missing keys (unions are left to --remote)' };
1127
+ },
1128
+ },
1129
+ ];
1130
+ const skipped = reportChecks(checks, {
1131
+ log: (l) => console.log(l),
1132
+ err: (l) => console.error(l),
1133
+ die,
1134
+ });
1131
1135
  // `--require-kb` is for CI, where a skip nobody reads is worse than a red build.
1132
1136
  if (skipped.length && flags['require-kb'] === true) {
1133
1137
  die(`--require-kb: ${skipped.length} check${skipped.length === 1 ? '' : 's'} could not run (${skipped.join(', ')})`);
@@ -1622,7 +1626,7 @@ async function cmdSeed(flags) {
1622
1626
  if (stepErrors.length) {
1623
1627
  // A kind failed but the rest ran — the reconcile softens each step. Say which,
1624
1628
  // and exit non-zero so a scripted `seed && chat` doesn't read as clean.
1625
- console.error(`
1629
+ console.error(`
1626
1630
  ⚠ ${stepErrors.length} step${stepErrors.length === 1 ? '' : 's'} failed — data may be incomplete:`);
1627
1631
  for (const e of stepErrors)
1628
1632
  console.error(` • ${e}`);
@@ -3155,12 +3159,36 @@ async function cmdWork(flags) {
3155
3159
  const name = pickLabel(q.name);
3156
3160
  console.log(` ${q.key}${name ? ` (${name})` : ''} ${q.open_count} open`);
3157
3161
  }
3158
- if (json?.unrouted_open_count)
3159
- console.log(` (unrouted: ${json.unrouted_open_count} open)`);
3162
+ /**
3163
+ * UNROUTED LEADS, because it is the finding — not a footnote to the list.
3164
+ *
3165
+ * The count was already here, as `(unrouted: N open)` tucked under the queues. That
3166
+ * is the wrong weight: a routed item is in a list someone works, and an unrouted one
3167
+ * is in NOBODY's list — it is the only line on this screen that means "these are
3168
+ * lost". A pack author spent five deploys cornering exactly that state in 2026-09
3169
+ * and reported `--queues` as the tool that finally showed it, which is the argument
3170
+ * for making it unmissable rather than for adding it.
3171
+ *
3172
+ * Still silent at zero: a clean project must stay quiet, or the warning stops
3173
+ * meaning anything.
3174
+ */
3175
+ const unrouted = Number(json?.unrouted_open_count ?? 0);
3176
+ if (unrouted > 0) {
3177
+ console.log(`\n⚠ ${unrouted} open item(s) are UNROUTED — in no queue, so nobody sees them.`);
3178
+ console.log(` Usually a routing declaration that resolves to nothing: check the item's`);
3179
+ console.log(` type against \`work.<entity>.types\` in worklist.yaml, and that the entity`);
3180
+ console.log(` has a \`queue:\`/\`route_by\` default for types that declare none.`);
3181
+ console.log(` List them: octwin work --unrouted`);
3182
+ }
3160
3183
  return;
3161
3184
  }
3162
3185
  if (!recordId) {
3163
- const { status, json } = await apiGet(`${base}/work?${pagingQs(flags)}`, t);
3186
+ // `--unrouted` narrows the inbox to the items in NO queue. The route has always
3187
+ // supported the filter; the CLI never passed it, so the count on `--queues` named a
3188
+ // problem it could not then show you. A number you cannot drill into is a dead end.
3189
+ const onlyUnrouted = flags.unrouted === true;
3190
+ const qs = `${pagingQs(flags)}${onlyUnrouted ? '&unrouted=true' : ''}`;
3191
+ const { status, json } = await apiGet(`${base}/work?${qs}`, t);
3164
3192
  if (status !== 200)
3165
3193
  workFail('the work inbox', status, json);
3166
3194
  if (asJson) {
@@ -3168,9 +3196,9 @@ async function cmdWork(flags) {
3168
3196
  return;
3169
3197
  }
3170
3198
  const page = readPage(json);
3171
- console.log(`Work items in ${targetLabel(t)}: ${page.total ?? page.rows.length} total`);
3199
+ console.log(`${onlyUnrouted ? 'UNROUTED work items' : 'Work items'} in ${targetLabel(t)}: ${page.total ?? page.rows.length} total`);
3172
3200
  if (page.rows.length === 0)
3173
- console.log(' (none)');
3201
+ console.log(onlyUnrouted ? ' (none — everything is routed)' : ' (none)');
3174
3202
  for (const w of page.rows) {
3175
3203
  const sla = w.sla_due_at ? ` sla:${w.sla_due_at}` : '';
3176
3204
  console.log(` #${w.record_number ?? '?'} ${w.entity}${w.type ? `/${w.type}` : ''} [${w.stage ?? '?'}] ${w.priority}${w.queue_key ? ` q:${w.queue_key}` : ''}${sla} ${w.record_id}`);
@@ -4956,371 +4984,372 @@ async function cmdUsage(flags) {
4956
4984
  console.log('\nThis is MODEL spend. WhatsApp/Meta message billing is operator-only — not reachable by an API token.');
4957
4985
  }
4958
4986
  function help() {
4959
- console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
4960
-
4961
- octwin --version # print the CLI version (+ any upgrade notice)
4962
- octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
4963
- octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
4964
- octwin login --url <platformUrl> --token oct_… # a deploy token from the console
4965
- octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
4966
- octwin projects [--archived] [--json] # the --project slugs this token can name
4967
- octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
4968
- [--request-listing | --withdraw-listing] # public marketplace — opt-in, see: octwin help deploy
4969
- octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
4970
- octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
4971
- octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
4972
- octwin work [recordId] [--queues] [--json] # inspect the work inbox (worked records) + timelines
4973
- octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
4974
- octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
4975
- octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image MEDIA- handle (needs media:generate scope)
4976
- octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
4977
- octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
4978
- octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
4979
- octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
4980
- octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
4981
- octwin automation [campaigns] [--json] # the jobs your declarations produced + health, last result each
4982
- octwin integrations [--json] # declared connections BESIDE what is configured (the silent-never-fires check)
4983
- octwin integrations deliveries [<id>] | events # the outbound delivery log / inbound events
4984
- octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition] [--stage <id>]
4985
- octwin performance [--detail] [--json] # the project's business indicators (value/conversion/duration)
4986
- octwin usage [--json] # model calls, tokens and cost (project if pinned, else workspace)
4987
- octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
4988
- octwin test [--dir .] # = validate --remote (the full platform check)
4989
- octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
4990
- octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
4991
-
4992
- Writes — exercise the state your pack creates (each needs the matching :write scope):
4993
- octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
4994
- octwin records tasks | task complete <taskId> [--outcome done|cancelled]
4995
- octwin work assign <id> --to user:<uuid>|none | note <id> "…" | stage <id> --to <stage>
4996
- octwin work decide <id> --action <a> [--param k=v] [--dry-run] # --dry-run previews, commits nothing
4997
- octwin orders transition <ref> --to <status> | refund <ref> --force
4998
- octwin catalog availability <sku> --to "in stock" | stock <sku> [--set-on-hand n]
4999
- octwin scheduling rules --resource <id> | rule add|rm | exception add|rm
5000
- octwin agents set <packId::agentId> [--model m] [--enable-tool t] [--disable-tool t]
5001
- octwin automation run <jobId> | pause <jobId> | resume <jobId> | send <campaignId>
5002
- octwin integrations test <key> # a LIVE call to the connection's health: operation
5003
- octwin integrations retry|cancel|send-now <deliveryId>
5004
- (octwin integrations preflight <key> needs only integrations:read — it makes no call)
5005
-
5006
- Multi-turn: the platform keeps ONE open conversation per --as handle — consecutive
5007
- \`octwin chat --as <h>\` calls continue the same conversation; press a rendered
5008
- button/row with \`--tap "<tap-id>"\` (chat prints every tap id).
5009
- Get a deploy token: console → your workspace → Settings → API tokens → Generate (tick records:read to inspect data).
5010
- octwin platform-kb pullwrites the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
5011
- Config (deploy): flags > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login (\`octwin login\` sets the default target).
4987
+ console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
4988
+
4989
+ octwin --version # print the CLI version (+ any upgrade notice)
4990
+ octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
4991
+ octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
4992
+ octwin login --url <platformUrl> --token oct_… # a deploy token from the console
4993
+ octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
4994
+ octwin projects [--archived] [--json] # the --project slugs this token can name
4995
+ octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
4996
+ [--request-listing | --withdraw-listing] # public marketplace — opt-in, see: octwin help deploy
4997
+ octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
4998
+ octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
4999
+ octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
5000
+ octwin work [recordId] [--queues] [--unrouted] [--json] # inspect the work inbox (worked records) + timelines
5001
+ # --queues: per-queue open counts + an UNROUTED warning · --unrouted: only the items in no queue
5002
+ octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
5003
+ octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
5004
+ octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image MEDIA- handle (needs media:generate scope)
5005
+ octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
5006
+ octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
5007
+ octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
5008
+ octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
5009
+ octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
5010
+ octwin automation [campaigns] [--json] # the jobs your declarations produced + health, last result each
5011
+ octwin integrations [--json] # declared connections BESIDE what is configured (the silent-never-fires check)
5012
+ octwin integrations deliveries [<id>] | events # the outbound delivery log / inbound events
5013
+ octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition] [--stage <id>]
5014
+ octwin performance [--detail] [--json] # the project's business indicators (value/conversion/duration)
5015
+ octwin usage [--json] # model calls, tokens and cost (project if pinned, else workspace)
5016
+ octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
5017
+ octwin test [--dir .] # = validate --remote (the full platform check)
5018
+ octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
5019
+ octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
5020
+
5021
+ Writes exercise the state your pack creates (each needs the matching :write scope):
5022
+ octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
5023
+ octwin records tasks | task complete <taskId> [--outcome done|cancelled]
5024
+ octwin work assign <id> --to user:<uuid>|none | note <id> "…" | stage <id> --to <stage>
5025
+ octwin work decide <id> --action <a> [--param k=v] [--dry-run] # --dry-run previews, commits nothing
5026
+ octwin orders transition <ref> --to <status> | refund <ref> --force
5027
+ octwin catalog availability <sku> --to "in stock" | stock <sku> [--set-on-hand n]
5028
+ octwin scheduling rules --resource <id> | rule add|rm | exception add|rm
5029
+ octwin agents set <packId::agentId> [--model m] [--enable-tool t] [--disable-tool t]
5030
+ octwin automation run <jobId> | pause <jobId> | resume <jobId> | send <campaignId>
5031
+ octwin integrations test <key> # a LIVE call to the connection's health: operation
5032
+ octwin integrations retry|cancel|send-now <deliveryId>
5033
+ (octwin integrations preflight <key> needs only integrations:read — it makes no call)
5034
+
5035
+ Multi-turn: the platform keeps ONE open conversation per --as handle consecutive
5036
+ \`octwin chat --as <h>\` calls continue the same conversation; press a rendered
5037
+ button/row with \`--tap "<tap-id>"\` (chat prints every tap id).
5038
+ Get a deploy token: console your workspace Settings API tokens → Generate (tick records:read to inspect data).
5039
+ octwin platform-kb pull writes the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
5040
+ Config (deploy): flags > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login (\`octwin login\` sets the default target).
5012
5041
  Per-command usage: octwin <command> --help`);
5013
5042
  }
5014
5043
  /** Per-subcommand usage — printed for `octwin <cmd> --help|-h` BEFORE any
5015
5044
  * network/auth work (a --help that 401s is worse than no help at all). */
5016
5045
  const COMMAND_HELP = {
5017
- init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
5046
+ init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
5018
5047
  Scaffold a pure-YAML starter pack into <dir>.`,
5019
- validate: `octwin validate [--dir .] [--remote] [--require-kb] [--strict-primitives]
5020
- Offline structural check, plus two checks driven by the pulled capability
5021
- reference (render-intent fields, primitive arguments). Those two SKIP when the
5022
- reference is missing — the run says so, and --require-kb turns the skip into a
5023
- failure for CI. --remote additionally runs the platform's FULL manifest +
5024
- flow-DSL validation and its flow lint (all errors at once) — same check as deploy.
5025
- --strict-primitives (with --remote) additionally type-checks LITERAL args:
5026
- values against each primitive's declared input schema; expression strings
5048
+ validate: `octwin validate [--dir .] [--remote] [--require-kb] [--strict-primitives]
5049
+ Offline structural check, plus two checks driven by the pulled capability
5050
+ reference (render-intent fields, primitive arguments). Those two SKIP when the
5051
+ reference is missing — the run says so, and --require-kb turns the skip into a
5052
+ failure for CI. --remote additionally runs the platform's FULL manifest +
5053
+ flow-DSL validation and its flow lint (all errors at once) — same check as deploy.
5054
+ --strict-primitives (with --remote) additionally type-checks LITERAL args:
5055
+ values against each primitive's declared input schema; expression strings
5027
5056
  ('$found.id', '{$t(…)}') are always exempt.`,
5028
- login: `octwin login --url <platformUrl> --token oct_…
5029
- Save a deploy token (console → Settings → API tokens) for that platform url,
5030
- make that url the DEFAULT deploy target for every later command, and echo the
5057
+ login: `octwin login --url <platformUrl> --token oct_…
5058
+ Save a deploy token (console → Settings → API tokens) for that platform url,
5059
+ make that url the DEFAULT deploy target for every later command, and echo the
5031
5060
  workspace + project pin + scopes the token reaches.`,
5032
- whoami: `octwin whoami [--url <url>] [--tenant <slug>]
5061
+ whoami: `octwin whoami [--url <url>] [--tenant <slug>]
5033
5062
  Verify the resolved token authenticates against the tenant.`,
5034
- projects: `octwin projects [--archived] [--json]
5035
- List the workspace's projects — the slugs every --project flag takes, with the
5036
- plan's project cap. --archived includes archived ones. A pack:deploy token
5037
- reaches this (it names a project in every other command).
5038
-
5039
- octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
5040
- Create a project. The URL slug is derived from the name unless --slug pins one.
5041
- --pack installs an ALREADY-published pack; the usual next step is instead
5042
- \`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
5043
-
5044
- octwin projects rm <slug> [--yes]
5045
- HARD delete — the project and everything cascading from it (conversations,
5046
- contacts, records, installs). No undo, and not the same as archiving.
5047
- WITHOUT --yes it only previews what would be destroyed, so the dry run is the
5048
- default. Together these make a disposable end-to-end environment:
5049
- octwin projects create "Scratch" && octwin deploy --project scratch --seed
5050
- octwin chat "hi" --project scratch
5051
- octwin projects rm scratch --yes
5063
+ projects: `octwin projects [--archived] [--json]
5064
+ List the workspace's projects — the slugs every --project flag takes, with the
5065
+ plan's project cap. --archived includes archived ones. A pack:deploy token
5066
+ reaches this (it names a project in every other command).
5067
+
5068
+ octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
5069
+ Create a project. The URL slug is derived from the name unless --slug pins one.
5070
+ --pack installs an ALREADY-published pack; the usual next step is instead
5071
+ \`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
5072
+
5073
+ octwin projects rm <slug> [--yes]
5074
+ HARD delete — the project and everything cascading from it (conversations,
5075
+ contacts, records, installs). No undo, and not the same as archiving.
5076
+ WITHOUT --yes it only previews what would be destroyed, so the dry run is the
5077
+ default. Together these make a disposable end-to-end environment:
5078
+ octwin projects create "Scratch" && octwin deploy --project scratch --seed
5079
+ octwin chat "hi" --project scratch
5080
+ octwin projects rm scratch --yes
5052
5081
  Both verbs need the \`projects:write\` scope — a pack:deploy token does NOT confer it.`,
5053
- deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
5054
- [--request-listing | --withdraw-listing]
5055
- Upload the pack bundle, validate server-side, install onto the project.
5056
- --seed additionally applies the pack's demo seed (streams progress).
5057
-
5058
- A plain deploy says NOTHING about the public marketplace — it is a test loop, so it
5059
- neither asks for a listing nor gives one up. The marketplace flags are opt-in:
5060
-
5061
- --request-listing ask an operator to review this pack for the public marketplace
5062
- (the pre-signup storefront at /packs). Requires 'public: true'
5063
- under 'listing:' in manifest.yaml — the manifest states that the
5064
- pack is a product, the flag is you choosing to ask.
5065
- --withdraw-listing retract the request, including an approved listing.
5066
-
5067
- An approval covers the CONTENT it was made against, so a later deploy that changes the
5082
+ deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
5083
+ [--request-listing | --withdraw-listing]
5084
+ Upload the pack bundle, validate server-side, install onto the project.
5085
+ --seed additionally applies the pack's demo seed (streams progress).
5086
+
5087
+ A plain deploy says NOTHING about the public marketplace — it is a test loop, so it
5088
+ neither asks for a listing nor gives one up. The marketplace flags are opt-in:
5089
+
5090
+ --request-listing ask an operator to review this pack for the public marketplace
5091
+ (the pre-signup storefront at /packs). Requires 'public: true'
5092
+ under 'listing:' in manifest.yaml — the manifest states that the
5093
+ pack is a product, the flag is you choosing to ask.
5094
+ --withdraw-listing retract the request, including an approved listing.
5095
+
5096
+ An approval covers the CONTENT it was made against, so a later deploy that changes the
5068
5097
  pack returns it to the review queue on its own — no flag needed, and the CLI says so.`,
5069
- seed: `octwin seed [--pack <packId>]
5070
- Apply the pack's demo/reference data to the project it is installed on, without
5071
- redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
5072
- and the demo operator topology. Reports what each kind produced.
5073
- Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
5074
- than regenerated, so a second pass costs nothing. --pack is only needed when a
5098
+ seed: `octwin seed [--pack <packId>]
5099
+ Apply the pack's demo/reference data to the project it is installed on, without
5100
+ redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
5101
+ and the demo operator topology. Reports what each kind produced.
5102
+ Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
5103
+ than regenerated, so a second pass costs nothing. --pack is only needed when a
5075
5104
  project somehow runs more than one.`,
5076
- status: `octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
5077
- Show installed vs live version + the flow list for this pack.
5078
- The pack id is read from manifest.yaml and QUALIFIED with your workspace slug
5079
- (a manifest declares a bare name; the owner is attached when you publish). Pass
5080
- <packId> explicitly to skip that lookup — \`octwin agents\` and \`octwin projects\`
5105
+ status: `octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
5106
+ Show installed vs live version + the flow list for this pack.
5107
+ The pack id is read from manifest.yaml and QUALIFIED with your workspace slug
5108
+ (a manifest declares a bare name; the owner is attached when you publish). Pass
5109
+ <packId> explicitly to skip that lookup — \`octwin agents\` and \`octwin projects\`
5081
5110
  both print the qualified form.`,
5082
- records: `octwin records [entity] [id] [--limit 50] [--offset n]
5083
- Inspect the pack's XRM data. No args = list entities. Worked records (cases,
5084
- tickets, anything routed to a queue) read best through \`octwin work\`.
5085
-
5086
- WRITES (need \`records:write\`; every one is re-checked by RBAC on the record):
5087
- octwin records create <entity> --set field=value [--set …] [--stage s] [--contact <id>]
5088
- octwin records patch <recordId> --entity <entity> --set field=value
5089
- octwin records stage <recordId> --to <stage> [--note "..."]
5090
- octwin records note <recordId> "the note text"
5091
- octwin records tasks # open follow-up tasks (\`tasks\` plan feature)
5092
- octwin records task complete <taskId> [--outcome done|cancelled] [--note "..."]
5093
-
5094
- --set coerces JSON scalars: \`--set rating=4.5\` sends a number, \`--set x=null\`
5095
- sends null. Use --fields-json '{"a":{"b":1}}' for anything nested.
5096
- \`patch\` needs --entity even though it has an id: the route resolves the field
5097
- validator from it. A leading \`create/patch/stage/note/tasks/task\` is read as a
5111
+ records: `octwin records [entity] [id] [--limit 50] [--offset n]
5112
+ Inspect the pack's XRM data. No args = list entities. Worked records (cases,
5113
+ tickets, anything routed to a queue) read best through \`octwin work\`.
5114
+
5115
+ WRITES (need \`records:write\`; every one is re-checked by RBAC on the record):
5116
+ octwin records create <entity> --set field=value [--set …] [--stage s] [--contact <id>]
5117
+ octwin records patch <recordId> --entity <entity> --set field=value
5118
+ octwin records stage <recordId> --to <stage> [--note "..."]
5119
+ octwin records note <recordId> "the note text"
5120
+ octwin records tasks # open follow-up tasks (\`tasks\` plan feature)
5121
+ octwin records task complete <taskId> [--outcome done|cancelled] [--note "..."]
5122
+
5123
+ --set coerces JSON scalars: \`--set rating=4.5\` sends a number, \`--set x=null\`
5124
+ sends null. Use --fields-json '{"a":{"b":1}}' for anything nested.
5125
+ \`patch\` needs --entity even though it has an id: the route resolves the field
5126
+ validator from it. A leading \`create/patch/stage/note/tasks/task\` is read as a
5098
5127
  VERB — to list an entity actually named one of those, use \`--entity <name>\`.`,
5099
- work: `octwin work [recordId] [--queues] [--limit 50] [--offset n] [--json]
5100
- Inspect the work inbox — every entity the pack declares worked (cases, orders
5101
- needing review, applications, …): the inbox, one item + its timeline
5102
- (+ applicable actions), or --queues for queue keys + open counts.
5103
-
5104
- WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
5105
- octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
5106
- octwin work note <recordId> "the note text"
5107
- octwin work stage <recordId> --to <stage> [--note "..."]
5108
- octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
5109
-
5110
- \`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
5111
- lists them with their params. --dry-run previews the customer-facing copy and the
5112
- resulting stage WITHOUT committing (that route needs only \`work:read\`).
5128
+ work: `octwin work [recordId] [--queues] [--unrouted] [--limit 50] [--offset n] [--json]
5129
+ Inspect the work inbox — every entity the pack declares worked (cases, orders
5130
+ needing review, applications, …): the inbox, one item + its timeline
5131
+ (+ applicable actions), or --queues for queue keys + open counts.
5132
+
5133
+ WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
5134
+ octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
5135
+ octwin work note <recordId> "the note text"
5136
+ octwin work stage <recordId> --to <stage> [--note "..."]
5137
+ octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
5138
+
5139
+ \`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
5140
+ lists them with their params. --dry-run previews the customer-facing copy and the
5141
+ resulting stage WITHOUT committing (that route needs only \`work:read\`).
5113
5142
  \`stage\` is the XRM records verb (one transition spelling platform-wide).`,
5114
- logs: `octwin logs [conversationId] [--as <handle>] [--json]
5115
- No id = recent conversations (handle, status, last activity; --as filters).
5116
- With id = the full event timeline including what each turn rendered.
5143
+ logs: `octwin logs [conversationId] [--as <handle>] [--json]
5144
+ No id = recent conversations (handle, status, last activity; --as filters).
5145
+ With id = the full event timeline including what each turn rendered.
5117
5146
  --json = raw events (verbatim payloads).`,
5118
- pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
5119
- Write a DEPLOYED pack's source back to disk — the inverse of deploy.
5120
- A pack pushed with 'octwin deploy' lives on the platform as an artifact the
5121
- runtime serves but nothing hands back, so its only source copy is the machine
5122
- that pushed it. Pull it, fix it, redeploy it.
5123
- Defaults to the version installed on the target project; --version overrides.
5124
- --dir defaults to ./<packId>; a non-empty dir needs --force.
5125
- The pulled dir redeploys where it came from — the target is your saved login.
5147
+ pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
5148
+ Write a DEPLOYED pack's source back to disk — the inverse of deploy.
5149
+ A pack pushed with 'octwin deploy' lives on the platform as an artifact the
5150
+ runtime serves but nothing hands back, so its only source copy is the machine
5151
+ that pushed it. Pull it, fix it, redeploy it.
5152
+ Defaults to the version installed on the target project; --version overrides.
5153
+ --dir defaults to ./<packId>; a non-empty dir needs --force.
5154
+ The pulled dir redeploys where it came from — the target is your saved login.
5126
5155
  You may pull a pack your tenant OWNS (deployed); an operator token pulls any.`,
5127
- chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
5128
- octwin chat --script <file> [--as <handle>] [--json]
5129
- Drive ONE turn through the dev web channel and print every render with its
5130
- tap ids. Same --as handle = same conversation (multi-turn works).
5131
- --tap presses a rendered button/list row instead of sending text.
5132
- --media uploads a local file (or a media id from 'media generate --json') as
5133
- an image/document/audio inbound — any "message" rides as its caption; feeds a
5134
- running media-collect flow (e.g. activate-app).
5135
- --json dumps the raw SSE envelopes for the turn.
5136
-
5137
- --script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
5138
- process over one connection — waiting for each turn to settle before sending
5139
- the next. Use this for any multi-step flow: chaining shell invocations races
5140
- the agent loop, because a turn ends on a quiet gap that can arrive while the
5141
- server is still working (the symptom is placeholder-filled fields or a second
5142
- workflow run). Blank lines and # comments are skipped:
5143
-
5144
- # book an appointment end to end
5145
- احجز موعد
5146
- tap:t:invoke:book-appointment:doctor_id=D1
5147
- media:./licence.jpg | here is my licence
5156
+ chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
5157
+ octwin chat --script <file> [--as <handle>] [--json]
5158
+ Drive ONE turn through the dev web channel and print every render with its
5159
+ tap ids. Same --as handle = same conversation (multi-turn works).
5160
+ --tap presses a rendered button/list row instead of sending text.
5161
+ --media uploads a local file (or a media id from 'media generate --json') as
5162
+ an image/document/audio inbound — any "message" rides as its caption; feeds a
5163
+ running media-collect flow (e.g. activate-app).
5164
+ --json dumps the raw SSE envelopes for the turn.
5165
+
5166
+ --script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
5167
+ process over one connection — waiting for each turn to settle before sending
5168
+ the next. Use this for any multi-step flow: chaining shell invocations races
5169
+ the agent loop, because a turn ends on a quiet gap that can arrive while the
5170
+ server is still working (the symptom is placeholder-filled fields or a second
5171
+ workflow run). Blank lines and # comments are skipped:
5172
+
5173
+ # book an appointment end to end
5174
+ احجز موعد
5175
+ tap:t:invoke:book-appointment:doctor_id=D1
5176
+ media:./licence.jpg | here is my licence
5148
5177
  tap:t:resume:book-appointment:run_id=R1;_ctl_approved=true`,
5149
- media: `octwin media generate "<prompt>" [--out <file.png>] [--json]
5150
- AI-generate an image (needs a media:generate-scoped token), store it as a
5151
- public asset, and print its MEDIA- handle + serve URL. --out downloads the
5152
- bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
5178
+ media: `octwin media generate "<prompt>" [--out <file.png>] [--json]
5179
+ AI-generate an image (needs a media:generate-scoped token), store it as a
5180
+ public asset, and print its MEDIA- handle + serve URL. --out downloads the
5181
+ bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
5153
5182
  bytes }. Pair with 'octwin chat --media' to drive media flows.`,
5154
- agents: `octwin agents [packId::agentId] [--prompt] [--json]
5155
- No args = the roster with each agent's EFFECTIVE model and which layer set it.
5156
- With an agent = every governed setting (model / memory.last_messages /
5157
- working_memory) plus the layer that won — an operator PLATFORM default can
5158
- override what your manifest declares, and this is where you see that.
5159
- --prompt = the exact system prompt the LLM sees for this project (pack
5160
- instructions + platform protocol + any project overlay). Needs agents:read.
5161
- The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
5162
-
5163
- WRITES (need \`agents:write\`):
5164
- octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
5165
- [--enable-tool <toolId>] [--disable-tool <toolId>]
5166
-
5167
- Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
5168
- so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
5183
+ agents: `octwin agents [packId::agentId] [--prompt] [--json]
5184
+ No args = the roster with each agent's EFFECTIVE model and which layer set it.
5185
+ With an agent = every governed setting (model / memory.last_messages /
5186
+ working_memory) plus the layer that won — an operator PLATFORM default can
5187
+ override what your manifest declares, and this is where you see that.
5188
+ --prompt = the exact system prompt the LLM sees for this project (pack
5189
+ instructions + platform protocol + any project overlay). Needs agents:read.
5190
+ The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
5191
+
5192
+ WRITES (need \`agents:write\`):
5193
+ octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
5194
+ [--enable-tool <toolId>] [--disable-tool <toolId>]
5195
+
5196
+ Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
5197
+ so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
5169
5198
  ids refuses --model with a 403 — the platform default governs there.`,
5170
- orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
5171
- No args = the order list (#number, status/payment, total, contact). With a
5172
- reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
5173
- payment_ref, and the allowed status transitions. Needs orders:read + the
5174
- \`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
5175
- so \`pending\` on a gateway-less workspace is expected, not a bug.
5176
-
5177
- WRITES (need \`orders:write\`):
5178
- octwin orders transition <reference_id> --to <status>
5179
- octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
5180
-
5181
- Refund is irreversible and moves money, hence --force. The route answers 200 even
5182
- when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
5183
- on a refusal rather than reporting a refund that never happened. Only a payment in
5199
+ orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
5200
+ No args = the order list (#number, status/payment, total, contact). With a
5201
+ reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
5202
+ payment_ref, and the allowed status transitions. Needs orders:read + the
5203
+ \`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
5204
+ so \`pending\` on a gateway-less workspace is expected, not a bug.
5205
+
5206
+ WRITES (need \`orders:write\`):
5207
+ octwin orders transition <reference_id> --to <status>
5208
+ octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
5209
+
5210
+ Refund is irreversible and moves money, hence --force. The route answers 200 even
5211
+ when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
5212
+ on a refusal rather than reporting a refund that never happened. Only a payment in
5184
5213
  \`captured\` state can be refunded; \`payment_status\` is never settable directly.`,
5185
- analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
5186
- No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
5187
- With an entity = stage-by-stage conversion (default --funnel) over the last 30
5188
- days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
5214
+ analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
5215
+ No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
5216
+ With an entity = stage-by-stage conversion (default --funnel) over the last 30
5217
+ days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
5189
5218
  range-filtered). Needs records:read + a \`view\` grant on \`record.<entity>\`.`,
5190
- catalog: `octwin catalog [--readiness] [--json]
5191
- The commerce \`product\` records + price, availability, stock (null = not
5192
- inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
5193
- Graph checklist (LIVE Graph calls; needs a bound access token). Needs
5194
- catalog:read + the \`catalog\` plan feature.
5195
-
5196
- WRITES (need \`catalog:write\`):
5197
- octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
5198
- octwin catalog stock <retailerId> [--set-on-hand <n>]
5199
-
5200
- \`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
5201
- inventory-tracked (always sellable), which is different from 0. Lowering on_hand
5202
- below the units already reserved for open carts is refused. Creating/deleting
5219
+ catalog: `octwin catalog [--readiness] [--json]
5220
+ The commerce \`product\` records + price, availability, stock (null = not
5221
+ inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
5222
+ Graph checklist (LIVE Graph calls; needs a bound access token). Needs
5223
+ catalog:read + the \`catalog\` plan feature.
5224
+
5225
+ WRITES (need \`catalog:write\`):
5226
+ octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
5227
+ octwin catalog stock <retailerId> [--set-on-hand <n>]
5228
+
5229
+ \`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
5230
+ inventory-tracked (always sellable), which is different from 0. Lowering on_hand
5231
+ below the units already reserved for open carts is refused. Creating/deleting
5203
5232
  products and the Meta catalog binding/sync stay in the console.`,
5204
- scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
5205
- No args = the engine state (bookable resource types, upcoming slots, booked
5206
- seats). --slots <recordId> computes the slots for one bookable resource
5207
- (occupancy included; --days is clamped to 1-31 server-side) — the way to verify
5208
- the availability rules a \`deploy --seed\` created. Needs scheduling:read.
5209
-
5210
- RULES (list needs scheduling:read; add/rm need scheduling:write):
5211
- octwin scheduling rules --resource <resourceRecordId>
5212
- octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
5213
- [--slot-minutes 30] [--capacity 1]
5214
- octwin scheduling rule rm <ruleId>
5215
- octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
5216
- [--start 09:00 --end 13:00]
5217
- octwin scheduling exception rm <exceptionId>
5218
-
5219
- --dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
5233
+ scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
5234
+ No args = the engine state (bookable resource types, upcoming slots, booked
5235
+ seats). --slots <recordId> computes the slots for one bookable resource
5236
+ (occupancy included; --days is clamped to 1-31 server-side) — the way to verify
5237
+ the availability rules a \`deploy --seed\` created. Needs scheduling:read.
5238
+
5239
+ RULES (list needs scheduling:read; add/rm need scheduling:write):
5240
+ octwin scheduling rules --resource <resourceRecordId>
5241
+ octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
5242
+ [--slot-minutes 30] [--capacity 1]
5243
+ octwin scheduling rule rm <ruleId>
5244
+ octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
5245
+ [--start 09:00 --end 13:00]
5246
+ octwin scheduling exception rm <exceptionId>
5247
+
5248
+ --dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
5220
5249
  \`--slots\` is how you check what a rule actually produces.`,
5221
- automation: `octwin automation [campaigns] [--limit n] [--offset n] [--json]
5222
- No args = every job the pack's automation declaration produced, with its status,
5223
- interval and LAST RESULT (matched / acted / errors), under a health line whose
5224
- counts come from SQL rather than from filtering the page — the job list is capped
5225
- server-side, so a client-side count would depend on the cap. Needs automation:read.
5226
-
5227
- Jobs are DERIVED from declarations. There is no \`create\`: no automation block in
5228
- the pack means no jobs, and \`octwin deploy\` is what installs them.
5229
-
5230
- WRITES (automation:write):
5231
- octwin automation run <jobId> # run once, now — prints matched/acted/errors
5232
- octwin automation pause|resume <jobId>
5233
- octwin automation send <campaignId> # enqueue a campaign; enqueued != delivered
5234
-
5235
- <jobId> is the \`key\` the list shows (its uuid works too). The routes themselves
5236
- accept only a uuid — the CLI resolves the key for you, and names the keys that do
5237
- exist when it cannot. A 403 on a write can be an RBAC grant gap rather than a
5250
+ automation: `octwin automation [campaigns] [--limit n] [--offset n] [--json]
5251
+ No args = every job the pack's automation declaration produced, with its status,
5252
+ interval and LAST RESULT (matched / acted / errors), under a health line whose
5253
+ counts come from SQL rather than from filtering the page — the job list is capped
5254
+ server-side, so a client-side count would depend on the cap. Needs automation:read.
5255
+
5256
+ Jobs are DERIVED from declarations. There is no \`create\`: no automation block in
5257
+ the pack means no jobs, and \`octwin deploy\` is what installs them.
5258
+
5259
+ WRITES (automation:write):
5260
+ octwin automation run <jobId> # run once, now — prints matched/acted/errors
5261
+ octwin automation pause|resume <jobId>
5262
+ octwin automation send <campaignId> # enqueue a campaign; enqueued != delivered
5263
+
5264
+ <jobId> is the \`key\` the list shows (its uuid works too). The routes themselves
5265
+ accept only a uuid — the CLI resolves the key for you, and names the keys that do
5266
+ exist when it cannot. A 403 on a write can be an RBAC grant gap rather than a
5238
5267
  missing scope: the action is re-checked against the job.`,
5239
- integrations: `octwin integrations [--json]
5240
- What the pack DECLARES beside what is actually CONFIGURED, in one view — because a
5241
- connection that is declared and never configured is the commonest reason an
5242
- integration silently never fires, and neither list alone can show it. Flags the
5243
- gap explicitly. Needs integrations:read.
5244
-
5245
- DIAGNOSE ONE CONNECTION:
5246
- octwin integrations preflight <key> # every check, with a fix hint. Makes NO
5247
- # outbound call — needs only integrations:read
5248
- octwin integrations test <key> # a LIVE call to its health: operation
5249
- # (integrations:write). Exits 1 when it fails.
5250
-
5251
- THE DELIVERY LOG:
5252
- octwin integrations deliveries [--status s] [--operation id] [--limit n]
5253
- octwin integrations deliveries <id> # + the redacted request/response snapshots
5254
- octwin integrations retry|cancel|send-now <id> # integrations:write
5255
- octwin integrations events # INBOUND events (what arrived at your webhook)
5256
-
5257
- retry/cancel answer 409 when the delivery is in the wrong state; the message
5268
+ integrations: `octwin integrations [--json]
5269
+ What the pack DECLARES beside what is actually CONFIGURED, in one view — because a
5270
+ connection that is declared and never configured is the commonest reason an
5271
+ integration silently never fires, and neither list alone can show it. Flags the
5272
+ gap explicitly. Needs integrations:read.
5273
+
5274
+ DIAGNOSE ONE CONNECTION:
5275
+ octwin integrations preflight <key> # every check, with a fix hint. Makes NO
5276
+ # outbound call — needs only integrations:read
5277
+ octwin integrations test <key> # a LIVE call to its health: operation
5278
+ # (integrations:write). Exits 1 when it fails.
5279
+
5280
+ THE DELIVERY LOG:
5281
+ octwin integrations deliveries [--status s] [--operation id] [--limit n]
5282
+ octwin integrations deliveries <id> # + the redacted request/response snapshots
5283
+ octwin integrations retry|cancel|send-now <id> # integrations:write
5284
+ octwin integrations events # INBOUND events (what arrived at your webhook)
5285
+
5286
+ retry/cancel answer 409 when the delivery is in the wrong state; the message
5258
5287
  carries the rule.`,
5259
- journeys: `octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition]
5260
- [--stage <stageId>] [--limit n] [--json]
5261
- No args = the journeys the pack declares. With an id, one of six views —
5262
- --funnel (default) stage-by-stage reach and drop-off · --overview entered vs
5263
- converted plus the biggest drop-off · --goals completions, contacts, value and
5264
- p50 time · --trends per-bucket activity · --cost tokens and dollars per goal ·
5265
- --definition what was DECLARED, unmeasured (the one view that works with no
5266
- traffic). Needs journeys:read.
5267
-
5268
- --stage <stageId> lists the runs sitting at a stage right now (a live snapshot,
5269
- not the funnel's cumulative reached counts).
5270
-
5271
- Same flag grammar as \`octwin analytics\` on purpose: a journey funnel and an
5272
- entity funnel are the same question about different subjects. Journeys carry RBAC
5273
- on top of the scope, so an empty answer can be a missing \`view\` grant rather
5288
+ journeys: `octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition]
5289
+ [--stage <stageId>] [--limit n] [--json]
5290
+ No args = the journeys the pack declares. With an id, one of six views —
5291
+ --funnel (default) stage-by-stage reach and drop-off · --overview entered vs
5292
+ converted plus the biggest drop-off · --goals completions, contacts, value and
5293
+ p50 time · --trends per-bucket activity · --cost tokens and dollars per goal ·
5294
+ --definition what was DECLARED, unmeasured (the one view that works with no
5295
+ traffic). Needs journeys:read.
5296
+
5297
+ --stage <stageId> lists the runs sitting at a stage right now (a live snapshot,
5298
+ not the funnel's cumulative reached counts).
5299
+
5300
+ Same flag grammar as \`octwin analytics\` on purpose: a journey funnel and an
5301
+ entity funnel are the same question about different subjects. Journeys carry RBAC
5302
+ on top of the scope, so an empty answer can be a missing \`view\` grant rather
5274
5303
  than missing data — the output says which causes are possible.`,
5275
- performance: `octwin performance [--detail] [--json]
5276
- The project's business indicators — value produced, conversion, duration — each
5277
- with its delta against the previous window and a \`why\` naming the declaration it
5278
- came from. --detail adds the per-indicator breakdown.
5279
-
5280
- Needs records:read, NOT a performance scope (there is none), so a read-only token
5281
- already reaches it. Indicators are DERIVED: a pack that declares no journey goal
5304
+ performance: `octwin performance [--detail] [--json]
5305
+ The project's business indicators — value produced, conversion, duration — each
5306
+ with its delta against the previous window and a \`why\` naming the declaration it
5307
+ came from. --detail adds the per-indicator breakdown.
5308
+
5309
+ Needs records:read, NOT a performance scope (there is none), so a read-only token
5310
+ already reaches it. Indicators are DERIVED: a pack that declares no journey goal
5282
5311
  value and no pipelined entity produces none, which is a different thing from zero.`,
5283
- usage: `octwin usage [--json]
5284
- Model calls, tokens and cost for the resolved scope — project when one is pinned
5285
- or passed with --project, otherwise the whole workspace. Broken down by model,
5286
- kind, agent and channel.
5287
-
5288
- Needs no particular scope: any valid token reaches it.
5289
-
5290
- This is MODEL spend only. WhatsApp/Meta message billing is operator-only and
5312
+ usage: `octwin usage [--json]
5313
+ Model calls, tokens and cost for the resolved scope — project when one is pinned
5314
+ or passed with --project, otherwise the whole workspace. Broken down by model,
5315
+ kind, agent and channel.
5316
+
5317
+ Needs no particular scope: any valid token reaches it.
5318
+
5319
+ This is MODEL spend only. WhatsApp/Meta message billing is operator-only and
5291
5320
  deliberately outside the token scope registry — no API token can read it.`,
5292
- 'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
5293
- Pull the platform capability reference (markdown + JSON catalogs) into
5294
- .octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
5295
- INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
5296
- OUTLINE.md (every heading with its line number).
5297
-
5298
- NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously,
5299
- and this command never sends one. --token is accepted and ignored, so an older
5300
- script that passes it keeps working.
5301
-
5302
- --if-stale poll the platform's content_hash first and skip the download when
5303
- nothing changed. Cheap enough to run at the start of every session.
5304
- --check report only, write nothing. Exit 0 = current, 2 = stale or never
5305
- pulled, 1 = could not tell (offline / no reference served). For
5321
+ 'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
5322
+ Pull the platform capability reference (markdown + JSON catalogs) into
5323
+ .octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
5324
+ INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
5325
+ OUTLINE.md (every heading with its line number).
5326
+
5327
+ NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously,
5328
+ and this command never sends one. --token is accepted and ignored, so an older
5329
+ script that passes it keeps working.
5330
+
5331
+ --if-stale poll the platform's content_hash first and skip the download when
5332
+ nothing changed. Cheap enough to run at the start of every session.
5333
+ --check report only, write nothing. Exit 0 = current, 2 = stale or never
5334
+ pulled, 1 = could not tell (offline / no reference served). For
5306
5335
  scripts and agent loops that want to branch without parsing prose.`,
5307
- test: `octwin test [--dir .]
5336
+ test: `octwin test [--dir .]
5308
5337
  Alias for \`octwin validate --remote\` — the full platform check.`,
5309
- memos: `octwin memos [--all] [--json]
5310
- Read what the platform has told you: a REPLY to a report you sent with
5311
- \`octwin feedback\`, or a NOTICE published to every author (a new capability,
5312
- a deprecation, a breaking change). Bodies are printed in full.
5313
- Reading marks them read, so the reminder stops. --all re-reads history and
5314
- acks nothing. --json to branch on \`severity\`
5338
+ memos: `octwin memos [--all] [--json]
5339
+ Read what the platform has told you: a REPLY to a report you sent with
5340
+ \`octwin feedback\`, or a NOTICE published to every author (a new capability,
5341
+ a deprecation, a breaking change). Bodies are printed in full.
5342
+ Reading marks them read, so the reminder stops. --all re-reads history and
5343
+ acks nothing. --json to branch on \`severity\`
5315
5344
  (info | action_required | breaking).`,
5316
- feedback: `octwin feedback [--dir .]
5317
- Submit this pack's FEEDBACK.md to the platform team.
5318
- The octwin-pack skill writes that file in its last step — findings grouped by
5319
- owner (A · CLI, B · Platform, C · Skill/KB). This delivers it instead of asking
5320
- you to paste it into a chat.
5321
- Attaches the pack id + version from manifest.yaml, this CLI's version, and the
5322
- content_hash of the capability reference in .octwin/platform-kb/ — triage needs
5323
- the last two to tell "the platform is wrong" from "that was already fixed" or
5345
+ feedback: `octwin feedback [--dir .]
5346
+ Submit this pack's FEEDBACK.md to the platform team.
5347
+ The octwin-pack skill writes that file in its last step — findings grouped by
5348
+ owner (A · CLI, B · Platform, C · Skill/KB). This delivers it instead of asking
5349
+ you to paste it into a chat.
5350
+ Attaches the pack id + version from manifest.yaml, this CLI's version, and the
5351
+ content_hash of the capability reference in .octwin/platform-kb/ — triage needs
5352
+ the last two to tell "the platform is wrong" from "that was already fixed" or
5324
5353
  "you were reading a stale reference". Needs the \`pack:deploy\` scope.`,
5325
5354
  };
5326
5355
  async function main() {