octwin-cli 0.1.4 → 0.1.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 CEQUENS
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CEQUENS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,133 +1,133 @@
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: edit pack.json, then log in with a deploy token
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, and `pack.json`). Options: `--id`, `--description`, `--display-name`. |
64
- | `octwin validate` | Check the pack locally (structure + pure-YAML rules). The platform re-validates the full manifest/flow schema on deploy. |
65
- | `octwin login` | Save a deploy token for a platform URL (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. |
68
- | `octwin status` | Report what the platform has live for this pack — installed vs. loaded version, and its flows. |
69
- | `octwin platform-kb pull` | Pull the platform's capability reference (built-ins, primitives, render intents, flow-DSL — as markdown + JSON) into `.octwin/platform-kb/`, for the **`octwin-pack`** Claude Code authoring plugin to consult. |
70
- | `octwin test` | Validate locally and print how to try the pack on your tenant. |
71
- | `octwin help` | Show usage. |
72
-
73
- Every command that talks to the platform accepts `--dir <path>` (the pack directory; defaults to
74
- the current directory) plus the target overrides `--url` / `--tenant` / `--project` / `--token`.
75
-
76
- ## Configuration
77
-
78
- The deploy target has four settings. Three live in a committed **`pack.json`** at the root of your
79
- pack; the **token is a secret** and is kept out of `pack.json` (set it with `octwin login`).
80
-
81
- ```jsonc
82
- // pack.json — created by `octwin init`
83
- {
84
- "platform_url": "https://your-octwin.example.com", // your Octwin platform
85
- "tenant": "my-tenant-slug", // your workspace (required)
86
- "project": "main" // defaults to "main"
87
- }
88
- ```
89
-
90
- ```bash
91
- octwin login --url https://your-octwin.example.com --token oct_… # saves the token once
92
- ```
93
-
94
- Each setting resolves in this order — **flag → environment variable → `pack.json` → default**:
95
-
96
- | Setting | Flag | Env var | `pack.json` key |
97
- | --- | --- | --- | --- |
98
- | Platform URL | `--url` | `PACK_PLATFORM_URL` | `platform_url` |
99
- | Tenant slug | `--tenant` | `PACK_TENANT` | `tenant` |
100
- | Project slug | `--project` | `PACK_PROJECT` | `project` (default `main`) |
101
- | Deploy token | `--token` | `PACK_TOKEN` | — *(use `octwin login`)* |
102
-
103
- For **CI**, skip `pack.json`/`login` entirely and pass `PACK_PLATFORM_URL`, `PACK_TENANT`,
104
- `PACK_PROJECT`, and `PACK_TOKEN` as environment variables.
105
-
106
- ## Authentication
107
-
108
- You authenticate with a tenant-scoped **deploy token** (prefixed `oct_…`) — not a password and not
109
- an operator token. Generate it in the Octwin console (**your workspace → API tokens → Generate**).
110
- It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your tenant but cannot
111
- manage members, billing, or other tenants, and it is revocable at any time.
112
-
113
- Add the optional **`media:generate`** scope to let a `--seed` deploy AI-generate seed images
114
- (for a demo record field like `photo: "generate:<prompt>"`); without it, such fields are seeded as
115
- text only.
116
-
117
- ## What a pack may contain
118
-
119
- A pack is **pure declarative data** — `.yaml` / `.yml` / `.md` / `.sql` / `.json` only. Executable
120
- code (`.ts`/`.js`), HTTP routes, DB clients, and custom primitives are **not** allowed (this is what
121
- makes an external pack safe to run on a shared platform; the server enforces it on deploy). For
122
- domain records, use Octwin's first-class storage modules — **XRM** (records with stage pipelines),
123
- **catalog** (products), or **casework** (tickets) — declared in `xrm.yaml` / `cases.yaml`, so a pack
124
- needs **no database of its own**.
125
-
126
- ## Links
127
-
128
- - **npm:** <https://www.npmjs.com/package/octwin-cli>
129
- - **Command help:** `octwin help`
130
-
131
- ## License
132
-
133
- [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: edit pack.json, then log in with a deploy token
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, and `pack.json`). Options: `--id`, `--description`, `--display-name`. |
64
+ | `octwin validate` | Check the pack locally (structure + pure-YAML rules). The platform re-validates the full manifest/flow schema on deploy. |
65
+ | `octwin login` | Save a deploy token for a platform URL (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. |
68
+ | `octwin status` | Report what the platform has live for this pack — installed vs. loaded version, and its flows. |
69
+ | `octwin platform-kb pull` | Pull the platform's capability reference (built-ins, primitives, render intents, flow-DSL — as markdown + JSON) into `.octwin/platform-kb/`, for the **`octwin-pack`** Claude Code authoring plugin to consult. |
70
+ | `octwin test` | Validate locally and print how to try the pack on your tenant. |
71
+ | `octwin help` | Show usage. |
72
+
73
+ Every command that talks to the platform accepts `--dir <path>` (the pack directory; defaults to
74
+ the current directory) plus the target overrides `--url` / `--tenant` / `--project` / `--token`.
75
+
76
+ ## Configuration
77
+
78
+ The deploy target has four settings. Three live in a committed **`pack.json`** at the root of your
79
+ pack; the **token is a secret** and is kept out of `pack.json` (set it with `octwin login`).
80
+
81
+ ```jsonc
82
+ // pack.json — created by `octwin init`
83
+ {
84
+ "platform_url": "https://your-octwin.example.com", // your Octwin platform
85
+ "tenant": "my-tenant-slug", // your workspace (required)
86
+ "project": "main" // defaults to "main"
87
+ }
88
+ ```
89
+
90
+ ```bash
91
+ octwin login --url https://your-octwin.example.com --token oct_… # saves the token once
92
+ ```
93
+
94
+ Each setting resolves in this order — **flag → environment variable → `pack.json` → default**:
95
+
96
+ | Setting | Flag | Env var | `pack.json` key |
97
+ | --- | --- | --- | --- |
98
+ | Platform URL | `--url` | `PACK_PLATFORM_URL` | `platform_url` |
99
+ | Tenant slug | `--tenant` | `PACK_TENANT` | `tenant` |
100
+ | Project slug | `--project` | `PACK_PROJECT` | `project` (default `main`) |
101
+ | Deploy token | `--token` | `PACK_TOKEN` | — *(use `octwin login`)* |
102
+
103
+ For **CI**, skip `pack.json`/`login` entirely and pass `PACK_PLATFORM_URL`, `PACK_TENANT`,
104
+ `PACK_PROJECT`, and `PACK_TOKEN` as environment variables.
105
+
106
+ ## Authentication
107
+
108
+ You authenticate with a tenant-scoped **deploy token** (prefixed `oct_…`) — not a password and not
109
+ an operator token. Generate it in the Octwin console (**your workspace → API tokens → Generate**).
110
+ It is **least-privilege** (scope `pack:deploy`): it can deploy packs to your tenant but cannot
111
+ manage members, billing, or other tenants, and it is revocable at any time.
112
+
113
+ Add the optional **`media:generate`** scope to let a `--seed` deploy AI-generate seed images
114
+ (for a demo record field like `photo: "generate:<prompt>"`); without it, such fields are seeded as
115
+ text only.
116
+
117
+ ## What a pack may contain
118
+
119
+ A pack is **pure declarative data** — `.yaml` / `.yml` / `.md` / `.sql` / `.json` only. Executable
120
+ code (`.ts`/`.js`), HTTP routes, DB clients, and custom primitives are **not** allowed (this is what
121
+ makes an external pack safe to run on a shared platform; the server enforces it on deploy). For
122
+ domain records, use Octwin's first-class storage modules — **XRM** (records with stage pipelines),
123
+ **catalog** (products), or **casework** (tickets) — declared in `xrm.yaml` / `cases.yaml`, so a pack
124
+ needs **no database of its own**.
125
+
126
+ ## Links
127
+
128
+ - **npm:** <https://www.npmjs.com/package/octwin-cli>
129
+ - **Command help:** `octwin help`
130
+
131
+ ## License
132
+
133
+ [MIT](./LICENSE) © CEQUENS
package/dist/index.js CHANGED
@@ -8,13 +8,16 @@
8
8
  * `npx octwin-cli <cmd>` (published as `octwin-cli`, command `octwin`).
9
9
  *
10
10
  * octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
11
- * octwin validate [--dir .]
11
+ * octwin validate [--dir .] [--remote] # --remote → the platform's FULL schema check, all errors at once
12
12
  * octwin login --url <platformUrl> --token oct_…
13
13
  * octwin whoami [--url <url>] [--tenant <slug>]
14
14
  * octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
15
15
  * octwin status [--dir .] # did my deploy land? which version is live?
16
+ * octwin records [entity] [id] # inspect the pack's XRM data (records:read token)
17
+ * octwin logs [conversationId] # list conversations / show one's timeline
18
+ * octwin chat "msg" [--as h] # drive one turn via the web channel + print the reply
16
19
  * octwin platform-kb [pull] [--dir .] # pull the platform capability reference for the authoring skill
17
- * octwin test [--dir .] # local validate + how to chat on your tenant
20
+ * octwin test [--dir .] # = validate --remote (the full platform check)
18
21
  *
19
22
  * Config resolution (deploy): flags > pack.json (in the pack dir) > env
20
23
  * (PACK_PLATFORM_URL / PACK_TENANT / PACK_PROJECT / PACK_TOKEN) > saved login.
@@ -168,11 +171,58 @@ function localValidate(packDir) {
168
171
  }
169
172
  return { id, version, files };
170
173
  }
171
- function cmdValidate(flags) {
174
+ async function cmdValidate(flags) {
172
175
  const packDir = resolve(flags.dir ?? '.');
173
- const { id, version, files } = localValidate(packDir);
174
- console.log(`✓ ${id}@${version} is a valid pure-YAML pack (${Object.keys(files).length} files)`);
175
- console.log(' (the platform re-validates manifest + flows against the full schema on deploy)');
176
+ const { id, version, files } = localValidate(packDir); // offline structural gate first (fast, no server/token)
177
+ console.log(`✓ ${id}@${version} passes the offline structural check (${Object.keys(files).length} files)`);
178
+ if (flags.remote !== true) {
179
+ console.log(' Run `octwin validate --remote` to run the platform\'s FULL manifest + flow-DSL validation');
180
+ console.log(' (all errors at once) before you deploy.');
181
+ return;
182
+ }
183
+ // Remote: the SAME validation the deploy route runs — manifest `.strict()` +
184
+ // every flow (schema/expression/structure) — returning ALL errors at once.
185
+ const { url, tenant, project, token } = resolveTarget(flags, packDir);
186
+ console.log(`→ Validating against ${tenant}/${project} @ ${url} …`);
187
+ const res = await fetch(`${url}/api/admin/tenants/${tenant}/projects/${project}/packs/validate`, {
188
+ method: 'POST',
189
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
190
+ body: JSON.stringify({ files }),
191
+ });
192
+ const text = await res.text();
193
+ let json;
194
+ try {
195
+ json = JSON.parse(text);
196
+ }
197
+ catch {
198
+ json = text;
199
+ }
200
+ if (!res.ok) {
201
+ // 404 → older platform without the endpoint.
202
+ if (res.status === 404)
203
+ die('this platform has no /packs/validate endpoint yet (older version) — deploy runs the full check');
204
+ console.error(`✗ remote validate failed (HTTP ${res.status})`);
205
+ console.error(typeof json === 'string' ? json : JSON.stringify(json, null, 2));
206
+ process.exit(1);
207
+ }
208
+ if (json?.ok) {
209
+ console.log(`✓ ${id}@${version} passes the platform's FULL validation — deploy won't reject on schema.`);
210
+ return;
211
+ }
212
+ const errors = (json?.errors ?? []);
213
+ const byWhere = new Map();
214
+ for (const e of errors) {
215
+ const a = byWhere.get(e.where) ?? [];
216
+ a.push(e.message);
217
+ byWhere.set(e.where, a);
218
+ }
219
+ console.error(`✗ ${id}@${version} — ${errors.length} validation error${errors.length === 1 ? '' : 's'}:`);
220
+ for (const [where, msgs] of byWhere) {
221
+ console.error(`\n [${where}]`);
222
+ for (const m of msgs)
223
+ console.error(` • ${m}`);
224
+ }
225
+ process.exit(1);
176
226
  }
177
227
  function cmdLogin(flags) {
178
228
  const url = flags.url ?? process.env.PACK_PLATFORM_URL ?? die('usage: octwin login --url <platformUrl> --token <t>');
@@ -399,27 +449,216 @@ async function cmdPlatformKb(flags) {
399
449
  console.log(` ${mdCount} markdown docs + ${jsonCount} JSON catalogs (reference version ${bundle.version ?? '?'})`);
400
450
  console.log(' The octwin-pack authoring skill reads these as the source of truth for what the platform supports.');
401
451
  }
402
- function cmdTest(flags) {
452
+ // ── records / logs / chat — headless inspect + test with the deploy token ────
453
+ /** GET an admin endpoint with the deploy token; returns `{ status, json }`. */
454
+ async function apiGet(endpoint, token) {
455
+ const res = await fetch(endpoint, { headers: { authorization: `Bearer ${token}` } });
456
+ const text = await res.text();
457
+ let json;
458
+ try {
459
+ json = JSON.parse(text);
460
+ }
461
+ catch {
462
+ json = text;
463
+ }
464
+ return { status: res.status, json };
465
+ }
466
+ /** `octwin records [entity] [id]` — inspect the pack's XRM data (needs a `records:read` token). */
467
+ async function cmdRecords(flags) {
403
468
  const packDir = resolve(flags.dir ?? '.');
404
- const { id, version } = localValidate(packDir);
405
- console.log(`✓ ${id}@${version} validates locally.`);
406
- console.log(' Deploy it (`octwin deploy`), then chat on your tenant via the web widget /');
407
- console.log(' console test page, and `octwin status` to confirm the live version.');
469
+ const { url, tenant, project, token } = resolveTarget(flags, packDir);
470
+ const base = `${url}/api/admin/tenants/${tenant}/projects/${project}`;
471
+ const entity = flags._[0];
472
+ const recordId = flags._[1];
473
+ if (!entity) {
474
+ const { status, json } = await apiGet(`${base}/xrm/entities`, token);
475
+ if (status !== 200)
476
+ die(`could not read entities (HTTP ${status})`);
477
+ if (json?.has_xrm === false) {
478
+ console.log('This pack declares no XRM entities.');
479
+ return;
480
+ }
481
+ const ents = (json?.entities ?? []);
482
+ if (ents.length === 0) {
483
+ console.log('No entities visible — mint a token with the `records:read` scope to inspect data.');
484
+ return;
485
+ }
486
+ console.log(`Entities in ${tenant}/${project}:`);
487
+ for (const e of ents)
488
+ console.log(` ${e.entity} (${e.open_count ?? 0} records)`);
489
+ console.log('\nList records: octwin records <entity>');
490
+ return;
491
+ }
492
+ if (!recordId) {
493
+ const limit = flags.limit ?? '50';
494
+ const { status, json } = await apiGet(`${base}/xrm/records?entity=${encodeURIComponent(entity)}&limit=${limit}`, token);
495
+ if (status === 403)
496
+ die('forbidden — the paged record list needs the `records` plan feature on this tenant');
497
+ if (status !== 200)
498
+ die(`could not read records (HTTP ${status})`);
499
+ const rows = (json?.records ?? []);
500
+ console.log(`${entity}: ${json?.total ?? rows.length} record(s)`);
501
+ if (rows.length === 0)
502
+ console.log(' (none — if you expected data, mint a `records:read` token and check `octwin deploy --seed`)');
503
+ for (const r of rows)
504
+ console.log(` #${r.record_number ?? '?'} ${r.title ?? '(untitled)'}${r.stage ? ` [${r.stage}]` : ''} ${r.id}`);
505
+ return;
506
+ }
507
+ const { status, json } = await apiGet(`${base}/xrm/records/${encodeURIComponent(recordId)}`, token);
508
+ if (status === 403)
509
+ die('forbidden — mint a token with the `records:read` scope');
510
+ if (status === 404)
511
+ die(`record '${recordId}' not found`);
512
+ if (status !== 200)
513
+ die(`could not read record (HTTP ${status})`);
514
+ console.log(JSON.stringify(json?.record ?? json, null, 2));
515
+ }
516
+ /** `octwin logs [conversationId]` — list conversations or show one's event timeline. */
517
+ async function cmdLogs(flags) {
518
+ const packDir = resolve(flags.dir ?? '.');
519
+ const { url, tenant, project, token } = resolveTarget(flags, packDir);
520
+ const base = `${url}/api/admin/tenants/${tenant}/projects/${project}`;
521
+ const convId = flags._[0];
522
+ if (!convId) {
523
+ const { status, json } = await apiGet(`${base}/conversations?limit=20`, token);
524
+ if (status !== 200)
525
+ die(`could not read conversations (HTTP ${status})`);
526
+ const convs = (json?.conversations ?? []);
527
+ if (convs.length === 0) {
528
+ console.log('No conversations yet — try `octwin chat "hi"` first.');
529
+ return;
530
+ }
531
+ console.log(`Recent conversations in ${tenant}/${project}:`);
532
+ for (const c of convs)
533
+ console.log(` ${c.id} ${c.contact?.display_name ?? c.contact?.channel_contact_handle ?? '?'} [${c.status}]`);
534
+ console.log('\nView a timeline: octwin logs <conversationId>');
535
+ return;
536
+ }
537
+ const { status, json } = await apiGet(`${base}/conversations/${encodeURIComponent(convId)}`, token);
538
+ if (status === 404)
539
+ die(`conversation '${convId}' not found`);
540
+ if (status !== 200)
541
+ die(`could not read conversation (HTTP ${status})`);
542
+ const events = (json?.events ?? []);
543
+ console.log(`Timeline for ${convId} (${events.length} events):`);
544
+ for (const e of events) {
545
+ const isErr = e.type === 'tool' && e.subtype === 'platform_error';
546
+ const tag = isErr ? '⚠ ERROR' : `${e.type}${e.subtype ? `/${e.subtype}` : ''}`;
547
+ const body = typeof e.content === 'string' ? e.content : JSON.stringify(e.content ?? '');
548
+ console.log(` ${e.ts ?? ''} ${tag}: ${(body ?? '').slice(0, 240)}`);
549
+ }
550
+ }
551
+ /** `octwin chat "message" [--as <handle>]` — drive ONE turn through the dev web
552
+ * channel and print the agent's reply. The web channel is unauthenticated, so no
553
+ * token is required here. */
554
+ async function cmdChat(flags) {
555
+ const packDir = resolve(flags.dir ?? '.');
556
+ const cfg = readPackConfig(packDir);
557
+ const url = (flags.url ?? process.env.PACK_PLATFORM_URL ?? cfg.platform_url ?? '').replace(/\/$/, '');
558
+ const tenant = flags.tenant ?? process.env.PACK_TENANT ?? cfg.tenant ?? '';
559
+ const project = flags.project ?? process.env.PACK_PROJECT ?? cfg.project ?? 'main';
560
+ if (!url)
561
+ die('no platform url — set it in pack.json, --url, or PACK_PLATFORM_URL');
562
+ if (!tenant)
563
+ die('no tenant — set it in pack.json, --tenant, or PACK_TENANT');
564
+ const from = flags.as ?? 'cli-tester';
565
+ const message = flags._[0];
566
+ if (!message)
567
+ die('usage: octwin chat "your message" [--as <handle>]');
568
+ const ctrl = new AbortController();
569
+ const timer = setTimeout(() => ctrl.abort(), 30_000);
570
+ let evRes;
571
+ try {
572
+ evRes = await fetch(`${url}/api/web/events/${tenant}/${project}/${encodeURIComponent(from)}`, { headers: { accept: 'text/event-stream' }, signal: ctrl.signal });
573
+ }
574
+ catch (err) {
575
+ clearTimeout(timer);
576
+ die(`could not open chat stream: ${err?.message ?? err}`);
577
+ return;
578
+ }
579
+ if (!evRes.ok || !evRes.body) {
580
+ clearTimeout(timer);
581
+ die(`could not open chat stream (HTTP ${evRes.status})`);
582
+ return;
583
+ }
584
+ console.log(`→ [${from}] ${message}`);
585
+ await fetch(`${url}/api/web/inbound/${tenant}/${project}`, {
586
+ method: 'POST',
587
+ headers: { 'content-type': 'application/json' },
588
+ body: JSON.stringify({ type: 'text', from, text: message, local_id: `cli-${from}` }),
589
+ }).catch((err) => { clearTimeout(timer); die(`could not send message: ${err?.message ?? err}`); });
590
+ const reader = evRes.body.getReader();
591
+ const decoder = new TextDecoder();
592
+ let buf = '';
593
+ let gotReply = false;
594
+ try {
595
+ outer: for (;;) {
596
+ const { done, value } = await reader.read();
597
+ if (done)
598
+ break;
599
+ buf += decoder.decode(value, { stream: true });
600
+ let idx;
601
+ while ((idx = buf.indexOf('\n\n')) >= 0) {
602
+ const frame = buf.slice(0, idx);
603
+ buf = buf.slice(idx + 2);
604
+ const line = frame.split('\n').find((l) => l.startsWith('data:'));
605
+ if (!line)
606
+ continue;
607
+ let ev;
608
+ try {
609
+ ev = JSON.parse(line.slice(5).trim());
610
+ }
611
+ catch {
612
+ continue;
613
+ }
614
+ if (ev.kind === 'render') {
615
+ console.log(`← ${ev.body ?? '(no text body)'}`);
616
+ const hint = ev.hint;
617
+ if (hint?.buttons?.length)
618
+ console.log(` [buttons: ${hint.buttons.map((b) => b.title).join(' | ')}]`);
619
+ if (hint?.sections?.length || hint?.rows?.length)
620
+ console.log(' [a list picker was rendered]');
621
+ gotReply = true;
622
+ break outer;
623
+ }
624
+ }
625
+ }
626
+ }
627
+ catch (err) {
628
+ if (ctrl.signal.aborted)
629
+ console.error(' (timed out after 30s waiting for a reply)');
630
+ else
631
+ console.error(` (stream error: ${err?.message ?? err})`);
632
+ }
633
+ finally {
634
+ clearTimeout(timer);
635
+ try {
636
+ await reader.cancel();
637
+ }
638
+ catch { /* ignore */ }
639
+ }
640
+ if (!gotReply) {
641
+ console.error(' no reply — the pack may not be warm yet, or the turn produced no render.');
642
+ process.exit(1);
643
+ }
408
644
  }
409
645
  function help() {
410
- console.log(`octwin — Octwin external-pack developer CLI (by CEQUENS)
411
-
412
- octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
413
- octwin validate [--dir .]
414
- octwin login --url <platformUrl> --token oct_… # a deploy token from the console
415
- octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
416
- octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
417
- octwin status [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
418
- octwin platform-kb [pull] [--dir .] [--url <url>] [--tenant <slug>] [--token <t>]
419
- octwin test [--dir .]
420
-
421
- Get a deploy token: console your workspace Settings → API tokens → Generate.
422
- octwin platform-kb pull writes the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
646
+ console.log(`octwin — Octwin external-pack developer CLI (by CEQUENS)
647
+
648
+ octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
649
+ octwin validate [--dir .] [--remote] # --remote runs the platform's FULL schema check (all errors at once)
650
+ octwin login --url <platformUrl> --token oct_… # a deploy token from the console
651
+ octwin whoami [--url <url>] [--tenant <slug>] # verify the token works
652
+ octwin deploy [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>] [--seed]
653
+ octwin status [--dir .] [--url <url>] [--tenant <slug>] [--project <slug>] [--token <t>]
654
+ octwin records [entity] [id] # inspect the pack's XRM data (needs a records:read token)
655
+ octwin logs [conversationId] # list conversations / show one's event timeline
656
+ octwin chat "message" [--as <handle>] # drive one turn through the web channel + print the reply
657
+ octwin platform-kb [pull] [--dir .] [--url <url>] [--tenant <slug>] [--token <t>]
658
+ octwin test [--dir .] # = validate --remote (the full platform check)
659
+
660
+ Get a deploy token: console → your workspace → Settings → API tokens → Generate (tick records:read to inspect data).
661
+ octwin platform-kb pull → writes the platform capability reference into .octwin/platform-kb/ (for the octwin-pack skill).
423
662
  Config (deploy): flags > pack.json > env (PACK_PLATFORM_URL/PACK_TENANT/PACK_PROJECT/PACK_TOKEN) > saved login.`);
424
663
  }
425
664
  async function main() {
@@ -430,7 +669,7 @@ async function main() {
430
669
  cmdInit(flags);
431
670
  break;
432
671
  case 'validate':
433
- cmdValidate(flags);
672
+ await cmdValidate(flags);
434
673
  break;
435
674
  case 'login':
436
675
  cmdLogin(flags);
@@ -444,12 +683,21 @@ async function main() {
444
683
  case 'status':
445
684
  await cmdStatus(flags);
446
685
  break;
686
+ case 'records':
687
+ await cmdRecords(flags);
688
+ break;
689
+ case 'logs':
690
+ await cmdLogs(flags);
691
+ break;
692
+ case 'chat':
693
+ await cmdChat(flags);
694
+ break;
447
695
  case 'platform-kb':
448
696
  await cmdPlatformKb(flags);
449
697
  break;
450
698
  case 'test':
451
- cmdTest(flags);
452
- break;
699
+ await cmdValidate({ ...flags, remote: true });
700
+ break; // A6: `test` = the full remote validate, not a validate-clone
453
701
  case undefined:
454
702
  case 'help':
455
703
  case '--help':
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "octwin-cli",
3
- "version": "0.1.4",
4
- "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
- "type": "module",
6
- "bin": {
7
- "octwin": "dist/index.js"
8
- },
9
- "files": [
10
- "dist",
11
- "templates",
12
- "README.md",
13
- "LICENSE"
14
- ],
15
- "engines": {
16
- "node": ">=20"
17
- },
18
- "scripts": {
19
- "build": "tsc -p tsconfig.json",
20
- "prepublishOnly": "npm run build"
21
- },
22
- "dependencies": {
23
- "yaml": "^2.6.0"
24
- },
25
- "devDependencies": {
26
- "@types/node": "^22.0.0",
27
- "typescript": "^5.7.0"
28
- },
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "keywords": ["octwin", "cequens", "cli", "whatsapp", "chatbot", "pack", "conversational-ai", "yaml"],
33
- "author": "CEQUENS",
34
- "homepage": "https://www.npmjs.com/package/octwin-cli",
35
- "license": "MIT"
36
- }
1
+ {
2
+ "name": "octwin-cli",
3
+ "version": "0.1.5",
4
+ "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
+ "type": "module",
6
+ "bin": {
7
+ "octwin": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "yaml": "^2.6.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "typescript": "^5.7.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "keywords": ["octwin", "cequens", "cli", "whatsapp", "chatbot", "pack", "conversational-ai", "yaml"],
33
+ "author": "CEQUENS",
34
+ "homepage": "https://www.npmjs.com/package/octwin-cli",
35
+ "license": "MIT"
36
+ }
@@ -1,14 +1,14 @@
1
- # Pack-level slash-command declarations (OPTIONAL but recommended).
2
- #
3
- # The platform's default `systemCommand` matches inbound text against these
4
- # BEFORE normal agent dispatch. Without a `commands.yaml`, a pack has NO slash
5
- # commands — e.g. `/clear` silently does nothing (it just goes to the agent as
6
- # text). Ship this file if you want `/clear` (and friends) to work like the
7
- # other packs.
8
- #
9
- # action: `close_conversation` (reset the conversation) | `noop` (reply only).
10
-
11
- - match: '/clear'
12
- action: close_conversation
13
- reply: 'تم مسح المحادثة ✅ تقدر تبدأ من جديد.'
14
- source: '/clear'
1
+ # Pack-level slash-command declarations (OPTIONAL but recommended).
2
+ #
3
+ # The platform's default `systemCommand` matches inbound text against these
4
+ # BEFORE normal agent dispatch. Without a `commands.yaml`, a pack has NO slash
5
+ # commands — e.g. `/clear` silently does nothing (it just goes to the agent as
6
+ # text). Ship this file if you want `/clear` (and friends) to work like the
7
+ # other packs.
8
+ #
9
+ # action: `close_conversation` (reset the conversation) | `noop` (reply only).
10
+
11
+ - match: '/clear'
12
+ action: close_conversation
13
+ reply: 'تم مسح المحادثة ✅ تقدر تبدأ من جديد.'
14
+ source: '/clear'