octwin-cli 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,52 @@ 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.5.1] - 2026-08-01
9
+
10
+ ### Fixed
11
+ - **`validate --remote` blamed the platform's version for a bad `--project`.** Every 404 from
12
+ `POST /api/self/p/packs/validate` printed *"this platform has no /packs/validate endpoint yet
13
+ (older version)"*. That was accurate when the route resolved nothing, but it now resolves the
14
+ tenant and project **before** validating — and an unknown project, or one outside a token's pin,
15
+ answers 404 by design. So a typo in `--project` sent you looking for a version mismatch instead of
16
+ at the flag. The two cases are now told apart by the response **body**, not the status: a missing
17
+ route is Fastify's `error: 'Not Found'` and keeps the old wording, while the platform's own
18
+ `project '<slug>' not found under tenant` is printed as-is, with the flags to check.
19
+
20
+ ## [0.5.0] - 2026-08-01
21
+
22
+ ### Added
23
+ - **`octwin seed [--pack <packId>]`** — apply the pack's demo/reference data to the project it is
24
+ installed on, without redeploying: `xrm.yaml` `demo:` records + scheduling availability, the
25
+ commerce catalog, and the demo operator topology. Reports what **each kind** produced.
26
+ Previously seeding was reachable only as `deploy --seed`, because the platform's seed endpoint was
27
+ keyed on an install id, guarded platform-admin, and carried no tenant/project segments — so the
28
+ `/api/self/**` surface could not reach it and a `pack:deploy` token never could. Re-seeding meant
29
+ a full redeploy or asking an operator. Idempotent and cheap to re-run: records upsert, and
30
+ existing media is REUSED rather than regenerated, so a second pass reports zero images.
31
+
32
+ ## [0.4.0] - 2026-08-01
33
+
34
+ ### Added
35
+ - **`octwin projects create` / `octwin projects rm`** — a **disposable end-to-end environment** from
36
+ the CLI, which is what marketplace developers have been missing. `octwin deploy` has always needed
37
+ a project that already existed and the CLI could only *list* them, so standing up a throwaway
38
+ full deployment meant opening the console or asking an operator. Now:
39
+ ```bash
40
+ octwin projects create "Scratch" # → slug `scratch`
41
+ octwin deploy --project scratch --seed # publish + install + demo data
42
+ octwin chat "hi" --project scratch # talk to it (--script for a whole conversation)
43
+ octwin projects rm scratch --yes # throw it away
44
+ ```
45
+ A throwaway environment is deliberately **not a special kind of thing** — it is an ordinary
46
+ project in your own workspace, so it inherits your plan, entitlements, RBAC and teardown with no
47
+ separate lifecycle. `create` derives the URL slug from the name (`--slug` pins one, `--pack`
48
+ installs an already-published pack). `rm` is a HARD delete — the project and everything cascading
49
+ from it, no undo, not the same as archiving — so **without `--yes` it only prints what would be
50
+ destroyed**, making the dry run the default; the impact list is derived from `pg_constraint`, the
51
+ same payload the console's confirm dialog renders. Both need `projects:write`, which a
52
+ `pack:deploy` token does **not** confer — the CLI names that scope in the 403 hint.
53
+
8
54
  ## [0.3.0] - 2026-07-31
9
55
 
10
56
  ### Added
package/README.md CHANGED
@@ -1,210 +1,211 @@
1
- # octwin-cli
2
-
3
- > The **Octwin** external-pack developer CLI — scaffold, validate, deploy, and manage pure-YAML packs on your own tenant. By **CEQUENS**.
4
-
5
- [![npm version](https://img.shields.io/npm/v/octwin-cli.svg)](https://www.npmjs.com/package/octwin-cli)
6
- [![license](https://img.shields.io/npm/l/octwin-cli.svg)](https://www.npmjs.com/package/octwin-cli)
7
- [![node](https://img.shields.io/node/v/octwin-cli.svg)](https://nodejs.org)
8
-
9
- Octwin is a pack-pluggable conversational-agent platform for WhatsApp and web. A **pack** is a
10
- self-contained bot domain — its agent, conversation flows, prompts, and data model — declared
11
- entirely in YAML. `octwin-cli` lets you build a **pure-YAML pack** in your own repo and deploy it
12
- to a running Octwin platform to test live on your own tenant: no platform checkout, no build step,
13
- and nothing untrusted to run (a pure-YAML pack is declarative data, so the platform can safely run
14
- it alongside other tenants).
15
-
16
- > The npm package is **`octwin-cli`**; the command it installs is **`octwin`**.
17
-
18
- ## Requirements
19
-
20
- - **Node.js ≥ 20**
21
- - Access to an **Octwin platform** (its base URL), a **tenant** (your workspace) on it, and a
22
- **deploy token** (generated in the Octwin console — see [Authentication](#authentication)).
23
-
24
- ## Install
25
-
26
- ```bash
27
- # zero-install — always the latest version
28
- npx octwin-cli@latest <command>
29
-
30
- # …or install the `octwin` command globally
31
- npm install -g octwin-cli
32
- octwin <command>
33
- ```
34
-
35
- ## Quick start
36
-
37
- ```bash
38
- # 1. Scaffold a standalone pure-YAML pack (this is your repo)
39
- octwin init ./my-pack --id my-pack --description "My business bot"
40
- cd ./my-pack
41
- git init && git add -A && git commit -m "init pack"
42
-
43
- # 2. Author it — edit manifest.yaml, flows/tools/main.flow.yaml (+ its locale),
44
- # and prompts/identity.md. Everything is pure YAML.
45
-
46
- # 3. Point it at your platform — one command, no config file
47
- # (Octwin console → your workspace → API tokens → Generate)
48
- octwin login --url https://your-octwin.example.com --token oct_…
49
-
50
- # 4. Validate → deploy → confirm it's live
51
- octwin validate
52
- octwin deploy --seed # --seed also loads any demo data the pack declares
53
- octwin status # "✓ live and current" once it's warm
54
-
55
- # 5. Chat with it on your tenant (web widget / console test page). Edit and
56
- # `octwin deploy` again — a redeploy hot-loads with no restart.
57
- ```
58
-
59
- ## Commands
60
-
61
- | Command | What it does |
62
- | --- | --- |
63
- | `octwin init <dir>` | Scaffold a new pure-YAML pack into `<dir>` (writes a starter `manifest.yaml`, flow, prompt, `.gitignore` and `README.md` — pack content only). Options: `--id`, `--description`, `--display-name`. |
64
- | `octwin validate` | Check the pack locally (structure + pure-YAML rules). `--remote` additionally runs the platform's **flow lint** — conventions the schema can't express, like an `assign:` whose value is a quoted literal, or a `$t()` key with no namespace. The lint needs a template-expanded `FlowDef`, which only the server builds, so it is a `--remote`-only check; a local ✓ does not cover it. |
65
- | `octwin login` | Save a deploy token for a platform URL **and make that URL the default target** (both stored in `~/.octwin/credentials.json`). `--url`, `--token`. |
66
- | `octwin whoami` | Verify the saved/passed token is valid for a tenant. `--url`, `--tenant`. |
67
- | `octwin deploy` | Upload + install the pack onto your tenant's project. `--seed` also runs the pack's demo seed. Reports the **marketplace-listing verdict** when the manifest carries `listing.public: true`. |
68
- | `octwin status` | Report what the platform has live for this pack — installed version, the **content sha** the instance loaded vs. the one the catalog holds (a redeploy of the *same* version changes it), its flows, and whether it is live on the public marketplace. |
69
- | `octwin pull <packId>` | Write a **deployed** pack's source back to disk — the inverse of `deploy`, and how a pack pushed from one machine is recovered. Defaults to the version installed on the target project; `--version` overrides, `--dir` defaults to `./<packId>`, a non-empty dir needs `--force`. You may pull a pack your tenant **owns**. |
70
- | `octwin chat "msg"` | Drive a turn through the dev web channel and print **every render with its tap ids**. `--as <handle>` picks the test user; `--tap "<tap-id>"` presses a rendered button/list row; `--json` dumps the raw envelopes. |
71
- | `octwin logs` | List recent conversations (handle, status, last activity; `--as` filters), or show one conversation's full event timeline — including what each turn rendered. `--json` for raw payloads. |
72
- | `octwin records` | Inspect the pack's XRM data (needs a `records:read` token). No args = list entities. |
73
- | `octwin cases` | Inspect casework (support tickets): the inbox, one case + its timeline and decisions, or `--queues` for queue keys + open counts. |
74
- | `octwin projects` | The `--project <slug>` values this token can name, with the plan's project cap. `--archived` includes archived ones. A `pack:deploy` token reaches it — it names a project in every other command, so this turns "guess the slug" into "read the list". |
75
- | `octwin agents` | The agent roster with each agent's **effective** model / history window and **which layer set it** (project override → platform default → pack manifest) — an operator platform default can override what your manifest declares. `--prompt` prints the exact system prompt the LLM sees. Needs `agents:read`. |
76
- | `octwin orders` | The orders a conversation produced. No args = the list; with a `reference_id` = line items, the subtotal/tax/shipping/discount/total breakdown, `payment_ref`, and the allowed transitions. Needs `orders:read` + the `orders` plan feature. |
77
- | `octwin analytics` | Stage-by-stage conversion for **any** entity declared with a `pipeline:` (`--overview` / `--milestones` / `--trends` / `--cost`; `--stage <id>` lists the records currently at a stage). Needs `records:read`. |
78
- | `octwin catalog` | Commerce products with price / availability / stock, plus the WhatsApp catalog binding. `--readiness` runs the Meta Graph checklist. Needs `catalog:read` + the `catalog` plan feature. |
79
- | `octwin scheduling` | The scheduling engine's state, or `--slots <resourceRecordId>` for the slots one bookable resource actually computes — how you verify the availability rules `deploy --seed` created. Needs `scheduling:read`. |
80
- | `octwin media generate "<prompt>"` | AI-generate an image, store it as a public asset, and print its `MEDIA-` handle + serve URL. `--out` downloads the bytes (WhatsApp renders only `.png`/`.jpg`); `--size`; `--json`. Pairs with `octwin chat --media` to drive media-collect flows. Needs `media:generate`. |
81
- | `octwin platform-kb pull` | Pull the platform's capability reference into `.octwin/platform-kb/` for the **`octwin-pack`** Claude Code authoring plugin: guides as markdown, plus **one JSON file per capability** (`primitives/record_list.json`, `render-intents/carousel.json`, `declarations/xrm.json`, …) and an **`INDEX.md`** mapping every entry to its file — so a lookup is a small targeted read, not a whole catalog. |
82
- | `octwin test` | Alias for `octwin validate --remote` — the platform's full manifest + flow-DSL check. |
83
- | `octwin feedback` | Submit this pack's `FEEDBACK.md` to the platform team, with the pack version, your CLI version and the `content_hash` of the capability reference you pulled — the two facts that separate a real platform gap from something already fixed or a stale KB. |
84
- | `octwin help` | Show usage. Every subcommand also answers `--help`. |
85
-
86
- ### Writing, not just reading
87
-
88
- Every read command above has a write half behind a **leading verb**, so `octwin cases` reads and
89
- `octwin cases note <id> "…"` writes. Each needs the matching `:write` scope — `octwin <cmd> --help`
90
- lists the verbs and their exact flags.
91
-
92
- | Command | Verbs |
93
- | --- | --- |
94
- | `octwin records` | `create <entity> --set k=v` · `patch <id> --entity <e>` · `stage <id> --to <s>` · `note <id> "…"` · `tasks` · `task complete <id>` |
95
- | `octwin cases` | `assign <id> --to user:<uuid>\|none` · `note` · `transition <id> --to <status>` · `decide <id> --action <a> [--dry-run]` |
96
- | `octwin orders` | `transition <ref> --to <status>` · `refund <ref> --force` |
97
- | `octwin catalog` | `availability <sku> --to "in stock"` · `stock <sku> [--set-on-hand n]` |
98
- | `octwin scheduling` | `rules --resource <id>` · `rule add\|rm` · `exception add\|rm` |
99
- | `octwin agents` | `set <ref> [--model m] [--enable-tool t] [--disable-tool t]` |
100
-
101
- `--set k=v` coerces JSON scalars (`--set rating=4.5` sends a number); `--fields-json` takes anything
102
- nested. Destructive verbs want `--force` rather than a prompt — the CLI is non-interactive by
103
- design. `cases decide --dry-run` previews the customer-facing copy and the resulting status without
104
- committing, and needs only `cases:read`.
105
-
106
- Every command that talks to the platform accepts `--dir <path>` (the pack directory; defaults to
107
- the current directory) plus the target overrides `--url` / `--tenant` / `--project` / `--token`.
108
-
109
- ### Debugging a live conversation
110
-
111
- The platform keeps **one open conversation per `--as` handle**, so consecutive `octwin chat` calls
112
- with the same handle **continue the same conversation** — agent memory, suspended flows, and all:
113
-
114
- ```bash
115
- octwin chat "hi" --as tester1 # turn 1 — prints the menu with each row's tap id
116
- octwin chat --tap "t:invoke:my-flow:x=1" --as tester1 # turn 2 — press a rendered row
117
- octwin chat "3 bedrooms" --as tester1 # turn 3 — free text into the running flow
118
- octwin logs --as tester1 # find the conversation, then:
119
- octwin logs <conversationId> # the full timeline (taps, renders, tool events)
120
- ```
121
-
122
- ### Reading back the state your pack created
123
-
124
- `chat`/`logs` show what the bot *said*; these show what it *did*. A 401/403 on any of them names the
125
- token scope (and plan feature) that command needs, so you can mint a wider token instead of guessing.
126
-
127
- ```bash
128
- octwin agents # effective model per agent + which layer set it
129
- octwin agents my-pack::assistant --prompt # the exact system prompt the LLM sees
130
- octwin orders # then: octwin orders <reference_id>
131
- octwin analytics # then: octwin analytics <entity> [--stage <id>]
132
- octwin catalog # products + stock + the WhatsApp binding
133
- octwin scheduling --slots <resourceRecordId> # the slots your availability rules compute
134
- ```
135
-
136
- Three things worth knowing when you read the output:
137
-
138
- - **A `pending` payment is usually correct.** The forward payment lifecycle is **webhook-owned**, and a
139
- workspace with no gateway runs the credential-free `manual` driver — so `payment_request` takes its
140
- `empty` port and your flow should confirm pay-on-delivery. `octwin orders <ref>` says this inline.
141
- - **Your declared model may not be the one running.** An operator platform default overrides the pack
142
- manifest; `octwin agents` is where that becomes visible.
143
- - **An empty funnel has two causes** — the entity has no `pipeline:`, or your token's role has no `view`
144
- grant on `record.<entity>`. The command prints both rather than a bare "no data".
145
-
146
- ## Configuration
147
-
148
- **`octwin login` is the configuration.** There is no config file in your pack — a pack directory
149
- holds pack content and nothing else, so the same repo deploys from any machine:
150
-
151
- ```bash
152
- octwin login --url https://your-octwin.example.com --token oct_…
153
- ```
154
-
155
- That stores the token *and* makes the URL your default target, in `~/.octwin/credentials.json`:
156
-
157
- ```jsonc
158
- {
159
- "default_url": "https://your-octwin.example.com", // set by the last `octwin login`
160
- "https://your-octwin.example.com": "oct_…" // token, keyed by platform url
161
- }
162
- ```
163
-
164
- Tenant and project need no setting at all — the **token carries its own tenant**, plus an optional
165
- project pin. `--tenant` / `--project` exist only as overrides (a multi-workspace human, or an
166
- unpinned token that must name a project).
167
-
168
- Each setting resolves **flag → environment variable → saved login**:
169
-
170
- | Setting | Flag | Env var | Saved login |
171
- | --- | --- | --- | --- |
172
- | Platform URL | `--url` | `PACK_PLATFORM_URL` | `default_url` |
173
- | Deploy token | `--token` | `PACK_TOKEN` | token for that URL |
174
- | Tenant slug *(override)* | `--tenant` | `PACK_TENANT` | — *(from the token)* |
175
- | Project slug *(override)* | `--project` | `PACK_PROJECT` | — *(from the token's pin)* |
176
-
177
- For **CI**, skip `login` entirely and pass `PACK_PLATFORM_URL` + `PACK_TOKEN` as environment
178
- variables (add `PACK_PROJECT` only if the token isn't pinned).
179
-
180
- ## Authentication
181
-
182
- You authenticate with a tenant-scoped **deploy token** (prefixed `oct_…`) — not a password and not
183
- an operator token. Generate it in the Octwin console (**your workspace → API tokens → Generate**).
184
- It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your tenant but cannot
185
- manage members, billing, or other tenants, and it is revocable at any time.
186
-
187
- Add the optional **`media:generate`** scope to let a `--seed` deploy AI-generate seed images
188
- (for a demo record field like `photo: "generate:<prompt>"`); without it, such fields are seeded as
189
- text only.
190
-
191
- ## What a pack may contain
192
-
193
- A pack is **pure declarative data** — `.yaml` / `.yml` / `.md` / `.sql` / `.json` only. Executable
194
- code (`.ts`/`.js`), HTTP routes, DB clients, and custom primitives are **not** allowed (this is what
195
- makes an external pack safe to run on a shared platform; the server enforces it on deploy). For
196
- domain records, use Octwin's first-class storage modules — **XRM** (records with stage pipelines),
197
- **catalog** (products), or **casework** (tickets) declared in `xrm.yaml` and `worklist.yaml`, so a
198
- pack needs **no database of its own**. (Casework rides `worklist.yaml`'s `work.<entity>` block;
199
- there is no ~~`cases.yaml`~~ grammar. `octwin platform-kb pull` ships the authoritative list of
200
- declaration files read its `INDEX.md` rather than this paragraph.)
201
-
202
- ## Links
203
-
204
- - **npm:** <https://www.npmjs.com/package/octwin-cli>
205
- - **Command help:** `octwin help` (each subcommand also answers `--help`)
206
- - **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
207
-
208
- ## License
209
-
210
- [MIT](./LICENSE) © CEQUENS
1
+ # octwin-cli
2
+
3
+ > The **Octwin** external-pack developer CLI — scaffold, validate, deploy, and manage pure-YAML packs on your own tenant. By **CEQUENS**.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/octwin-cli.svg)](https://www.npmjs.com/package/octwin-cli)
6
+ [![license](https://img.shields.io/npm/l/octwin-cli.svg)](https://www.npmjs.com/package/octwin-cli)
7
+ [![node](https://img.shields.io/node/v/octwin-cli.svg)](https://nodejs.org)
8
+
9
+ Octwin is a pack-pluggable conversational-agent platform for WhatsApp and web. A **pack** is a
10
+ self-contained bot domain — its agent, conversation flows, prompts, and data model — declared
11
+ entirely in YAML. `octwin-cli` lets you build a **pure-YAML pack** in your own repo and deploy it
12
+ to a running Octwin platform to test live on your own tenant: no platform checkout, no build step,
13
+ and nothing untrusted to run (a pure-YAML pack is declarative data, so the platform can safely run
14
+ it alongside other tenants).
15
+
16
+ > The npm package is **`octwin-cli`**; the command it installs is **`octwin`**.
17
+
18
+ ## Requirements
19
+
20
+ - **Node.js ≥ 20**
21
+ - Access to an **Octwin platform** (its base URL), a **tenant** (your workspace) on it, and a
22
+ **deploy token** (generated in the Octwin console — see [Authentication](#authentication)).
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ # zero-install — always the latest version
28
+ npx octwin-cli@latest <command>
29
+
30
+ # …or install the `octwin` command globally
31
+ npm install -g octwin-cli
32
+ octwin <command>
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ```bash
38
+ # 1. Scaffold a standalone pure-YAML pack (this is your repo)
39
+ octwin init ./my-pack --id my-pack --description "My business bot"
40
+ cd ./my-pack
41
+ git init && git add -A && git commit -m "init pack"
42
+
43
+ # 2. Author it — edit manifest.yaml, flows/tools/main.flow.yaml (+ its locale),
44
+ # and prompts/identity.md. Everything is pure YAML.
45
+
46
+ # 3. Point it at your platform — one command, no config file
47
+ # (Octwin console → your workspace → API tokens → Generate)
48
+ octwin login --url https://your-octwin.example.com --token oct_…
49
+
50
+ # 4. Validate → deploy → confirm it's live
51
+ octwin validate
52
+ octwin deploy --seed # --seed also loads any demo data the pack declares
53
+ octwin status # "✓ live and current" once it's warm
54
+
55
+ # 5. Chat with it on your tenant (web widget / console test page). Edit and
56
+ # `octwin deploy` again — a redeploy hot-loads with no restart.
57
+ ```
58
+
59
+ ## Commands
60
+
61
+ | Command | What it does |
62
+ | --- | --- |
63
+ | `octwin init <dir>` | Scaffold a new pure-YAML pack into `<dir>` (writes a starter `manifest.yaml`, flow, prompt, `.gitignore` and `README.md` — pack content only). Options: `--id`, `--description`, `--display-name`. |
64
+ | `octwin validate` | Check the pack locally (structure + pure-YAML rules). `--remote` additionally runs the platform's **flow lint** — conventions the schema can't express, like an `assign:` whose value is a quoted literal, or a `$t()` key with no namespace. The lint needs a template-expanded `FlowDef`, which only the server builds, so it is a `--remote`-only check; a local ✓ does not cover it. |
65
+ | `octwin login` | Save a deploy token for a platform URL **and make that URL the default target** (both stored in `~/.octwin/credentials.json`). `--url`, `--token`. |
66
+ | `octwin whoami` | Verify the saved/passed token is valid for a tenant. `--url`, `--tenant`. |
67
+ | `octwin deploy` | Upload + install the pack onto your tenant's project. `--seed` also runs the pack's demo seed. Reports the **marketplace-listing verdict** when the manifest carries `listing.public: true`. |
68
+ | `octwin status` | Report what the platform has live for this pack — installed version, the **content sha** the instance loaded vs. the one the catalog holds (a redeploy of the *same* version changes it), its flows, and whether it is live on the public marketplace. |
69
+ | `octwin pull <packId>` | Write a **deployed** pack's source back to disk — the inverse of `deploy`, and how a pack pushed from one machine is recovered. Defaults to the version installed on the target project; `--version` overrides, `--dir` defaults to `./<packId>`, a non-empty dir needs `--force`. You may pull a pack your tenant **owns**. |
70
+ | `octwin chat "msg"` | Drive a turn through the dev web channel and print **every render with its tap ids**. `--as <handle>` picks the test user; `--tap "<tap-id>"` presses a rendered button/list row; `--json` dumps the raw envelopes. |
71
+ | `octwin logs` | List recent conversations (handle, status, last activity; `--as` filters), or show one conversation's full event timeline — including what each turn rendered. `--json` for raw payloads. |
72
+ | `octwin records` | Inspect the pack's XRM data (needs a `records:read` token). No args = list entities. |
73
+ | `octwin cases` | Inspect casework (support tickets): the inbox, one case + its timeline and decisions, or `--queues` for queue keys + open counts. |
74
+ | `octwin projects` | The `--project <slug>` values this token can name, with the plan's project cap. `--archived` includes archived ones. A `pack:deploy` token reaches it — it names a project in every other command, so this turns "guess the slug" into "read the list". |
75
+ | `octwin agents` | The agent roster with each agent's **effective** model / history window and **which layer set it** (project override → platform default → pack manifest) — an operator platform default can override what your manifest declares. `--prompt` prints the exact system prompt the LLM sees. Needs `agents:read`. |
76
+ | `octwin orders` | The orders a conversation produced. No args = the list; with a `reference_id` = line items, the subtotal/tax/shipping/discount/total breakdown, `payment_ref`, and the allowed transitions. Needs `orders:read` + the `orders` plan feature. |
77
+ | `octwin analytics` | Stage-by-stage conversion for **any** entity declared with a `pipeline:` (`--overview` / `--milestones` / `--trends` / `--cost`; `--stage <id>` lists the records currently at a stage). Needs `records:read`. |
78
+ | `octwin catalog` | Commerce products with price / availability / stock, plus the WhatsApp catalog binding. `--readiness` runs the Meta Graph checklist. Needs `catalog:read` + the `catalog` plan feature. |
79
+ | `octwin scheduling` | The scheduling engine's state, or `--slots <resourceRecordId>` for the slots one bookable resource actually computes — how you verify the availability rules `deploy --seed` created. Needs `scheduling:read`. |
80
+ | `octwin media generate "<prompt>"` | AI-generate an image, store it as a public asset, and print its `MEDIA-` handle + serve URL. `--out` downloads the bytes (WhatsApp renders only `.png`/`.jpg`); `--size`; `--json`. Pairs with `octwin chat --media` to drive media-collect flows. Needs `media:generate`. |
81
+ | `octwin platform-kb pull` | Pull the platform's capability reference into `.octwin/platform-kb/` for the **`octwin-pack`** Claude Code authoring plugin: guides as markdown, plus **one JSON file per capability** (`primitives/record_list.json`, `render-intents/carousel.json`, `declarations/xrm.json`, …) and an **`INDEX.md`** mapping every entry to its file — so a lookup is a small targeted read, not a whole catalog. |
82
+ | `octwin test` | Alias for `octwin validate --remote` — the platform's full manifest + flow-DSL check. |
83
+ | `octwin feedback` | Submit this pack's `FEEDBACK.md` to the platform team, with the pack version, your CLI version and the `content_hash` of the capability reference you pulled — the two facts that separate a real platform gap from something already fixed or a stale KB. |
84
+ | `octwin help` | Show usage. Every subcommand also answers `--help`. |
85
+
86
+ ### Writing, not just reading
87
+
88
+ Every read command above has a write half behind a **leading verb**, so `octwin cases` reads and
89
+ `octwin cases note <id> "…"` writes. Each needs the matching `:write` scope — `octwin <cmd> --help`
90
+ lists the verbs and their exact flags.
91
+
92
+ | Command | Verbs |
93
+ | --- | --- |
94
+ | `octwin records` | `create <entity> --set k=v` · `patch <id> --entity <e>` · `stage <id> --to <s>` · `note <id> "…"` · `tasks` · `task complete <id>` |
95
+ | `octwin cases` | `assign <id> --to user:<uuid>\|none` · `note` · `transition <id> --to <status>` · `decide <id> --action <a> [--dry-run]` |
96
+ | `octwin orders` | `transition <ref> --to <status>` · `refund <ref> --force` |
97
+ | `octwin catalog` | `availability <sku> --to "in stock"` · `stock <sku> [--set-on-hand n]` |
98
+ | `octwin scheduling` | `rules --resource <id>` · `rule add\|rm` · `exception add\|rm` |
99
+ | `octwin agents` | `set <ref> [--model m] [--enable-tool t] [--disable-tool t]` |
100
+
101
+ `--set k=v` coerces JSON scalars (`--set rating=4.5` sends a number); `--fields-json` takes anything
102
+ nested. Destructive verbs want `--force` rather than a prompt — the CLI is non-interactive by
103
+ design. `cases decide --dry-run` previews the customer-facing copy and the resulting status without
104
+ committing, and needs only `cases:read`.
105
+
106
+ Every command that talks to the platform accepts `--dir <path>` (the pack directory; defaults to
107
+ the current directory) plus the target overrides `--url` / `--tenant` / `--project` / `--token`.
108
+
109
+ ### Debugging a live conversation
110
+
111
+ The platform keeps **one open conversation per `--as` handle**, so consecutive `octwin chat` calls
112
+ with the same handle **continue the same conversation** — agent memory, suspended flows, and all:
113
+
114
+ ```bash
115
+ octwin chat "hi" --as tester1 # turn 1 — prints the menu with each row's tap id
116
+ octwin chat --tap "t:invoke:my-flow:x=1" --as tester1 # turn 2 — press a rendered row
117
+ octwin chat "3 bedrooms" --as tester1 # turn 3 — free text into the running flow
118
+ octwin logs --as tester1 # find the conversation, then:
119
+ octwin logs <conversationId> # the full timeline (taps, renders, tool events)
120
+ ```
121
+
122
+ ### Reading back the state your pack created
123
+
124
+ `chat`/`logs` show what the bot *said*; these show what it *did*. A 401/403 on any of them names the
125
+ token scope (and plan feature) that command needs, so you can mint a wider token instead of guessing.
126
+
127
+ ```bash
128
+ octwin agents # effective model per agent + which layer set it
129
+ octwin agents my-pack::assistant --prompt # the exact system prompt the LLM sees
130
+ octwin orders # then: octwin orders <reference_id>
131
+ octwin analytics # then: octwin analytics <entity> [--stage <id>]
132
+ octwin catalog # products + stock + the WhatsApp binding
133
+ octwin scheduling --slots <resourceRecordId> # the slots your availability rules compute
134
+ ```
135
+
136
+ Three things worth knowing when you read the output:
137
+
138
+ - **A `pending` payment is usually correct.** The forward payment lifecycle is **webhook-owned**, and a
139
+ workspace with no gateway runs the credential-free `manual` driver — so `payment_request` takes its
140
+ `empty` port and your flow should confirm pay-on-delivery. `octwin orders <ref>` says this inline.
141
+ - **Your declared model may not be the one running.** An operator platform default overrides the pack
142
+ manifest; `octwin agents` is where that becomes visible.
143
+ - **An empty funnel has two causes** — the entity has no `pipeline:`, or your token's role has no `view`
144
+ grant on `record.<entity>`. The command prints both rather than a bare "no data".
145
+
146
+ ## Configuration
147
+
148
+ **`octwin login` is the configuration.** There is no config file in your pack — a pack directory
149
+ holds pack content and nothing else, so the same repo deploys from any machine:
150
+
151
+ ```bash
152
+ octwin login --url https://your-octwin.example.com --token oct_…
153
+ ```
154
+
155
+ That stores the token *and* makes the URL your default target, in `~/.octwin/credentials.json`:
156
+
157
+ ```jsonc
158
+ {
159
+ "default_url": "https://your-octwin.example.com", // set by the last `octwin login`
160
+ "https://your-octwin.example.com": "oct_…" // token, keyed by platform url
161
+ }
162
+ ```
163
+
164
+ Tenant and project need no setting at all — the **token carries its own tenant**, plus an optional
165
+ project pin. `--tenant` / `--project` exist only as overrides (a multi-workspace human, or an
166
+ unpinned token that must name a project).
167
+
168
+ Each setting resolves **flag → environment variable → saved login**:
169
+
170
+ | Setting | Flag | Env var | Saved login |
171
+ | --- | --- | --- | --- |
172
+ | Platform URL | `--url` | `PACK_PLATFORM_URL` | `default_url` |
173
+ | Deploy token | `--token` | `PACK_TOKEN` | token for that URL |
174
+ | Tenant slug *(override)* | `--tenant` | `PACK_TENANT` | — *(from the token)* |
175
+ | Project slug *(override)* | `--project` | `PACK_PROJECT` | — *(from the token's pin)* |
176
+
177
+ For **CI**, skip `login` entirely and pass `PACK_PLATFORM_URL` + `PACK_TOKEN` as environment
178
+ variables (add `PACK_PROJECT` only if the token isn't pinned).
179
+
180
+ ## Authentication
181
+
182
+ You authenticate with a tenant-scoped **deploy token** (prefixed `oct_…`) — not a password and not
183
+ an operator token. Generate it in the Octwin console (**your workspace → API tokens → Generate**).
184
+ It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your tenant but cannot
185
+ manage members, billing, or other tenants, and it is revocable at any time.
186
+
187
+ Add the optional **`media:generate`** scope to let a `--seed` deploy AI-generate seed images
188
+ (for a demo record field like `photo: "generate:<prompt>"`); without it, such fields are seeded as
189
+ text only.
190
+
191
+ ## What a pack may contain
192
+
193
+ A pack is **pure declarative data** — `.yaml` / `.yml` / `.md` / `.json` only. Executable
194
+ code (`.ts`/`.js`), HTTP routes, DB clients, custom primitives and **`.sql`** are **not** allowed
195
+ (this is what makes an external pack safe to run on a shared platform; the server enforces it on
196
+ deploy, and so does `octwin validate`). For
197
+ domain records, use Octwin's first-class storage modules **XRM** (records with stage pipelines),
198
+ **catalog** (products), or **casework** (tickets) declared in `xrm.yaml` and `worklist.yaml`, so a
199
+ pack needs **no database of its own**. (Casework rides `worklist.yaml`'s `work.<entity>` block;
200
+ there is no ~~`cases.yaml`~~ grammar. `octwin platform-kb pull` ships the authoritative list of
201
+ declaration files — read its `INDEX.md` rather than this paragraph.)
202
+
203
+ ## Links
204
+
205
+ - **npm:** <https://www.npmjs.com/package/octwin-cli>
206
+ - **Command help:** `octwin help` (each subcommand also answers `--help`)
207
+ - **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
208
+
209
+ ## License
210
+
211
+ [MIT](./LICENSE) © CEQUENS
package/dist/index.js CHANGED
@@ -197,9 +197,15 @@ const VERB_REQUIREMENTS = {
197
197
  'scheduling rule': { scope: 'scheduling:write' },
198
198
  'scheduling exception': { scope: 'scheduling:write' },
199
199
  'agents set': { scope: 'agents:write' },
200
+ // Creating and destroying a project are the same scope as editing one. Worth
201
+ // spelling out because the natural token for the deploy loop is `pack:deploy`,
202
+ // which does NOT confer this — that 403 is otherwise baffling.
203
+ 'projects create': { scope: 'projects:write' },
204
+ 'projects rm': { scope: 'projects:write' },
200
205
  };
201
206
  const COMMAND_REQUIREMENTS = {
202
207
  deploy: { scope: 'pack:deploy' },
208
+ seed: { scope: 'pack:deploy' },
203
209
  validate: { scope: 'pack:deploy' },
204
210
  status: { scope: 'pack:deploy' },
205
211
  test: { scope: 'pack:deploy' },
@@ -587,7 +593,8 @@ function commandTouchesPlatform(command, flags) {
587
593
  case 'analytics':
588
594
  case 'catalog':
589
595
  case 'scheduling':
590
- case 'projects': return true;
596
+ case 'projects':
597
+ case 'seed': return true;
591
598
  default: return false;
592
599
  }
593
600
  }
@@ -730,9 +737,25 @@ async function cmdValidate(flags) {
730
737
  json = text;
731
738
  }
732
739
  if (!res.ok) {
733
- // 404 older platform without the endpoint.
734
- if (res.status === 404)
735
- die('this platform has no /packs/validate endpoint yet (older version) deploy runs the full check');
740
+ // A 404 here is AMBIGUOUS and must not be collapsed. The route resolves the
741
+ // tenant and the project BEFORE it validates anything, so a 404 is usually an
742
+ // unknown `--tenant`/`--project` and a token's project PIN answers 404 by
743
+ // design (an out-of-pin project is deliberately indistinguishable from one that
744
+ // does not exist). Reporting all of those as "older platform" sends the author
745
+ // hunting for a version mismatch that does not exist.
746
+ //
747
+ // The two are told apart by the BODY, not the status: the platform has no
748
+ // custom not-found handler, so a missing route is Fastify's default
749
+ // `{ statusCode, error: 'Not Found', message: 'Route … not found' }`, whereas
750
+ // `resolveTenantOr404`/`resolveProjectOr404` send a bare `{ error: "<what> not
751
+ // found" }`. The server's own message already names the slug it tried, so the
752
+ // hint carries the fix rather than repeating the target.
753
+ if (res.status === 404) {
754
+ const routeMissing = typeof json !== 'object' || json === null || json.error === 'Not Found';
755
+ if (routeMissing)
756
+ die('this platform has no /packs/validate endpoint yet (older version) — deploy runs the full check');
757
+ die(`remote validate${errDetail(json)} — check --tenant/--project (or PACK_TENANT/PACK_PROJECT); \`octwin projects\` lists what this token can reach`);
758
+ }
736
759
  console.error(`✗ remote validate failed (HTTP ${res.status})`);
737
760
  printAuthHint(res.status, url);
738
761
  console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
@@ -1015,6 +1038,71 @@ function printDeploySuccess(id, version, t, r) {
1015
1038
  printPublicListing(r?.public_listing, r?.public_review_note);
1016
1039
  console.log(`\nChat with it: octwin chat "hi" --as tester (or the web widget / console test page).`);
1017
1040
  }
1041
+ /**
1042
+ * `octwin seed [--pack <id>]` — apply the pack's demo/reference data to the project it
1043
+ * is installed on, without redeploying.
1044
+ *
1045
+ * Exists because seeding used to be reachable only as `deploy --seed`: the platform's
1046
+ * seed endpoint was keyed on an install id, guarded `requirePlatformAdmin`, and carried
1047
+ * no tenant/project segments — so the `/api/self/**` rewrite could not reach it and a
1048
+ * `pack:deploy` token never could. Re-seeding meant a full redeploy, or asking an
1049
+ * operator.
1050
+ *
1051
+ * Reuses `readDeployProgress` verbatim: the platform emits ONE seed-progress vocabulary
1052
+ * now (`stage:'seed'` with a `kind`), so a second reader would only be a second thing to
1053
+ * keep in step.
1054
+ */
1055
+ async function cmdSeed(flags) {
1056
+ const t = resolveTarget(flags);
1057
+ const { url } = t;
1058
+ const packId = typeof flags.pack === 'string' ? flags.pack : undefined;
1059
+ console.log(`→ Seeding ${packId ?? 'the installed pack'} on ${targetLabel(t)} …`);
1060
+ const res = await fetchOrDie(`${url}/api/self/p/packs/seed`, {
1061
+ method: 'POST',
1062
+ headers: { 'content-type': 'application/json', accept: 'text/event-stream', ...authHeaders(t) },
1063
+ body: JSON.stringify(packId ? { pack_id: packId } : {}),
1064
+ }, 'seed');
1065
+ if (res.ok && (res.headers.get('content-type') ?? '').includes('text/event-stream') && res.body) {
1066
+ const { terminal: final, stepErrors } = await readDeployProgress(res.body);
1067
+ if (!final || final.stage === 'error')
1068
+ die(`seed failed${final?.message ? `: ${final.message}` : ' (stream ended early)'}`);
1069
+ console.log(`
1070
+ ✓ ${final.message ?? 'seed complete'}`);
1071
+ printSeedCounts(final.result?.seeded);
1072
+ if (stepErrors.length) {
1073
+ // A kind failed but the rest ran — the reconcile softens each step. Say which,
1074
+ // and exit non-zero so a scripted `seed && chat` doesn't read as clean.
1075
+ console.error(`
1076
+ ⚠ ${stepErrors.length} step${stepErrors.length === 1 ? '' : 's'} failed — data may be incomplete:`);
1077
+ for (const e of stepErrors)
1078
+ console.error(` • ${e}`);
1079
+ process.exit(1);
1080
+ }
1081
+ return;
1082
+ }
1083
+ const text = await res.text();
1084
+ let json;
1085
+ try {
1086
+ json = JSON.parse(text);
1087
+ }
1088
+ catch {
1089
+ json = text;
1090
+ }
1091
+ if (!res.ok) {
1092
+ console.error(`✗ seed failed (HTTP ${res.status})${errDetail(json)}`);
1093
+ printAuthHint(res.status, url);
1094
+ process.exit(1);
1095
+ }
1096
+ console.log('✓ seed complete');
1097
+ printSeedCounts(json?.seeded);
1098
+ }
1099
+ /** Per-kind counts, one line each. Prints nothing when the pack declared nothing. */
1100
+ function printSeedCounts(seeded) {
1101
+ for (const [kind, counts] of Object.entries(seeded ?? {})) {
1102
+ const detail = Object.entries(counts).filter(([, v]) => v > 0).map(([k, v]) => `${v} ${k}`).join(' · ');
1103
+ console.log(` ${kind.padEnd(11)} ${detail || '—'}`);
1104
+ }
1105
+ }
1018
1106
  async function cmdDeploy(flags) {
1019
1107
  const packDir = resolve(flags.dir ?? '.');
1020
1108
  const t = resolveTarget(flags);
@@ -2523,6 +2611,10 @@ async function cmdAgentsWrite(flags) {
2523
2611
  * (`/api/self/t/`), unlike `agents` — the list is a property of the workspace.
2524
2612
  */
2525
2613
  async function cmdProjects(flags) {
2614
+ if (flags._[0] === 'create')
2615
+ return cmdProjectsCreate(flags);
2616
+ if (flags._[0] === 'rm')
2617
+ return cmdProjectsRm(flags);
2526
2618
  const t = resolveTarget(flags);
2527
2619
  const { url } = t;
2528
2620
  const asJson = flags.json === true;
@@ -2558,6 +2650,128 @@ async function cmdProjects(flags) {
2558
2650
  console.log('\nUse one as: octwin deploy --project <slug>');
2559
2651
  if (!archived)
2560
2652
  console.log('Archived too: octwin projects --archived');
2653
+ console.log('New one: octwin projects create "<name>"');
2654
+ }
2655
+ /**
2656
+ * `octwin projects create "<name>" [--slug <slug>] [--pack <packId>]`
2657
+ *
2658
+ * The missing half of the deploy loop. `octwin deploy` has always needed a project
2659
+ * that already exists, and the CLI could only LIST them — so standing up a throwaway
2660
+ * end-to-end deployment meant opening the console or asking an operator. With this,
2661
+ * a full disposable environment is two commands:
2662
+ *
2663
+ * octwin projects create "Scratch" # → slug `scratch`
2664
+ * octwin deploy --project scratch --seed # publish + install + demo data
2665
+ * octwin chat "hi" --project scratch # talk to it
2666
+ * octwin projects rm scratch --yes # throw it away
2667
+ *
2668
+ * A demo is deliberately NOT a special kind of thing — it is an ordinary project in
2669
+ * the developer's own workspace, so it inherits their plan, entitlements, RBAC and
2670
+ * teardown with no bespoke lifecycle to keep honest.
2671
+ *
2672
+ * `packs: []` is the default because the very next step is normally `octwin deploy`,
2673
+ * which publishes the working tree AND installs it. `--pack` is for an ALREADY
2674
+ * published pack (it resolves through `pack_registry` and fails fast if absent).
2675
+ */
2676
+ async function cmdProjectsCreate(flags) {
2677
+ // Argument check BEFORE `resolveTarget`, so a missing name reports the usage line
2678
+ // rather than "no platform url" — an argument mistake must not be masked by a
2679
+ // config one the author may not even have.
2680
+ const name = flags._[1];
2681
+ if (!name)
2682
+ die('usage: octwin projects create "<name>" [--slug <slug>] [--pack <packId>]');
2683
+ const t = resolveTarget(flags);
2684
+ const { url } = t;
2685
+ // The Project URL is DERIVED from the name and uniquified server-side unless the
2686
+ // caller pins one — same contract the console's create form uses, so the two
2687
+ // cannot disagree about what slug a given name produces.
2688
+ const body = { name, packs: flags.pack ? [flags.pack] : [] };
2689
+ if (typeof flags.slug === 'string')
2690
+ body.slug = flags.slug;
2691
+ console.log(`→ Creating project "${name}" in ${targetLabel(t)} …`);
2692
+ const { status, json } = await apiSend('POST', `${url}/api/self/t/projects`, body, t);
2693
+ // 402 is the plan cap, and it is the ONE failure here with a non-obvious fix, so it
2694
+ // gets the server's own sentence rather than a generic write failure.
2695
+ if (status === 402)
2696
+ die(`${json?.error ?? 'project limit reached'} — free the slot with \`octwin projects rm <slug> --yes\`, or upgrade the plan.`);
2697
+ if (status !== 200 && status !== 201)
2698
+ writeFail(`create project "${name}"`, status, json, url);
2699
+ if (flags.json === true) {
2700
+ console.log(JSON.stringify(json, null, 2));
2701
+ return;
2702
+ }
2703
+ const slug = json?.slug ?? flags.slug ?? '(unknown)';
2704
+ const installed = (json?.installed_packs ?? []);
2705
+ console.log(`✓ Project created — ${slug}`);
2706
+ for (const p of installed)
2707
+ console.log(` installed ${p.pack_id}@${p.version}`);
2708
+ console.log('\nNext:');
2709
+ console.log(` octwin deploy --project ${slug} --seed`);
2710
+ console.log(` octwin chat "hi" --project ${slug}`);
2711
+ }
2712
+ /**
2713
+ * `octwin projects rm <slug> --yes`
2714
+ *
2715
+ * HARD delete — the row and everything the FK graph cascades from it (conversations,
2716
+ * contacts, records, installs, webhooks). Not the archive verb; there is no undo.
2717
+ *
2718
+ * `--yes` is required rather than prompted because the CLI is non-interactive by
2719
+ * design (it runs under `npx`, in scripts and in CI, where a prompt reads EOF and a
2720
+ * "safe" default would be a lie). Without it this prints the same impact preview the
2721
+ * console's confirm dialog shows — derived from `pg_constraint`, not a hand-written
2722
+ * list — and stops. That makes the dry run the DEFAULT, which is the right way round
2723
+ * for an irreversible verb.
2724
+ */
2725
+ async function cmdProjectsRm(flags) {
2726
+ const slug = flags._[1];
2727
+ if (!slug)
2728
+ die('usage: octwin projects rm <slug> --yes (omit --yes to preview what it destroys)');
2729
+ const t = resolveTarget(flags);
2730
+ const { url } = t;
2731
+ const preview = await apiGet(`${url}/api/self/t/projects/${encodeURIComponent(slug)}/preview-hard-delete`, t);
2732
+ if (preview.status === 404)
2733
+ die(`no project '${slug}' in ${targetLabel(t)} — \`octwin projects\` lists them`);
2734
+ if (preview.status !== 200)
2735
+ die(`could not preview the delete (HTTP ${preview.status})${errDetail(preview.json)}${authFailureDetail(preview.status, url)}`);
2736
+ if (flags.json === true && flags.yes !== true) {
2737
+ console.log(JSON.stringify(preview.json, null, 2));
2738
+ return;
2739
+ }
2740
+ // Shapes come from `HardDeletePreview` (routes/_hard-delete-preview.ts) — the same
2741
+ // payload the console's confirm dialog renders, so the two can't disagree about
2742
+ // what a delete costs.
2743
+ const tables = (preview.json?.tables ?? []);
2744
+ const hits = tables.filter(r => r.count > 0);
2745
+ const totals = preview.json?.totals ?? {};
2746
+ console.log(`Deleting project ${slug} from ${targetLabel(t)} destroys:`);
2747
+ if (hits.length === 0)
2748
+ console.log(' (nothing — the project has no rows yet)');
2749
+ for (const r of hits) {
2750
+ const mark = r.disposition === 'cascade' ? '' : ` [${r.disposition}]`;
2751
+ console.log(` ${r.count}${r.capped ? '+' : ''}\t${r.schema}.${r.table}${mark}`);
2752
+ }
2753
+ if (hits.length > 0) {
2754
+ console.log(` — ${totals.rows_deleted}${totals.rows_deleted_capped ? '+' : ''} rows across ${totals.tables_affected} tables`);
2755
+ }
2756
+ // Side effects no FK walk can see (storage blobs, agent memory, Meta registrations).
2757
+ // Anything not `deleted` is what SURVIVES the delete — the part worth reading.
2758
+ const residue = (preview.json?.residue ?? []);
2759
+ const surviving = residue.filter(r => r.disposition !== 'deleted');
2760
+ if (surviving.length > 0) {
2761
+ console.log('\nNot removed by the cascade:');
2762
+ for (const r of surviving)
2763
+ console.log(` [${r.disposition}] ${r.label}${r.count != null ? ` (${r.count})` : ''} — ${r.detail}`);
2764
+ }
2765
+ if (totals.blocked > 0)
2766
+ console.log(`\n! ${totals.blocked} table(s) would BLOCK this delete.`);
2767
+ if (flags.yes !== true) {
2768
+ console.log('\nNothing was deleted. Re-run with --yes to go through with it.');
2769
+ return;
2770
+ }
2771
+ const { status, json } = await apiSend('DELETE', `${url}/api/self/t/projects/${encodeURIComponent(slug)}/hard`, undefined, t);
2772
+ if (status !== 200 && status !== 204)
2773
+ writeFail(`delete project '${slug}'`, status, json, url);
2774
+ console.log(`\n✓ Deleted ${slug}.`);
2561
2775
  }
2562
2776
  async function cmdAgents(flags) {
2563
2777
  if (flags._[0] === 'set')
@@ -3287,10 +3501,32 @@ const COMMAND_HELP = {
3287
3501
  projects: `octwin projects [--archived] [--json]
3288
3502
  List the workspace's projects — the slugs every --project flag takes, with the
3289
3503
  plan's project cap. --archived includes archived ones. A pack:deploy token
3290
- reaches this (it names a project in every other command).`,
3504
+ reaches this (it names a project in every other command).
3505
+
3506
+ octwin projects create "<name>" [--slug <slug>] [--pack <packId>]
3507
+ Create a project. The URL slug is derived from the name unless --slug pins one.
3508
+ --pack installs an ALREADY-published pack; the usual next step is instead
3509
+ \`octwin deploy --project <slug>\`, which publishes this working tree and installs it.
3510
+
3511
+ octwin projects rm <slug> [--yes]
3512
+ HARD delete — the project and everything cascading from it (conversations,
3513
+ contacts, records, installs). No undo, and not the same as archiving.
3514
+ WITHOUT --yes it only previews what would be destroyed, so the dry run is the
3515
+ default. Together these make a disposable end-to-end environment:
3516
+ octwin projects create "Scratch" && octwin deploy --project scratch --seed
3517
+ octwin chat "hi" --project scratch
3518
+ octwin projects rm scratch --yes
3519
+ Both verbs need the \`projects:write\` scope — a pack:deploy token does NOT confer it.`,
3291
3520
  deploy: `octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
3292
3521
  Upload the pack bundle, validate server-side, install onto the project.
3293
3522
  --seed additionally applies the pack's demo seed (streams progress).`,
3523
+ seed: `octwin seed [--pack <packId>]
3524
+ Apply the pack's demo/reference data to the project it is installed on, without
3525
+ redeploying: xrm \`demo:\` records + scheduling availability, the commerce catalog,
3526
+ and the demo operator topology. Reports what each kind produced.
3527
+ Idempotent and safe to re-run — records upsert, and existing media is REUSED rather
3528
+ than regenerated, so a second pass costs nothing. --pack is only needed when a
3529
+ project somehow runs more than one.`,
3294
3530
  status: `octwin status [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>]
3295
3531
  Show installed vs live version + the flow list for this pack.`,
3296
3532
  records: `octwin records [entity] [id] [--limit 50] [--offset n]
@@ -3498,6 +3734,9 @@ async function main() {
3498
3734
  case 'media':
3499
3735
  await cmdMedia(flags);
3500
3736
  break;
3737
+ case 'seed':
3738
+ await cmdSeed(flags);
3739
+ break;
3501
3740
  case 'projects':
3502
3741
  await cmdProjects(flags);
3503
3742
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.1",
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": {