octwin-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * tenant. Standalone: no platform checkout, no build step for the developer —
8
8
  * `npx octwin-cli <cmd>` (published as `octwin-cli`, command `octwin`).
9
9
  *
10
+ * octwin --version | -v # print the CLI version (+ any upgrade notice)
10
11
  * octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
11
12
  * octwin validate [--dir .] [--remote] # --remote → the platform's FULL schema check, all errors at once
12
13
  * octwin login --url <platformUrl> --token oct_…
@@ -37,6 +38,17 @@ import { validatePackBundle } from './lib/validate.js';
37
38
  // level under the package root), so `../templates/starter` resolves for the
38
39
  // built CLI and `tsx` dev alike.
39
40
  const TEMPLATE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'starter');
41
+ /** This CLI's version — read from its own package.json (one level up from dist/,
42
+ * always shipped in the npm tarball). Falls back to '0.0.0' if unreadable. */
43
+ const VERSION = (() => {
44
+ try {
45
+ const pkg = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
46
+ return typeof pkg?.version === 'string' ? pkg.version : '0.0.0';
47
+ }
48
+ catch {
49
+ return '0.0.0';
50
+ }
51
+ })();
40
52
  function parseFlags(argv) {
41
53
  const f = { _: [] };
42
54
  for (let i = 0; i < argv.length; i++) {
@@ -119,6 +131,67 @@ function writeCreds(map) {
119
131
  mkdirSync(join(homedir(), '.octwin'), { recursive: true });
120
132
  writeFileSync(credsPath(), JSON.stringify(map, null, 2), 'utf8');
121
133
  }
134
+ // ── update check (daily, fail-silent, TTY-only) ──────────────────────────────
135
+ function updateCachePath() { return join(homedir(), '.octwin', 'update-check.json'); }
136
+ /** True when semver `a` is strictly greater than `b` (simple x.y.z compare). */
137
+ function isNewer(a, b) {
138
+ const pa = a.split('.').map(n => parseInt(n, 10));
139
+ const pb = b.split('.').map(n => parseInt(n, 10));
140
+ for (let i = 0; i < 3; i++) {
141
+ const x = pa[i] ?? 0, y = pb[i] ?? 0;
142
+ if (Number.isNaN(x) || Number.isNaN(y))
143
+ return false;
144
+ if (x !== y)
145
+ return x > y;
146
+ }
147
+ return false;
148
+ }
149
+ /** The latest published `octwin-cli` version, cached for a day. Fail-silent:
150
+ * returns null on any error / offline — a version check must never break a command. */
151
+ async function latestPublishedVersion() {
152
+ const DAY = 24 * 60 * 60 * 1000;
153
+ try {
154
+ const cache = JSON.parse(readFileSync(updateCachePath(), 'utf8'));
155
+ if (cache.latest && typeof cache.checkedAt === 'number' && Date.now() - cache.checkedAt < DAY)
156
+ return cache.latest;
157
+ }
158
+ catch { /* no / stale cache → fetch below */ }
159
+ try {
160
+ const ctrl = new AbortController();
161
+ const timer = setTimeout(() => ctrl.abort(), 1500);
162
+ const res = await fetch('https://registry.npmjs.org/octwin-cli/latest', { signal: ctrl.signal });
163
+ clearTimeout(timer);
164
+ if (!res.ok)
165
+ return null;
166
+ const latest = (await res.json()).version;
167
+ if (typeof latest !== 'string')
168
+ return null;
169
+ try {
170
+ mkdirSync(join(homedir(), '.octwin'), { recursive: true });
171
+ writeFileSync(updateCachePath(), JSON.stringify({ checkedAt: Date.now(), latest }), 'utf8');
172
+ }
173
+ catch { /* cache is best-effort */ }
174
+ return latest;
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ }
180
+ /** Print a one-line upgrade notice (to stderr) when a newer octwin-cli is published.
181
+ * Skipped when not attached to a TTY (CI / piped) so it never adds noise to scripts.
182
+ * Never throws. */
183
+ async function notifyIfOutdated() {
184
+ if (!process.stdout.isTTY)
185
+ return;
186
+ try {
187
+ const latest = await latestPublishedVersion();
188
+ if (latest && isNewer(latest, VERSION)) {
189
+ console.error(`\n⬆ octwin-cli ${latest} is available (you have ${VERSION}).`);
190
+ console.error(' Upgrade: npm i -g octwin-cli@latest (or just use npx octwin-cli@latest)');
191
+ }
192
+ }
193
+ catch { /* a version check must never break the CLI */ }
194
+ }
122
195
  // ── commands ────────────────────────────────────────────────────────────────
123
196
  function cmdInit(flags) {
124
197
  const target = flags._[0] ?? die('usage: octwin init <dir> [--id my-pack]');
@@ -148,7 +221,7 @@ function cmdInit(flags) {
148
221
  }, null, 2) + '\n', 'utf8');
149
222
  writeFileSync(join(dir, '.gitignore'), 'node_modules/\n.pack-bundles/\n.octwin/\n', 'utf8');
150
223
  if (!existsSync(join(dir, 'README.md'))) {
151
- writeFileSync(join(dir, 'README.md'), `# ${id}\n\nA pure-YAML pack for the Octwin platform.\n\n- Edit \`manifest.yaml\`, \`flows/tools/main.flow.yaml\`, \`prompts/identity.md\`\n- \`octwin validate\` — check it locally\n- \`octwin deploy\` — deploy + install onto your tenant\n`, 'utf8');
224
+ writeFileSync(join(dir, 'README.md'), `# ${id}\n\nA pure-YAML pack for the Octwin platform.\n\n- \`flows/tools/home.flow.yaml\` — the menu hub (your front door); add a row + tool per journey\n- Edit \`manifest.yaml\`, \`prompts/identity.md\`\n- \`octwin validate --remote\` — full platform check before deploy\n- \`octwin deploy\` — deploy + install onto your tenant\n`, 'utf8');
152
225
  }
153
226
  console.log(`✓ Scaffolded pure-YAML pack '${id}' at ${dir}`);
154
227
  console.log('\nNext:');
@@ -643,8 +716,9 @@ async function cmdChat(flags) {
643
716
  }
644
717
  }
645
718
  function help() {
646
- console.log(`octwin — Octwin external-pack developer CLI (by CEQUENS)
719
+ console.log(`octwin ${VERSION} — Octwin external-pack developer CLI (by CEQUENS)
647
720
 
721
+ octwin --version # print the CLI version (+ any upgrade notice)
648
722
  octwin init <dir> [--id my-pack] [--description "..."] [--display-name "..."]
649
723
  octwin validate [--dir .] [--remote] # --remote runs the platform's FULL schema check (all errors at once)
650
724
  octwin login --url <platformUrl> --token oct_… # a deploy token from the console
@@ -698,6 +772,11 @@ async function main() {
698
772
  case 'test':
699
773
  await cmdValidate({ ...flags, remote: true });
700
774
  break; // A6: `test` = the full remote validate, not a validate-clone
775
+ case '-v':
776
+ case '--version':
777
+ case 'version':
778
+ console.log(`octwin-cli ${VERSION}`);
779
+ break;
701
780
  case undefined:
702
781
  case 'help':
703
782
  case '--help':
@@ -706,5 +785,6 @@ async function main() {
706
785
  break;
707
786
  default: die(`unknown command '${command}' — run \`octwin help\``);
708
787
  }
788
+ await notifyIfOutdated(); // trailing, fail-silent, TTY-only "newer version available" notice
709
789
  }
710
790
  main().catch((err) => die(err?.message ?? String(err)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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": {
@@ -0,0 +1,24 @@
1
+ # An example capability the home hub invokes. Replace this with your real tool
2
+ # (record_list + a carousel, a collect: intake, a case_open, …). Kept trivial here
3
+ # so the scaffold is chattable end-to-end: home menu → tap "browse" → this renders.
4
+ flow_id: browse
5
+ version: 1
6
+ description: An example capability, reached from the home menu. Replace with your real tool.
7
+
8
+ input:
9
+ message:
10
+ type: string
11
+ optional: true
12
+ describe: 'Optional note from the user.'
13
+
14
+ entry:
15
+ - else: true
16
+ goto: show
17
+
18
+ steps:
19
+ show:
20
+ - render:
21
+ render_intent: text_card
22
+ body: '{$t("browse.body")}'
23
+ memory_note: '{$t("browse.memory_note")}'
24
+ - end: applied
@@ -0,0 +1,5 @@
1
+ flow_id: browse
2
+
3
+ strings:
4
+ body: 'هنا يظهر المحتوى ✨ عدّل أداة "browse" لتبني ميزتك الحقيقية.'
5
+ memory_note: 'Showed the browse example.'
@@ -0,0 +1,32 @@
1
+ # The HOME hub — your pack's front door / menu, and the FIRST tool the agent calls
2
+ # on a greeting or an unclear message. A pure-render `list_picker`: one row per
3
+ # primary capability, each routing a tap STRAIGHT to that tool via `on_select.invoke`
4
+ # (no LLM turn). Add a row as you add each journey. Every shipped reference pack
5
+ # ships a home flow like this — see references/ux-patterns.md in the octwin-pack skill.
6
+ flow_id: home
7
+ version: 1
8
+ description: Main menu / welcome. Call on a greeting, an unclear or off-topic message, or "what can you do?".
9
+
10
+ input:
11
+ message:
12
+ type: string
13
+ optional: true
14
+ describe: 'Optional ONE short greeting line YOU compose for this user (e.g. greet them by name).'
15
+
16
+ entry:
17
+ - else: true
18
+ goto: show
19
+
20
+ steps:
21
+ show:
22
+ - render:
23
+ render_intent: list_picker # the universal chooser (WhatsApp list / web menu)
24
+ header: '{$t("home.header")}'
25
+ body: '$coalesce($input.message, $t("home.greeting"))' # agent line if given, else the default
26
+ cta: '{$t("home.cta")}' # the WhatsApp list-button label (required for list_picker)
27
+ items: # ONE row per capability — the tap routes via on_select
28
+ - { title: '{$t("home.row_browse")}', on_select: { invoke: 'browse' } }
29
+ # Add a row per tool as you build them, e.g.:
30
+ # - { title: '{$t("home.row_x")}', on_select: { invoke: 'some-tool', with: { mode: x } } }
31
+ memory_note: 'Showed the home menu.' # English breadcrumb — surfaced to the agent next turn
32
+ - end: applied
@@ -0,0 +1,9 @@
1
+ # Every user-visible string for the home flow, keyed by `$t("home.<key>")`.
2
+ # Lead labels with an emoji — it makes the menu scannable (all reference packs do this).
3
+ flow_id: home
4
+
5
+ strings:
6
+ header: 'القائمة الرئيسية'
7
+ greeting: 'أهلاً بك 👋 اختر من القائمة:'
8
+ cta: 'اختر'
9
+ row_browse: '📦 تصفّح'
@@ -17,10 +17,12 @@ required_adapters: [messaging]
17
17
  default_settings:
18
18
  locale: ar
19
19
 
20
- # Agent-callable flow tools. Each id maps to
21
- # `flows/tools/<id>.flow.yaml` + `<id>.primitives.ts`.
20
+ # Agent-callable flow tools. Each id maps to `flows/tools/<id>.flow.yaml`
21
+ # (+ its `<id>.locale.<lang>.yaml`). List the HOME hub first — it's the pack's
22
+ # front door; add each new tool here as you build it.
22
23
  flows:
23
- - main
24
+ - home # the menu / navigation hub (see references/ux-patterns.md)
25
+ - browse # an example tool the hub invokes — replace with your real capability
24
26
 
25
27
  # One agent. The platform's universal agent protocol is auto-appended by
26
28
  # `createPackAgents` — `instructions:` carries only pack-supplied parts.
@@ -30,9 +32,10 @@ agents:
30
32
  # `openrouter/` prefix routes via OpenRouter. Operators override the
31
33
  # model per-project from the console.
32
34
  default_model: 'openrouter/google/gemini-3.1-flash-lite-preview'
33
- # Flow tools this agent can call (subset of `flows:` above).
35
+ # Flow tools this agent can call (subset of `flows:` above; hub first).
34
36
  tools:
35
- - main
37
+ - home
38
+ - browse
36
39
  include_platform_protocol: true
37
40
  # Instruction parts, joined with '\n\n'. `file:` paths are pack-root
38
41
  # relative; `.md` files may use built-in placeholders like {{pack.id}}.
@@ -1,5 +1,5 @@
1
- You are the Starter Assistant — a tiny example bot that ships with the platform as a reference pack.
2
-
3
- For any user message, call the `main` tool to greet them. Pass the user's message as the `message` argument when they say something specific you can echo back; otherwise call `main` with no arguments.
4
-
5
- Keep replies short and friendly. This pack exists to be copied and customized once you scaffold your own pack, replace this prompt and the `main` flow with your real domain.
1
+ You are the Starter Assistant — a tiny example bot that ships as a reference pack.
2
+
3
+ On a greeting, an unclear or off-topic message, or "what can you do?", call the `home` tool it shows the menu (the pack's front door). Pass a short one-line greeting as `message` when you can personalize it (e.g. greet the user by name); otherwise call `home` with no arguments.
4
+
5
+ `browse` is an example capability reached from the menu replace it, and add a row + tool per journey as you build your domain (see the home flow's comments). Keep replies short and friendly.
@@ -1,39 +0,0 @@
1
- # main — the starter greeting flow.
2
- #
3
- # The canonical pack-author shape, in miniature — PURE YAML, no TypeScript:
4
- # • input: the tool's schema (drives the LLM tool catalogue + validation)
5
- # • entry: routing rules (here: always go to `greet`)
6
- # • steps: compute the card body with an `assign` expr → render → end
7
- # • all user-visible copy lives in main.locale.ar.yaml (never inline here)
8
- #
9
- # Conventions worth copying:
10
- # • Build data with expr builtins (`$trim`/`$coalesce`/`$t`) — reach for a
11
- # pack TS primitive only for logic the expr grammar genuinely can't express.
12
- # • `{$body}` interpolates the assigned value into the card.
13
- # • `memory_note` leaves the agent a breadcrumb of what the user saw.
14
-
15
- flow_id: main
16
- version: 1
17
- description: Greet the user. Call for any message while building out this pack.
18
-
19
- input:
20
- message:
21
- type: string
22
- optional: true
23
- describe: 'Optional note from the user to echo back in the greeting.'
24
-
25
- entry:
26
- - else: true
27
- goto: greet
28
-
29
- steps:
30
- greet:
31
- # Echo the user's note when present, else the default greeting — a ternary
32
- # over `$trim` + `$t(key, { … })`. (Was a `build_greeting` primitive.)
33
- - assign:
34
- body: '$trim($coalesce($input.message, "")) != "" ? $t("main.body_with_note", { note: $trim($input.message) }) : $t("main.body_default")'
35
- - render:
36
- render_intent: text_card
37
- body: '{$body}'
38
- memory_note: '{$t("main.memory_note")}'
39
- - end: applied
@@ -1,16 +0,0 @@
1
- # main — Arabic copy.
2
- #
3
- # Every user-facing string for the `main` flow lives here, keyed by a stable
4
- # name. The flow YAML references each via `$t("main.<key>", { … })`.
5
- # Placeholder syntax: `{var}` — substituted from the second arg of `$t()`.
6
- # Adding a new locale = drop a sibling `main.locale.<lang>.yaml` with the same
7
- # keys translated.
8
-
9
- flow_id: main
10
-
11
- strings:
12
- body_default: 'أهلاً بك 👋 أنا بوت البداية. اكتب أي رسالة لنبدأ.'
13
- body_with_note: 'أهلاً بك 👋 قلت: "{note}". أنا بوت البداية — جاهز نبدأ.'
14
-
15
- # Agent breadcrumb (kept in English like other packs' memory notes).
16
- memory_note: 'Showed starter greeting card'