octwin-cli 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/index.js +415 -351
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ Format: [Keep a Changelog](https://keepachangelog.com/) — newest first, bucket
5
5
  **Added · Changed · Deprecated · Removed · Fixed · Security**. The platform-wide view lives in the
6
6
  repo root [`CHANGELOG.md`](../../CHANGELOG.md); this file is the CLI-only cut that ships with the package.
7
7
 
8
+ ## [0.8.5] - 2026-09-02
9
+
10
+ ### Added
11
+ - **`octwin work --unrouted`** — the inbox filtered to items in NO queue. The route has always
12
+ supported the filter; the CLI never passed it, so `--queues` could tell you a count and not
13
+ show you the items behind it.
14
+
15
+ ### Changed
16
+ - **`octwin work --queues` leads with the unrouted count.** It was already printed, as
17
+ `(unrouted: N open)` tucked under the queue list. A routed item sits in a list someone works;
18
+ an unrouted one sits in nobody's, so it is the only line on that screen that means "these are
19
+ lost". It is now a warning block that names the likely cause and points at `--unrouted`. Still
20
+ silent at zero — a clean project stays quiet, or the warning stops meaning anything.
21
+
22
+ ## [0.8.4] - 2026-09-02
23
+
24
+ ### Fixed
25
+ - **A deploy whose seed failed still led with `✓ Deployed`.** The step errors were never
26
+ missing — `readDeployProgress` has collected every `status:'error'` frame into `stepErrors`
27
+ since 0.1.14, printed them, and exited non-zero. They were printed *after* `printDeploySuccess`,
28
+ so the first and largest line said `✓ Deployed` and the ⚠ came underneath. A pack author
29
+ reported their deploy as a clean success while its demo seed had failed; they were reading the
30
+ transcript exactly as written. The count now goes INTO the headline, which reads
31
+ `⚠ Deployed <id>@<v> onto <target> WITH n failure(s) — data may be incomplete`. `octwin seed`
32
+ gets the same treatment. *A signal that arrives after the verdict is not a signal.*
33
+ - **The non-streaming deploy and seed paths had no step-error check at all.** With no SSE frames
34
+ to read, a failed seed printed a clean `✓` on that path even after the streaming one was fixed
35
+ in 0.1.14. Both now read `seed_failed` from the response body (new in the platform alongside
36
+ this release), print each failed kind with its reason, and exit non-zero.
37
+ - **A partial demo seed is now reported as partial.** The platform no longer abandons the rest of
38
+ the seed when one demo row throws, so `Seeded:` gains an `n row(s) FAILED` part — without it a
39
+ partial seed reads as a complete one that simply had fewer records than the author wrote.
40
+
8
41
  ## [0.8.3] - 2026-09-02
9
42
 
10
43
  ### Fixed
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)
@@ -1535,8 +1535,22 @@ function printPublicListing(state, note, live, justAsked = false) {
1535
1535
  break;
1536
1536
  }
1537
1537
  }
1538
- function printDeploySuccess(id, version, t, r, listing) {
1539
- console.log(`✓ Deployed ${id}@${version} and installed onto ${targetLabel(t)}`);
1538
+ /**
1539
+ * THE HEADLINE MUST BE HONEST, because it is the line people actually read.
1540
+ *
1541
+ * The step errors were never missing — `readDeployProgress` has collected them since
1542
+ * 0.1.14, and the caller prints them and exits non-zero. But it printed them AFTER this
1543
+ * function, so the first and largest line said `✓ Deployed` and the ⚠ came underneath.
1544
+ * A pack author reported the deploy as a clean success while its demo seed had failed;
1545
+ * they were reading the transcript exactly as it was written.
1546
+ *
1547
+ * So `problems` is passed IN rather than handled by the caller afterwards: a signal that
1548
+ * arrives after the verdict is not a signal.
1549
+ */
1550
+ function printDeploySuccess(id, version, t, r, listing, problems = 0) {
1551
+ console.log(problems > 0
1552
+ ? `⚠ Deployed ${id}@${version} onto ${targetLabel(t)} WITH ${problems} failure(s) — data may be incomplete`
1553
+ : `✓ Deployed ${id}@${version} and installed onto ${targetLabel(t)}`);
1540
1554
  if (r?.warning)
1541
1555
  console.log(` ⚠ ${r.warning}`);
1542
1556
  const s = r?.summary;
@@ -1550,6 +1564,11 @@ function printDeploySuccess(id, version, t, r, listing) {
1550
1564
  parts.push(`${s.images} image(s) generated`);
1551
1565
  if (s.rules)
1552
1566
  parts.push(`${s.rules} availability rule(s)`);
1567
+ // A count of rows that threw. The seed keeps going past a bad row now, so a
1568
+ // partial seed is a real outcome and has to be said out loud — the alternative
1569
+ // reads as a complete one with fewer records than the author wrote.
1570
+ if (s.failed)
1571
+ parts.push(`${s.failed} row(s) FAILED`);
1553
1572
  if (parts.length)
1554
1573
  console.log(` Seeded: ${parts.join(', ')}`);
1555
1574
  }
@@ -1596,13 +1615,14 @@ async function cmdSeed(flags) {
1596
1615
  const { terminal: final, stepErrors } = await readDeployProgress(res.body);
1597
1616
  if (!final || final.stage === 'error')
1598
1617
  die(`seed failed${final?.message ? `: ${final.message}` : ' (stream ended early)'}`);
1599
- console.log(`
1600
- ${final.message ?? 'seed complete'}`);
1618
+ // Same rule as `printDeploySuccess`: the verdict leads. A ✓ above the failures
1619
+ // is the line that gets read and quoted.
1620
+ console.log(`\n${stepErrors.length ? '⚠' : '✓'} ${final.message ?? 'seed complete'}`);
1601
1621
  printSeedCounts(final.result?.seeded);
1602
1622
  if (stepErrors.length) {
1603
1623
  // A kind failed but the rest ran — the reconcile softens each step. Say which,
1604
1624
  // and exit non-zero so a scripted `seed && chat` doesn't read as clean.
1605
- console.error(`
1625
+ console.error(`
1606
1626
  ⚠ ${stepErrors.length} step${stepErrors.length === 1 ? '' : 's'} failed — data may be incomplete:`);
1607
1627
  for (const e of stepErrors)
1608
1628
  console.error(` • ${e}`);
@@ -1623,8 +1643,15 @@ async function cmdSeed(flags) {
1623
1643
  printAuthHint(res.status, url);
1624
1644
  exitNow(1);
1625
1645
  }
1626
- console.log('✓ seed complete');
1646
+ // No frames on this path — `seed_failed` in the body is the only evidence a kind threw.
1647
+ const failedKinds = Object.entries((json?.seed_failed ?? {}));
1648
+ console.log(failedKinds.length ? `⚠ seed INCOMPLETE — ${failedKinds.length} kind(s) failed` : '✓ seed complete');
1627
1649
  printSeedCounts(json?.seeded);
1650
+ if (failedKinds.length) {
1651
+ for (const [kind, why] of failedKinds)
1652
+ console.error(` • ${kind}: ${why}`);
1653
+ exitNow(1);
1654
+ }
1628
1655
  }
1629
1656
  /** Per-kind counts, one line each. Prints nothing when the pack declared nothing. */
1630
1657
  function printSeedCounts(seeded) {
@@ -1658,12 +1685,14 @@ async function cmdDeploy(flags) {
1658
1685
  const { terminal: final, stepErrors } = await readDeployProgress(res.body);
1659
1686
  if (!final || final.stage === 'error')
1660
1687
  die(`deploy failed${final?.message ? `: ${final.message}` : ' (stream ended early)'}`);
1661
- printDeploySuccess(id, version, t, final, { intent: listing, packDir });
1688
+ // The count goes IN, so the headline itself carries the verdict. It used to be
1689
+ // printed underneath a `✓ Deployed` line, which is what a reporting author read.
1690
+ printDeploySuccess(id, version, t, final, { intent: listing, packDir }, stepErrors.length);
1662
1691
  if (stepErrors.length) {
1663
- // The pack IS installed, but a step (e.g. the demo seed) failed — say so
1664
- // plainly and exit non-zero so CI / a `deploy && chat` chain doesn't treat
1665
- // an incomplete install as a clean success.
1666
- console.error(`\n⚠ Deployed with ${stepErrors.length} warning${stepErrors.length === 1 ? '' : 's'} — data may be incomplete:`);
1692
+ // The pack IS installed, but a step (e.g. the demo seed) failed — list what,
1693
+ // and exit non-zero so CI / a `deploy && chat` chain doesn't treat an
1694
+ // incomplete install as a clean success.
1695
+ console.error(`\nFailures:`);
1667
1696
  for (const e of stepErrors)
1668
1697
  console.error(` • ${e}`);
1669
1698
  exitNow(1);
@@ -1686,7 +1715,17 @@ async function cmdDeploy(flags) {
1686
1715
  console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
1687
1716
  exitNow(1);
1688
1717
  }
1689
- printDeploySuccess(id, version, t, json, { intent: listing, packDir });
1718
+ // This path had NO step-error check at all. With no SSE frames to read, the only
1719
+ // evidence a seed kind threw is `seed_failed` in the body — so a broken seed printed
1720
+ // a clean `✓ Deployed` here even after the streaming path was fixed in 0.1.14.
1721
+ const failedKinds = Object.entries((json?.seed_failed ?? {}));
1722
+ printDeploySuccess(id, version, t, json, { intent: listing, packDir }, failedKinds.length);
1723
+ if (failedKinds.length) {
1724
+ console.error(`\nFailures:`);
1725
+ for (const [kind, why] of failedKinds)
1726
+ console.error(` • ${kind}: ${why}`);
1727
+ exitNow(1);
1728
+ }
1690
1729
  }
1691
1730
  /**
1692
1731
  * The QUALIFIED pack id (`<owner>.<name>`) for a manifest's bare name.
@@ -3116,12 +3155,36 @@ async function cmdWork(flags) {
3116
3155
  const name = pickLabel(q.name);
3117
3156
  console.log(` ${q.key}${name ? ` (${name})` : ''} ${q.open_count} open`);
3118
3157
  }
3119
- if (json?.unrouted_open_count)
3120
- console.log(` (unrouted: ${json.unrouted_open_count} open)`);
3158
+ /**
3159
+ * UNROUTED LEADS, because it is the finding — not a footnote to the list.
3160
+ *
3161
+ * The count was already here, as `(unrouted: N open)` tucked under the queues. That
3162
+ * is the wrong weight: a routed item is in a list someone works, and an unrouted one
3163
+ * is in NOBODY's list — it is the only line on this screen that means "these are
3164
+ * lost". A pack author spent five deploys cornering exactly that state in 2026-09
3165
+ * and reported `--queues` as the tool that finally showed it, which is the argument
3166
+ * for making it unmissable rather than for adding it.
3167
+ *
3168
+ * Still silent at zero: a clean project must stay quiet, or the warning stops
3169
+ * meaning anything.
3170
+ */
3171
+ const unrouted = Number(json?.unrouted_open_count ?? 0);
3172
+ if (unrouted > 0) {
3173
+ console.log(`\n⚠ ${unrouted} open item(s) are UNROUTED — in no queue, so nobody sees them.`);
3174
+ console.log(` Usually a routing declaration that resolves to nothing: check the item's`);
3175
+ console.log(` type against \`work.<entity>.types\` in worklist.yaml, and that the entity`);
3176
+ console.log(` has a \`queue:\`/\`route_by\` default for types that declare none.`);
3177
+ console.log(` List them: octwin work --unrouted`);
3178
+ }
3121
3179
  return;
3122
3180
  }
3123
3181
  if (!recordId) {
3124
- const { status, json } = await apiGet(`${base}/work?${pagingQs(flags)}`, t);
3182
+ // `--unrouted` narrows the inbox to the items in NO queue. The route has always
3183
+ // supported the filter; the CLI never passed it, so the count on `--queues` named a
3184
+ // problem it could not then show you. A number you cannot drill into is a dead end.
3185
+ const onlyUnrouted = flags.unrouted === true;
3186
+ const qs = `${pagingQs(flags)}${onlyUnrouted ? '&unrouted=true' : ''}`;
3187
+ const { status, json } = await apiGet(`${base}/work?${qs}`, t);
3125
3188
  if (status !== 200)
3126
3189
  workFail('the work inbox', status, json);
3127
3190
  if (asJson) {
@@ -3129,9 +3192,9 @@ async function cmdWork(flags) {
3129
3192
  return;
3130
3193
  }
3131
3194
  const page = readPage(json);
3132
- console.log(`Work items in ${targetLabel(t)}: ${page.total ?? page.rows.length} total`);
3195
+ console.log(`${onlyUnrouted ? 'UNROUTED work items' : 'Work items'} in ${targetLabel(t)}: ${page.total ?? page.rows.length} total`);
3133
3196
  if (page.rows.length === 0)
3134
- console.log(' (none)');
3197
+ console.log(onlyUnrouted ? ' (none — everything is routed)' : ' (none)');
3135
3198
  for (const w of page.rows) {
3136
3199
  const sla = w.sla_due_at ? ` sla:${w.sla_due_at}` : '';
3137
3200
  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}`);
@@ -4917,371 +4980,372 @@ async function cmdUsage(flags) {
4917
4980
  console.log('\nThis is MODEL spend. WhatsApp/Meta message billing is operator-only — not reachable by an API token.');
4918
4981
  }
4919
4982
  function help() {
4920
- console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
4921
-
4922
- octwin --version # print the CLI version (+ any upgrade notice)
4923
- octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
4924
- octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
4925
- octwin login --url <platformUrl> --token oct_… # a deploy token from the console
4926
- octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
4927
- octwin projects [--archived] [--json] # the --project slugs this token can name
4928
- octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
4929
- [--request-listing | --withdraw-listing] # public marketplace — opt-in, see: octwin help deploy
4930
- octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
4931
- octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
4932
- octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
4933
- octwin work [recordId] [--queues] [--json] # inspect the work inbox (worked records) + timelines
4934
- octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
4935
- octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
4936
- octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image MEDIA- handle (needs media:generate scope)
4937
- octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
4938
- octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
4939
- octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
4940
- octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
4941
- octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
4942
- octwin automation [campaigns] [--json] # the jobs your declarations produced + health, last result each
4943
- octwin integrations [--json] # declared connections BESIDE what is configured (the silent-never-fires check)
4944
- octwin integrations deliveries [<id>] | events # the outbound delivery log / inbound events
4945
- octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition] [--stage <id>]
4946
- octwin performance [--detail] [--json] # the project's business indicators (value/conversion/duration)
4947
- octwin usage [--json] # model calls, tokens and cost (project if pinned, else workspace)
4948
- octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
4949
- octwin test [--dir .] # = validate --remote (the full platform check)
4950
- octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
4951
- octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
4952
-
4953
- Writes — exercise the state your pack creates (each needs the matching :write scope):
4954
- octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
4955
- octwin records tasks | task complete <taskId> [--outcome done|cancelled]
4956
- octwin work assign <id> --to user:<uuid>|none | note <id> "…" | stage <id> --to <stage>
4957
- octwin work decide <id> --action <a> [--param k=v] [--dry-run] # --dry-run previews, commits nothing
4958
- octwin orders transition <ref> --to <status> | refund <ref> --force
4959
- octwin catalog availability <sku> --to "in stock" | stock <sku> [--set-on-hand n]
4960
- octwin scheduling rules --resource <id> | rule add|rm | exception add|rm
4961
- octwin agents set <packId::agentId> [--model m] [--enable-tool t] [--disable-tool t]
4962
- octwin automation run <jobId> | pause <jobId> | resume <jobId> | send <campaignId>
4963
- octwin integrations test <key> # a LIVE call to the connection's health: operation
4964
- octwin integrations retry|cancel|send-now <deliveryId>
4965
- (octwin integrations preflight <key> needs only integrations:read — it makes no call)
4966
-
4967
- Multi-turn: the platform keeps ONE open conversation per --as handle — consecutive
4968
- \`octwin chat --as <h>\` calls continue the same conversation; press a rendered
4969
- button/row with \`--tap "<tap-id>"\` (chat prints every tap id).
4970
- Get a deploy token: console → your workspace → Settings → API tokens → Generate (tick records:read to inspect data).
4971
- octwin platform-kb pullwrites the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
4972
- Config (deploy): flags > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login (\`octwin login\` sets the default target).
4983
+ console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
4984
+
4985
+ octwin --version # print the CLI version (+ any upgrade notice)
4986
+ octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
4987
+ octwin validate [--dir .] [--remote] [--require-kb] # --remote runs the platform's FULL schema check + lint (all errors at once)
4988
+ octwin login --url <platformUrl> --token oct_… # a deploy token from the console
4989
+ octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
4990
+ octwin projects [--archived] [--json] # the --project slugs this token can name
4991
+ octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
4992
+ [--request-listing | --withdraw-listing] # public marketplace — opt-in, see: octwin help deploy
4993
+ octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
4994
+ octwin pull <packId> [--dir <out>] [--version <v>] [--force] # write a DEPLOYED pack's source back to disk (the inverse of deploy)
4995
+ octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
4996
+ octwin work [recordId] [--queues] [--unrouted] [--json] # inspect the work inbox (worked records) + timelines
4997
+ # --queues: per-queue open counts + an UNROUTED warning · --unrouted: only the items in no queue
4998
+ octwin logs [conversationId] [--as <handle>] [--json] # list conversations / show one's event timeline
4999
+ octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json] # drive a turn (+ send media) + print every render
5000
+ octwin media generate "<prompt>" [--out <file.png>] [--json] # AI-generate an image MEDIA- handle (needs media:generate scope)
5001
+ octwin agents [packId::agentId] [--prompt] [--json] # effective model/memory + WHICH layer won; --prompt = the resolved system prompt
5002
+ octwin orders [reference_id] [--status s] [--payment p] [--json] # the orders a conversation produced + money + payment state
5003
+ octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] # stage conversion for any pipelined entity
5004
+ octwin catalog [--readiness] [--json] # commerce products + stock + the WhatsApp catalog binding
5005
+ octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] # engine state / computed slots
5006
+ octwin automation [campaigns] [--json] # the jobs your declarations produced + health, last result each
5007
+ octwin integrations [--json] # declared connections BESIDE what is configured (the silent-never-fires check)
5008
+ octwin integrations deliveries [<id>] | events # the outbound delivery log / inbound events
5009
+ octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition] [--stage <id>]
5010
+ octwin performance [--detail] [--json] # the project's business indicators (value/conversion/duration)
5011
+ octwin usage [--json] # model calls, tokens and cost (project if pinned, else workspace)
5012
+ octwin platform-kb [pull] [--if-stale|--check] [--dir .] [--url <url>] # no token needed
5013
+ octwin test [--dir .] # = validate --remote (the full platform check)
5014
+ octwin feedback [--dir .] # submit this pack's FEEDBACK.md to the platform team
5015
+ octwin memos [--all] [--json] # read the platform's replies + notices (a reply to your feedback lands here)
5016
+
5017
+ Writes exercise the state your pack creates (each needs the matching :write scope):
5018
+ octwin records create <entity> --set field=value … # also: patch <id> --entity <e>, stage <id> --to <s>, note <id> "…"
5019
+ octwin records tasks | task complete <taskId> [--outcome done|cancelled]
5020
+ octwin work assign <id> --to user:<uuid>|none | note <id> "…" | stage <id> --to <stage>
5021
+ octwin work decide <id> --action <a> [--param k=v] [--dry-run] # --dry-run previews, commits nothing
5022
+ octwin orders transition <ref> --to <status> | refund <ref> --force
5023
+ octwin catalog availability <sku> --to "in stock" | stock <sku> [--set-on-hand n]
5024
+ octwin scheduling rules --resource <id> | rule add|rm | exception add|rm
5025
+ octwin agents set <packId::agentId> [--model m] [--enable-tool t] [--disable-tool t]
5026
+ octwin automation run <jobId> | pause <jobId> | resume <jobId> | send <campaignId>
5027
+ octwin integrations test <key> # a LIVE call to the connection's health: operation
5028
+ octwin integrations retry|cancel|send-now <deliveryId>
5029
+ (octwin integrations preflight <key> needs only integrations:read — it makes no call)
5030
+
5031
+ Multi-turn: the platform keeps ONE open conversation per --as handle consecutive
5032
+ \`octwin chat --as <h>\` calls continue the same conversation; press a rendered
5033
+ button/row with \`--tap "<tap-id>"\` (chat prints every tap id).
5034
+ Get a deploy token: console your workspace Settings API tokens → Generate (tick records:read to inspect data).
5035
+ octwin platform-kb pull writes the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
5036
+ Config (deploy): flags > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login (\`octwin login\` sets the default target).
4973
5037
  Per-command usage: octwin <command> --help`);
4974
5038
  }
4975
5039
  /** Per-subcommand usage — printed for `octwin <cmd> --help|-h` BEFORE any
4976
5040
  * network/auth work (a --help that 401s is worse than no help at all). */
4977
5041
  const COMMAND_HELP = {
4978
- init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
5042
+ init: `octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
4979
5043
  Scaffold a pure-YAML starter pack into <dir>.`,
4980
- validate: `octwin validate [--dir .] [--remote] [--require-kb] [--strict-primitives]
4981
- Offline structural check, plus two checks driven by the pulled capability
4982
- reference (render-intent fields, primitive arguments). Those two SKIP when the
4983
- reference is missing — the run says so, and --require-kb turns the skip into a
4984
- failure for CI. --remote additionally runs the platform's FULL manifest +
4985
- flow-DSL validation and its flow lint (all errors at once) — same check as deploy.
4986
- --strict-primitives (with --remote) additionally type-checks LITERAL args:
4987
- values against each primitive's declared input schema; expression strings
5044
+ validate: `octwin validate [--dir .] [--remote] [--require-kb] [--strict-primitives]
5045
+ Offline structural check, plus two checks driven by the pulled capability
5046
+ reference (render-intent fields, primitive arguments). Those two SKIP when the
5047
+ reference is missing — the run says so, and --require-kb turns the skip into a
5048
+ failure for CI. --remote additionally runs the platform's FULL manifest +
5049
+ flow-DSL validation and its flow lint (all errors at once) — same check as deploy.
5050
+ --strict-primitives (with --remote) additionally type-checks LITERAL args:
5051
+ values against each primitive's declared input schema; expression strings
4988
5052
  ('$found.id', '{$t(…)}') are always exempt.`,
4989
- login: `octwin login --url <platformUrl> --token oct_…
4990
- Save a deploy token (console → Settings → API tokens) for that platform url,
4991
- make that url the DEFAULT deploy target for every later command, and echo the
5053
+ login: `octwin login --url <platformUrl> --token oct_…
5054
+ Save a deploy token (console → Settings → API tokens) for that platform url,
5055
+ make that url the DEFAULT deploy target for every later command, and echo the
4992
5056
  workspace + project pin + scopes the token reaches.`,
4993
- whoami: `octwin whoami [--url <url>] [--tenant <slug>]
5057
+ whoami: `octwin whoami [--url <url>] [--tenant <slug>]
4994
5058
  Verify the resolved token authenticates against the tenant.`,
4995
- projects: `octwin projects [--archived] [--json]
4996
- List the workspace's projects — the slugs every --project flag takes, with the
4997
- plan's project cap. --archived includes archived ones. A pack:deploy token
4998
- reaches this (it names a project in every other command).
4999
-
5000
- octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
5001
- Create a project. The URL slug is derived from the name unless --slug pins one.
5002
- --pack installs an ALREADY-published pack; the usual next step is instead
5003
- \`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
5004
-
5005
- octwin projects rm <slug> [--yes]
5006
- HARD delete — the project and everything cascading from it (conversations,
5007
- contacts, records, installs). No undo, and not the same as archiving.
5008
- WITHOUT --yes it only previews what would be destroyed, so the dry run is the
5009
- default. Together these make a disposable end-to-end environment:
5010
- octwin projects create "Scratch" && octwin deploy --project scratch --seed
5011
- octwin chat "hi" --project scratch
5012
- octwin projects rm scratch --yes
5059
+ projects: `octwin projects [--archived] [--json]
5060
+ List the workspace's projects — the slugs every --project flag takes, with the
5061
+ plan's project cap. --archived includes archived ones. A pack:deploy token
5062
+ reaches this (it names a project in every other command).
5063
+
5064
+ octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
5065
+ Create a project. The URL slug is derived from the name unless --slug pins one.
5066
+ --pack installs an ALREADY-published pack; the usual next step is instead
5067
+ \`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
5068
+
5069
+ octwin projects rm <slug> [--yes]
5070
+ HARD delete — the project and everything cascading from it (conversations,
5071
+ contacts, records, installs). No undo, and not the same as archiving.
5072
+ WITHOUT --yes it only previews what would be destroyed, so the dry run is the
5073
+ default. Together these make a disposable end-to-end environment:
5074
+ octwin projects create "Scratch" && octwin deploy --project scratch --seed
5075
+ octwin chat "hi" --project scratch
5076
+ octwin projects rm scratch --yes
5013
5077
  Both verbs need the \`projects:write\` scope — a pack:deploy token does NOT confer it.`,
5014
- deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
5015
- [--request-listing | --withdraw-listing]
5016
- Upload the pack bundle, validate server-side, install onto the project.
5017
- --seed additionally applies the pack's demo seed (streams progress).
5018
-
5019
- A plain deploy says NOTHING about the public marketplace — it is a test loop, so it
5020
- neither asks for a listing nor gives one up. The marketplace flags are opt-in:
5021
-
5022
- --request-listing ask an operator to review this pack for the public marketplace
5023
- (the pre-signup storefront at /packs). Requires 'public: true'
5024
- under 'listing:' in manifest.yaml — the manifest states that the
5025
- pack is a product, the flag is you choosing to ask.
5026
- --withdraw-listing retract the request, including an approved listing.
5027
-
5028
- An approval covers the CONTENT it was made against, so a later deploy that changes the
5078
+ deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
5079
+ [--request-listing | --withdraw-listing]
5080
+ Upload the pack bundle, validate server-side, install onto the project.
5081
+ --seed additionally applies the pack's demo seed (streams progress).
5082
+
5083
+ A plain deploy says NOTHING about the public marketplace — it is a test loop, so it
5084
+ neither asks for a listing nor gives one up. The marketplace flags are opt-in:
5085
+
5086
+ --request-listing ask an operator to review this pack for the public marketplace
5087
+ (the pre-signup storefront at /packs). Requires 'public: true'
5088
+ under 'listing:' in manifest.yaml — the manifest states that the
5089
+ pack is a product, the flag is you choosing to ask.
5090
+ --withdraw-listing retract the request, including an approved listing.
5091
+
5092
+ An approval covers the CONTENT it was made against, so a later deploy that changes the
5029
5093
  pack returns it to the review queue on its own — no flag needed, and the CLI says so.`,
5030
- seed: `octwin seed [--pack <packId>]
5031
- Apply the pack's demo/reference data to the project it is installed on, without
5032
- redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
5033
- and the demo operator topology. Reports what each kind produced.
5034
- Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
5035
- than regenerated, so a second pass costs nothing. --pack is only needed when a
5094
+ seed: `octwin seed [--pack <packId>]
5095
+ Apply the pack's demo/reference data to the project it is installed on, without
5096
+ redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
5097
+ and the demo operator topology. Reports what each kind produced.
5098
+ Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
5099
+ than regenerated, so a second pass costs nothing. --pack is only needed when a
5036
5100
  project somehow runs more than one.`,
5037
- status: `octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
5038
- Show installed vs live version + the flow list for this pack.
5039
- The pack id is read from manifest.yaml and QUALIFIED with your workspace slug
5040
- (a manifest declares a bare name; the owner is attached when you publish). Pass
5041
- <packId> explicitly to skip that lookup — \`octwin agents\` and \`octwin projects\`
5101
+ status: `octwin status [<packId>] [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
5102
+ Show installed vs live version + the flow list for this pack.
5103
+ The pack id is read from manifest.yaml and QUALIFIED with your workspace slug
5104
+ (a manifest declares a bare name; the owner is attached when you publish). Pass
5105
+ <packId> explicitly to skip that lookup — \`octwin agents\` and \`octwin projects\`
5042
5106
  both print the qualified form.`,
5043
- records: `octwin records [entity] [id] [--limit 50] [--offset n]
5044
- Inspect the pack's XRM data. No args = list entities. Worked records (cases,
5045
- tickets, anything routed to a queue) read best through \`octwin work\`.
5046
-
5047
- WRITES (need \`records:write\`; every one is re-checked by RBAC on the record):
5048
- octwin records create <entity> --set field=value [--set …] [--stage s] [--contact <id>]
5049
- octwin records patch <recordId> --entity <entity> --set field=value
5050
- octwin records stage <recordId> --to <stage> [--note "..."]
5051
- octwin records note <recordId> "the note text"
5052
- octwin records tasks # open follow-up tasks (\`tasks\` plan feature)
5053
- octwin records task complete <taskId> [--outcome done|cancelled] [--note "..."]
5054
-
5055
- --set coerces JSON scalars: \`--set rating=4.5\` sends a number, \`--set x=null\`
5056
- sends null. Use --fields-json '{"a":{"b":1}}' for anything nested.
5057
- \`patch\` needs --entity even though it has an id: the route resolves the field
5058
- validator from it. A leading \`create/patch/stage/note/tasks/task\` is read as a
5107
+ records: `octwin records [entity] [id] [--limit 50] [--offset n]
5108
+ Inspect the pack's XRM data. No args = list entities. Worked records (cases,
5109
+ tickets, anything routed to a queue) read best through \`octwin work\`.
5110
+
5111
+ WRITES (need \`records:write\`; every one is re-checked by RBAC on the record):
5112
+ octwin records create <entity> --set field=value [--set …] [--stage s] [--contact <id>]
5113
+ octwin records patch <recordId> --entity <entity> --set field=value
5114
+ octwin records stage <recordId> --to <stage> [--note "..."]
5115
+ octwin records note <recordId> "the note text"
5116
+ octwin records tasks # open follow-up tasks (\`tasks\` plan feature)
5117
+ octwin records task complete <taskId> [--outcome done|cancelled] [--note "..."]
5118
+
5119
+ --set coerces JSON scalars: \`--set rating=4.5\` sends a number, \`--set x=null\`
5120
+ sends null. Use --fields-json '{"a":{"b":1}}' for anything nested.
5121
+ \`patch\` needs --entity even though it has an id: the route resolves the field
5122
+ validator from it. A leading \`create/patch/stage/note/tasks/task\` is read as a
5059
5123
  VERB — to list an entity actually named one of those, use \`--entity <name>\`.`,
5060
- work: `octwin work [recordId] [--queues] [--limit 50] [--offset n] [--json]
5061
- Inspect the work inbox — every entity the pack declares worked (cases, orders
5062
- needing review, applications, …): the inbox, one item + its timeline
5063
- (+ applicable actions), or --queues for queue keys + open counts.
5064
-
5065
- WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
5066
- octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
5067
- octwin work note <recordId> "the note text"
5068
- octwin work stage <recordId> --to <stage> [--note "..."]
5069
- octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
5070
-
5071
- \`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
5072
- lists them with their params. --dry-run previews the customer-facing copy and the
5073
- resulting stage WITHOUT committing (that route needs only \`work:read\`).
5124
+ work: `octwin work [recordId] [--queues] [--unrouted] [--limit 50] [--offset n] [--json]
5125
+ Inspect the work inbox — every entity the pack declares worked (cases, orders
5126
+ needing review, applications, …): the inbox, one item + its timeline
5127
+ (+ applicable actions), or --queues for queue keys + open counts.
5128
+
5129
+ WRITES (need \`work:write\`; \`stage\` needs \`records:write\`):
5130
+ octwin work assign <recordId> --to user:<uuid>|team:<uuid>|none
5131
+ octwin work note <recordId> "the note text"
5132
+ octwin work stage <recordId> --to <stage> [--note "..."]
5133
+ octwin work decide <recordId> --action <action> [--param k=v] [--note "..."] [--dry-run]
5134
+
5135
+ \`decide\` applies one of the entity's declared operator actions — \`octwin work <id>\`
5136
+ lists them with their params. --dry-run previews the customer-facing copy and the
5137
+ resulting stage WITHOUT committing (that route needs only \`work:read\`).
5074
5138
  \`stage\` is the XRM records verb (one transition spelling platform-wide).`,
5075
- logs: `octwin logs [conversationId] [--as <handle>] [--json]
5076
- No id = recent conversations (handle, status, last activity; --as filters).
5077
- With id = the full event timeline including what each turn rendered.
5139
+ logs: `octwin logs [conversationId] [--as <handle>] [--json]
5140
+ No id = recent conversations (handle, status, last activity; --as filters).
5141
+ With id = the full event timeline including what each turn rendered.
5078
5142
  --json = raw events (verbatim payloads).`,
5079
- pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
5080
- Write a DEPLOYED pack's source back to disk — the inverse of deploy.
5081
- A pack pushed with 'octwin deploy' lives on the platform as an artifact the
5082
- runtime serves but nothing hands back, so its only source copy is the machine
5083
- that pushed it. Pull it, fix it, redeploy it.
5084
- Defaults to the version installed on the target project; --version overrides.
5085
- --dir defaults to ./<packId>; a non-empty dir needs --force.
5086
- The pulled dir redeploys where it came from — the target is your saved login.
5143
+ pull: `octwin pull <packId> [--dir <out>] [--version <v>] [--force]
5144
+ Write a DEPLOYED pack's source back to disk — the inverse of deploy.
5145
+ A pack pushed with 'octwin deploy' lives on the platform as an artifact the
5146
+ runtime serves but nothing hands back, so its only source copy is the machine
5147
+ that pushed it. Pull it, fix it, redeploy it.
5148
+ Defaults to the version installed on the target project; --version overrides.
5149
+ --dir defaults to ./<packId>; a non-empty dir needs --force.
5150
+ The pulled dir redeploys where it came from — the target is your saved login.
5087
5151
  You may pull a pack your tenant OWNS (deployed); an operator token pulls any.`,
5088
- chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
5089
- octwin chat --script <file> [--as <handle>] [--json]
5090
- Drive ONE turn through the dev web channel and print every render with its
5091
- tap ids. Same --as handle = same conversation (multi-turn works).
5092
- --tap presses a rendered button/list row instead of sending text.
5093
- --media uploads a local file (or a media id from 'media generate --json') as
5094
- an image/document/audio inbound — any "message" rides as its caption; feeds a
5095
- running media-collect flow (e.g. activate-app).
5096
- --json dumps the raw SSE envelopes for the turn.
5097
-
5098
- --script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
5099
- process over one connection — waiting for each turn to settle before sending
5100
- the next. Use this for any multi-step flow: chaining shell invocations races
5101
- the agent loop, because a turn ends on a quiet gap that can arrive while the
5102
- server is still working (the symptom is placeholder-filled fields or a second
5103
- workflow run). Blank lines and # comments are skipped:
5104
-
5105
- # book an appointment end to end
5106
- احجز موعد
5107
- tap:t:invoke:book-appointment:doctor_id=D1
5108
- media:./licence.jpg | here is my licence
5152
+ chat: `octwin chat "message" [--as <handle>] [--tap <tap-id>] [--media <file|id>] [--json]
5153
+ octwin chat --script <file> [--as <handle>] [--json]
5154
+ Drive ONE turn through the dev web channel and print every render with its
5155
+ tap ids. Same --as handle = same conversation (multi-turn works).
5156
+ --tap presses a rendered button/list row instead of sending text.
5157
+ --media uploads a local file (or a media id from 'media generate --json') as
5158
+ an image/document/audio inbound — any "message" rides as its caption; feeds a
5159
+ running media-collect flow (e.g. activate-app).
5160
+ --json dumps the raw SSE envelopes for the turn.
5161
+
5162
+ --script drives a WHOLE conversation from a file, ONE TURN PER LINE, in one
5163
+ process over one connection — waiting for each turn to settle before sending
5164
+ the next. Use this for any multi-step flow: chaining shell invocations races
5165
+ the agent loop, because a turn ends on a quiet gap that can arrive while the
5166
+ server is still working (the symptom is placeholder-filled fields or a second
5167
+ workflow run). Blank lines and # comments are skipped:
5168
+
5169
+ # book an appointment end to end
5170
+ احجز موعد
5171
+ tap:t:invoke:book-appointment:doctor_id=D1
5172
+ media:./licence.jpg | here is my licence
5109
5173
  tap:t:resume:book-appointment:run_id=R1;_ctl_approved=true`,
5110
- media: `octwin media generate "<prompt>" [--out <file.png>] [--json]
5111
- AI-generate an image (needs a media:generate-scoped token), store it as a
5112
- public asset, and print its MEDIA- handle + serve URL. --out downloads the
5113
- bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
5174
+ media: `octwin media generate "<prompt>" [--out <file.png>] [--json]
5175
+ AI-generate an image (needs a media:generate-scoped token), store it as a
5176
+ public asset, and print its MEDIA- handle + serve URL. --out downloads the
5177
+ bytes (WhatsApp renders only .png/.jpg); --json emits { media_id, url, mime,
5114
5178
  bytes }. Pair with 'octwin chat --media' to drive media flows.`,
5115
- agents: `octwin agents [packId::agentId] [--prompt] [--json]
5116
- No args = the roster with each agent's EFFECTIVE model and which layer set it.
5117
- With an agent = every governed setting (model / memory.last_messages /
5118
- working_memory) plus the layer that won — an operator PLATFORM default can
5119
- override what your manifest declares, and this is where you see that.
5120
- --prompt = the exact system prompt the LLM sees for this project (pack
5121
- instructions + platform protocol + any project overlay). Needs agents:read.
5122
- The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
5123
-
5124
- WRITES (need \`agents:write\`):
5125
- octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
5126
- [--enable-tool <toolId>] [--disable-tool <toolId>]
5127
-
5128
- Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
5129
- so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
5179
+ agents: `octwin agents [packId::agentId] [--prompt] [--json]
5180
+ No args = the roster with each agent's EFFECTIVE model and which layer set it.
5181
+ With an agent = every governed setting (model / memory.last_messages /
5182
+ working_memory) plus the layer that won — an operator PLATFORM default can
5183
+ override what your manifest declares, and this is where you see that.
5184
+ --prompt = the exact system prompt the LLM sees for this project (pack
5185
+ instructions + platform protocol + any project overlay). Needs agents:read.
5186
+ The agent ref is the compound \`<packId>::<agentId>\` key or the override-row UUID.
5187
+
5188
+ WRITES (need \`agents:write\`):
5189
+ octwin agents set <ref> [--model <m>] [--enabled true|false] [--overlay "..."|none]
5190
+ [--enable-tool <toolId>] [--disable-tool <toolId>]
5191
+
5192
+ Only what you pass is changed. Tool flags read-modify-write \`config_json.tools\`
5193
+ so a sibling decision isn't dropped; absent = enabled. A workspace that hides model
5130
5194
  ids refuses --model with a 403 — the platform default governs there.`,
5131
- orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
5132
- No args = the order list (#number, status/payment, total, contact). With a
5133
- reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
5134
- payment_ref, and the allowed status transitions. Needs orders:read + the
5135
- \`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
5136
- so \`pending\` on a gateway-less workspace is expected, not a bug.
5137
-
5138
- WRITES (need \`orders:write\`):
5139
- octwin orders transition <reference_id> --to <status>
5140
- octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
5141
-
5142
- Refund is irreversible and moves money, hence --force. The route answers 200 even
5143
- when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
5144
- on a refusal rather than reporting a refund that never happened. Only a payment in
5195
+ orders: `octwin orders [reference_id] [--status s] [--payment p] [--limit 50] [--json]
5196
+ No args = the order list (#number, status/payment, total, contact). With a
5197
+ reference_id = line items, the subtotal/tax/shipping/discount/total breakdown,
5198
+ payment_ref, and the allowed status transitions. Needs orders:read + the
5199
+ \`orders\` plan feature. Note: the forward payment lifecycle is webhook-owned,
5200
+ so \`pending\` on a gateway-less workspace is expected, not a bug.
5201
+
5202
+ WRITES (need \`orders:write\`):
5203
+ octwin orders transition <reference_id> --to <status>
5204
+ octwin orders refund <reference_id> [--reason "..."] [--mark-returned] --force
5205
+
5206
+ Refund is irreversible and moves money, hence --force. The route answers 200 even
5207
+ when the GATEWAY refuses, so the CLI reads the gateway verdict and exits non-zero
5208
+ on a refusal rather than reporting a refund that never happened. Only a payment in
5145
5209
  \`captured\` state can be refunded; \`payment_status\` is never settable directly.`,
5146
- analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
5147
- No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
5148
- With an entity = stage-by-stage conversion (default --funnel) over the last 30
5149
- days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
5210
+ analytics: `octwin analytics [entity] [--funnel|--overview|--milestones|--trends|--cost] [--stage <id>] [--json]
5211
+ No args = the entities that carry a \`pipeline:\` (a funnel needs stages).
5212
+ With an entity = stage-by-stage conversion (default --funnel) over the last 30
5213
+ days. --stage <id> lists the records CURRENTLY at a stage (a live snapshot, not
5150
5214
  range-filtered). Needs records:read + a \`view\` grant on \`record.<entity>\`.`,
5151
- catalog: `octwin catalog [--readiness] [--json]
5152
- The commerce \`product\` records + price, availability, stock (null = not
5153
- inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
5154
- Graph checklist (LIVE Graph calls; needs a bound access token). Needs
5155
- catalog:read + the \`catalog\` plan feature.
5156
-
5157
- WRITES (need \`catalog:write\`):
5158
- octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
5159
- octwin catalog stock <retailerId> [--set-on-hand <n>]
5160
-
5161
- \`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
5162
- inventory-tracked (always sellable), which is different from 0. Lowering on_hand
5163
- below the units already reserved for open carts is refused. Creating/deleting
5215
+ catalog: `octwin catalog [--readiness] [--json]
5216
+ The commerce \`product\` records + price, availability, stock (null = not
5217
+ inventory-tracked) and the WhatsApp catalog binding. --readiness runs the Meta
5218
+ Graph checklist (LIVE Graph calls; needs a bound access token). Needs
5219
+ catalog:read + the \`catalog\` plan feature.
5220
+
5221
+ WRITES (need \`catalog:write\`):
5222
+ octwin catalog availability <retailerId> --to "in stock"|"out of stock"|…
5223
+ octwin catalog stock <retailerId> [--set-on-hand <n>]
5224
+
5225
+ \`stock\` with no --set-on-hand READS it; \`null\` means the SKU is not
5226
+ inventory-tracked (always sellable), which is different from 0. Lowering on_hand
5227
+ below the units already reserved for open carts is refused. Creating/deleting
5164
5228
  products and the Meta catalog binding/sync stay in the console.`,
5165
- scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
5166
- No args = the engine state (bookable resource types, upcoming slots, booked
5167
- seats). --slots <recordId> computes the slots for one bookable resource
5168
- (occupancy included; --days is clamped to 1-31 server-side) — the way to verify
5169
- the availability rules a \`deploy --seed\` created. Needs scheduling:read.
5170
-
5171
- RULES (list needs scheduling:read; add/rm need scheduling:write):
5172
- octwin scheduling rules --resource <resourceRecordId>
5173
- octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
5174
- [--slot-minutes 30] [--capacity 1]
5175
- octwin scheduling rule rm <ruleId>
5176
- octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
5177
- [--start 09:00 --end 13:00]
5178
- octwin scheduling exception rm <exceptionId>
5179
-
5180
- --dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
5229
+ scheduling: `octwin scheduling [--slots <resourceRecordId>] [--from YYYY-MM-DD] [--days n] [--json]
5230
+ No args = the engine state (bookable resource types, upcoming slots, booked
5231
+ seats). --slots <recordId> computes the slots for one bookable resource
5232
+ (occupancy included; --days is clamped to 1-31 server-side) — the way to verify
5233
+ the availability rules a \`deploy --seed\` created. Needs scheduling:read.
5234
+
5235
+ RULES (list needs scheduling:read; add/rm need scheduling:write):
5236
+ octwin scheduling rules --resource <resourceRecordId>
5237
+ octwin scheduling rule add --resource <id> --dow 1 --start 09:00 --end 17:00
5238
+ [--slot-minutes 30] [--capacity 1]
5239
+ octwin scheduling rule rm <ruleId>
5240
+ octwin scheduling exception add --resource <id> --date YYYY-MM-DD --kind closed|extra
5241
+ [--start 09:00 --end 13:00]
5242
+ octwin scheduling exception rm <exceptionId>
5243
+
5244
+ --dow is 0-6, 0 = Sunday. \`rules\` is how you find an id to remove, and
5181
5245
  \`--slots\` is how you check what a rule actually produces.`,
5182
- automation: `octwin automation [campaigns] [--limit n] [--offset n] [--json]
5183
- No args = every job the pack's automation declaration produced, with its status,
5184
- interval and LAST RESULT (matched / acted / errors), under a health line whose
5185
- counts come from SQL rather than from filtering the page — the job list is capped
5186
- server-side, so a client-side count would depend on the cap. Needs automation:read.
5187
-
5188
- Jobs are DERIVED from declarations. There is no \`create\`: no automation block in
5189
- the pack means no jobs, and \`octwin deploy\` is what installs them.
5190
-
5191
- WRITES (automation:write):
5192
- octwin automation run <jobId> # run once, now — prints matched/acted/errors
5193
- octwin automation pause|resume <jobId>
5194
- octwin automation send <campaignId> # enqueue a campaign; enqueued != delivered
5195
-
5196
- <jobId> is the \`key\` the list shows (its uuid works too). The routes themselves
5197
- accept only a uuid — the CLI resolves the key for you, and names the keys that do
5198
- exist when it cannot. A 403 on a write can be an RBAC grant gap rather than a
5246
+ automation: `octwin automation [campaigns] [--limit n] [--offset n] [--json]
5247
+ No args = every job the pack's automation declaration produced, with its status,
5248
+ interval and LAST RESULT (matched / acted / errors), under a health line whose
5249
+ counts come from SQL rather than from filtering the page — the job list is capped
5250
+ server-side, so a client-side count would depend on the cap. Needs automation:read.
5251
+
5252
+ Jobs are DERIVED from declarations. There is no \`create\`: no automation block in
5253
+ the pack means no jobs, and \`octwin deploy\` is what installs them.
5254
+
5255
+ WRITES (automation:write):
5256
+ octwin automation run <jobId> # run once, now — prints matched/acted/errors
5257
+ octwin automation pause|resume <jobId>
5258
+ octwin automation send <campaignId> # enqueue a campaign; enqueued != delivered
5259
+
5260
+ <jobId> is the \`key\` the list shows (its uuid works too). The routes themselves
5261
+ accept only a uuid — the CLI resolves the key for you, and names the keys that do
5262
+ exist when it cannot. A 403 on a write can be an RBAC grant gap rather than a
5199
5263
  missing scope: the action is re-checked against the job.`,
5200
- integrations: `octwin integrations [--json]
5201
- What the pack DECLARES beside what is actually CONFIGURED, in one view — because a
5202
- connection that is declared and never configured is the commonest reason an
5203
- integration silently never fires, and neither list alone can show it. Flags the
5204
- gap explicitly. Needs integrations:read.
5205
-
5206
- DIAGNOSE ONE CONNECTION:
5207
- octwin integrations preflight <key> # every check, with a fix hint. Makes NO
5208
- # outbound call — needs only integrations:read
5209
- octwin integrations test <key> # a LIVE call to its health: operation
5210
- # (integrations:write). Exits 1 when it fails.
5211
-
5212
- THE DELIVERY LOG:
5213
- octwin integrations deliveries [--status s] [--operation id] [--limit n]
5214
- octwin integrations deliveries <id> # + the redacted request/response snapshots
5215
- octwin integrations retry|cancel|send-now <id> # integrations:write
5216
- octwin integrations events # INBOUND events (what arrived at your webhook)
5217
-
5218
- retry/cancel answer 409 when the delivery is in the wrong state; the message
5264
+ integrations: `octwin integrations [--json]
5265
+ What the pack DECLARES beside what is actually CONFIGURED, in one view — because a
5266
+ connection that is declared and never configured is the commonest reason an
5267
+ integration silently never fires, and neither list alone can show it. Flags the
5268
+ gap explicitly. Needs integrations:read.
5269
+
5270
+ DIAGNOSE ONE CONNECTION:
5271
+ octwin integrations preflight <key> # every check, with a fix hint. Makes NO
5272
+ # outbound call — needs only integrations:read
5273
+ octwin integrations test <key> # a LIVE call to its health: operation
5274
+ # (integrations:write). Exits 1 when it fails.
5275
+
5276
+ THE DELIVERY LOG:
5277
+ octwin integrations deliveries [--status s] [--operation id] [--limit n]
5278
+ octwin integrations deliveries <id> # + the redacted request/response snapshots
5279
+ octwin integrations retry|cancel|send-now <id> # integrations:write
5280
+ octwin integrations events # INBOUND events (what arrived at your webhook)
5281
+
5282
+ retry/cancel answer 409 when the delivery is in the wrong state; the message
5219
5283
  carries the rule.`,
5220
- journeys: `octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition]
5221
- [--stage <stageId>] [--limit n] [--json]
5222
- No args = the journeys the pack declares. With an id, one of six views —
5223
- --funnel (default) stage-by-stage reach and drop-off · --overview entered vs
5224
- converted plus the biggest drop-off · --goals completions, contacts, value and
5225
- p50 time · --trends per-bucket activity · --cost tokens and dollars per goal ·
5226
- --definition what was DECLARED, unmeasured (the one view that works with no
5227
- traffic). Needs journeys:read.
5228
-
5229
- --stage <stageId> lists the runs sitting at a stage right now (a live snapshot,
5230
- not the funnel's cumulative reached counts).
5231
-
5232
- Same flag grammar as \`octwin analytics\` on purpose: a journey funnel and an
5233
- entity funnel are the same question about different subjects. Journeys carry RBAC
5234
- on top of the scope, so an empty answer can be a missing \`view\` grant rather
5284
+ journeys: `octwin journeys [journeyId] [--funnel|--overview|--goals|--trends|--cost|--definition]
5285
+ [--stage <stageId>] [--limit n] [--json]
5286
+ No args = the journeys the pack declares. With an id, one of six views —
5287
+ --funnel (default) stage-by-stage reach and drop-off · --overview entered vs
5288
+ converted plus the biggest drop-off · --goals completions, contacts, value and
5289
+ p50 time · --trends per-bucket activity · --cost tokens and dollars per goal ·
5290
+ --definition what was DECLARED, unmeasured (the one view that works with no
5291
+ traffic). Needs journeys:read.
5292
+
5293
+ --stage <stageId> lists the runs sitting at a stage right now (a live snapshot,
5294
+ not the funnel's cumulative reached counts).
5295
+
5296
+ Same flag grammar as \`octwin analytics\` on purpose: a journey funnel and an
5297
+ entity funnel are the same question about different subjects. Journeys carry RBAC
5298
+ on top of the scope, so an empty answer can be a missing \`view\` grant rather
5235
5299
  than missing data — the output says which causes are possible.`,
5236
- performance: `octwin performance [--detail] [--json]
5237
- The project's business indicators — value produced, conversion, duration — each
5238
- with its delta against the previous window and a \`why\` naming the declaration it
5239
- came from. --detail adds the per-indicator breakdown.
5240
-
5241
- Needs records:read, NOT a performance scope (there is none), so a read-only token
5242
- already reaches it. Indicators are DERIVED: a pack that declares no journey goal
5300
+ performance: `octwin performance [--detail] [--json]
5301
+ The project's business indicators — value produced, conversion, duration — each
5302
+ with its delta against the previous window and a \`why\` naming the declaration it
5303
+ came from. --detail adds the per-indicator breakdown.
5304
+
5305
+ Needs records:read, NOT a performance scope (there is none), so a read-only token
5306
+ already reaches it. Indicators are DERIVED: a pack that declares no journey goal
5243
5307
  value and no pipelined entity produces none, which is a different thing from zero.`,
5244
- usage: `octwin usage [--json]
5245
- Model calls, tokens and cost for the resolved scope — project when one is pinned
5246
- or passed with --project, otherwise the whole workspace. Broken down by model,
5247
- kind, agent and channel.
5248
-
5249
- Needs no particular scope: any valid token reaches it.
5250
-
5251
- This is MODEL spend only. WhatsApp/Meta message billing is operator-only and
5308
+ usage: `octwin usage [--json]
5309
+ Model calls, tokens and cost for the resolved scope — project when one is pinned
5310
+ or passed with --project, otherwise the whole workspace. Broken down by model,
5311
+ kind, agent and channel.
5312
+
5313
+ Needs no particular scope: any valid token reaches it.
5314
+
5315
+ This is MODEL spend only. WhatsApp/Meta message billing is operator-only and
5252
5316
  deliberately outside the token scope registry — no API token can read it.`,
5253
- 'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
5254
- Pull the platform capability reference (markdown + JSON catalogs) into
5255
- .octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
5256
- INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
5257
- OUTLINE.md (every heading with its line number).
5258
-
5259
- NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously,
5260
- and this command never sends one. --token is accepted and ignored, so an older
5261
- script that passes it keeps working.
5262
-
5263
- --if-stale poll the platform's content_hash first and skip the download when
5264
- nothing changed. Cheap enough to run at the start of every session.
5265
- --check report only, write nothing. Exit 0 = current, 2 = stale or never
5266
- pulled, 1 = could not tell (offline / no reference served). For
5317
+ 'platform-kb': `octwin platform-kb [pull] [--if-stale] [--check] [--dir .] [--url <url>] [--token <t>]
5318
+ Pull the platform capability reference (markdown + JSON catalogs) into
5319
+ .octwin/platform-kb/ for the octwin-pack authoring skill, plus three maps:
5320
+ INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
5321
+ OUTLINE.md (every heading with its line number).
5322
+
5323
+ NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously,
5324
+ and this command never sends one. --token is accepted and ignored, so an older
5325
+ script that passes it keeps working.
5326
+
5327
+ --if-stale poll the platform's content_hash first and skip the download when
5328
+ nothing changed. Cheap enough to run at the start of every session.
5329
+ --check report only, write nothing. Exit 0 = current, 2 = stale or never
5330
+ pulled, 1 = could not tell (offline / no reference served). For
5267
5331
  scripts and agent loops that want to branch without parsing prose.`,
5268
- test: `octwin test [--dir .]
5332
+ test: `octwin test [--dir .]
5269
5333
  Alias for \`octwin validate --remote\` — the full platform check.`,
5270
- memos: `octwin memos [--all] [--json]
5271
- Read what the platform has told you: a REPLY to a report you sent with
5272
- \`octwin feedback\`, or a NOTICE published to every author (a new capability,
5273
- a deprecation, a breaking change). Bodies are printed in full.
5274
- Reading marks them read, so the reminder stops. --all re-reads history and
5275
- acks nothing. --json to branch on \`severity\`
5334
+ memos: `octwin memos [--all] [--json]
5335
+ Read what the platform has told you: a REPLY to a report you sent with
5336
+ \`octwin feedback\`, or a NOTICE published to every author (a new capability,
5337
+ a deprecation, a breaking change). Bodies are printed in full.
5338
+ Reading marks them read, so the reminder stops. --all re-reads history and
5339
+ acks nothing. --json to branch on \`severity\`
5276
5340
  (info | action_required | breaking).`,
5277
- feedback: `octwin feedback [--dir .]
5278
- Submit this pack's FEEDBACK.md to the platform team.
5279
- The octwin-pack skill writes that file in its last step — findings grouped by
5280
- owner (A · CLI, B · Platform, C · Skill/KB). This delivers it instead of asking
5281
- you to paste it into a chat.
5282
- Attaches the pack id + version from manifest.yaml, this CLI's version, and the
5283
- content_hash of the capability reference in .octwin/platform-kb/ — triage needs
5284
- the last two to tell "the platform is wrong" from "that was already fixed" or
5341
+ feedback: `octwin feedback [--dir .]
5342
+ Submit this pack's FEEDBACK.md to the platform team.
5343
+ The octwin-pack skill writes that file in its last step — findings grouped by
5344
+ owner (A · CLI, B · Platform, C · Skill/KB). This delivers it instead of asking
5345
+ you to paste it into a chat.
5346
+ Attaches the pack id + version from manifest.yaml, this CLI's version, and the
5347
+ content_hash of the capability reference in .octwin/platform-kb/ — triage needs
5348
+ the last two to tell "the platform is wrong" from "that was already fixed" or
5285
5349
  "you were reading a stale reference". Needs the \`pack:deploy\` scope.`,
5286
5350
  };
5287
5351
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
5
  "type": "module",
6
6
  "bin": {