infaira-canvas 0.1.9 → 0.2.0
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/README.md +7 -0
- package/dist/commands/init.js +23 -2
- package/dist/index.js +0 -0
- package/package.json +5 -3
- package/templates/ICan-Widget-Development-Guide.md +55 -1
- package/templates/ICan-Widget-Theming-Guide.md +14 -1
- package/templates/ican.d.ts +35 -1
- package/templates/index.html +18 -1
- package/templates/ui.html +13 -1
package/README.md
CHANGED
|
@@ -11,6 +11,13 @@ CLI toolkit for building and publishing widgets for the **InfAIra Canvas (ICan)*
|
|
|
11
11
|
1. **Scaffold** a new widget project with all required files
|
|
12
12
|
2. **Upload** a built widget bundle to an ICan portal
|
|
13
13
|
|
|
14
|
+
Widgets scaffolded with this CLI can call:
|
|
15
|
+
|
|
16
|
+
- **ICan native actions** — `icanContext.executeAction('Buildings', 'List', { siteId })`
|
|
17
|
+
- **Mythos workflow actions** — `icanContext.executeAction('mythos:Inventory', 'UpdateStock', { sku, qty })`
|
|
18
|
+
|
|
19
|
+
The only difference at the call site is the `mythos:` prefix on the model name. The ICan backend handles routing (set `MYTHOS_BASE_URL` on the backend to enable). See **section 2a** of the bundled `ICan-Widget-Development-Guide.md` for the full Mythos integration walkthrough.
|
|
20
|
+
|
|
14
21
|
---
|
|
15
22
|
|
|
16
23
|
## Installation
|
package/dist/commands/init.js
CHANGED
|
@@ -174,7 +174,7 @@ export interface IWidgetProps {
|
|
|
174
174
|
const ${componentName}: React.FC<IWidgetProps> = ({ icanContext, instanceId: _instanceId }) => {
|
|
175
175
|
const [data, setData] = React.useState<unknown>(null);
|
|
176
176
|
const [loading, setLoading] = React.useState(true);
|
|
177
|
-
const [error,
|
|
177
|
+
const [error, _setError] = React.useState<string | null>(null);
|
|
178
178
|
|
|
179
179
|
React.useEffect(() => {
|
|
180
180
|
if (!icanContext) {
|
|
@@ -184,7 +184,7 @@ const ${componentName}: React.FC<IWidgetProps> = ({ icanContext, instanceId: _in
|
|
|
184
184
|
// Example: fetch data from Orch
|
|
185
185
|
// icanContext.executeAction('MyModel', 'GetAll', {}, { json: true })
|
|
186
186
|
// .then((result) => { setData(result); setLoading(false); })
|
|
187
|
-
// .catch((err: unknown) => {
|
|
187
|
+
// .catch((err: unknown) => { _setError(String(err)); setLoading(false); });
|
|
188
188
|
setData('Hello from ${widgetName}!');
|
|
189
189
|
setLoading(false);
|
|
190
190
|
}, [icanContext]);
|
|
@@ -294,14 +294,35 @@ export interface IActionOptions {
|
|
|
294
294
|
executeImmediately?: boolean;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/** A Mythos action published and callable from this widget. Returned by
|
|
298
|
+
* IContextProvider.listMythosActions. Invoke one via
|
|
299
|
+
* executeAction('mythos:' + model_name, action_name, params). */
|
|
300
|
+
export interface IMythosAction {
|
|
301
|
+
model_id: string;
|
|
302
|
+
model_name: string;
|
|
303
|
+
action_id: string;
|
|
304
|
+
action_name: string;
|
|
305
|
+
description?: string;
|
|
306
|
+
capability?: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
297
309
|
export interface IContextProvider {
|
|
298
310
|
environment: 'dev' | 'prod';
|
|
299
311
|
orchUrl?: string;
|
|
300
312
|
userKey: string;
|
|
301
313
|
root: string;
|
|
302
314
|
scriptFiles?: string[];
|
|
315
|
+
/** Invoke a model action.
|
|
316
|
+
* - executeAction('Buildings', 'List', { siteId }) → ICan native
|
|
317
|
+
* - executeAction('mythos:Inventory', 'UpdateStock', p) → Mythos engine
|
|
318
|
+
* The 'mythos:' prefix is the only difference at the call site; the
|
|
319
|
+
* ICan backend routes the call appropriately. */
|
|
303
320
|
executeAction(model: string, action: string, parameters: unknown, options?: IActionOptions): Promise<unknown>;
|
|
304
321
|
executeService(app: string, service: string, parameters: unknown, options?: IActionOptions): Promise<unknown>;
|
|
322
|
+
/** List Mythos actions callable via executeAction('mythos:...', ...).
|
|
323
|
+
* Optional — present only when MYTHOS_BASE_URL is configured on the ICan
|
|
324
|
+
* backend. Treat absence as "Mythos integration disabled". */
|
|
325
|
+
listMythosActions?(): Promise<IMythosAction[]>;
|
|
305
326
|
fireEvent(eventId: string): Promise<void>;
|
|
306
327
|
hasAppRole(app: string, role: string): boolean;
|
|
307
328
|
themeName?: string;
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infaira-canvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "InfAIra Canvas CLI —
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "InfAIra Canvas CLI — scaffold widgets that talk to ICan and Mythos.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"infaira",
|
|
7
7
|
"ican",
|
|
8
|
+
"mythos",
|
|
8
9
|
"widget",
|
|
9
10
|
"cli",
|
|
10
|
-
"scaffold"
|
|
11
|
+
"scaffold",
|
|
12
|
+
"workflow"
|
|
11
13
|
],
|
|
12
14
|
"homepage": "https://infaira.co",
|
|
13
15
|
"license": "MIT",
|
|
@@ -38,7 +38,8 @@ In the portal, `icanContext` is always defined — the widget is never mounted u
|
|
|
38
38
|
| `language` | `string` | Active language code, e.g. `'en'` |
|
|
39
39
|
| `themeName` | `string \| undefined` | e.g. `'Dark'`, `'Glass Light'` |
|
|
40
40
|
| `themeType` | `'Dark' \| 'Light' \| 'Glass-Dark' \| 'Glass-Light' \| undefined` | Structured theme type for chart colours |
|
|
41
|
-
| `executeAction` | `(model, action, params?, options?) => Promise<unknown>` | Call an
|
|
41
|
+
| `executeAction` | `(model, action, params?, options?) => Promise<unknown>` | Call an action. ICan native by default; prefix `model` with `mythos:` to route to the Mythos engine instead. |
|
|
42
|
+
| `listMythosActions` | `() => Promise<IMythosAction[]> \| undefined` | List Mythos actions callable from this widget. Present only when the ICan backend has `MYTHOS_BASE_URL` configured. |
|
|
42
43
|
| `fireEvent` | `(eventId) => Promise<void>` | Trigger a portal event |
|
|
43
44
|
| `hasAppRole` | `(app, role) => boolean` | Check if the user has a role |
|
|
44
45
|
| `$L` | `(code, params?) => string` | Translate a localisation key |
|
|
@@ -73,6 +74,59 @@ The dev harness always sets `environment: 'dev'`. The portal always sets `enviro
|
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
77
|
+
## 2a. Calling Mythos actions — the `mythos:` prefix
|
|
78
|
+
|
|
79
|
+
ICan widgets can invoke actions running in the Mythos workflow engine through the exact same `executeAction` call, just by prefixing the `model` argument with `mythos:`. The host backend inspects the prefix and routes the request to the Mythos engine via the shared JWT.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// 1. Discover what's available (call once at mount and cache)
|
|
83
|
+
const actions = await icanContext.listMythosActions?.();
|
|
84
|
+
// → [{ model_id, model_name: 'Inventory', action_id, action_name: 'UpdateStock', description }]
|
|
85
|
+
|
|
86
|
+
// 2. Invoke a Mythos action — only the `mythos:` prefix differs from a
|
|
87
|
+
// native ICan call
|
|
88
|
+
const result = await icanContext.executeAction(
|
|
89
|
+
'mythos:Inventory', // <- the prefix is what triggers Mythos routing
|
|
90
|
+
'UpdateStock',
|
|
91
|
+
{ sku: 'ABC-123', qty: 5 },
|
|
92
|
+
);
|
|
93
|
+
// → { run_id: '...', status: 'ok', output?: {...} }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### When to use Mythos vs native
|
|
97
|
+
|
|
98
|
+
| Use Mythos when… | Use native ICan when… |
|
|
99
|
+
|---|---|
|
|
100
|
+
| The logic is a multi-step workflow with branches, retries, or external calls | The call is a direct read/write to a backing service (Locations, Assets, etc.) |
|
|
101
|
+
| You want the call recorded in Mythos's execution log + debugger | You don't need step-level tracing |
|
|
102
|
+
| Multiple widgets need to call the same business operation | The operation is widget-local |
|
|
103
|
+
| You want to publish the same logic over a REST endpoint too | The widget owns the orchestration |
|
|
104
|
+
|
|
105
|
+
### Failure modes to handle
|
|
106
|
+
|
|
107
|
+
`executeAction('mythos:...')` can reject with:
|
|
108
|
+
|
|
109
|
+
- **`mythos integration not configured`** — the ICan backend doesn't have `MYTHOS_BASE_URL` set. Surface this to the user and fall back gracefully.
|
|
110
|
+
- **`mythos model "X" not found or not published`** — the action either doesn't exist or isn't toggled `published: true` in Mythos.
|
|
111
|
+
- **HTTP / network errors** — same as any `executeAction` call.
|
|
112
|
+
|
|
113
|
+
`listMythosActions` is also `undefined` when integration is disabled — so guard with `?.()`.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
const handleRun = async () => {
|
|
117
|
+
try {
|
|
118
|
+
const r = await icanContext.executeAction('mythos:Inventory', 'UpdateStock', { sku, qty });
|
|
119
|
+
setResult(r);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
setError(e instanceof Error ? e.message : 'Failed');
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A working reference widget lives at `Widgets/ICan widgets/mythosdemo/` — pick an action, fill params, run, render the response.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
76
130
|
## 3. Widget settings panel — `IWidgetPropConfig`
|
|
77
131
|
|
|
78
132
|
Widgets can expose a settings form in the portal. Users open it by clicking the ⚙ button on a widget cell in edit mode. You define the fields in `registerWidget`:
|
|
@@ -594,7 +594,10 @@ interface IContextProvider {
|
|
|
594
594
|
orchUrl?: string;
|
|
595
595
|
apiKey?: string;
|
|
596
596
|
|
|
597
|
-
//
|
|
597
|
+
// Invoke an action. Prefix model with `mythos:` to route to the Mythos
|
|
598
|
+
// workflow engine instead of the default ICan native pipeline:
|
|
599
|
+
// executeAction('Buildings', 'List', { siteId }) → ICan native
|
|
600
|
+
// executeAction('mythos:Inventory', 'UpdateStock', { ... }) → Mythos engine
|
|
598
601
|
executeAction(
|
|
599
602
|
model: string,
|
|
600
603
|
action: string,
|
|
@@ -602,6 +605,16 @@ interface IContextProvider {
|
|
|
602
605
|
options?: IActionOptions
|
|
603
606
|
): Promise<unknown>;
|
|
604
607
|
|
|
608
|
+
// List Mythos actions callable via executeAction('mythos:...', ...).
|
|
609
|
+
// Optional — present only when MYTHOS_BASE_URL is set on the ICan backend.
|
|
610
|
+
listMythosActions?(): Promise<Array<{
|
|
611
|
+
model_id: string;
|
|
612
|
+
model_name: string;
|
|
613
|
+
action_id: string;
|
|
614
|
+
action_name: string;
|
|
615
|
+
description?: string;
|
|
616
|
+
}>>;
|
|
617
|
+
|
|
605
618
|
executeService(
|
|
606
619
|
app: string,
|
|
607
620
|
service: string,
|
package/templates/ican.d.ts
CHANGED
|
@@ -701,17 +701,51 @@ declare module "ican/context" {
|
|
|
701
701
|
email?: string;
|
|
702
702
|
roles?: string[];
|
|
703
703
|
};
|
|
704
|
-
/**
|
|
704
|
+
/**
|
|
705
|
+
* Invoke a model action on this dashboard.
|
|
706
|
+
*
|
|
707
|
+
* Routing happens server-side by inspecting `model`:
|
|
708
|
+
* - `executeAction('Buildings', 'List', { siteId })`
|
|
709
|
+
* → ICan native action (existing Orch pipeline).
|
|
710
|
+
* - `executeAction('mythos:Inventory', 'UpdateStock', { sku, qty })`
|
|
711
|
+
* → routed to the Mythos engine via the `mythos:` prefix. Resolves
|
|
712
|
+
* to the published Mythos action with that (model_name, action_name)
|
|
713
|
+
* and POSTs to `/api/models/{id}/actions/{id}/run`.
|
|
714
|
+
*
|
|
715
|
+
* Both call shapes return a Promise that resolves to the action's output
|
|
716
|
+
* envelope. For Mythos, the envelope is `{ run_id, status, output? }`.
|
|
717
|
+
*/
|
|
705
718
|
executeAction(
|
|
706
719
|
model: string,
|
|
707
720
|
action: string,
|
|
708
721
|
params?: Record<string, unknown>
|
|
709
722
|
): Promise<unknown>;
|
|
723
|
+
/**
|
|
724
|
+
* List every Mythos action published and callable from this widget.
|
|
725
|
+
* Optional — present only when the ICan backend has MYTHOS_BASE_URL set.
|
|
726
|
+
* Widgets that build action pickers (settings panels, etc.) call this
|
|
727
|
+
* once at mount and cache the result themselves.
|
|
728
|
+
*/
|
|
729
|
+
listMythosActions?: () => Promise<IMythosAction[]>;
|
|
710
730
|
/** Resolve a relative ICan backend path against the portal base URL. */
|
|
711
731
|
resolveUrl(path: string): string;
|
|
712
732
|
/** Logging helper namespaced to this widget instance. */
|
|
713
733
|
log(level: "info" | "warn" | "error", ...args: unknown[]): void;
|
|
714
734
|
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* A single Mythos action published as callable from a widget. Returned by
|
|
738
|
+
* `icanContext.listMythosActions()`. To invoke one, call
|
|
739
|
+
* `icanContext.executeAction('mythos:' + model_name, action_name, params)`.
|
|
740
|
+
*/
|
|
741
|
+
export interface IMythosAction {
|
|
742
|
+
model_id: string;
|
|
743
|
+
model_name: string;
|
|
744
|
+
action_id: string;
|
|
745
|
+
action_name: string;
|
|
746
|
+
description?: string;
|
|
747
|
+
capability?: string;
|
|
748
|
+
}
|
|
715
749
|
}
|
|
716
750
|
|
|
717
751
|
// ─── Global runtime declarations ─────────────────────────────────────────────
|
package/templates/index.html
CHANGED
|
@@ -1419,13 +1419,30 @@
|
|
|
1419
1419
|
orchUrl: localStorage.getItem(LS_ORCH_URL) || '',
|
|
1420
1420
|
apiKey: localStorage.getItem(LS_API_KEY) || '',
|
|
1421
1421
|
executeAction: function (model, action, params) {
|
|
1422
|
-
|
|
1422
|
+
// Mythos-routed calls use the `mythos:<ModelName>` prefix in production.
|
|
1423
|
+
// In the dev harness we just log them distinctively so authors can see
|
|
1424
|
+
// whether their widget is targeting ICan native or Mythos.
|
|
1425
|
+
var isMythos = typeof model === 'string' && model.toLowerCase().indexOf('mythos:') === 0;
|
|
1426
|
+
console.log('[ICan Dev]' + (isMythos ? ' [Mythos]' : '') + ' executeAction:', model, action, params);
|
|
1427
|
+
if (isMythos) {
|
|
1428
|
+
// Mock the Mythos response envelope: { run_id, status, output }.
|
|
1429
|
+
return Promise.resolve({ run_id: 'dev-' + Date.now(), status: 'ok', output: {} });
|
|
1430
|
+
}
|
|
1423
1431
|
return Promise.resolve([]);
|
|
1424
1432
|
},
|
|
1425
1433
|
executeService: function (app, service, params) {
|
|
1426
1434
|
console.log('[ICan Dev] executeService:', app, service, params);
|
|
1427
1435
|
return Promise.resolve(null);
|
|
1428
1436
|
},
|
|
1437
|
+
listMythosActions: function () {
|
|
1438
|
+
// Dev stub — returns a couple of synthetic actions so widgets that
|
|
1439
|
+
// populate dropdowns from discovery don't render empty.
|
|
1440
|
+
console.log('[ICan Dev] listMythosActions');
|
|
1441
|
+
return Promise.resolve([
|
|
1442
|
+
{ model_id: 'dev-model-1', model_name: 'Inventory', action_id: 'dev-action-1', action_name: 'UpdateStock', description: 'Dev stub' },
|
|
1443
|
+
{ model_id: 'dev-model-2', model_name: 'Buildings', action_id: 'dev-action-2', action_name: 'List', description: 'Dev stub' },
|
|
1444
|
+
]);
|
|
1445
|
+
},
|
|
1429
1446
|
fireEvent: function (id) {
|
|
1430
1447
|
console.log('[ICan Dev] fireEvent:', id);
|
|
1431
1448
|
return Promise.resolve();
|
package/templates/ui.html
CHANGED
|
@@ -1123,13 +1123,25 @@
|
|
|
1123
1123
|
orchUrl: localStorage.getItem(LS_ORCH_URL) || '',
|
|
1124
1124
|
apiKey: localStorage.getItem(LS_API_KEY) || '',
|
|
1125
1125
|
executeAction: function (model, action, params) {
|
|
1126
|
-
|
|
1126
|
+
// Mythos-routed calls use the `mythos:<ModelName>` prefix.
|
|
1127
|
+
var isMythos = typeof model === 'string' && model.toLowerCase().indexOf('mythos:') === 0;
|
|
1128
|
+
console.log('[ICan Dev]' + (isMythos ? ' [Mythos]' : '') + ' executeAction:', model, action, params);
|
|
1129
|
+
if (isMythos) {
|
|
1130
|
+
return Promise.resolve({ run_id: 'dev-' + Date.now(), status: 'ok', output: {} });
|
|
1131
|
+
}
|
|
1127
1132
|
return Promise.resolve([]);
|
|
1128
1133
|
},
|
|
1129
1134
|
executeService: function (app, service, params) {
|
|
1130
1135
|
console.log('[ICan Dev] executeService:', app, service, params);
|
|
1131
1136
|
return Promise.resolve(null);
|
|
1132
1137
|
},
|
|
1138
|
+
listMythosActions: function () {
|
|
1139
|
+
console.log('[ICan Dev] listMythosActions');
|
|
1140
|
+
return Promise.resolve([
|
|
1141
|
+
{ model_id: 'dev-model-1', model_name: 'Inventory', action_id: 'dev-action-1', action_name: 'UpdateStock', description: 'Dev stub' },
|
|
1142
|
+
{ model_id: 'dev-model-2', model_name: 'Buildings', action_id: 'dev-action-2', action_name: 'List', description: 'Dev stub' },
|
|
1143
|
+
]);
|
|
1144
|
+
},
|
|
1133
1145
|
fireEvent: function (id) {
|
|
1134
1146
|
console.log('[ICan Dev] fireEvent:', id);
|
|
1135
1147
|
return Promise.resolve();
|