yaml-flow 4.0.0 → 5.0.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 (95) hide show
  1. package/browser/board-livegraph-runtime.js +1453 -0
  2. package/browser/board-livegraph-runtime.js.map +1 -0
  3. package/browser/card-compute.js +36 -17
  4. package/browser/live-cards.js +848 -109
  5. package/browser/live-cards.schema.json +46 -21
  6. package/dist/board-livegraph-runtime/index.cjs +1448 -0
  7. package/dist/board-livegraph-runtime/index.cjs.map +1 -0
  8. package/dist/board-livegraph-runtime/index.d.cts +101 -0
  9. package/dist/board-livegraph-runtime/index.d.ts +101 -0
  10. package/dist/board-livegraph-runtime/index.js +1441 -0
  11. package/dist/board-livegraph-runtime/index.js.map +1 -0
  12. package/dist/card-compute/index.cjs +159 -44
  13. package/dist/card-compute/index.cjs.map +1 -1
  14. package/dist/card-compute/index.d.cts +36 -11
  15. package/dist/card-compute/index.d.ts +36 -11
  16. package/dist/card-compute/index.js +156 -44
  17. package/dist/card-compute/index.js.map +1 -1
  18. package/dist/cli/board-live-cards-cli.cjs +476 -105
  19. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  20. package/dist/cli/board-live-cards-cli.d.cts +8 -16
  21. package/dist/cli/board-live-cards-cli.d.ts +8 -16
  22. package/dist/cli/board-live-cards-cli.js +476 -106
  23. package/dist/cli/board-live-cards-cli.js.map +1 -1
  24. package/dist/continuous-event-graph/index.cjs +74 -33
  25. package/dist/continuous-event-graph/index.cjs.map +1 -1
  26. package/dist/continuous-event-graph/index.d.cts +7 -23
  27. package/dist/continuous-event-graph/index.d.ts +7 -23
  28. package/dist/continuous-event-graph/index.js +73 -32
  29. package/dist/continuous-event-graph/index.js.map +1 -1
  30. package/dist/index.cjs +1440 -56
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +21 -3
  33. package/dist/index.d.ts +21 -3
  34. package/dist/index.js +1434 -56
  35. package/dist/index.js.map +1 -1
  36. package/dist/journal-DRfJiheM.d.cts +28 -0
  37. package/dist/journal-NLYuqege.d.ts +28 -0
  38. package/dist/{journal-B_2JnBMF.d.ts → live-cards-bridge-Or7fdEJV.d.ts} +5 -32
  39. package/dist/{journal-BJDjWb5Q.d.cts → live-cards-bridge-vGJ6tMzN.d.cts} +5 -32
  40. package/dist/schedule-CMcZe5Ny.d.ts +21 -0
  41. package/dist/schedule-CiucyCan.d.cts +21 -0
  42. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
  43. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +3 -3
  44. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
  45. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +3 -3
  46. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
  47. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +33 -5
  48. package/examples/browser/livecards-browser/index.html +37 -684
  49. package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  50. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
  51. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  52. package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
  53. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +2 -2
  54. package/examples/example-board/board.yaml +23 -0
  55. package/examples/example-board/bootstrap_payload.json +1 -0
  56. package/examples/example-board/cards/card-chain-region-alert.json +39 -0
  57. package/examples/example-board/cards/card-chain-region-totals.json +26 -0
  58. package/examples/example-board/cards/card-chain-top-region.json +24 -0
  59. package/examples/example-board/cards/card-ex-actions.json +32 -0
  60. package/examples/example-board/cards/card-ex-chart.json +30 -0
  61. package/examples/example-board/cards/card-ex-filter.json +36 -0
  62. package/examples/example-board/cards/card-ex-filtered-by-preference.json +59 -0
  63. package/examples/example-board/cards/card-ex-form.json +91 -0
  64. package/examples/example-board/cards/card-ex-list.json +22 -0
  65. package/examples/example-board/cards/card-ex-markdown.json +17 -0
  66. package/examples/example-board/cards/card-ex-metric.json +19 -0
  67. package/examples/example-board/cards/card-ex-narrative.json +36 -0
  68. package/examples/example-board/cards/card-ex-source-http.json +28 -0
  69. package/examples/example-board/cards/card-ex-source.json +21 -0
  70. package/examples/example-board/cards/card-ex-status.json +35 -0
  71. package/examples/example-board/cards/card-ex-table.json +30 -0
  72. package/examples/example-board/cards/card-ex-todo.json +29 -0
  73. package/examples/example-board/demo-chat-handler.js +69 -0
  74. package/examples/example-board/demo-server.js +87 -0
  75. package/examples/example-board/demo-shell-browser.html +806 -0
  76. package/examples/example-board/demo-shell-with-server.html +280 -0
  77. package/examples/example-board/demo-shell.html +62 -0
  78. package/examples/example-board/demo-task-executor.js +255 -0
  79. package/examples/example-board/mock.db +15 -0
  80. package/examples/example-board/reusable-board-runtime-client.js +265 -0
  81. package/examples/example-board/reusable-runtime-artifacts-adapter.js +233 -0
  82. package/examples/example-board/reusable-server-runtime.js +1284 -0
  83. package/examples/index.html +16 -9
  84. package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +17 -17
  85. package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +23 -23
  86. package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  87. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
  88. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  89. package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +1 -1
  90. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
  91. package/package.json +16 -2
  92. package/schema/card-runtime.schema.json +25 -0
  93. package/schema/live-cards.schema.json +46 -21
  94. package/browser/ingest-board.js +0 -296
  95. package/examples/ingest.js +0 -733
@@ -0,0 +1,280 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Example Board Demo (Server Runtime)</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
9
+ <script src="../../browser/card-compute.js" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/card-compute.js';"></script>
10
+ <script src="../../browser/live-cards.js" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/live-cards.js';"></script>
11
+ <script src="./reusable-runtime-artifacts-adapter.js"></script>
12
+ <script src="./reusable-board-runtime-client.js"></script>
13
+ </head>
14
+ <body class="bg-light">
15
+ <div class="container-fluid py-3">
16
+ <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
17
+ <div>
18
+ <h1 class="h4 mb-0" id="boardTitle">Example Board (Server Runtime)</h1>
19
+ <div class="small text-muted" id="boardDesc"></div>
20
+ </div>
21
+ <div class="d-flex align-items-center gap-2">
22
+ <a class="btn btn-sm btn-outline-secondary" href="demo-shell-browser.html">Open Browser Runtime Shell</a>
23
+ <select class="form-select form-select-sm" id="boardSelector" style="max-width:180px" title="Active board"></select>
24
+ <button class="btn btn-sm btn-outline-success" id="addBoardBtn">+ Board</button>
25
+ <div id="addBoardForm" class="d-flex align-items-center gap-1" style="display:none!important">
26
+ <input class="form-control form-control-sm" id="newBoardId" placeholder="board-id" style="max-width:110px" />
27
+ <input class="form-control form-control-sm" id="newBoardLabel" placeholder="Label (optional)" style="max-width:150px" />
28
+ <button class="btn btn-sm btn-success" id="addBoardSubmit">Add</button>
29
+ <button class="btn btn-sm btn-secondary" id="addBoardCancel">Cancel</button>
30
+ </div>
31
+ <button class="btn btn-sm btn-outline-primary" id="modeBoard">Board</button>
32
+ <button class="btn btn-sm btn-outline-primary" id="modeCanvas">Canvas</button>
33
+ <button class="btn btn-sm btn-outline-secondary" id="autoLayout">Auto Layout</button>
34
+ <button class="btn btn-sm btn-outline-dark" id="refreshAll" style="display:none">Refresh All</button>
35
+ <div class="form-check ms-2">
36
+ <input class="form-check-input" type="checkbox" id="devModeToggle" />
37
+ <label class="form-check-label" for="devModeToggle">Dev Mode</label>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="alert alert-info small py-2 mb-3">
43
+ Server runtime mode: updates stream in via SSE from the thin demo-server bridge to board-live-cards-cli.
44
+ Source execution and event processing happen on the server side.
45
+ </div>
46
+
47
+ <div class="alert alert-secondary small py-2 mb-3">
48
+ Contract and decision notes live in <a href="./demo-shell.html#assumptions-and-tradeoffs">Assumptions and Tradeoffs</a>.
49
+ </div>
50
+
51
+ <div id="boardRoot">
52
+ <div class="d-flex align-items-center justify-content-center" style="height: 72vh;">
53
+ <div class="text-center">
54
+ <div class="spinner-border mb-3" role="status">
55
+ <span class="visually-hidden">Loading...</span>
56
+ </div>
57
+ <p class="text-muted">Initializing board...</p>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <script>
64
+ (function () {
65
+ let currentMode = 'board';
66
+ let bootstrapCompleted = false;
67
+ let activeBoardId = 'default';
68
+ let runtimeClient = null;
69
+
70
+ function boardPaths(boardId) {
71
+ const b = encodeURIComponent(boardId || activeBoardId || 'default');
72
+ const base = `/api/boards/${b}`;
73
+ return {
74
+ demoSetup: `${base}/demo-setup`,
75
+ initBoard: `${base}/init-board`,
76
+ bootstrapCards: `${base}/bootstrap-cards`,
77
+ bootstrap: `${base}/bootstrap`,
78
+ stream: `${base}/sse`,
79
+ patchCard: function (id) { return `${base}/cards/${encodeURIComponent(id)}`; },
80
+ cardAction: function (id) { return `${base}/cards/${encodeURIComponent(id)}/actions`; },
81
+ cardFile: function (id) { return `${base}/cards/${encodeURIComponent(id)}/files`; },
82
+ cardChats: function (id) { return `${base}/cards/${encodeURIComponent(id)}/chats`; },
83
+ };
84
+ }
85
+
86
+ function localServerOrigins() {
87
+ const params = new URLSearchParams(window.location.search || '');
88
+ const fromQuery = params.get('demoServerOrigin');
89
+ const fromWindow = window.DEMO_SERVER_ORIGIN;
90
+ const hostPref = window.location.hostname === 'localhost' ? 'localhost' : '127.0.0.1';
91
+ const hostAlt = hostPref === 'localhost' ? '127.0.0.1' : 'localhost';
92
+ const defaults = [
93
+ `http://${hostPref}:7799`,
94
+ `http://${hostAlt}:7799`,
95
+ ];
96
+
97
+ const unique = [];
98
+ const push = (v) => {
99
+ if (!v || typeof v !== 'string') return;
100
+ if (unique.includes(v)) return;
101
+ unique.push(v);
102
+ };
103
+
104
+ push(fromWindow);
105
+ push(fromQuery);
106
+ defaults.forEach(push);
107
+ return unique;
108
+ }
109
+
110
+ let activeServerOrigin = null;
111
+
112
+ async function fetchServer(path, init) {
113
+ const origins = activeServerOrigin ? [activeServerOrigin] : localServerOrigins();
114
+ const failures = [];
115
+
116
+ for (const origin of origins) {
117
+ const url = `${origin}${path}`;
118
+ try {
119
+ const res = await fetch(url, init);
120
+ activeServerOrigin = origin;
121
+ return res;
122
+ } catch (err) {
123
+ failures.push(`${url} -> ${String(err && err.message || err)}`);
124
+ }
125
+ }
126
+
127
+ const tip = 'Start the API bridge from the yaml-flow folder: node examples/example-board/demo-server.js';
128
+ throw new Error(`Unable to reach example-board demo server. Tried: ${failures.join(' | ')}. ${tip}`);
129
+ }
130
+
131
+ runtimeClient = window.ReusableBoardRuntimeClient.createBoardRuntimeClient({
132
+ fetchServer,
133
+ boardPaths,
134
+ buildLiveCardModelsFromArtifacts,
135
+ getServerOrigin: function () { return activeServerOrigin; },
136
+ initialMode: currentMode,
137
+ canvas: { height: '72vh', overflow: 'auto' },
138
+ });
139
+
140
+ function resolveTaskExecutorPathParam() {
141
+ const params = new URLSearchParams(window.location.search || '');
142
+ const fromQuery = params.get('taskExecutorPath');
143
+ const fromWindow = window.DEMO_TASK_EXECUTOR_PATH;
144
+ if (typeof fromQuery === 'string' && fromQuery.trim()) return fromQuery.trim();
145
+ if (typeof fromWindow === 'string' && fromWindow.trim()) return fromWindow.trim();
146
+ return '';
147
+ }
148
+
149
+ async function loadBoardList() {
150
+ try {
151
+ const res = await fetchServer('/api/boards');
152
+ if (!res.ok) return;
153
+ const data = await res.json();
154
+ const boards = Array.isArray(data && data.boards) ? data.boards : [];
155
+ const sel = document.getElementById('boardSelector');
156
+ const prev = sel.value;
157
+ sel.innerHTML = boards.map((b) =>
158
+ `<option value="${b.id}"${b.id === (prev || activeBoardId) ? ' selected' : ''}>${b.label || b.id}</option>`
159
+ ).join('');
160
+ if (boards.length && !boards.some((b) => b.id === activeBoardId)) {
161
+ activeBoardId = boards[0].id;
162
+ sel.value = activeBoardId;
163
+ }
164
+ } catch (err) {
165
+ console.warn('Could not load board list', err);
166
+ }
167
+ }
168
+
169
+ async function switchBoard(boardId) {
170
+ if (boardId === activeBoardId && bootstrapCompleted) return;
171
+ runtimeClient.dispose();
172
+ bootstrapCompleted = false;
173
+ activeBoardId = boardId;
174
+ document.getElementById('boardRoot').innerHTML =
175
+ '<div class="d-flex align-items-center justify-content-center" style="height:72vh">'
176
+ + '<div class="text-center"><div class="spinner-border mb-3" role="status">'
177
+ + '<span class="visually-hidden">Loading...</span></div>'
178
+ + '<p class="text-muted">Loading board...</p></div></div>';
179
+ await bootstrap(boardId);
180
+ }
181
+
182
+ async function bootstrap(boardId) {
183
+ const bid = boardId || activeBoardId || 'default';
184
+
185
+ // Board title from registry label or fallback
186
+ document.getElementById('boardTitle').textContent = bid + ' (Server Runtime)';
187
+ document.getElementById('boardDesc').textContent = '';
188
+
189
+ await runtimeClient.bootstrapBoard({
190
+ boardId: bid,
191
+ taskExecutorPath: resolveTaskExecutorPathParam(),
192
+ rootElement: document.getElementById('boardRoot'),
193
+ runDemoSetup: true,
194
+ mode: currentMode,
195
+ });
196
+
197
+ bootstrapCompleted = true;
198
+ }
199
+
200
+ document.getElementById('modeBoard').addEventListener('click', function () {
201
+ currentMode = 'board';
202
+ runtimeClient.setMode('board');
203
+ });
204
+
205
+ document.getElementById('modeCanvas').addEventListener('click', function () {
206
+ currentMode = 'canvas';
207
+ runtimeClient.setMode('canvas');
208
+ });
209
+
210
+ document.getElementById('autoLayout').addEventListener('click', function () {
211
+ currentMode = 'canvas';
212
+ runtimeClient.autoLayout();
213
+ });
214
+
215
+ document.getElementById('devModeToggle').addEventListener('change', function () {
216
+ runtimeClient.setDevMode(this.checked);
217
+ });
218
+
219
+ document.getElementById('boardSelector').addEventListener('change', function () {
220
+ switchBoard(this.value).catch(function (err) {
221
+ console.error(err);
222
+ document.getElementById('boardRoot').innerHTML =
223
+ `<div class="alert alert-danger">Failed to switch board: ${String(err && err.message || err)}</div>`;
224
+ });
225
+ });
226
+
227
+ document.getElementById('addBoardBtn').addEventListener('click', function () {
228
+ document.getElementById('addBoardForm').style.removeProperty('display');
229
+ document.getElementById('newBoardId').focus();
230
+ });
231
+
232
+ document.getElementById('addBoardCancel').addEventListener('click', function () {
233
+ document.getElementById('addBoardForm').style.display = 'none';
234
+ document.getElementById('newBoardId').value = '';
235
+ document.getElementById('newBoardLabel').value = '';
236
+ });
237
+
238
+ document.getElementById('addBoardSubmit').addEventListener('click', async function () {
239
+ const id = document.getElementById('newBoardId').value.trim();
240
+ const label = document.getElementById('newBoardLabel').value.trim();
241
+ if (!id) {
242
+ alert('Board id is required');
243
+ return;
244
+ }
245
+ try {
246
+ const res = await fetchServer('/api/boards', {
247
+ method: 'POST',
248
+ headers: { 'content-type': 'application/json' },
249
+ body: JSON.stringify({ id, label: label || id }),
250
+ });
251
+ const data = await res.json();
252
+ if (!res.ok) {
253
+ alert(data.error || 'Failed to add board');
254
+ return;
255
+ }
256
+ document.getElementById('addBoardForm').style.display = 'none';
257
+ document.getElementById('newBoardId').value = '';
258
+ document.getElementById('newBoardLabel').value = '';
259
+ await loadBoardList();
260
+ await switchBoard(id);
261
+ } catch (err) {
262
+ alert('Error adding board: ' + String((err && err.message) || err));
263
+ }
264
+ });
265
+
266
+ loadBoardList().then(function () {
267
+ return bootstrap(activeBoardId);
268
+ }).catch(function (err) {
269
+ console.error(err);
270
+ document.getElementById('boardRoot').innerHTML =
271
+ `<div class="alert alert-danger">Failed to start server runtime demo: ${String(err && err.message || err)}</div>`;
272
+ });
273
+
274
+ window.addEventListener('beforeunload', function () {
275
+ runtimeClient.dispose();
276
+ });
277
+ })();
278
+ </script>
279
+ </body>
280
+ </html>
@@ -0,0 +1,62 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Example Board Demo Shell</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ </head>
9
+ <body class="bg-light">
10
+ <div class="container py-4">
11
+ <h1 class="h3 mb-2">Example Board Demo Shell</h1>
12
+ <p class="text-muted mb-4">Choose which runtime architecture you want to exercise.</p>
13
+
14
+ <div class="row g-3">
15
+ <div class="col-md-6">
16
+ <div class="card h-100 shadow-sm">
17
+ <div class="card-body">
18
+ <h2 class="h5">Browser Runtime</h2>
19
+ <p class="small text-muted mb-3">
20
+ Uses <code>board-livegraph-runtime.js</code> directly in-browser.
21
+ Source definitions remain opaque and are executed by an in-page task executor shim.
22
+ </p>
23
+ <a class="btn btn-primary" href="./demo-shell-browser.html">Open Browser Runtime Shell</a>
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="col-md-6">
29
+ <div class="card h-100 shadow-sm">
30
+ <div class="card-body">
31
+ <h2 class="h5">Server Runtime</h2>
32
+ <p class="small text-muted mb-3">
33
+ Uses a thin API/SSE bridge over <code>board-live-cards-cli</code>.
34
+ All source execution and event processing happens server-side.
35
+ </p>
36
+ <a class="btn btn-outline-primary" href="./demo-shell-with-server.html">Open Server Runtime Shell</a>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="alert alert-secondary small mt-4 mb-0">
43
+ For server mode, run <code>node examples/example-board/demo-server.js</code> from the <code>yaml-flow</code> folder.
44
+ </div>
45
+
46
+ <div id="assumptions-and-tradeoffs" class="card mt-3 border-0 shadow-sm">
47
+ <div class="card-body">
48
+ <h2 class="h6 mb-2">Assumptions and Tradeoffs (Authoritative Notes)</h2>
49
+ <ul class="small text-muted mb-0">
50
+ <li>Runtime artifact contract is intentionally minimal: <code>schema_version</code>, <code>card_id</code>, and <code>computed_values</code>. Rich context like <code>fetched_sources</code> is transport/runtime detail, not persistence contract.</li>
51
+ <li>Card definitions are treated as source-of-truth for authored state (<code>card_data</code>). Runtime artifacts capture evaluated outputs, not authored configuration.</li>
52
+ <li>Source definitions are intentionally opaque to the framework. Executor-specific fields are owned by domain teams and passed through without interpretation.</li>
53
+ <li>Token-level interoperability is the stable boundary: producers emit via <code>provides.bindTo</code>, consumers depend via <code>requires</code>. Domain logic should evolve behind this boundary.</li>
54
+ <li>Server and browser shells are equivalent by intent, not byte-identical implementations. Browser mode prioritizes local iteration; server mode prioritizes process boundaries and operational realism.</li>
55
+ <li>Patch and restart behavior is explicit: UI state updates are persisted through card patching, and recomputation/retrigger is a deliberate step, not an implicit side effect.</li>
56
+ <li>This example is a reference surface for decisions and contracts, not a canonical list of all features. Agents should infer implementation details directly from files in this folder.</li>
57
+ </ul>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </body>
62
+ </html>
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * demo-task-executor.js — Simple mock source executor for example-board.
5
+ *
6
+ * Protocol (invoked by board-live-cards-cli):
7
+ * node demo-task-executor.js run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
8
+ *
9
+ * Expected source definition:
10
+ * { "bindTo": "...", "outputFile": "...", "mock": "keyName" }
11
+ *
12
+ * Behavior:
13
+ * 1. Read mock.db (JSON file next to this script)
14
+ * 2. Look up source.mock value as a key in mock.db
15
+ * 3. Write corresponding value to --out file (as JSON)
16
+ * 4. Exit 0 on success, exit 1 on error
17
+ */
18
+
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { execFileSync } from 'node:child_process';
22
+ import { fileURLToPath } from 'node:url';
23
+
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+ const MOCK_DB_PATH = path.join(__dirname, 'mock.db');
26
+
27
+ function readJson(filePath) {
28
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
29
+ }
30
+
31
+ function interpolatePrompt(template, args) {
32
+ return String(template).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_, key) => {
33
+ const v = args?.[key];
34
+ if (v === undefined) return '';
35
+ if (typeof v === 'string') return v;
36
+ return JSON.stringify(v);
37
+ });
38
+ }
39
+
40
+ function stripCopilotFooter(rawText) {
41
+ const lines = String(rawText ?? '').split(/\r?\n/);
42
+
43
+ // Remove trailing blank lines first.
44
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
45
+
46
+ // Remove the standard trailing Copilot metadata footer, if present.
47
+ if (
48
+ lines.length >= 3 &&
49
+ /^Changes\b/i.test(lines[lines.length - 3]) &&
50
+ /^Requests\b/i.test(lines[lines.length - 2]) &&
51
+ /^Tokens\b/i.test(lines[lines.length - 1])
52
+ ) {
53
+ lines.splice(lines.length - 3, 3);
54
+ }
55
+
56
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
57
+ return lines.join('\n');
58
+ }
59
+
60
+ function resolveCopilotPrompt(sourceDef) {
61
+ const cfg = sourceDef?.copilot && typeof sourceDef.copilot === 'object' ? sourceDef.copilot : {};
62
+ const template = cfg.prompt_template ?? sourceDef.prompt_template;
63
+ const args = cfg.args ?? cfg.prompt_args ?? sourceDef.prompt_args ?? sourceDef.args ?? {};
64
+
65
+ // Merge explicit args with context from card-handler (_requires includes card-level computed values)
66
+ const interpolationContext = { ...sourceDef._requires, ...args };
67
+
68
+ if (!template || typeof template !== 'string') return null;
69
+ return interpolatePrompt(template, interpolationContext);
70
+ }
71
+
72
+ function resolveCopilotExecutable() {
73
+ const envBin = process.env.COPILOT_BIN;
74
+ if (envBin && fs.existsSync(envBin)) {
75
+ return envBin;
76
+ }
77
+
78
+ if (process.platform === 'win32') {
79
+ try {
80
+ const out = execFileSync('where.exe', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
81
+ const candidates = out
82
+ .split(/\r?\n/)
83
+ .map((s) => s.trim())
84
+ .filter(Boolean);
85
+ const preferred = candidates.find((p) => /\.(cmd|exe|bat)$/i.test(p));
86
+ if (preferred) return preferred;
87
+ if (candidates[0]) return candidates[0];
88
+ } catch {}
89
+ } else {
90
+ try {
91
+ const out = execFileSync('which', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
92
+ const first = out.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
93
+ if (first) return first;
94
+ } catch {}
95
+ }
96
+
97
+ return 'copilot';
98
+ }
99
+
100
+ function runCopilotPrompt(prompt) {
101
+ const copilotBin = resolveCopilotExecutable();
102
+ const copilotArgs = ['--allow-all'];
103
+
104
+ try {
105
+ // Prefer stdin prompt delivery to avoid shell/path quoting issues.
106
+ return execFileSync(copilotBin, copilotArgs, {
107
+ input: String(prompt),
108
+ encoding: 'utf-8',
109
+ stdio: ['pipe', 'pipe', 'pipe'],
110
+ maxBuffer: 10 * 1024 * 1024,
111
+ });
112
+ } catch (directErr) {
113
+ // Fallback for Git Bash / Windows wrapper path quoting issues.
114
+ if (process.platform === 'win32') {
115
+ const isCmdShim = /\.(bat|cmd)$/i.test(copilotBin);
116
+
117
+ if (isCmdShim) {
118
+ try {
119
+ return execFileSync(copilotBin, copilotArgs, {
120
+ input: String(prompt),
121
+ encoding: 'utf-8',
122
+ stdio: ['pipe', 'pipe', 'pipe'],
123
+ maxBuffer: 10 * 1024 * 1024,
124
+ shell: true,
125
+ });
126
+ } catch {}
127
+ }
128
+
129
+ try {
130
+ // Final fallback: resolve through cmd PATH lookup, still piping prompt on stdin.
131
+ return execFileSync('cmd.exe', ['/d', '/c', 'copilot --allow-all'], {
132
+ input: String(prompt),
133
+ encoding: 'utf-8',
134
+ stdio: ['pipe', 'pipe', 'pipe'],
135
+ maxBuffer: 10 * 1024 * 1024,
136
+ });
137
+ } catch (cmdErr) {
138
+ const stderrDirect = directErr && typeof directErr === 'object' && 'stderr' in directErr
139
+ ? String(directErr.stderr || '')
140
+ : '';
141
+ const stderrCmd = cmdErr && typeof cmdErr === 'object' && 'stderr' in cmdErr
142
+ ? String(cmdErr.stderr || '')
143
+ : '';
144
+ const msg = [stderrDirect.trim(), stderrCmd.trim(), String(cmdErr && cmdErr.message || cmdErr)]
145
+ .filter(Boolean)
146
+ .join(' | ');
147
+ throw new Error(msg || 'copilot invocation failed');
148
+ }
149
+ }
150
+
151
+ const stderrDirect = directErr && typeof directErr === 'object' && 'stderr' in directErr
152
+ ? String(directErr.stderr || '')
153
+ : '';
154
+ const msg = [stderrDirect.trim(), String(directErr && directErr.message || directErr)]
155
+ .filter(Boolean)
156
+ .join(' | ');
157
+ throw new Error(msg || 'copilot invocation failed');
158
+ }
159
+ }
160
+
161
+ function fail(msg, errFile) {
162
+ if (errFile) {
163
+ try {
164
+ fs.writeFileSync(errFile, msg);
165
+ } catch {}
166
+ }
167
+ console.error(`[demo-task-executor] ${msg}`);
168
+ process.exit(1);
169
+ }
170
+
171
+ function runSourceFetchSubcommand(argv) {
172
+ const inIdx = argv.indexOf('--in');
173
+ const outIdx = argv.indexOf('--out');
174
+ const errIdx = argv.indexOf('--err');
175
+ const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
176
+ const outFile = outIdx !== -1 ? argv[outIdx + 1] : undefined;
177
+ const errFile = errIdx !== -1 ? argv[errIdx + 1] : undefined;
178
+
179
+ if (!inFile || !outFile) {
180
+ fail('Usage: run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]', errFile);
181
+ }
182
+
183
+ if (!fs.existsSync(inFile)) {
184
+ fail(`Input file not found: ${inFile}`, errFile);
185
+ }
186
+
187
+ let sourceDef;
188
+ try {
189
+ sourceDef = readJson(inFile);
190
+ } catch (err) {
191
+ fail(`Cannot parse source file: ${String(err && err.message || err)}`, errFile);
192
+ }
193
+
194
+ let resultValue;
195
+
196
+ if (sourceDef.copilot || sourceDef.prompt_template) {
197
+ const prompt = resolveCopilotPrompt(sourceDef);
198
+ if (!prompt) {
199
+ fail('Source definition missing copilot.prompt_template (or prompt_template)', errFile);
200
+ }
201
+
202
+ let rawOutput = '';
203
+ try {
204
+ rawOutput = runCopilotPrompt(prompt);
205
+ } catch (err) {
206
+ const msg = String(err && err.message || err);
207
+ fail(`copilot invocation failed: ${msg}`, errFile);
208
+ }
209
+
210
+ resultValue = stripCopilotFooter(rawOutput);
211
+ } else {
212
+ // Default mode: mockdb lookup
213
+ let mockDb;
214
+ try {
215
+ if (!fs.existsSync(MOCK_DB_PATH)) {
216
+ fail(`mock.db not found at ${MOCK_DB_PATH}`, errFile);
217
+ }
218
+ mockDb = readJson(MOCK_DB_PATH);
219
+ } catch (err) {
220
+ fail(`Cannot parse mock.db: ${String(err && err.message || err)}`, errFile);
221
+ }
222
+
223
+ const mockKey = sourceDef.mock;
224
+ if (!mockKey) {
225
+ fail('Source definition missing "mock" field (key to lookup)', errFile);
226
+ }
227
+
228
+ resultValue = mockDb[mockKey];
229
+ if (resultValue === undefined) {
230
+ fail(`Key "${mockKey}" not found in mock.db`, errFile);
231
+ }
232
+ }
233
+
234
+ // Write result to --out as JSON payload, same contract as current mock mode.
235
+ try {
236
+ fs.writeFileSync(outFile, JSON.stringify(resultValue, null, 2));
237
+ } catch (err) {
238
+ fail(`Cannot write output file: ${String(err && err.message || err)}`, errFile);
239
+ }
240
+
241
+ process.exit(0);
242
+ }
243
+
244
+ function main() {
245
+ const sub = process.argv[2];
246
+ if (sub === 'run-source-fetch') {
247
+ runSourceFetchSubcommand(process.argv.slice(3));
248
+ return;
249
+ }
250
+
251
+ console.warn(`[demo-task-executor] Unknown subcommand: ${sub}`);
252
+ process.exit(0);
253
+ }
254
+
255
+ main();
@@ -0,0 +1,15 @@
1
+ {
2
+ "orders": [
3
+ { "id": "ORD-1001", "product": "Widget A", "quantity": 3, "amount": 12400, "region": "North" },
4
+ { "id": "ORD-1002", "product": "Widget B", "quantity": 2, "amount": 8700, "region": "South" },
5
+ { "id": "ORD-1003", "product": "Widget A", "quantity": 4, "amount": 15200, "region": "East" },
6
+ { "id": "ORD-1004", "product": "Widget C", "quantity": 1, "amount": 6300, "region": "West" },
7
+ { "id": "ORD-1005", "product": "Widget B", "quantity": 2, "amount": 9100, "region": "North" },
8
+ { "id": "ORD-1006", "product": "Widget C", "quantity": 3, "amount": 9800, "region": "South" }
9
+ ],
10
+ "prices": [
11
+ { "product": "Widget A", "price": 4133.33, "currency": "USD" },
12
+ { "product": "Widget B", "price": 4450.0, "currency": "USD" },
13
+ { "product": "Widget C", "price": 3266.67, "currency": "USD" }
14
+ ]
15
+ }