yaml-flow 5.0.0 → 5.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.
Files changed (105) hide show
  1. package/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +103 -24
  2. package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
  3. package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
  4. package/browser/board-livegraph-engine.js.map +1 -0
  5. package/browser/live-cards.js +362 -38
  6. package/browser/live-cards.schema.json +20 -4
  7. package/dist/board-livegraph-runtime/index.cjs +210 -14
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.d.cts +49 -5
  10. package/dist/board-livegraph-runtime/index.d.ts +49 -5
  11. package/dist/board-livegraph-runtime/index.js +209 -15
  12. package/dist/board-livegraph-runtime/index.js.map +1 -1
  13. package/dist/card-compute/index.cjs +63 -7
  14. package/dist/card-compute/index.cjs.map +1 -1
  15. package/dist/card-compute/index.d.cts +2 -2
  16. package/dist/card-compute/index.d.ts +2 -2
  17. package/dist/card-compute/index.js +63 -7
  18. package/dist/card-compute/index.js.map +1 -1
  19. package/dist/cli/board-live-cards-cli.cjs +664 -75
  20. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  21. package/dist/cli/board-live-cards-cli.d.cts +33 -5
  22. package/dist/cli/board-live-cards-cli.d.ts +33 -5
  23. package/dist/cli/board-live-cards-cli.js +661 -76
  24. package/dist/cli/board-live-cards-cli.js.map +1 -1
  25. package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
  26. package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
  27. package/dist/continuous-event-graph/index.cjs +47 -14
  28. package/dist/continuous-event-graph/index.cjs.map +1 -1
  29. package/dist/continuous-event-graph/index.d.cts +9 -9
  30. package/dist/continuous-event-graph/index.d.ts +9 -9
  31. package/dist/continuous-event-graph/index.js +47 -14
  32. package/dist/continuous-event-graph/index.js.map +1 -1
  33. package/dist/event-graph/index.cjs +29 -12
  34. package/dist/event-graph/index.cjs.map +1 -1
  35. package/dist/event-graph/index.d.cts +5 -5
  36. package/dist/event-graph/index.d.ts +5 -5
  37. package/dist/event-graph/index.js +29 -12
  38. package/dist/event-graph/index.js.map +1 -1
  39. package/dist/index.cjs +93 -20
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +7 -7
  42. package/dist/index.d.ts +7 -7
  43. package/dist/index.js +93 -20
  44. package/dist/index.js.map +1 -1
  45. package/dist/inference/index.cjs +29 -12
  46. package/dist/inference/index.cjs.map +1 -1
  47. package/dist/inference/index.d.cts +2 -2
  48. package/dist/inference/index.d.ts +2 -2
  49. package/dist/inference/index.js +29 -12
  50. package/dist/inference/index.js.map +1 -1
  51. package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
  52. package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
  53. package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
  54. package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
  55. package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
  56. package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
  57. package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
  58. package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
  59. package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
  60. package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
  61. package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
  62. package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
  63. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
  64. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
  65. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
  66. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
  67. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  68. package/examples/example-board/agent-instructions.md +603 -0
  69. package/examples/example-board/cards/card-concentration.json +42 -0
  70. package/examples/example-board/cards/card-market-prices.json +51 -0
  71. package/examples/example-board/cards/card-portfolio-action.json +19 -0
  72. package/examples/example-board/cards/card-portfolio-risks.json +19 -0
  73. package/examples/example-board/cards/card-portfolio-value.json +62 -0
  74. package/examples/example-board/cards/card-portfolio.json +44 -0
  75. package/examples/example-board/demo-chat-handler.js +373 -33
  76. package/examples/example-board/demo-server-config.json +5 -0
  77. package/examples/example-board/demo-server.js +83 -7
  78. package/examples/example-board/demo-shell-browser.html +75 -207
  79. package/examples/example-board/demo-shell-with-server.html +14 -9
  80. package/examples/example-board/demo-shell.html +1 -1
  81. package/examples/example-board/demo-task-executor.js +259 -41
  82. package/package.json +6 -2
  83. package/schema/live-cards.schema.json +20 -4
  84. package/browser/board-livegraph-runtime.js.map +0 -1
  85. package/examples/example-board/board.yaml +0 -23
  86. package/examples/example-board/bootstrap_payload.json +0 -1
  87. package/examples/example-board/cards/card-chain-region-alert.json +0 -39
  88. package/examples/example-board/cards/card-chain-region-totals.json +0 -26
  89. package/examples/example-board/cards/card-chain-top-region.json +0 -24
  90. package/examples/example-board/cards/card-ex-actions.json +0 -32
  91. package/examples/example-board/cards/card-ex-chart.json +0 -30
  92. package/examples/example-board/cards/card-ex-filter.json +0 -36
  93. package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
  94. package/examples/example-board/cards/card-ex-form.json +0 -91
  95. package/examples/example-board/cards/card-ex-list.json +0 -22
  96. package/examples/example-board/cards/card-ex-markdown.json +0 -17
  97. package/examples/example-board/cards/card-ex-metric.json +0 -19
  98. package/examples/example-board/cards/card-ex-narrative.json +0 -36
  99. package/examples/example-board/cards/card-ex-source-http.json +0 -28
  100. package/examples/example-board/cards/card-ex-source.json +0 -21
  101. package/examples/example-board/cards/card-ex-status.json +0 -35
  102. package/examples/example-board/cards/card-ex-table.json +0 -30
  103. package/examples/example-board/cards/card-ex-todo.json +0 -29
  104. package/examples/example-board/mock.db +0 -15
  105. package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
@@ -0,0 +1,603 @@
1
+ # Agent Instructions — Authoring yaml-flow Boards & Cards
2
+
3
+ ## What Is This?
4
+
5
+ A **board** is a `board.yaml` file plus a folder of **card** JSON files. Cards are purely declarative — the runtime (board-livegraph-runtime) owns all state mutation and reactivity. Cards never imperative-call each other; they declare data dependencies and the runtime handles everything.
6
+
7
+ ---
8
+
9
+ ## Board File (`board.yaml`)
10
+
11
+ ```yaml
12
+ name: My Dashboard
13
+ desc: Short description
14
+
15
+ connects:
16
+ - id: my-db
17
+ type: script # or: http
18
+ url: https://... # for http type
19
+ desc: Human description
20
+
21
+ vocabulary:
22
+ tokens:
23
+ healthy: { color: "green", label: "Healthy" }
24
+ warning: { color: "orange", label: "Warning" }
25
+ alert: { color: "red", label: "Alert" }
26
+ ```
27
+
28
+ `connects` entries describe available data connections. Their fields are passed to the task-executor and are not schema-enforced beyond `id` and `type`.
29
+
30
+ ---
31
+
32
+ ## Card File Structure
33
+
34
+ ```json
35
+ {
36
+ "id": "my-card",
37
+ "meta": { "title": "Card Title", "tags": ["tag1"], "desc": "What this card does" },
38
+
39
+ "requires": ["token-a", "token-b"],
40
+
41
+ "sources": [
42
+ { "bindTo": "raw", "outputFile": "my-card-raw.json", /* task-executor fields */ }
43
+ ],
44
+
45
+ "compute": [
46
+ { "bindTo": "result", "expr": "/* JSONata expression */" }
47
+ ],
48
+
49
+ "provides": [
50
+ { "bindTo": "published-token", "src": "computed_values.result" }
51
+ ],
52
+
53
+ "view": {
54
+ "elements": [ /* see Element Kinds below */ ],
55
+ "layout": {
56
+ "board": { "col": 4, "order": 1 },
57
+ "canvas": { "x": 50, "y": 50, "w": 280, "h": 180 }
58
+ },
59
+ "features": { "refresh": true, "chat": true }
60
+ },
61
+
62
+ "card_data": { /* initial mutable state */ }
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Strict Compute Order (within a card)
69
+
70
+ ### Stage 1 — Sources
71
+ - **Runs first.** Parameterised by `requires.*` and `card_data.*` only.
72
+ - Each entry: `{ "bindTo": "key", "outputFile": "cache.json", ...customFields }`
73
+ - `bindTo` → key under `fetched_sources`
74
+ - `outputFile` → where the fetched result is cached
75
+ - `customFields` → interpreted by the registered **task-executor** (examples: `mock`, `copilot`, `http`, `script`)
76
+ - Produces: `fetched_sources.*`
77
+
78
+ ### Stage 2 — Compute
79
+ - **Runs after sources.** Reads `requires.*`, `fetched_sources.*`, `card_data.*`.
80
+ - Each entry: `{ "bindTo": "key", "expr": "<JSONata>" }`
81
+ - Produces: `computed_values.*`
82
+
83
+ ### Stage 3 — Views & Provides
84
+ - **Resolved last.** Can reference all four namespaces:
85
+ `requires.*`, `fetched_sources.*`, `card_data.*`, `computed_values.*`
86
+ - `view.elements[].data.bind` paths are resolved here.
87
+ - `provides[].src` paths are resolved here and published to the graph.
88
+
89
+ ---
90
+
91
+ ## provides / requires — The Dependency Graph
92
+
93
+ ```json
94
+ // Publishing a token:
95
+ "provides": [
96
+ { "bindTo": "orders", "src": "fetched_sources.raw" },
97
+ { "bindTo": "regionTotals","src": "computed_values.byRegion" },
98
+ { "bindTo": "my-card", "src": "card_data" }
99
+ ]
100
+
101
+ // Consuming tokens:
102
+ "requires": ["orders", "selections", "my-card"]
103
+ ```
104
+
105
+ - `provides` maps a token key to a dot-path inside this card's own namespaces.
106
+ - `requires` is a plain array of token key strings.
107
+ - In compute/source expressions, consumed tokens are at `requires.<key>`.
108
+ - For hyphenated keys, use JSONata `$lookup(requires, 'my-card')`.
109
+ - **These declarations are the only wiring needed** — the runtime builds the reactive DAG automatically.
110
+
111
+ ---
112
+
113
+ ## Reactivity
114
+
115
+ Every card is a live entity. Any of these events triggers automatic recompute of all downstream dependents in dependency order:
116
+ - A source finishes fetching (`fetched_sources` changes)
117
+ - User edits `card_data` via a form/filter/todo/actions element
118
+ - An upstream card's `provides` value changes
119
+ - A card definition is updated via server API from the UI
120
+
121
+ **Authors never trigger recompute manually — just declare the data shape.**
122
+
123
+ ---
124
+
125
+ ## Task Completion
126
+
127
+ Task completion is determined by one rule: **a card is complete when all non-optional `sources[]` have been fetched**.
128
+
129
+ If completion requires a judgment call — e.g. "is the data sufficient?", "does this narrative indicate done?" — model it as data using the standard source → compute → provides chain (see LLM source pattern below). The card is complete when that source has been fetched.
130
+
131
+ ---
132
+
133
+ ## Element Kinds Reference
134
+
135
+ ### `metric`
136
+ Single KPI value.
137
+ ```json
138
+ { "kind": "metric", "label": "Revenue", "data": { "bind": "computed_values.total" } }
139
+ ```
140
+
141
+ ### `badge`
142
+ Colour-coded status pill using Bootstrap colour names (`success`, `warning`, `danger`, `info`, `secondary`).
143
+ ```json
144
+ { "kind": "badge", "data": { "bind": "computed_values.health", "colorMap": { "Healthy": "success", "Low": "danger" } } }
145
+ ```
146
+
147
+ ### `text`
148
+ Plain or styled text. `style`: `muted`, `muted-italic`. Use `hideIfEmpty: true` to suppress if blank.
149
+ ```json
150
+ { "kind": "text", "label": "Note", "style": "muted-italic", "data": { "bind": "card_data.note", "hideIfEmpty": true } }
151
+ ```
152
+
153
+ ### `list`
154
+ Ordered list from an array. Optional `template` for string interpolation: `"{field1} — {field2}"`.
155
+ ```json
156
+ { "kind": "list", "data": { "bind": "computed_values.topItems", "template": "{name} — ${amount}" } }
157
+ ```
158
+
159
+ ### `table`
160
+ Data table. `columns` selects fields. `sortable: true` enables click-to-sort. `maxRows` caps display.
161
+ ```json
162
+ { "kind": "table", "data": { "bind": "fetched_sources.raw", "columns": ["id","name","value"], "sortable": true, "maxRows": 20 } }
163
+ ```
164
+
165
+ ### `editable-table`
166
+ Inline-editable table. Each cell is an `<input>`; changes save on blur. `writeTo` persists the updated array back to `card_data`. `columns` controls which fields appear. Optional `schema.properties` per column sets `type` (`number`/`integer` renders a numeric input). `addRow` (default `true`) shows a "+ Add row" button; `deleteRow` (default `true`) shows per-row delete buttons.
167
+ ```json
168
+ {
169
+ "kind": "editable-table",
170
+ "label": "Holdings",
171
+ "data": {
172
+ "bind": "card_data.holdings",
173
+ "writeTo": "card_data.holdings",
174
+ "columns": ["ticker", "quantity"],
175
+ "schema": {
176
+ "properties": {
177
+ "quantity": { "type": "number" }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+ Pair with a `provides` pointing at `card_data.holdings` so downstream cards receive the live array. Use `addRow: false` / `deleteRow: false` to make the table append-only or read-fixed-length.
184
+
185
+ ### `chart`
186
+ Chart.js chart. `chartType`: `bar`, `line`, `pie`, `doughnut`, etc. `chartOptions` passed to Chart.js.
187
+ ```json
188
+ { "kind": "chart", "label": "By Region", "data": { "bind": "computed_values.regionCounts", "chartType": "bar", "chartOptions": { "indexAxis": "x" } } }
189
+ ```
190
+
191
+ ### `filter`
192
+ Interactive filter controls. `writeTo` persists selections into `card_data`. `fields` is a JSON Schema describing the filter fields.
193
+ ```json
194
+ {
195
+ "kind": "filter",
196
+ "data": {
197
+ "bind": "computed_values",
198
+ "writeTo": "card_data.fieldValues",
199
+ "fields": {
200
+ "type": "object",
201
+ "properties": {
202
+ "region": { "type": "string", "title": "Region" },
203
+ "product": { "type": "string", "title": "Product" }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+ Pair with a `provides` to publish `card_data.fieldValues` as a named token for downstream cards.
210
+
211
+ ### `form`
212
+ Full editable form. `writeTo` path in `card_data`. `fields` is a JSON Schema (supports `enum`, `type`, `minimum`, `maximum`, `required`, etc.).
213
+ ```json
214
+ {
215
+ "kind": "form",
216
+ "label": "Settings",
217
+ "data": {
218
+ "writeTo": "card_data.prefs",
219
+ "fields": {
220
+ "type": "object",
221
+ "properties": {
222
+ "region": { "type": "string", "title": "Region", "enum": ["North","South","East","West"] },
223
+ "limit": { "type": "number", "title": "Limit", "minimum": 0 }
224
+ },
225
+ "required": ["region"]
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### `todo`
232
+ Interactive checklist. Items are `{ text, done }` objects stored in `card_data`.
233
+ ```json
234
+ { "kind": "todo", "label": "Tasks", "data": { "bind": "card_data.items", "writeTo": "card_data.items", "placeholder": "Add a task…" } }
235
+ ```
236
+
237
+ ### `actions`
238
+ Button row. Events bubble to the board-level chat/action handler. `style` is a Bootstrap colour name.
239
+ ```json
240
+ {
241
+ "kind": "actions",
242
+ "label": "Workflow",
243
+ "data": {
244
+ "buttons": [
245
+ { "id": "approve", "label": "Approve", "style": "success" },
246
+ { "id": "escalate", "label": "Escalate", "style": "danger" }
247
+ ]
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### `markdown`
253
+ Free-text Markdown rendered in the card. Typically bound to a `card_data` string field.
254
+ ```json
255
+ { "kind": "markdown", "data": { "bind": "card_data.content" } }
256
+ ```
257
+ Set initial content in `card_data.content`.
258
+
259
+ ### `narrative`
260
+ AI-generated streaming text. Bind to a `fetched_sources` path populated by a `copilot` source.
261
+ ```json
262
+ { "kind": "narrative", "data": { "bind": "fetched_sources.raw" } }
263
+ ```
264
+ Pair with `"features": { "refresh": true, "chat": true }` in `view`.
265
+
266
+ ### `notes`
267
+ Editable free-text notes field (similar to markdown but editable by the user).
268
+
269
+ ### `alert`
270
+ Alert/callout display element.
271
+
272
+ ### `custom`
273
+ Custom element kind — behaviour defined by the host application.
274
+
275
+ ---
276
+
277
+ ## Common Card Patterns
278
+
279
+ ### Root source card
280
+ ```
281
+ sources[mock/http/script] → fetched_sources.raw
282
+ provides: [{ bindTo: "orders", src: "fetched_sources.raw" }]
283
+ view: table showing the raw data
284
+ ```
285
+
286
+ ### Compute chain card
287
+ ```
288
+ requires: ["orders"]
289
+ compute: JSONata aggregation → computed_values.result
290
+ provides: [{ bindTo: "regionTotals", src: "computed_values.result" }]
291
+ view: table or metric
292
+ ```
293
+
294
+ ### Multi-level chain (4 levels example)
295
+ ```
296
+ card-source provides "orders"
297
+ card-totals requires "orders" provides "regionTotals"
298
+ card-top requires "regionTotals" provides "topRegion"
299
+ card-alert requires "topRegion", "regionTotals" (no further provides)
300
+ ```
301
+
302
+ ### Filter → filtered table
303
+ ```
304
+ card-filter provides "selections" (src: card_data.fieldValues)
305
+ card-table requires "orders", "selections"
306
+ compute: $filter(requires.orders, fn → match selections)
307
+ ```
308
+
309
+ ### Form → dependent card
310
+ ```
311
+ card-form provides "card-ex-form" (src: card_data)
312
+ card-child requires "card-ex-form"
313
+ compute: $lookup(requires, 'card-ex-form').prefs.field
314
+ ```
315
+
316
+ ### LLM verdict card (completion gating via source)
317
+ ```
318
+ requires: ["some-data"]
319
+ sources: [{ bindTo: "verdict", outputFile: "...", copilot: { prompt_template: "..." } }]
320
+ compute: [{ bindTo: "isReady", expr: "fetched_sources.verdict.isTaskCompleted" }]
321
+ provides: [{ bindTo: "readiness-verdict", src: "fetched_sources.verdict" }]
322
+ view: badge(computed_values.isReady, colorMap) + text(fetched_sources.verdict.reason)
323
+ ```
324
+ The task executor calls the LLM, writes `{ isTaskCompleted: bool, reason: string }` to `--out`.
325
+ The card is complete when the source is fetched. Downstream cards `requires: ["readiness-verdict"]`.
326
+
327
+ ---
328
+
329
+ ## Card Design Principles & Layout
330
+
331
+ See [agent-instructions-cardlayout.md](agent-instructions-cardlayout.md).
332
+
333
+ ---
334
+
335
+ ## Source `customFields` and the Task Executor
336
+
337
+ Every field on a source entry beyond `bindTo` and `outputFile` is a **customField**. The runtime passes the entire source object unchanged to the registered task executor — the executor is the sole interpreter of these fields.
338
+
339
+ > **Key principle:** The card author and the task executor author must agree on which customFields are supported. If a card uses `"http": {...}` but the executor only handles `"mock"` and `"copilot"`, the source fetch will fail. The executor must be designed to handle every source kind combination used in your board's cards.
340
+
341
+ Common customField conventions (demo executor supports `mock` and `copilot`):
342
+
343
+ | Field | Meaning |
344
+ |---|---|
345
+ | `"mock": "key"` | Reads from `mock.db` by key — local dev only |
346
+ | `"copilot": { "prompt_template": "...", "args": {} }` | LLM call; `{{key}}` interpolated from `_requires`, `_sourcesData`, `_computed_values`, then explicit `args` |
347
+ | `"prompt_template": "..."` | Shorthand top-level LLM call (equivalent to `copilot.prompt_template`) |
348
+ | `"http": { "url": "...", "method": "GET" }` | HTTP/REST — implement in your executor |
349
+ | `"graphapi": { "query": "..." }` | Microsoft Graph API — implement in your executor |
350
+ | `"script": { "path": "...", "args": {} }` | Local script — implement in your executor |
351
+ | `"teams"`, `"mail"`, `"incidentdb"` | Any domain integration — define in your executor |
352
+
353
+ Sources can reference upstream data in their customFields (e.g. `"url": "https://api.example.com/{{orderId}}"`) — the executor receives `_requires`, `_sourcesData`, `_computed_values` to resolve such references. See [Task Executor Protocol](#task-executor-protocol) for the full `--in` payload shape.
354
+
355
+ ### Optional source field
356
+ - `optionalForCompletionGating: true` — marks this source as optional for default task-completion gating. If set, the card can complete even if this source hasn't been fetched yet.
357
+
358
+ ## LLM Calls — Use a Source
359
+
360
+ **All LLM calls belong in sources[], handled by the task executor.** There is one mechanism for external calls — sources.
361
+
362
+ To incorporate LLM reasoning into a card:
363
+
364
+ 1. Add a source entry with a `copilot` (or equivalent) customField and an `outputFile`.
365
+ 2. The task executor calls the LLM and writes the result JSON to `--out`.
366
+ 3. Card compute reads `fetched_sources.<bindTo>` and derives tokens from it.
367
+ 4. The card provides those tokens downstream like any other.
368
+
369
+ ```json
370
+ "sources": [
371
+ {
372
+ "bindTo": "verdict",
373
+ "outputFile": "my-card-verdict.json",
374
+ "copilot": {
375
+ "prompt_template": "Given this data: {{positions}} — is the portfolio sufficiently diversified? Return JSON: { \"isTaskCompleted\": bool, \"reason\": string }"
376
+ }
377
+ }
378
+ ]
379
+ ```
380
+
381
+ If the LLM needs computed values (which compute first), chain two cards: Card A computes → Card B `requires` Card A's provides → Card B's source calls the LLM with those values.
382
+
383
+ > **One mechanism for everything.** Sources → compute → provides is the complete model. Every card an agent authors follows this shape regardless of whether the data comes from a database, an API, or an LLM.
384
+
385
+ ---
386
+
387
+ ## Validating Cards
388
+
389
+ ### CLI (recommended for authoring)
390
+ ```bash
391
+ # Validate all cards in example-board
392
+ npm run validate:cards -- "examples/example-board/cards/*.json"
393
+
394
+ # Validate any glob pattern
395
+ npm run validate:cards -- "path/to/cards/*.json"
396
+ ```
397
+ Uses `validateLiveCardDefinition` — structural + schema checks, reports all errors per file.
398
+
399
+ ### Programmatic
400
+ ```typescript
401
+ import { validateLiveCardSchema } from 'yaml-flow/card-compute';
402
+ // or for the most thorough check:
403
+ import { validateLiveCardDefinition } from 'yaml-flow/src/card-compute/schema-validator.js';
404
+
405
+ const result = validateLiveCardDefinition(cardObject);
406
+ if (!result.ok) {
407
+ console.error(result.errors); // string[]
408
+ }
409
+ ```
410
+
411
+ | Function | Validates |
412
+ |---|---|
413
+ | `validateLiveCardDefinition` | Full structural + schema check (most thorough, used by CLI) |
414
+ | `validateLiveCardSchema` | AJV JSON Schema check against `schema/live-cards.schema.json` |
415
+ | `validateLiveCardRuntimeExpressions` | JSONata expression syntax in `compute[].expr` |
416
+ | `validateLiveCard` | Basic structural shape check |
417
+
418
+ ### Schema reference
419
+ When in doubt about allowed fields, consult:
420
+ - `yaml-flow/schema/live-cards.schema.json` — canonical JSON Schema for cards
421
+ - `yaml-flow/browser/live-cards.schema.json` — browser-bundled copy (same content)
422
+
423
+ ### What the validator checks
424
+ - `id` required, non-empty string
425
+ - `card_data` required, must be an object
426
+ - `requires` must be array of strings (if present)
427
+ - `provides` must be array of `{ bindTo: string, src: string }` (if present)
428
+ - `compute[]` each entry must have `bindTo` + `expr` strings
429
+ - `sources[]` each entry must have `bindTo` + `outputFile` strings; both must be unique across the array
430
+ - `view.elements` required, non-empty; each element must have a valid `kind`
431
+ - Top-level unknown keys are flagged as errors
432
+ - Valid element `kind` values: `metric`, `table`, `editable-table`, `chart`, `form`, `filter`, `list`, `notes`, `todo`, `alert`, `narrative`, `badge`, `text`, `markdown`, `custom`
433
+
434
+ ---
435
+
436
+ ## mock.db
437
+
438
+ A JSON file at the board root keyed by mock name. Used by `"mock": "key"` sources. Replace with real task-executor integrations in production.
439
+
440
+ ---
441
+
442
+ ## Task Executor Protocol
443
+
444
+ The task executor is a **card-source-driven** component — its behaviour is determined entirely by the `customFields` defined on each card's `sources[]` entries. One executor is registered for the whole board, but it must know how to handle every source kind (`mock`, `copilot`, `http`, `graphapi`, etc.) used by any card on the board. The executor is the only handler where the card's source definition directly drives what the handler needs to do. It is registered once per board:
445
+
446
+ ```bash
447
+ node board-live-cards-cli.js init ./my-board --task-executor ./my-executor.js
448
+ # stores path in <boardDir>/.task-executor
449
+ ```
450
+
451
+ ### Invocation
452
+
453
+ ```bash
454
+ node <executor.js> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
455
+ ```
456
+
457
+ - **`--in`** — path to a JSON file containing a single source object (one entry from `sources[]`), enriched by the runtime with extra context fields:
458
+
459
+ ```json
460
+ {
461
+ "bindTo": "raw",
462
+ "outputFile": "my-card-raw.json",
463
+ "cwd": "/absolute/path/to/board",
464
+ "boardDir": "/absolute/path/to/board",
465
+ "_requires": { "token-a": { ... }, "token-b": { ... } },
466
+ "_sourcesData": { "previously-fetched-source-key": { ... } },
467
+ "_computed_values": { "result": "..." },
468
+ /* ...any other customFields from the card source definition */
469
+ }
470
+ ```
471
+
472
+ - `_requires` — resolved values for all card `requires` tokens
473
+ - `_sourcesData` — already-fetched sources for this card (earlier in sources[] order)
474
+ - `_computed_values` — current computed_values for the card
475
+
476
+ - **`--out`** — path to write the fetched value as raw JSON (any shape; stored under `fetched_sources.<bindTo>`)
477
+ - **`--err`** — optional path to write a plain-text error message on failure
478
+
479
+ ### Supported source kinds (by convention)
480
+
481
+ | `customField` | Meaning |
482
+ |---|---|
483
+ | `"mock": "key"` | Lookup `key` in `mock.db` — local dev |
484
+ | `"copilot": { "prompt_template": "..." }` | LLM call; supports `{{key}}` interpolation against `_requires`, `_sourcesData`, `_computed_values` |
485
+ | `"http": { "url": "...", "method": "GET" }` | HTTP/REST fetch |
486
+ | `"graphapi": { "query": "..." }` | Microsoft Graph API query |
487
+ | `"script": { "path": "...", "args": {} }` | Run a local script |
488
+ | `"teams"`, `"mail"`, `"incidentdb"` | Custom integrations in your executor |
489
+
490
+ All customFields are executor-defined — the runtime passes them through unchanged.
491
+
492
+ ### Exit codes
493
+ - **0** — success; runtime reads `--out`
494
+ - **non-zero** — failure; runtime reads `--err` if present
495
+
496
+ ### Probing a source before deploying
497
+
498
+ Before adding a card to a running board, agents should validate that each source can actually be fetched. Use the `probe-source` command — it reads the card file, extracts the source at the chosen index, builds the exact same `--in` payload the runtime would build, invokes the registered executor, and reports pass/fail.
499
+
500
+ ```bash
501
+ node board-live-cards-cli.js probe-source \
502
+ --card cards/card-market-prices.json \
503
+ --source-idx 0 \
504
+ --rg <boardRuntimeDir> \
505
+ --mock-requires '{"holdings":[{"ticker":"AAPL","quantity":10},{"ticker":"MSFT","quantity":5}]}'
506
+ ```
507
+
508
+ | Flag | Required | Description |
509
+ |---|---|---|
510
+ | `--card <card.json>` | yes | Path to the card file to probe |
511
+ | `--source-idx <n>` | no (default 0) | 0-based index into `sources[]` |
512
+ | `--source-bind <name>` | no | Select source by `bindTo` name instead of index |
513
+ | `--mock-requires <json>` | no | JSON string (or `@file.json`) providing the `_requires` token values the source needs. If omitted, `_requires` is `{}`. |
514
+ | `--rg <boardDir>` | no | Board runtime directory used to locate `.task-executor`. Defaults to the card file's directory. |
515
+ | `--out <result.json>` | no | Write the raw fetch result to this path |
516
+
517
+ **Output:** the command prints a human-readable report ending with a machine-readable `[probe-source:result]` JSON line. Exit `0` = `PROBE_PASS`, exit `1` = `PROBE_FAIL`.
518
+
519
+ **`--mock-requires` is the agent's responsibility.** Craft the minimal payload that exercises the source — for example, if the card `requires: ["holdings"]` and the source uses `tickersFrom: "holdings.ticker"`, supply `{"holdings":[{"ticker":"AAPL","quantity":1}]}`.
520
+
521
+ **Workflow for agents authoring a new card:**
522
+ 1. Author the card JSON with `sources[]`.
523
+ 2. For each source, run `probe-source` with representative `--mock-requires` data.
524
+ 3. If `PROBE_PASS` → proceed with `add-cards` or `upsert-card`.
525
+ 4. If `PROBE_FAIL` → inspect the error, fix the source definition or executor, retry.
526
+
527
+ ---
528
+
529
+
530
+
531
+ ---
532
+
533
+ ## Chat Handler Protocol
534
+
535
+ The chat handler is a **universal LLM component** — it does not depend on card sources, card model fields, or board state. Its sole job is: read the conversation, call the LLM, write the response.
536
+
537
+ Enable chat on a card by setting `"chat": true` in the card's `view.features`:
538
+ ```json
539
+ "view": {
540
+ "features": { "chat": true }
541
+ }
542
+ ```
543
+
544
+ Register once per board:
545
+ ```bash
546
+ node board-live-cards-cli.js init ./my-board --chat-handler ./my-chat-handler.js
547
+ # stores path in <boardDir>/.chat-handler
548
+ ```
549
+
550
+ ### Invocation
551
+
552
+ ```bash
553
+ node <handler.js> --boardId <id> --cardId <id> --extraEncJson <base64json>
554
+ ```
555
+
556
+ - **`--boardId`** — board identifier
557
+ - **`--cardId`** — the specific card where the user clicked the chat button
558
+ - **`--extraEncJson`** — base64-encoded JSON: `{ chatDir, boardDir, lastChatFile }`
559
+ - `chatDir` — absolute path to the directory holding all chat message files for this card
560
+ - `boardDir` — absolute path to the board runtime directory
561
+ - `lastChatFile` — filename of the user message just written (e.g. `007_user.txt`)
562
+
563
+ ### Chat message files
564
+
565
+ Messages are stored as serial-numbered `.txt` files in `chatDir`:
566
+ - `001_user.txt`, `002-assistant.txt`, `003_user.txt`, … (alternating)
567
+ - The handler reads **all** files to reconstruct conversation history, writes the next `<serial>-assistant.txt`
568
+
569
+ ### What the handler must do
570
+
571
+ 1. Read all `*_user.txt` / `*-assistant.txt` files from `chatDir` (sorted) → conversation history
572
+ 2. Build a system prompt scoped to `cardId` / `boardId` as grounding context
573
+ 3. Call the LLM directly (e.g. Copilot CLI) with `cwd: boardDir` — running from `boardDir` gives the LLM natural file context
574
+ 4. Write the response to `<nextSerial>-assistant.txt` in `chatDir`
575
+
576
+ ### System prompt guidance
577
+
578
+ The chat is always scoped to the card where the chat button is embedded. The LLM should:
579
+ - Help the user understand, explore, or act on **that card's data**
580
+ - Be concise — this is an inline embedded chat, not a full conversation window
581
+ - Reference specific values when answering
582
+ - Ask one short clarifying question if intent is ambiguous
583
+
584
+ ```javascript
585
+ // Minimal system prompt example:
586
+ `You are a helpful assistant embedded in a live data card (card: "${cardId}", board: "${boardId}").
587
+ Help the user understand and act on the data shown in this card.
588
+ Be concise. Ground answers in the card's data context.`
589
+ ```
590
+
591
+ ### LLM invocation
592
+
593
+ Call Copilot CLI directly from `boardDir` — same pattern as `demo-task-executor.js`:
594
+ ```javascript
595
+ execFileSync(copilotBin, ['--allow-all'], {
596
+ input: fullPrompt,
597
+ encoding: 'utf-8',
598
+ cwd: boardDir, // ← run from boardDir for natural file context
599
+ stdio: ['pipe', 'pipe', 'pipe'],
600
+ });
601
+ ```
602
+
603
+ No rule-based fallback needed. If the LLM fails, write a short acknowledgment message so the user sees something rather than silence.
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "card-portfolio-intelligence",
3
+ "meta": {
4
+ "title": "Portfolio Intelligence",
5
+ "tags": ["portfolio", "analysis"],
6
+ "desc": "AI analysis of portfolio mix and P&L. Publishes structured insights as tokens for downstream risk and action cards."
7
+ },
8
+ "requires": ["positions"],
9
+ "sources": [
10
+ {
11
+ "bindTo": "analysis",
12
+ "outputFile": "card-concentration-analysis.json",
13
+ "copilot": {
14
+ "prompt_template": "You are a concise equity analyst. Use web search to get today's news and upcoming events for each ticker before answering.\n\nPortfolio positions:\n{{positions}}\n\nFields: ticker, quantity, cost_basis ($), price ($), value ($), gain_$ (unrealised P&L), gain_% (unrealised return), chg_$ (today's position P&L $), chg_pct (today's % move).\n\nIMPORTANT OUTPUT RULES:\n- Return ONLY a valid JSON object. No markdown code fences, no preamble, no trailing text.\n- Start your response with { and end with }.\n- No emojis anywhere.\n- Each value is a Markdown string using - bullet points. No sub-headings inside values.\n- Maximum 40 words per section.\n\n{\n \"mix\": \"- <dominant holding ticker, % of total, and whether that is a risk>\\n- <sector clustering in one line>\",\n \"pnl\": \"- Best: <ticker> +X% (+$Y)\\n- Worst: <ticker> +X% (+$Y)\",\n \"risks\": \"- <TICKER>: <one specific current risk with event name or date>\\n- <TICKER>: ...\\n- (one bullet per position from today's news)\",\n \"action\": \"- <one sentence: specific action and reason grounded in today's market>\"\n}"
15
+ }
16
+ }
17
+ ],
18
+ "compute": [
19
+ { "bindTo": "mix", "expr": "fetched_sources.analysis.mix" },
20
+ { "bindTo": "pnl", "expr": "fetched_sources.analysis.pnl" },
21
+ { "bindTo": "risks", "expr": "fetched_sources.analysis.risks" },
22
+ { "bindTo": "action", "expr": "fetched_sources.analysis.action" }
23
+ ],
24
+ "provides": [
25
+ { "bindTo": "portfolio_mix", "src": "computed_values.mix" },
26
+ { "bindTo": "portfolio_pnl", "src": "computed_values.pnl" },
27
+ { "bindTo": "portfolio_risks", "src": "computed_values.risks" },
28
+ { "bindTo": "portfolio_action", "src": "computed_values.action" }
29
+ ],
30
+ "view": {
31
+ "elements": [
32
+ { "kind": "markdown", "label": "Mix", "data": { "bind": "computed_values.mix" } },
33
+ { "kind": "markdown", "label": "P&L", "data": { "bind": "computed_values.pnl" } }
34
+ ],
35
+ "layout": {
36
+ "board": { "col": 3, "order": 4 },
37
+ "canvas": { "x": 1300, "y": 50, "w": 340, "h": 200 }
38
+ },
39
+ "features": { "refresh": true, "chat": true }
40
+ },
41
+ "card_data": {}
42
+ }