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 +82 -2
- package/package.json +1 -1
- package/templates/starter/flows/tools/browse.flow.yaml +24 -0
- package/templates/starter/flows/tools/browse.locale.ar.yaml +5 -0
- package/templates/starter/flows/tools/home.flow.yaml +32 -0
- package/templates/starter/flows/tools/home.locale.ar.yaml +9 -0
- package/templates/starter/manifest.yaml +8 -5
- package/templates/starter/prompts/identity.md +5 -5
- package/templates/starter/flows/tools/main.flow.yaml +0 -39
- package/templates/starter/flows/tools/main.locale.ar.yaml +0 -16
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-
|
|
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
|
@@ -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,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
|
-
#
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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'
|