octwin-cli 0.8.15 → 0.8.17
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 +1109 -1017
- package/README.md +37 -29
- package/dist/cli.js +989 -467
- package/dist/lib/base.js +85 -0
- package/dist/lib/init-id.js +13 -0
- package/dist/lib/journey-check.js +119 -0
- package/dist/lib/kb-index.js +142 -18
- package/dist/lib/recipe.js +190 -0
- package/dist/lib/refusal.js +56 -0
- package/dist/lib/rename.js +1 -1
- package/package.json +46 -46
- package/templates/starter/commands.yaml +11 -12
- package/templates/starter/flows/tools/home.flow.yaml +34 -32
- package/templates/starter/flows/tools/home.locale.ar.yaml +8 -9
- package/templates/starter/flows/tools/home.locale.en.yaml +8 -0
- package/templates/starter/flows/tools/request.flow.yaml +95 -0
- package/templates/starter/flows/tools/request.locale.ar.yaml +14 -0
- package/templates/starter/flows/tools/request.locale.en.yaml +14 -0
- package/templates/starter/journeys/request.journey.yaml +12 -0
- package/templates/starter/locale.ar.yaml +6 -10
- package/templates/starter/locale.en.yaml +9 -0
- package/templates/starter/manifest.yaml +44 -45
- package/templates/starter/messages.ar.yaml +3 -10
- package/templates/starter/messages.en.yaml +19 -0
- package/templates/starter/prompts/identity.md +7 -5
- package/templates/starter/xrm.yaml +32 -0
- package/templates/starter/flows/tools/browse.flow.yaml +0 -31
- package/templates/starter/flows/tools/browse.locale.ar.yaml +0 -6
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deploy the platform's checks refused — told the way the console tells it
|
|
3
|
+
* (`docs/design/pack-findings/`, capture 04): first what did NOT change (the live version keeps
|
|
4
|
+
* running), then what to fix, grouped by what a customer feels, then the author's own messages under
|
|
5
|
+
* each. It used to print the raw JSON body, which read like a crash.
|
|
6
|
+
*
|
|
7
|
+
* The body's `details` carry each finding's `kind` — the owner's sentence and whether customers notice it
|
|
8
|
+
* now — when the platform has one (`DryRunValidationError`); a detail without one is listed under
|
|
9
|
+
* "Also fix", never dropped. Pure, so the shape is tested without a network.
|
|
10
|
+
*/
|
|
11
|
+
/** Messages shown per group — `octwin validate` lists every one. */
|
|
12
|
+
const PER_GROUP = 10;
|
|
13
|
+
export function formatChecksRefusal(body) {
|
|
14
|
+
const b = body;
|
|
15
|
+
if (!b || !Array.isArray(b.details) || b.details.length === 0)
|
|
16
|
+
return null;
|
|
17
|
+
const details = b.details.filter(d => d && typeof d.message === 'string');
|
|
18
|
+
const live = typeof b.live_version === 'string' ? b.live_version : null;
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
const other = [];
|
|
21
|
+
for (const d of details) {
|
|
22
|
+
if (!d.kind) {
|
|
23
|
+
other.push(d);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const g = groups.get(d.kind.key);
|
|
27
|
+
if (g)
|
|
28
|
+
g.items.push(d);
|
|
29
|
+
else
|
|
30
|
+
groups.set(d.kind.key, { owner: d.kind.owner, now: d.kind.impact === 'now', items: [d] });
|
|
31
|
+
}
|
|
32
|
+
const ordered = [...groups.values()].sort((a, b2) => Number(b2.now) - Number(a.now) || b2.items.length - a.items.length);
|
|
33
|
+
const things = ordered.length + (other.length > 0 ? 1 : 0);
|
|
34
|
+
const lines = [
|
|
35
|
+
live
|
|
36
|
+
? `✗ Not deployed — your live version ${live} keeps running, unchanged.`
|
|
37
|
+
: '✗ Not deployed — nothing on the platform changed.',
|
|
38
|
+
` Fix ${things === 1 ? 'this' : `these ${things} things`}, then deploy again:`,
|
|
39
|
+
'',
|
|
40
|
+
];
|
|
41
|
+
const messages = (items) => {
|
|
42
|
+
for (const d of items.slice(0, PER_GROUP))
|
|
43
|
+
lines.push(` [${d.where}] ${d.message}`);
|
|
44
|
+
if (items.length > PER_GROUP)
|
|
45
|
+
lines.push(` … and ${items.length - PER_GROUP} more — \`octwin validate\` lists them all`);
|
|
46
|
+
};
|
|
47
|
+
for (const g of ordered) {
|
|
48
|
+
lines.push(` ${g.now ? '!' : '·'} ${g.owner}${g.items.length > 1 ? ` (${g.items.length} places)` : ''}${g.now ? '' : ' (tidy-up)'}`);
|
|
49
|
+
messages(g.items);
|
|
50
|
+
}
|
|
51
|
+
if (other.length > 0) {
|
|
52
|
+
lines.push(ordered.length > 0 ? ' · Also fix:' : ' · Fix:');
|
|
53
|
+
messages(other);
|
|
54
|
+
}
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
package/dist/lib/rename.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* vendored copy of ~~`scripts/pack-scaffold/rename.ts`~~, deleted with the in-repo packs;
|
|
6
6
|
* this is the only copy now.) `octwin init` copies the in-package
|
|
7
7
|
* `templates/starter/` tree (packId `starter-kit`, agent `assistant`, flows
|
|
8
|
-
* `home` + `
|
|
8
|
+
* `home` + `request`) then this rewrites the copy into a fresh pack:
|
|
9
9
|
* • `starter-kit` → <pack-id> (manifest id, comments, README)
|
|
10
10
|
* • agent display name (when --display-name given)
|
|
11
11
|
* • manifest description (when --description given)
|
package/package.json
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "octwin-cli",
|
|
3
|
-
"version": "0.8.
|
|
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
|
-
"CHANGELOG.md",
|
|
14
|
-
"LICENSE"
|
|
15
|
-
],
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=22"
|
|
18
|
-
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.json",
|
|
21
|
-
"prepublishOnly": "npm run build"
|
|
22
|
-
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"yaml": "^2.6.0"
|
|
25
|
-
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"@types/node": "^22.0.0",
|
|
28
|
-
"typescript": "^5.7.0"
|
|
29
|
-
},
|
|
30
|
-
"publishConfig": {
|
|
31
|
-
"access": "public"
|
|
32
|
-
},
|
|
33
|
-
"keywords": [
|
|
34
|
-
"octwin",
|
|
35
|
-
"cequens",
|
|
36
|
-
"cli",
|
|
37
|
-
"whatsapp",
|
|
38
|
-
"chatbot",
|
|
39
|
-
"pack",
|
|
40
|
-
"conversational-ai",
|
|
41
|
-
"yaml"
|
|
42
|
-
],
|
|
43
|
-
"author": "CEQUENS",
|
|
44
|
-
"homepage": "https://octwin.ai",
|
|
45
|
-
"license": "MIT"
|
|
46
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "octwin-cli",
|
|
3
|
+
"version": "0.8.17",
|
|
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
|
+
"CHANGELOG.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"yaml": "^2.6.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"typescript": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"octwin",
|
|
35
|
+
"cequens",
|
|
36
|
+
"cli",
|
|
37
|
+
"whatsapp",
|
|
38
|
+
"chatbot",
|
|
39
|
+
"pack",
|
|
40
|
+
"conversational-ai",
|
|
41
|
+
"yaml"
|
|
42
|
+
],
|
|
43
|
+
"author": "CEQUENS",
|
|
44
|
+
"homepage": "https://octwin.ai",
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Slash commands — matched BEFORE the AI Agent sees the message. Without this file a pack has none,
|
|
2
|
+
# and `/clear` reaches the agent as ordinary text. Guide: `craft-ux` §7 (the escape hatch).
|
|
2
3
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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).
|
|
4
|
+
# action: `close_conversation` (start over) | `noop` (reply only) | `cancel_handoff` (a customer
|
|
5
|
+
# waiting for a person goes back to the AI Agent; takes no `reply:`).
|
|
6
|
+
# `reply:` is one text per language this pack speaks; each customer reads theirs.
|
|
10
7
|
|
|
11
|
-
- match:
|
|
12
|
-
action:
|
|
13
|
-
reply:
|
|
14
|
-
|
|
8
|
+
- match: '/clear'
|
|
9
|
+
action: close_conversation
|
|
10
|
+
reply:
|
|
11
|
+
ar: 'تم مسح المحادثة ✅ ماذا تريد أن تفعل؟'
|
|
12
|
+
en: 'Cleared ✅ — what would you like to do?'
|
|
13
|
+
source: '/clear'
|
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
# The HOME hub —
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
# The HOME hub — the pack's front door, and the FIRST tool the agent calls on a greeting or an
|
|
2
|
+
# unclear message. A pure-render list: one row per capability, each tap routed STRAIGHT to its tool
|
|
3
|
+
# (no AI Agent turn). Add a row as you add each journey. Guide: `craft-ux` §1.
|
|
4
|
+
flow_id: home
|
|
5
|
+
version: 1
|
|
6
|
+
description: Main menu / welcome. Call on a greeting, an unclear or off-topic message, or "what can you do?".
|
|
7
|
+
|
|
8
|
+
input:
|
|
9
|
+
message:
|
|
10
|
+
type: string
|
|
11
|
+
optional: true
|
|
12
|
+
describe: 'Optional ONE short greeting line YOU compose for this customer (e.g. greet them by name).'
|
|
13
|
+
|
|
14
|
+
entry:
|
|
15
|
+
- else: true
|
|
16
|
+
goto: show
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
show:
|
|
20
|
+
# Marks the funnel's first stage — what the customer DID, not what the flow did. The goal is
|
|
21
|
+
# reached by the request record's own hook (xrm.yaml). Guide: `craft-measure`.
|
|
22
|
+
- track: { journey: request, stage_enter: menu }
|
|
23
|
+
- render:
|
|
24
|
+
render_intent: list_picker
|
|
25
|
+
header: '{$t("home.header")}'
|
|
26
|
+
body: '$coalesce($input.message, $t("home.greeting"))'
|
|
27
|
+
footer: '{$t("home.footer")}'
|
|
28
|
+
cta: '{$t("home.cta")}'
|
|
29
|
+
items:
|
|
30
|
+
- { title: '{$t("home.row_request")}', description: '{$t("home.row_request_about")}', on_select: { invoke: request } }
|
|
31
|
+
# A row per journey you add, e.g.:
|
|
32
|
+
# - { title: '{$t("home.row_x")}', on_select: { invoke: some-tool } }
|
|
33
|
+
memory_note: 'Showed the home menu.'
|
|
34
|
+
- end: applied
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
row_browse: '📦 تصفّح'
|
|
1
|
+
flow_id: home
|
|
2
|
+
strings:
|
|
3
|
+
header: '👋 أهلاً بك'
|
|
4
|
+
greeting: 'كيف نقدر نساعدك اليوم؟'
|
|
5
|
+
footer: 'اكتب /clear في أي وقت لتبدأ من جديد'
|
|
6
|
+
cta: '📋 القائمة'
|
|
7
|
+
row_request: '🔧 طلب زيارة'
|
|
8
|
+
row_request_about: 'فني يأتي إليك'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
flow_id: request
|
|
2
|
+
version: 1
|
|
3
|
+
description: Ask for a technician's visit. Call when the customer reports a problem at home. Pass `issue` only when they named it.
|
|
4
|
+
|
|
5
|
+
input:
|
|
6
|
+
area: { type: string, agent_fillable: false } # an area record id — only a tapped row sets it
|
|
7
|
+
issue: { type: string, optional: true, enum: [leak, power, ac, other] }
|
|
8
|
+
|
|
9
|
+
entry:
|
|
10
|
+
- else: true
|
|
11
|
+
goto: intake
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
intake:
|
|
15
|
+
- collect:
|
|
16
|
+
fields:
|
|
17
|
+
# A fetched picker that holds an ID: `bind:` named, `rows:` read off it, `page: true`
|
|
18
|
+
# (free when the list fits), `on_empty:` because a typed area name is not an area id,
|
|
19
|
+
# and `selection:` so the label travels with the id for the summary.
|
|
20
|
+
- field: area
|
|
21
|
+
label: '$t("request.ask_area")'
|
|
22
|
+
options:
|
|
23
|
+
body: '{$t("request.ask_area")}'
|
|
24
|
+
fetch: { do: record_list, args: { entity: area, all: true, limit: 50 }, bind: areas }
|
|
25
|
+
rows: '$areas.rows'
|
|
26
|
+
map: { value: '$item.id', label: '$item.fields.name', selection: { area_name: '$item.fields.name' } }
|
|
27
|
+
page: true
|
|
28
|
+
on_empty: no_areas
|
|
29
|
+
# A fixed set: `type: choice` with an `enum:` — the labels are `<pack_id>.issue.<value>` in
|
|
30
|
+
# the pack-level locale file, and `$enum_label` reads them back. A typed answer matches too.
|
|
31
|
+
- field: issue
|
|
32
|
+
type: choice
|
|
33
|
+
label: '$t("request.ask_issue")'
|
|
34
|
+
options:
|
|
35
|
+
body: '{$t("request.ask_issue")}'
|
|
36
|
+
enum: issue
|
|
37
|
+
values: [leak, power, ac, other]
|
|
38
|
+
# A phone: the native share on WhatsApp, a typed local number everywhere; `dial_code` reads
|
|
39
|
+
# 01005551111 as +20…
|
|
40
|
+
- field: phone
|
|
41
|
+
type: phone
|
|
42
|
+
dial_code: '20'
|
|
43
|
+
label: '$t("request.ask_phone")'
|
|
44
|
+
# A number with a reject rule. `when` is TRUE when the value is WRONG, and it runs only once
|
|
45
|
+
# the field has a value — so it needs no null guard.
|
|
46
|
+
- field: floor
|
|
47
|
+
type: number
|
|
48
|
+
label: '$t("request.ask_floor")'
|
|
49
|
+
validate:
|
|
50
|
+
when: '$state.floor < 0 || $state.floor > 60'
|
|
51
|
+
error: '{$t("request.floor_error")}'
|
|
52
|
+
# Optional: never blocks `complete`, never asked on its own — filled when the customer
|
|
53
|
+
# volunteers it or the agent passes it. (An optional `type: media` IS offered, with Skip.)
|
|
54
|
+
- field: notes
|
|
55
|
+
required: false
|
|
56
|
+
label: '$t("request.ask_notes")'
|
|
57
|
+
complete: [{ goto: confirm }]
|
|
58
|
+
|
|
59
|
+
confirm:
|
|
60
|
+
- use: approve_apply
|
|
61
|
+
with:
|
|
62
|
+
header: '{$t("request.confirm_header")}'
|
|
63
|
+
preview: '{$t("request.preview", { area: $state.area_name, issue: $enum_label($state.issue, "issue"), phone: $state.phone, floor: $state.floor, notes: $coalesce($state.notes, "—") })}'
|
|
64
|
+
apply: save
|
|
65
|
+
abort_render: { render_intent: text_card, body: '{$t("request.not_sent")}' }
|
|
66
|
+
on_other: intake # a typed correction re-enters the collect; filled fields are skipped
|
|
67
|
+
memory_note: 'Asked the customer to confirm the visit request.'
|
|
68
|
+
|
|
69
|
+
save:
|
|
70
|
+
- do: record_save
|
|
71
|
+
args:
|
|
72
|
+
entity: request
|
|
73
|
+
fields:
|
|
74
|
+
area: '$state.area'
|
|
75
|
+
issue: '$state.issue'
|
|
76
|
+
phone: '$state.phone'
|
|
77
|
+
floor: '$state.floor'
|
|
78
|
+
notes: '$coalesce($state.notes, "")'
|
|
79
|
+
bind: saved
|
|
80
|
+
outputs:
|
|
81
|
+
ok:
|
|
82
|
+
- render:
|
|
83
|
+
render_intent: detail_card
|
|
84
|
+
body: '{$t("request.saved", { number: $saved.record_number })}'
|
|
85
|
+
buttons:
|
|
86
|
+
- { title: '{$t("request.btn_home")}', on_select: { invoke: home } }
|
|
87
|
+
memory_note: 'Saved visit request #{$saved.record_number}.'
|
|
88
|
+
- end: applied
|
|
89
|
+
invalid:
|
|
90
|
+
- assign: { state.phone: null }
|
|
91
|
+
- goto: intake
|
|
92
|
+
failed: [{ end: failed }]
|
|
93
|
+
|
|
94
|
+
no_areas:
|
|
95
|
+
- end: { status: aborted, body: '{$t("request.no_areas")}' }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flow_id: request
|
|
2
|
+
strings:
|
|
3
|
+
ask_area: '📍 في أي منطقة أنت؟'
|
|
4
|
+
ask_issue: '🔧 ما المشكلة؟'
|
|
5
|
+
ask_phone: '📱 رقم موبايلك، ليتصل بك الفني؟'
|
|
6
|
+
ask_floor: '🏢 في أي دور؟'
|
|
7
|
+
floor_error: 'من فضلك اكتب دورًا بين 0 و60.'
|
|
8
|
+
ask_notes: 'هل هناك شيء آخر يجب أن نعرفه؟'
|
|
9
|
+
confirm_header: '📋 من فضلك راجع طلبك'
|
|
10
|
+
preview: "📍 {area}\n🔧 {issue}\n📱 {phone}\n🏢 الدور {floor}\n📝 {notes}"
|
|
11
|
+
not_sent: 'حسنًا، لم نرسل أي طلب.'
|
|
12
|
+
saved: "✅ رقم الطلب {number}. سيتصل بك الفني لتحديد موعد."
|
|
13
|
+
btn_home: '🏠 القائمة'
|
|
14
|
+
no_areas: 'لا نستقبل طلبات في أي منطقة حتى الآن. من فضلك تحدث مع الاستقبال من القائمة.'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flow_id: request
|
|
2
|
+
strings:
|
|
3
|
+
ask_area: '📍 Which area are you in?'
|
|
4
|
+
ask_issue: '🔧 What is the problem?'
|
|
5
|
+
ask_phone: '📱 Your mobile number, so the technician can call you?'
|
|
6
|
+
ask_floor: '🏢 Which floor?'
|
|
7
|
+
floor_error: 'A floor between 0 and 60, please.'
|
|
8
|
+
ask_notes: 'Anything else we should know?'
|
|
9
|
+
confirm_header: '📋 Please check your request'
|
|
10
|
+
preview: "📍 {area}\n🔧 {issue}\n📱 {phone}\n🏢 Floor {floor}\n📝 {notes}"
|
|
11
|
+
not_sent: 'OK, nothing was sent.'
|
|
12
|
+
saved: "✅ Request number {number}. A technician will call you to agree a time."
|
|
13
|
+
btn_home: '🏠 Menu'
|
|
14
|
+
no_areas: 'We are not taking requests in any area yet. Please talk to reception from the menu.'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# A journey is the funnel the owner reads on the Analytics and Performance pages: stages are what
|
|
2
|
+
# the customer did; the goal is the outcome. Guide: `craft-measure` (the journey recipe).
|
|
3
|
+
id: request
|
|
4
|
+
label: { en: 'Asking for a visit', ar: 'طلب زيارة' }
|
|
5
|
+
lifecycle:
|
|
6
|
+
reentry: after_exit # a customer who asks again later is a second run
|
|
7
|
+
terminal: { goal: requested } # what completes a run
|
|
8
|
+
stages:
|
|
9
|
+
- { id: menu, order: 1, label: { en: 'Opened the menu', ar: 'فتح القائمة' } }
|
|
10
|
+
- { id: requested, order: 2, label: { en: 'Requested a visit', ar: 'طلب زيارة' } }
|
|
11
|
+
goals:
|
|
12
|
+
- { id: requested, stage: requested, label: { en: 'Visit requested', ar: 'تم طلب الزيارة' } }
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
# starter-kit —
|
|
2
|
-
#
|
|
3
|
-
# Entries here are shared across ALL flows in this pack (cross-flow labels,
|
|
4
|
-
# common error messages, status verbs). Flow-specific strings live in
|
|
5
|
-
# `flows/tools/<flow>.locale.ar.yaml`. Reference these via `$t("starter-kit.<key>")`
|
|
6
|
-
# — every key is namespaced (pack-level by `pack_id`; flow-level by `flow_id`).
|
|
7
|
-
# A bare `$t("<key>")` does NOT resolve (returns the raw key); the flow linter's
|
|
8
|
-
# `locale-key-not-namespaced` rule fails the build on a bare key.
|
|
1
|
+
# Pack-wide Arabic strings, read as `$t("starter-kit.<key>")` — or by `$enum_label` for a `choice`
|
|
2
|
+
# field's labels (`issue.<value>`). A flow's own strings live beside the flow. Guide: `craft-language`.
|
|
9
3
|
|
|
10
4
|
pack_id: starter-kit
|
|
11
5
|
strings:
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
issue.leak: '💧 تسريب مياه'
|
|
7
|
+
issue.power: '⚡ الكهرباء'
|
|
8
|
+
issue.ac: '❄️ التكييف'
|
|
9
|
+
issue.other: '🔧 شيء آخر'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Pack-wide English strings, read as `$t("starter-kit.<key>")` — or by `$enum_label` for a `choice`
|
|
2
|
+
# field's labels (`issue.<value>`). A flow's own strings live beside the flow. Guide: `craft-language`.
|
|
3
|
+
|
|
4
|
+
pack_id: starter-kit
|
|
5
|
+
strings:
|
|
6
|
+
issue.leak: '💧 Water leak'
|
|
7
|
+
issue.power: '⚡ Power'
|
|
8
|
+
issue.ac: '❄️ Air conditioning'
|
|
9
|
+
issue.other: '🔧 Something else'
|
|
@@ -1,65 +1,64 @@
|
|
|
1
|
-
# manifest.yaml —
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# re-validates the full schema on `octwin deploy`.
|
|
1
|
+
# manifest.yaml — the one file that declares your pack. Required: id, version (a STRING,
|
|
2
|
+
# e.g. '1.0.0'), description. Everything else grows with your domain. `octwin validate` runs the
|
|
3
|
+
# platform's full check. Guide: `octwin platform-kb pull`, then read `craft-manifest`.
|
|
5
4
|
|
|
6
5
|
id: starter-kit
|
|
7
|
-
version: 1.0.0
|
|
8
|
-
description: 'A
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
description: 'A starter pack — a home menu and a visit request. Edit it into your business.'
|
|
9
8
|
|
|
10
|
-
# Channels this pack supports + platform capabilities it needs. A pack with
|
|
11
|
-
# no media or search only needs `messaging`. Add embedding / vision / storage
|
|
12
|
-
# as your domain grows. (Your data model goes in `xrm.yaml` — packs have no
|
|
13
|
-
# database of their own.)
|
|
14
9
|
supported_channels: [whatsapp, web]
|
|
15
10
|
required_adapters: [messaging]
|
|
16
11
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
12
|
+
# The languages this pack speaks — held to the root locale.<lang>.yaml files, and every customer
|
|
13
|
+
# string must exist in each (a missing key refuses the deploy). Guide: `craft-language`.
|
|
14
|
+
supported_languages: [ar, en]
|
|
15
|
+
|
|
16
|
+
default_settings:
|
|
17
|
+
locale: ar # the language a customer reads before the platform knows theirs
|
|
18
|
+
|
|
19
|
+
# A VOICE NOTE cannot be blocked on WhatsApp, and an absent block means `passthrough` — no
|
|
20
|
+
# speech-to-text, so the AI Agent gets "a voice message arrived" and has to improvise.
|
|
21
|
+
# `transcribe` is right for a conversational pack. Guide: `craft-ux` §5.
|
|
23
22
|
inbound_preprocessing:
|
|
24
23
|
voicenote:
|
|
25
24
|
mode: transcribe
|
|
26
25
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
# How the pack looks in a catalog a business browses. Optional; without it the catalog shows your
|
|
27
|
+
# id and description. Guide: `craft-manifest` (listing).
|
|
28
|
+
# listing:
|
|
29
|
+
# title: 'My business'
|
|
30
|
+
# tagline: 'The one line a buyer reads first'
|
|
31
|
+
# category: retail
|
|
32
|
+
|
|
33
|
+
# Your chat face — the name, colour and welcome the web widget shows. It SEEDS the project's settings
|
|
34
|
+
# at install only, so get it right before the first install. Guide: `craft-manifest` (widget).
|
|
35
|
+
# widget:
|
|
36
|
+
# name: 'My business'
|
|
37
|
+
# accent: '#0369a1'
|
|
38
|
+
# welcome:
|
|
39
|
+
# en: 'Hi! How can we help?'
|
|
40
|
+
# ar: 'أهلاً! كيف نقدر نساعدك؟'
|
|
30
41
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# front door; add each new tool here as you build it.
|
|
42
|
+
# The flow tools. Each id is `flows/tools/<id>.flow.yaml` with a locale file per language. The HOME
|
|
43
|
+
# hub comes first — it is the pack's front door. Guide: `craft-ux` §1.
|
|
34
44
|
flows:
|
|
35
|
-
- home
|
|
36
|
-
-
|
|
45
|
+
- home # the menu
|
|
46
|
+
- request # a visit request — the `craft-collect` recipe; replace it with your own journey
|
|
37
47
|
|
|
38
|
-
# One agent. The platform's universal agent protocol is auto-appended by
|
|
39
|
-
# `createPackAgents` — `instructions:` carries only pack-supplied parts.
|
|
40
48
|
agents:
|
|
41
|
-
- id:
|
|
42
|
-
display_name:
|
|
43
|
-
# `openrouter
|
|
44
|
-
# model per
|
|
45
|
-
default_model:
|
|
46
|
-
#
|
|
49
|
+
- id: assistant
|
|
50
|
+
display_name: 'Assistant'
|
|
51
|
+
# A model id names its provider: `openrouter/<vendor>/<model>`. A bare id is refused.
|
|
52
|
+
# Operators can override the model per project in the console.
|
|
53
|
+
default_model: 'openrouter/google/gemini-3.1-flash-lite-preview'
|
|
54
|
+
# The flows this agent may call — its whole world. List the hub first. Guide: `craft-manifest`.
|
|
47
55
|
tools:
|
|
48
56
|
- home
|
|
49
|
-
-
|
|
57
|
+
- request
|
|
50
58
|
include_platform_protocol: true
|
|
51
|
-
# Instruction parts, joined with '\n\n'. `file:` paths are pack-root
|
|
52
|
-
# relative; `.md` files may use built-in placeholders like {{pack.id}}.
|
|
53
59
|
instructions:
|
|
54
60
|
- file: prompts/identity.md
|
|
55
|
-
# No working_memory
|
|
56
|
-
#
|
|
57
|
-
# a "call it every turn" instruction: an extra LLM generation on most turns,
|
|
58
|
-
# and small models (like the lite default above) parrot the
|
|
59
|
-
# <working_memory_data> envelope straight into user-visible replies. Session
|
|
60
|
-
# identity (name/lang/phone) already reaches the model via the platform's
|
|
61
|
-
# first-turn [ctx: …] header, and tools read it from the request context —
|
|
62
|
-
# so you gain nothing here. Leave it off unless you're on a large model and
|
|
63
|
-
# have a concrete slot schema to maintain.
|
|
61
|
+
# No `working_memory:` — it adds a tool call on most turns and small models echo it to the
|
|
62
|
+
# customer. The platform already gives the agent the customer's name, language and phone.
|
|
64
63
|
default_memory:
|
|
65
|
-
lastMessages: 10
|
|
64
|
+
lastMessages: 10
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# Two blocks, both auto-loaded by definePack (lang from default_settings.locale):
|
|
4
|
-
# • errors: copy the PLATFORM emits on failure paths (tool throw, workflow
|
|
5
|
-
# plumbing). Optional, but worth having so users never sit in
|
|
6
|
-
# silence. Schema: src/platform/manifest/messages.ts.
|
|
7
|
-
# • agents: per-agent loop copy, keyed by the manifest agent id. OPTIONAL —
|
|
8
|
-
# the platform ships default copy; resolveMessages merges this block
|
|
9
|
-
# (whole or partial) over it at turn start. Shown here as an example.
|
|
1
|
+
# Words the PLATFORM says for this pack in Arabic — when something fails, or the AI Agent cannot
|
|
2
|
+
# answer. Optional: the platform has its own; these replace them in your voice. Guide: `craft-language`.
|
|
10
3
|
|
|
11
4
|
errors:
|
|
12
5
|
failed:
|
|
@@ -14,7 +7,7 @@ errors:
|
|
|
14
7
|
unhandled_tool_error:
|
|
15
8
|
failure: { body: 'عذراً، حدث خطأ غير متوقع. حاول مرة أخرى بعد لحظة. 🙏' }
|
|
16
9
|
|
|
17
|
-
#
|
|
10
|
+
# Keyed by the manifest agent id (`assistant`).
|
|
18
11
|
agents:
|
|
19
12
|
assistant:
|
|
20
13
|
agentDisabled: 'الخدمة الذكية غير متاحة مؤقتاً. حاول مرة أخرى لاحقاً. 🙏'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Words the PLATFORM says for this pack in English — when something fails, or the AI Agent cannot
|
|
2
|
+
# answer. Optional: the platform has its own; these replace them in your voice. Guide: `craft-language`.
|
|
3
|
+
|
|
4
|
+
errors:
|
|
5
|
+
failed:
|
|
6
|
+
failure: { body: 'Sorry, something went wrong. Please try again.' }
|
|
7
|
+
unhandled_tool_error:
|
|
8
|
+
failure: { body: 'Sorry, something went wrong. Please try again in a moment. 🙏' }
|
|
9
|
+
|
|
10
|
+
# Keyed by the manifest agent id (`assistant`).
|
|
11
|
+
agents:
|
|
12
|
+
assistant:
|
|
13
|
+
agentDisabled: 'The assistant is not available right now. Please try again later. 🙏'
|
|
14
|
+
transientError: 'Sorry, a temporary error. Please try again in a moment. 🙏'
|
|
15
|
+
workflowExpired: 'Sorry, that session ended. Please start again.'
|
|
16
|
+
workflowError: 'Sorry, something went wrong finishing that. Please try again.'
|
|
17
|
+
workflowDisabled: 'This feature is not turned on right now.'
|
|
18
|
+
toolDisabled: 'This feature is not turned on right now.'
|
|
19
|
+
directTapError: 'Sorry, something went wrong. Please try again.'
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
You are the
|
|
2
|
-
|
|
3
|
-
On a greeting, an unclear or off-topic message, or "what can you do?", call the `home` tool — it shows the menu
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
You are the Assistant for this business. Keep replies short, warm and in the customer's language.
|
|
2
|
+
|
|
3
|
+
On a greeting, an unclear or off-topic message, or "what can you do?", call the `home` tool — it shows the menu. Pass a short one-line greeting as `message` when you can personalise it (for example, greet the customer by name); otherwise call `home` with no arguments.
|
|
4
|
+
|
|
5
|
+
When the customer reports a problem at home or asks for a visit, call `request`. Pass `issue` only when they named it in their own words.
|
|
6
|
+
|
|
7
|
+
Never promise what no tool did: a request exists only when the tool says it was saved.
|