yaml-flow 3.1.0 → 4.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 (149) hide show
  1. package/README.md +81 -20
  2. package/board-live-cards-cli.js +37 -0
  3. package/browser/card-compute.js +132 -431
  4. package/browser/live-cards.js +41 -27
  5. package/browser/live-cards.schema.json +59 -77
  6. package/dist/card-compute/index.cjs +135 -415
  7. package/dist/card-compute/index.cjs.map +1 -1
  8. package/dist/card-compute/index.d.cts +52 -49
  9. package/dist/card-compute/index.d.ts +52 -49
  10. package/dist/card-compute/index.js +134 -415
  11. package/dist/card-compute/index.js.map +1 -1
  12. package/dist/cli/board-live-cards-cli.cjs +2379 -0
  13. package/dist/cli/board-live-cards-cli.cjs.map +1 -0
  14. package/dist/cli/board-live-cards-cli.d.cts +213 -0
  15. package/dist/cli/board-live-cards-cli.d.ts +213 -0
  16. package/dist/cli/board-live-cards-cli.js +2332 -0
  17. package/dist/cli/board-live-cards-cli.js.map +1 -0
  18. package/dist/{constants-B2zqu10b.d.ts → constants-DuzE5n03.d.ts} +2 -2
  19. package/dist/{constants-DJZU1pwJ.d.cts → constants-ozjf1Ejw.d.cts} +2 -2
  20. package/dist/continuous-event-graph/index.cjs +201 -448
  21. package/dist/continuous-event-graph/index.cjs.map +1 -1
  22. package/dist/continuous-event-graph/index.d.cts +16 -340
  23. package/dist/continuous-event-graph/index.d.ts +16 -340
  24. package/dist/continuous-event-graph/index.js +198 -448
  25. package/dist/continuous-event-graph/index.js.map +1 -1
  26. package/dist/event-graph/index.cjs +4 -4
  27. package/dist/event-graph/index.cjs.map +1 -1
  28. package/dist/event-graph/index.d.cts +5 -5
  29. package/dist/event-graph/index.d.ts +5 -5
  30. package/dist/event-graph/index.js +4 -4
  31. package/dist/event-graph/index.js.map +1 -1
  32. package/dist/index.cjs +278 -533
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +8 -7
  35. package/dist/index.d.ts +8 -7
  36. package/dist/index.js +278 -533
  37. package/dist/index.js.map +1 -1
  38. package/dist/inference/index.cjs +138 -19
  39. package/dist/inference/index.cjs.map +1 -1
  40. package/dist/inference/index.d.cts +2 -2
  41. package/dist/inference/index.d.ts +2 -2
  42. package/dist/inference/index.js +138 -19
  43. package/dist/inference/index.js.map +1 -1
  44. package/dist/journal-BJDjWb5Q.d.cts +343 -0
  45. package/dist/journal-B_2JnBMF.d.ts +343 -0
  46. package/dist/step-machine/index.cjs +18 -1
  47. package/dist/step-machine/index.cjs.map +1 -1
  48. package/dist/step-machine/index.d.cts +2 -2
  49. package/dist/step-machine/index.d.ts +2 -2
  50. package/dist/step-machine/index.js +18 -1
  51. package/dist/step-machine/index.js.map +1 -1
  52. package/dist/stores/file.d.cts +1 -1
  53. package/dist/stores/file.d.ts +1 -1
  54. package/dist/stores/index.d.cts +1 -1
  55. package/dist/stores/index.d.ts +1 -1
  56. package/dist/stores/localStorage.d.cts +1 -1
  57. package/dist/stores/localStorage.d.ts +1 -1
  58. package/dist/stores/memory.d.cts +1 -1
  59. package/dist/stores/memory.d.ts +1 -1
  60. package/dist/{types-BwvgvlOO.d.cts → types-BzLD8bjb.d.cts} +1 -1
  61. package/dist/{types-ClRA8hzC.d.ts → types-C2eJ7DAV.d.ts} +1 -1
  62. package/dist/{types-DEj7OakX.d.cts → types-CMFSIjpc.d.cts} +39 -4
  63. package/dist/{types-DEj7OakX.d.ts → types-CMFSIjpc.d.ts} +39 -4
  64. package/dist/{types-FZ_eyErS.d.cts → types-ycun84cq.d.cts} +1 -0
  65. package/dist/{types-FZ_eyErS.d.ts → types-ycun84cq.d.ts} +1 -0
  66. package/dist/{validate-DEZ2Ymdb.d.ts → validate-DJQTQ6bP.d.ts} +1 -1
  67. package/dist/{validate-DqKTZg_o.d.cts → validate-ke92Cleg.d.cts} +1 -1
  68. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +22 -0
  69. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +16 -0
  70. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +15 -0
  71. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +15 -0
  72. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +43 -0
  73. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +7 -0
  74. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +189 -0
  75. package/examples/browser/livecards-browser/index.html +688 -0
  76. package/examples/browser/step-machine-browser/index.html +367 -0
  77. package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
  78. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
  79. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
  80. package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
  81. package/examples/cli/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
  82. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
  83. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
  84. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
  85. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
  86. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
  87. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
  88. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
  89. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
  90. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
  91. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
  92. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
  93. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
  94. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +36 -0
  95. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +30 -0
  96. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +19 -0
  97. package/examples/cli/step-machine-demo/step-cli-echo-y.js +15 -0
  98. package/examples/cli/step-machine-demo/step2-double-cli.js +39 -0
  99. package/examples/cli/step-machine-demo/two-step-math-handlers.js +32 -0
  100. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +31 -0
  101. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +24 -0
  102. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +35 -0
  103. package/examples/index.html +792 -0
  104. package/examples/ingest.js +733 -0
  105. package/examples/npm-libs/batch/batch-step-machine.ts +121 -0
  106. package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +215 -0
  107. package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +555 -0
  108. package/examples/npm-libs/continuous-event-graph/portfolio-tracker.ts +287 -0
  109. package/examples/npm-libs/continuous-event-graph/reactive-monitoring.ts +265 -0
  110. package/examples/npm-libs/continuous-event-graph/reactive-pipeline.ts +168 -0
  111. package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +287 -0
  112. package/examples/npm-libs/continuous-event-graph/stock-dashboard.ts +229 -0
  113. package/examples/npm-libs/event-graph/ci-cd-pipeline.ts +243 -0
  114. package/examples/npm-libs/event-graph/executor-diamond.ts +165 -0
  115. package/examples/npm-libs/event-graph/executor-pipeline.ts +161 -0
  116. package/examples/npm-libs/event-graph/research-pipeline.ts +137 -0
  117. package/examples/npm-libs/flows/ai-conversation.yaml +116 -0
  118. package/examples/npm-libs/flows/order-processing.yaml +143 -0
  119. package/examples/npm-libs/flows/simple-greeting.yaml +54 -0
  120. package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +307 -0
  121. package/examples/npm-libs/graph-of-graphs/url-processing-pipeline.ts +254 -0
  122. package/examples/npm-libs/inference/azure-deployment.ts +149 -0
  123. package/examples/npm-libs/inference/copilot-cli.ts +138 -0
  124. package/examples/npm-libs/inference/data-pipeline.ts +145 -0
  125. package/examples/npm-libs/inference/pluggable-adapters.ts +254 -0
  126. package/examples/npm-libs/node/ai-conversation.ts +195 -0
  127. package/examples/npm-libs/node/simple-greeting.ts +101 -0
  128. package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
  129. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
  130. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
  131. package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
  132. package/examples/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
  133. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
  134. package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
  135. package/examples/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
  136. package/examples/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
  137. package/examples/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
  138. package/examples/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
  139. package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
  140. package/examples/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
  141. package/examples/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
  142. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
  143. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
  144. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
  145. package/package.json +14 -2
  146. package/schema/board-status.schema.json +118 -0
  147. package/schema/flow.schema.json +5 -0
  148. package/schema/live-cards.schema.json +59 -77
  149. package/step-machine-cli.js +674 -0
@@ -0,0 +1,2332 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { randomUUID, createHash } from 'crypto';
5
+ import { execFileSync, spawn, execFile } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import fg from 'fast-glob';
8
+ import { lockSync } from 'proper-lockfile';
9
+ import jsonata from 'jsonata';
10
+ import 'ajv-formats';
11
+
12
+ // src/cli/board-live-cards-cli.ts
13
+
14
+ // src/event-graph/constants.ts
15
+ var TASK_STATUS = {
16
+ RUNNING: "running",
17
+ COMPLETED: "completed",
18
+ FAILED: "failed",
19
+ INACTIVATED: "inactivated"
20
+ };
21
+
22
+ // src/event-graph/graph-helpers.ts
23
+ function getProvides(task) {
24
+ if (!task) return [];
25
+ if (Array.isArray(task.provides)) return task.provides;
26
+ return [];
27
+ }
28
+ function getRequires(task) {
29
+ if (!task) return [];
30
+ if (Array.isArray(task.requires)) return task.requires;
31
+ return [];
32
+ }
33
+ function getAllTasks(graph) {
34
+ return graph.tasks ?? {};
35
+ }
36
+ function isNonActiveTask(taskState) {
37
+ if (!taskState) return false;
38
+ return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
39
+ }
40
+ function getRefreshStrategy(taskConfig, graphSettings) {
41
+ return taskConfig.refreshStrategy ?? graphSettings?.refreshStrategy ?? "data-changed";
42
+ }
43
+ function getMaxExecutions(taskConfig) {
44
+ return taskConfig.maxExecutions;
45
+ }
46
+ function computeAvailableOutputs(graph, taskStates) {
47
+ const outputs = /* @__PURE__ */ new Set();
48
+ for (const [taskName, taskState] of Object.entries(taskStates)) {
49
+ if (taskState.status === TASK_STATUS.COMPLETED) {
50
+ const taskConfig = graph.tasks[taskName];
51
+ if (taskConfig) {
52
+ const provides = getProvides(taskConfig);
53
+ provides.forEach((output) => outputs.add(output));
54
+ }
55
+ }
56
+ }
57
+ return Array.from(outputs);
58
+ }
59
+ function groupTasksByProvides(candidateTaskNames, tasks) {
60
+ const outputGroups = {};
61
+ candidateTaskNames.forEach((taskName) => {
62
+ const task = tasks[taskName];
63
+ if (!task) return;
64
+ const provides = getProvides(task);
65
+ provides.forEach((output) => {
66
+ if (!outputGroups[output]) {
67
+ outputGroups[output] = [];
68
+ }
69
+ outputGroups[output].push(taskName);
70
+ });
71
+ });
72
+ return outputGroups;
73
+ }
74
+
75
+ // src/event-graph/task-transitions.ts
76
+ function applyTaskStart(state, taskName) {
77
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
78
+ const updatedTask = {
79
+ ...existingTask,
80
+ status: "running",
81
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
82
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
83
+ progress: 0,
84
+ error: void 0
85
+ };
86
+ return {
87
+ ...state,
88
+ tasks: { ...state.tasks, [taskName]: updatedTask },
89
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
90
+ };
91
+ }
92
+ function applyTaskCompletion(state, graph, taskName, result, dataHash, data) {
93
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
94
+ const taskConfig = graph.tasks[taskName];
95
+ if (!taskConfig) {
96
+ throw new Error(`Task "${taskName}" not found in graph`);
97
+ }
98
+ let outputTokens;
99
+ if (result && taskConfig.on && taskConfig.on[result]) {
100
+ outputTokens = taskConfig.on[result];
101
+ } else {
102
+ outputTokens = getProvides(taskConfig);
103
+ }
104
+ const lastConsumedHashes = { ...existingTask.lastConsumedHashes };
105
+ const requires = taskConfig.requires ?? [];
106
+ for (const token of requires) {
107
+ for (const [otherName, otherConfig] of Object.entries(graph.tasks)) {
108
+ if (getProvides(otherConfig).includes(token)) {
109
+ const otherState = state.tasks[otherName];
110
+ if (otherState?.lastDataHash) {
111
+ lastConsumedHashes[token] = otherState.lastDataHash;
112
+ }
113
+ break;
114
+ }
115
+ }
116
+ }
117
+ const updatedTask = {
118
+ ...existingTask,
119
+ status: "completed",
120
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
121
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
122
+ executionCount: existingTask.executionCount + 1,
123
+ lastEpoch: existingTask.executionCount + 1,
124
+ lastDataHash: dataHash,
125
+ data,
126
+ lastConsumedHashes,
127
+ error: void 0
128
+ };
129
+ const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
130
+ return {
131
+ ...state,
132
+ tasks: { ...state.tasks, [taskName]: updatedTask },
133
+ availableOutputs: newOutputs,
134
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
135
+ };
136
+ }
137
+ function applyTaskFailure(state, graph, taskName, error) {
138
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
139
+ const taskConfig = graph.tasks[taskName];
140
+ if (taskConfig?.retry) {
141
+ const retryCount = existingTask.retryCount + 1;
142
+ if (retryCount <= taskConfig.retry.max_attempts) {
143
+ const updatedTask2 = {
144
+ ...existingTask,
145
+ status: "not-started",
146
+ retryCount,
147
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
148
+ error
149
+ };
150
+ return {
151
+ ...state,
152
+ tasks: { ...state.tasks, [taskName]: updatedTask2 },
153
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
154
+ };
155
+ }
156
+ }
157
+ const updatedTask = {
158
+ ...existingTask,
159
+ status: "failed",
160
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
161
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
162
+ error,
163
+ executionCount: existingTask.executionCount + 1
164
+ };
165
+ let newOutputs = state.availableOutputs;
166
+ if (taskConfig?.on_failure && taskConfig.on_failure.length > 0) {
167
+ newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...taskConfig.on_failure])];
168
+ }
169
+ if (taskConfig?.circuit_breaker && updatedTask.executionCount >= taskConfig.circuit_breaker.max_executions) {
170
+ const breakTokens = taskConfig.circuit_breaker.on_break;
171
+ newOutputs = [.../* @__PURE__ */ new Set([...newOutputs, ...breakTokens])];
172
+ }
173
+ return {
174
+ ...state,
175
+ tasks: { ...state.tasks, [taskName]: updatedTask },
176
+ availableOutputs: newOutputs,
177
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
178
+ };
179
+ }
180
+ function applyTaskProgress(state, taskName, message, progress) {
181
+ const existingTask = state.tasks[taskName] ?? createDefaultGraphEngineStore();
182
+ const updatedTask = {
183
+ ...existingTask,
184
+ progress: typeof progress === "number" ? progress : existingTask.progress,
185
+ messages: [
186
+ ...existingTask.messages ?? [],
187
+ ...message ? [{ message, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: existingTask.status }] : []
188
+ ],
189
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
190
+ };
191
+ return {
192
+ ...state,
193
+ tasks: { ...state.tasks, [taskName]: updatedTask },
194
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
195
+ };
196
+ }
197
+ function applyTaskRestart(state, taskName) {
198
+ const existingTask = state.tasks[taskName];
199
+ if (!existingTask) return state;
200
+ const updatedTask = {
201
+ ...existingTask,
202
+ status: "not-started",
203
+ startedAt: void 0,
204
+ completedAt: void 0,
205
+ failedAt: void 0,
206
+ error: void 0,
207
+ data: void 0,
208
+ progress: null,
209
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
210
+ };
211
+ return {
212
+ ...state,
213
+ tasks: { ...state.tasks, [taskName]: updatedTask },
214
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
215
+ };
216
+ }
217
+ function createDefaultGraphEngineStore() {
218
+ return {
219
+ status: "not-started",
220
+ executionCount: 0,
221
+ retryCount: 0,
222
+ lastEpoch: 0,
223
+ messages: [],
224
+ progress: null
225
+ };
226
+ }
227
+
228
+ // src/continuous-event-graph/core.ts
229
+ function createLiveGraph(config, executionId) {
230
+ const id = `live-${Date.now()}`;
231
+ const tasks = {};
232
+ for (const taskName of Object.keys(config.tasks)) {
233
+ tasks[taskName] = createDefaultGraphEngineStore2();
234
+ }
235
+ const state = {
236
+ status: "running",
237
+ tasks,
238
+ availableOutputs: [],
239
+ stuckDetection: { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] },
240
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
241
+ executionId: id,
242
+ executionConfig: {
243
+ executionMode: config.settings.execution_mode ?? "eligibility-mode",
244
+ conflictStrategy: config.settings.conflict_strategy ?? "alphabetical",
245
+ completionStrategy: config.settings.completion
246
+ }
247
+ };
248
+ return { config, state };
249
+ }
250
+ function applyEvent(live, event) {
251
+ const { config, state } = live;
252
+ if ("executionId" in event && event.executionId && event.executionId !== state.executionId) {
253
+ return live;
254
+ }
255
+ switch (event.type) {
256
+ // --- Execution state transitions ---
257
+ case "task-started":
258
+ return { config, state: applyTaskStart(state, event.taskName) };
259
+ case "task-completed":
260
+ return { config, state: applyTaskCompletion(state, config, event.taskName, event.result, event.dataHash, event.data) };
261
+ case "task-failed":
262
+ return { config, state: applyTaskFailure(state, config, event.taskName, event.error) };
263
+ case "task-progress":
264
+ return { config, state: applyTaskProgress(state, event.taskName, event.message, event.progress) };
265
+ case "task-restart":
266
+ return { config, state: applyTaskRestart(state, event.taskName) };
267
+ case "inject-tokens":
268
+ return {
269
+ config,
270
+ state: {
271
+ ...state,
272
+ availableOutputs: [.../* @__PURE__ */ new Set([...state.availableOutputs, ...event.tokens])],
273
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
274
+ }
275
+ };
276
+ case "agent-action":
277
+ return { config, state: applyAgentAction(state, event.action) };
278
+ // --- Structural mutations ---
279
+ case "task-upsert":
280
+ return addNode(live, event.taskName, event.taskConfig);
281
+ case "task-removal":
282
+ return removeNode(live, event.taskName);
283
+ case "node-requires-add":
284
+ return addRequires(live, event.nodeName, event.tokens);
285
+ case "node-requires-remove":
286
+ return removeRequires(live, event.nodeName, event.tokens);
287
+ case "node-provides-add":
288
+ return addProvides(live, event.nodeName, event.tokens);
289
+ case "node-provides-remove":
290
+ return removeProvides(live, event.nodeName, event.tokens);
291
+ default:
292
+ return live;
293
+ }
294
+ }
295
+ function applyEvents(live, events) {
296
+ return events.reduce((current, event) => applyEvent(current, event), live);
297
+ }
298
+ function addNode(live, name, taskConfig) {
299
+ const exists = !!live.config.tasks[name];
300
+ return {
301
+ config: {
302
+ ...live.config,
303
+ tasks: { ...live.config.tasks, [name]: taskConfig }
304
+ },
305
+ state: {
306
+ ...live.state,
307
+ tasks: {
308
+ ...live.state.tasks,
309
+ [name]: exists ? live.state.tasks[name] : createDefaultGraphEngineStore2()
310
+ },
311
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
312
+ }
313
+ };
314
+ }
315
+ function removeNode(live, name) {
316
+ if (!live.config.tasks[name]) return live;
317
+ const { [name]: _removedConfig, ...remainingTasks } = live.config.tasks;
318
+ const { [name]: _removedState, ...remainingStates } = live.state.tasks;
319
+ return {
320
+ config: {
321
+ ...live.config,
322
+ tasks: remainingTasks
323
+ },
324
+ state: {
325
+ ...live.state,
326
+ tasks: remainingStates,
327
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
328
+ }
329
+ };
330
+ }
331
+ function addRequires(live, nodeName, tokens) {
332
+ const task = live.config.tasks[nodeName];
333
+ if (!task) return live;
334
+ const current = getRequires(task);
335
+ const toAdd = tokens.filter((t) => !current.includes(t));
336
+ if (toAdd.length === 0) return live;
337
+ return {
338
+ config: {
339
+ ...live.config,
340
+ tasks: {
341
+ ...live.config.tasks,
342
+ [nodeName]: { ...task, requires: [...current, ...toAdd] }
343
+ }
344
+ },
345
+ state: live.state
346
+ };
347
+ }
348
+ function removeRequires(live, nodeName, tokens) {
349
+ const task = live.config.tasks[nodeName];
350
+ if (!task) return live;
351
+ const current = getRequires(task);
352
+ const remaining = current.filter((t) => !tokens.includes(t));
353
+ if (remaining.length === current.length) return live;
354
+ return {
355
+ config: {
356
+ ...live.config,
357
+ tasks: {
358
+ ...live.config.tasks,
359
+ [nodeName]: { ...task, requires: remaining }
360
+ }
361
+ },
362
+ state: live.state
363
+ };
364
+ }
365
+ function addProvides(live, nodeName, tokens) {
366
+ const task = live.config.tasks[nodeName];
367
+ if (!task) return live;
368
+ const current = getProvides(task);
369
+ const toAdd = tokens.filter((t) => !current.includes(t));
370
+ if (toAdd.length === 0) return live;
371
+ return {
372
+ config: {
373
+ ...live.config,
374
+ tasks: {
375
+ ...live.config.tasks,
376
+ [nodeName]: { ...task, provides: [...current, ...toAdd] }
377
+ }
378
+ },
379
+ state: live.state
380
+ };
381
+ }
382
+ function removeProvides(live, nodeName, tokens) {
383
+ const task = live.config.tasks[nodeName];
384
+ if (!task) return live;
385
+ const current = getProvides(task);
386
+ const remaining = current.filter((t) => !tokens.includes(t));
387
+ if (remaining.length === current.length) return live;
388
+ return {
389
+ config: {
390
+ ...live.config,
391
+ tasks: {
392
+ ...live.config.tasks,
393
+ [nodeName]: { ...task, provides: remaining }
394
+ }
395
+ },
396
+ state: live.state
397
+ };
398
+ }
399
+ function snapshot(live) {
400
+ return {
401
+ version: 1,
402
+ config: live.config,
403
+ state: live.state,
404
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString()
405
+ };
406
+ }
407
+ function restore(data) {
408
+ if (!data || typeof data !== "object") {
409
+ throw new Error("Invalid snapshot: expected an object");
410
+ }
411
+ const snap = data;
412
+ if (!snap.config || typeof snap.config !== "object") {
413
+ throw new Error('Invalid snapshot: missing or invalid "config"');
414
+ }
415
+ if (!snap.state || typeof snap.state !== "object") {
416
+ throw new Error('Invalid snapshot: missing or invalid "state"');
417
+ }
418
+ const config = snap.config;
419
+ const state = snap.state;
420
+ if (!config.settings || typeof config.settings !== "object") {
421
+ throw new Error("Invalid snapshot: config.settings missing");
422
+ }
423
+ if (!config.tasks || typeof config.tasks !== "object") {
424
+ throw new Error("Invalid snapshot: config.tasks missing");
425
+ }
426
+ if (!state.tasks || typeof state.tasks !== "object") {
427
+ throw new Error("Invalid snapshot: state.tasks missing");
428
+ }
429
+ if (!Array.isArray(state.availableOutputs)) {
430
+ throw new Error("Invalid snapshot: state.availableOutputs must be an array");
431
+ }
432
+ return { config, state };
433
+ }
434
+ function createDefaultGraphEngineStore2() {
435
+ return {
436
+ status: "not-started",
437
+ executionCount: 0,
438
+ retryCount: 0,
439
+ lastEpoch: 0,
440
+ messages: [],
441
+ progress: null
442
+ };
443
+ }
444
+ function applyAgentAction(state, action) {
445
+ const now = (/* @__PURE__ */ new Date()).toISOString();
446
+ switch (action) {
447
+ case "stop":
448
+ return { ...state, status: "stopped", lastUpdated: now };
449
+ case "pause":
450
+ return { ...state, status: "paused", lastUpdated: now };
451
+ case "resume":
452
+ return { ...state, status: "running", lastUpdated: now };
453
+ default:
454
+ return state;
455
+ }
456
+ }
457
+
458
+ // src/continuous-event-graph/schedule.ts
459
+ function schedule(live) {
460
+ const { config, state } = live;
461
+ const graphTasks = getAllTasks(config);
462
+ const taskNames = Object.keys(graphTasks);
463
+ if (taskNames.length === 0) {
464
+ return { eligible: [], pending: [], unresolved: [], blocked: [], conflicts: {} };
465
+ }
466
+ const producerMap = buildProducerMap(graphTasks);
467
+ const computedOutputs = computeAvailableOutputs(config, state.tasks);
468
+ const availableOutputs = /* @__PURE__ */ new Set([...computedOutputs, ...state.availableOutputs]);
469
+ const eligible = [];
470
+ const pending = [];
471
+ const unresolved = [];
472
+ const blocked = [];
473
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
474
+ const taskState = state.tasks[taskName];
475
+ const strategy = getRefreshStrategy(taskConfig, config.settings);
476
+ const rerunnable = strategy !== "once";
477
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
478
+ continue;
479
+ }
480
+ const maxExec = getMaxExecutions(taskConfig);
481
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
482
+ continue;
483
+ }
484
+ if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
485
+ continue;
486
+ }
487
+ if (!rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
488
+ continue;
489
+ }
490
+ if (rerunnable && taskState?.status === TASK_STATUS.COMPLETED) {
491
+ const requires2 = getRequires(taskConfig);
492
+ let shouldSkip = false;
493
+ switch (strategy) {
494
+ case "data-changed": {
495
+ if (requires2.length > 0) {
496
+ const hasChangedData = requires2.some((req) => {
497
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
498
+ if (getProvides(otherConfig).includes(req)) {
499
+ const otherState = state.tasks[otherName];
500
+ if (!otherState) continue;
501
+ const consumed = taskState.lastConsumedHashes?.[req];
502
+ if (otherState.lastDataHash == null) {
503
+ return otherState.executionCount > taskState.lastEpoch;
504
+ }
505
+ return otherState.lastDataHash !== consumed;
506
+ }
507
+ }
508
+ return false;
509
+ });
510
+ if (!hasChangedData) shouldSkip = true;
511
+ } else {
512
+ shouldSkip = true;
513
+ }
514
+ break;
515
+ }
516
+ case "epoch-changed": {
517
+ if (requires2.length > 0) {
518
+ const hasRefreshed = requires2.some((req) => {
519
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
520
+ if (getProvides(otherConfig).includes(req)) {
521
+ const otherState = state.tasks[otherName];
522
+ if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
523
+ }
524
+ }
525
+ return false;
526
+ });
527
+ if (!hasRefreshed) shouldSkip = true;
528
+ } else {
529
+ shouldSkip = true;
530
+ }
531
+ break;
532
+ }
533
+ case "time-based": {
534
+ const interval = taskConfig.refreshInterval ?? 0;
535
+ if (interval <= 0) {
536
+ shouldSkip = true;
537
+ break;
538
+ }
539
+ const completedAt = taskState.completedAt;
540
+ if (!completedAt) {
541
+ shouldSkip = true;
542
+ break;
543
+ }
544
+ const elapsedSec = (Date.now() - Date.parse(completedAt)) / 1e3;
545
+ if (elapsedSec < interval) shouldSkip = true;
546
+ break;
547
+ }
548
+ case "manual":
549
+ shouldSkip = true;
550
+ break;
551
+ }
552
+ if (shouldSkip) continue;
553
+ }
554
+ const requires = getRequires(taskConfig);
555
+ if (requires.length === 0) {
556
+ eligible.push(taskName);
557
+ continue;
558
+ }
559
+ const missingTokens = [];
560
+ const pendingTokens = [];
561
+ const failedTokenInfo = [];
562
+ for (const token of requires) {
563
+ if (availableOutputs.has(token)) continue;
564
+ const producers = producerMap[token] || [];
565
+ if (producers.length === 0) {
566
+ missingTokens.push(token);
567
+ } else {
568
+ const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
569
+ if (allFailed) {
570
+ failedTokenInfo.push({ token, failedProducer: producers[0] });
571
+ } else {
572
+ pendingTokens.push(token);
573
+ }
574
+ }
575
+ }
576
+ if (missingTokens.length > 0) {
577
+ unresolved.push({ taskName, missingTokens });
578
+ } else if (failedTokenInfo.length > 0) {
579
+ blocked.push({
580
+ taskName,
581
+ failedTokens: failedTokenInfo.map((f) => f.token),
582
+ failedProducers: [...new Set(failedTokenInfo.map((f) => f.failedProducer))]
583
+ });
584
+ } else if (pendingTokens.length > 0) {
585
+ pending.push({ taskName, waitingOn: pendingTokens });
586
+ } else {
587
+ eligible.push(taskName);
588
+ }
589
+ }
590
+ const conflicts = {};
591
+ if (eligible.length > 1) {
592
+ const outputGroups = groupTasksByProvides(eligible, graphTasks);
593
+ for (const [outputKey, groupTasks] of Object.entries(outputGroups)) {
594
+ if (groupTasks.length > 1) {
595
+ conflicts[outputKey] = groupTasks;
596
+ }
597
+ }
598
+ }
599
+ return { eligible, pending, unresolved, blocked, conflicts };
600
+ }
601
+ function buildProducerMap(tasks) {
602
+ const map = {};
603
+ for (const [name, config] of Object.entries(tasks)) {
604
+ for (const token of getProvides(config)) {
605
+ if (!map[token]) map[token] = [];
606
+ map[token].push(name);
607
+ }
608
+ if (config.on) {
609
+ for (const tokens of Object.values(config.on)) {
610
+ for (const token of tokens) {
611
+ if (!map[token]) map[token] = [];
612
+ if (!map[token].includes(name)) map[token].push(name);
613
+ }
614
+ }
615
+ }
616
+ if (config.on_failure) {
617
+ for (const token of config.on_failure) {
618
+ if (!map[token]) map[token] = [];
619
+ if (!map[token].includes(name)) map[token].push(name);
620
+ }
621
+ }
622
+ }
623
+ return map;
624
+ }
625
+
626
+ // src/continuous-event-graph/journal.ts
627
+ var MemoryJournal = class {
628
+ buffer = [];
629
+ append(event) {
630
+ this.buffer.push(event);
631
+ }
632
+ drain() {
633
+ const events = this.buffer;
634
+ this.buffer = [];
635
+ return events;
636
+ }
637
+ get size() {
638
+ return this.buffer.length;
639
+ }
640
+ };
641
+ function computeDataHash(data) {
642
+ const json = stableStringify(data);
643
+ return createHash("sha256").update(json).digest("hex").slice(0, 16);
644
+ }
645
+ function stableStringify(value) {
646
+ if (value === null || value === void 0 || typeof value !== "object") {
647
+ return JSON.stringify(value);
648
+ }
649
+ if (Array.isArray(value)) {
650
+ return "[" + value.map(stableStringify).join(",") + "]";
651
+ }
652
+ const obj = value;
653
+ const keys = Object.keys(obj).sort();
654
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
655
+ }
656
+ function encodeCallbackToken(taskName) {
657
+ const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
658
+ return Buffer.from(payload).toString("base64url");
659
+ }
660
+ function decodeCallbackToken(token) {
661
+ try {
662
+ const payload = JSON.parse(Buffer.from(token, "base64url").toString());
663
+ if (typeof payload?.t === "string") return { taskName: payload.t };
664
+ return null;
665
+ } catch {
666
+ return null;
667
+ }
668
+ }
669
+ function createReactiveGraph(configOrLive, options, executionId) {
670
+ const {
671
+ handlers: initialHandlers,
672
+ onDrain
673
+ } = options;
674
+ const inputQueue = new MemoryJournal();
675
+ let live = "state" in configOrLive && "config" in configOrLive ? configOrLive : createLiveGraph(configOrLive);
676
+ let disposed = false;
677
+ const handlers = new Map(Object.entries(initialHandlers));
678
+ const internalJournal = new MemoryJournal();
679
+ let draining = false;
680
+ let drainQueued = false;
681
+ function drain() {
682
+ if (disposed) return;
683
+ if (draining) {
684
+ drainQueued = true;
685
+ return;
686
+ }
687
+ draining = true;
688
+ try {
689
+ do {
690
+ drainQueued = false;
691
+ drainOnce();
692
+ } while (drainQueued);
693
+ } finally {
694
+ draining = false;
695
+ }
696
+ }
697
+ function drainOnce() {
698
+ const internalEvents = internalJournal.drain();
699
+ const inputEvents = inputQueue.drain();
700
+ const events = [...internalEvents, ...inputEvents];
701
+ if (events.length > 0) {
702
+ live = applyEvents(live, events);
703
+ }
704
+ const result = schedule(live);
705
+ if (events.length > 0) {
706
+ onDrain?.(events, live, result);
707
+ }
708
+ for (const taskName of result.eligible) {
709
+ dispatchTask(taskName);
710
+ }
711
+ for (const event of events) {
712
+ if (event.type === "task-progress") {
713
+ const { taskName, update } = event;
714
+ const taskConfig = live.config.tasks[taskName];
715
+ if (!taskConfig) continue;
716
+ const taskState = live.state.tasks[taskName];
717
+ if (!taskState || taskState.status !== "running") continue;
718
+ const callbackToken = encodeCallbackToken(taskName);
719
+ runPipeline(taskName, callbackToken, update).catch((error) => {
720
+ if (disposed) return;
721
+ internalJournal.append({
722
+ type: "task-failed",
723
+ taskName,
724
+ error: error.message ?? String(error),
725
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
726
+ });
727
+ drain();
728
+ });
729
+ }
730
+ }
731
+ }
732
+ function resolveUpstreamState(taskName) {
733
+ const taskConfig = live.config.tasks[taskName];
734
+ const requires = taskConfig.requires ?? [];
735
+ const tokenToTask = /* @__PURE__ */ new Map();
736
+ for (const [name, cfg] of Object.entries(live.config.tasks)) {
737
+ for (const token of cfg.provides ?? []) {
738
+ tokenToTask.set(token, name);
739
+ }
740
+ }
741
+ const state = {};
742
+ for (const token of requires) {
743
+ const producerTask = tokenToTask.get(token);
744
+ if (producerTask) {
745
+ state[token] = live.state.tasks[producerTask]?.data;
746
+ } else {
747
+ state[token] = void 0;
748
+ }
749
+ }
750
+ return state;
751
+ }
752
+ async function runPipeline(taskName, callbackToken, update) {
753
+ const taskConfig = live.config.tasks[taskName];
754
+ const handlerNames = taskConfig.taskHandlers ?? [];
755
+ const upstreamState = resolveUpstreamState(taskName);
756
+ for (const handlerName of handlerNames) {
757
+ const handler = handlers.get(handlerName);
758
+ if (!handler) {
759
+ throw new Error(`Handler '${handlerName}' not found in registry (task '${taskName}')`);
760
+ }
761
+ const input = {
762
+ nodeId: taskName,
763
+ state: upstreamState,
764
+ taskState: live.state.tasks[taskName],
765
+ config: taskConfig,
766
+ callbackToken,
767
+ update
768
+ };
769
+ const status = await handler(input);
770
+ if (status === "task-initiate-failure") {
771
+ throw new Error(`Handler '${handlerName}' returned task-initiate-failure (task '${taskName}')`);
772
+ }
773
+ }
774
+ }
775
+ function dispatchTask(taskName) {
776
+ const taskConfig = live.config.tasks[taskName];
777
+ const handlerNames = taskConfig?.taskHandlers;
778
+ if (!handlerNames || handlerNames.length === 0) {
779
+ return;
780
+ }
781
+ internalJournal.append({
782
+ type: "task-started",
783
+ taskName,
784
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
785
+ });
786
+ drain();
787
+ const callbackToken = encodeCallbackToken(taskName);
788
+ runPipeline(taskName, callbackToken).catch((error) => {
789
+ if (disposed) return;
790
+ internalJournal.append({
791
+ type: "task-failed",
792
+ taskName,
793
+ error: error.message ?? String(error),
794
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
795
+ });
796
+ drain();
797
+ });
798
+ }
799
+ return {
800
+ push(event) {
801
+ if (disposed) return;
802
+ if (event.type === "task-completed" && event.data && !event.dataHash) {
803
+ event = { ...event, dataHash: computeDataHash(event.data) };
804
+ }
805
+ inputQueue.append(event);
806
+ drain();
807
+ },
808
+ pushAll(events) {
809
+ if (disposed) return;
810
+ for (const event of events) {
811
+ if (event.type === "task-completed" && event.data && !event.dataHash) {
812
+ inputQueue.append({ ...event, dataHash: computeDataHash(event.data) });
813
+ } else {
814
+ inputQueue.append(event);
815
+ }
816
+ }
817
+ drain();
818
+ },
819
+ resolveCallback(callbackToken, data, errors) {
820
+ if (disposed) return;
821
+ const decoded = decodeCallbackToken(callbackToken);
822
+ if (!decoded) return;
823
+ const { taskName } = decoded;
824
+ if (!live.config.tasks[taskName]) return;
825
+ if (errors && errors.length > 0) {
826
+ inputQueue.append({
827
+ type: "task-failed",
828
+ taskName,
829
+ error: errors.join("; "),
830
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
831
+ });
832
+ } else {
833
+ const dataHash = data && Object.keys(data).length > 0 ? computeDataHash(data) : void 0;
834
+ inputQueue.append({
835
+ type: "task-completed",
836
+ taskName,
837
+ data,
838
+ dataHash,
839
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
840
+ });
841
+ }
842
+ drain();
843
+ },
844
+ addNode(name, taskConfig) {
845
+ if (disposed) return;
846
+ inputQueue.append({ type: "task-upsert", taskName: name, taskConfig, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
847
+ drain();
848
+ },
849
+ removeNode(name) {
850
+ if (disposed) return;
851
+ inputQueue.append({ type: "task-removal", taskName: name, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
852
+ drain();
853
+ },
854
+ addRequires(nodeName, tokens) {
855
+ if (disposed) return;
856
+ inputQueue.append({ type: "node-requires-add", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
857
+ drain();
858
+ },
859
+ removeRequires(nodeName, tokens) {
860
+ if (disposed) return;
861
+ inputQueue.append({ type: "node-requires-remove", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
862
+ drain();
863
+ },
864
+ addProvides(nodeName, tokens) {
865
+ if (disposed) return;
866
+ inputQueue.append({ type: "node-provides-add", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
867
+ drain();
868
+ },
869
+ removeProvides(nodeName, tokens) {
870
+ if (disposed) return;
871
+ inputQueue.append({ type: "node-provides-remove", nodeName, tokens, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
872
+ drain();
873
+ },
874
+ registerHandler(name, fn) {
875
+ handlers.set(name, fn);
876
+ },
877
+ unregisterHandler(name) {
878
+ handlers.delete(name);
879
+ },
880
+ retrigger(taskName) {
881
+ if (disposed) return;
882
+ if (!live.config.tasks[taskName]) return;
883
+ inputQueue.append({
884
+ type: "task-restart",
885
+ taskName,
886
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
887
+ });
888
+ drain();
889
+ },
890
+ retriggerAll(taskNames) {
891
+ if (disposed) return;
892
+ for (const name of taskNames) {
893
+ if (!live.config.tasks[name]) continue;
894
+ inputQueue.append({
895
+ type: "task-restart",
896
+ taskName: name,
897
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
898
+ });
899
+ }
900
+ drain();
901
+ },
902
+ snapshot() {
903
+ return snapshot(live);
904
+ },
905
+ getState() {
906
+ return live;
907
+ },
908
+ getSchedule() {
909
+ return schedule(live);
910
+ },
911
+ dispose() {
912
+ disposed = true;
913
+ }
914
+ };
915
+ }
916
+
917
+ // src/card-compute/index.ts
918
+ function deepGet(obj, path2) {
919
+ if (!path2 || !obj) return void 0;
920
+ const parts = path2.split(".");
921
+ let cur = obj;
922
+ for (let i = 0; i < parts.length; i++) {
923
+ if (cur == null) return void 0;
924
+ cur = cur[parts[i]];
925
+ }
926
+ return cur;
927
+ }
928
+ function deepSet(obj, path2, value) {
929
+ const parts = path2.split(".");
930
+ let cur = obj;
931
+ for (let i = 0; i < parts.length - 1; i++) {
932
+ if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
933
+ cur = cur[parts[i]];
934
+ }
935
+ cur[parts[parts.length - 1]] = value;
936
+ }
937
+ async function run(node, options) {
938
+ if (!node?.compute?.length) return node;
939
+ if (!node.state) node.state = {};
940
+ node.computed_values = {};
941
+ node._sourcesData = options?.sourcesData ?? {};
942
+ const ctx = {
943
+ state: node.state,
944
+ requires: node.requires ?? {},
945
+ sources: node._sourcesData,
946
+ computed_values: node.computed_values
947
+ };
948
+ for (const step of node.compute) {
949
+ try {
950
+ const val = await jsonata(step.expr).evaluate(ctx);
951
+ deepSet(node.computed_values, step.bindTo, val);
952
+ ctx.computed_values = node.computed_values;
953
+ } catch (err) {
954
+ console.error(`CardCompute.run error on "${node.id ?? "?"}.${step.bindTo}":`, err);
955
+ }
956
+ }
957
+ return node;
958
+ }
959
+ async function evalExpr(expr, node) {
960
+ const ctx = {
961
+ state: node.state ?? {},
962
+ requires: node.requires ?? {},
963
+ sources: node._sourcesData ?? {},
964
+ computed_values: node.computed_values ?? {}
965
+ };
966
+ return jsonata(expr).evaluate(ctx);
967
+ }
968
+ function resolve(node, path2) {
969
+ if (path2.startsWith("sources.")) {
970
+ return deepGet(node._sourcesData ?? {}, path2.slice("sources.".length));
971
+ }
972
+ return deepGet(node, path2);
973
+ }
974
+ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
975
+ "metric",
976
+ "table",
977
+ "chart",
978
+ "form",
979
+ "filter",
980
+ "list",
981
+ "notes",
982
+ "todo",
983
+ "alert",
984
+ "narrative",
985
+ "badge",
986
+ "text",
987
+ "markdown",
988
+ "custom"
989
+ ]);
990
+ var VALID_STATUSES = /* @__PURE__ */ new Set(["fresh", "stale", "loading", "error"]);
991
+ var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "state", "compute", "sources"]);
992
+ function validateNode(node) {
993
+ const errors = [];
994
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
995
+ return { ok: false, errors: ["Node must be a non-null object"] };
996
+ }
997
+ const n = node;
998
+ if (typeof n.id !== "string" || !n.id) errors.push("id: required, must be a non-empty string");
999
+ for (const key of Object.keys(n)) {
1000
+ if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
1001
+ }
1002
+ if (n.state == null || typeof n.state !== "object" || Array.isArray(n.state)) {
1003
+ errors.push("state: required, must be an object");
1004
+ } else {
1005
+ const state = n.state;
1006
+ if (state.status != null && !VALID_STATUSES.has(state.status)) {
1007
+ errors.push(`state.status: must be one of: ${[...VALID_STATUSES].join(", ")}`);
1008
+ }
1009
+ }
1010
+ if (n.meta != null) {
1011
+ if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
1012
+ errors.push("meta: must be an object");
1013
+ } else {
1014
+ const meta = n.meta;
1015
+ if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
1016
+ if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
1017
+ }
1018
+ }
1019
+ if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
1020
+ if (n.provides != null) {
1021
+ if (!Array.isArray(n.provides)) {
1022
+ errors.push("provides: must be an array of { bindTo, src } bindings");
1023
+ } else {
1024
+ n.provides.forEach((p, i) => {
1025
+ if (!p || typeof p !== "object" || Array.isArray(p)) {
1026
+ errors.push(`provides[${i}]: must be an object with bindTo and src`);
1027
+ } else {
1028
+ const b = p;
1029
+ if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
1030
+ if (typeof b.src !== "string" || !b.src) errors.push(`provides[${i}]: missing required "src" string`);
1031
+ }
1032
+ });
1033
+ }
1034
+ }
1035
+ if (n.compute != null) {
1036
+ if (!Array.isArray(n.compute)) {
1037
+ errors.push("compute: must be an array of compute steps");
1038
+ } else {
1039
+ n.compute.forEach((step, i) => {
1040
+ if (!step || typeof step !== "object" || Array.isArray(step)) {
1041
+ errors.push(`compute[${i}]: must be a compute step object`);
1042
+ } else {
1043
+ const s = step;
1044
+ if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`compute[${i}]: missing required "bindTo" property`);
1045
+ if (typeof s.expr !== "string" || !s.expr) errors.push(`compute[${i}]: missing required "expr" string (JSONata expression)`);
1046
+ }
1047
+ });
1048
+ }
1049
+ }
1050
+ if (n.sources != null) {
1051
+ if (!Array.isArray(n.sources)) {
1052
+ errors.push("sources: must be an array");
1053
+ } else {
1054
+ n.sources.forEach((src, i) => {
1055
+ if (!src || typeof src !== "object" || Array.isArray(src)) {
1056
+ errors.push(`sources[${i}]: must be an object`);
1057
+ } else {
1058
+ const s = src;
1059
+ if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`sources[${i}]: missing required "bindTo" property`);
1060
+ if (s.outputFile != null && typeof s.outputFile !== "string") errors.push(`sources[${i}]: outputFile must be a string`);
1061
+ if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
1062
+ errors.push(`sources[${i}]: optionalForCompletionGating must be a boolean`);
1063
+ }
1064
+ }
1065
+ });
1066
+ }
1067
+ }
1068
+ if (n.view != null) {
1069
+ if (typeof n.view !== "object" || Array.isArray(n.view)) {
1070
+ errors.push("view: must be an object");
1071
+ } else {
1072
+ const view = n.view;
1073
+ if (!Array.isArray(view.elements) || view.elements.length === 0) {
1074
+ errors.push("view.elements: required, must be a non-empty array");
1075
+ } else {
1076
+ view.elements.forEach((elem, i) => {
1077
+ if (!elem || typeof elem !== "object") {
1078
+ errors.push(`view.elements[${i}]: must be an object`);
1079
+ return;
1080
+ }
1081
+ if (!elem.kind || typeof elem.kind !== "string") {
1082
+ errors.push(`view.elements[${i}].kind: required, must be a string`);
1083
+ } else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
1084
+ errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
1085
+ }
1086
+ if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
1087
+ errors.push(`view.elements[${i}].data: must be an object`);
1088
+ }
1089
+ });
1090
+ }
1091
+ if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) errors.push("view.layout: must be an object");
1092
+ if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) errors.push("view.features: must be an object");
1093
+ }
1094
+ }
1095
+ return { ok: errors.length === 0, errors };
1096
+ }
1097
+ var CardCompute = {
1098
+ run,
1099
+ eval: evalExpr,
1100
+ resolve,
1101
+ validate: validateNode
1102
+ };
1103
+
1104
+ // src/cli/board-live-cards-cli.ts
1105
+ var BOARD_FILE = "board-graph.json";
1106
+ var JOURNAL_FILE = "board-journal.jsonl";
1107
+ var INVENTORY_FILE = "cards-inventory.jsonl";
1108
+ var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
1109
+ var BoardJournal = class {
1110
+ journalPath;
1111
+ lastDrainedId;
1112
+ constructor(journalPath, lastDrainedJournalId) {
1113
+ this.journalPath = journalPath;
1114
+ this.lastDrainedId = lastDrainedJournalId;
1115
+ }
1116
+ append(event) {
1117
+ const entry = { id: randomUUID(), event };
1118
+ fs.appendFileSync(this.journalPath, JSON.stringify(entry) + "\n", "utf-8");
1119
+ }
1120
+ drain() {
1121
+ if (!fs.existsSync(this.journalPath)) return [];
1122
+ const content = fs.readFileSync(this.journalPath, "utf-8").trim();
1123
+ if (!content) return [];
1124
+ const entries = content.split("\n").map((l) => JSON.parse(l));
1125
+ let startIdx = 0;
1126
+ if (this.lastDrainedId) {
1127
+ const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
1128
+ if (drainedIdx !== -1) startIdx = drainedIdx + 1;
1129
+ }
1130
+ const undrained = entries.slice(startIdx);
1131
+ if (undrained.length > 0) {
1132
+ this.lastDrainedId = undrained[undrained.length - 1].id;
1133
+ }
1134
+ return undrained.map((e) => e.event);
1135
+ }
1136
+ get size() {
1137
+ if (!fs.existsSync(this.journalPath)) return 0;
1138
+ const content = fs.readFileSync(this.journalPath, "utf-8").trim();
1139
+ if (!content) return 0;
1140
+ const entries = content.split("\n").map((l) => JSON.parse(l));
1141
+ if (!this.lastDrainedId) return entries.length;
1142
+ const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
1143
+ return drainedIdx === -1 ? entries.length : entries.length - drainedIdx - 1;
1144
+ }
1145
+ get lastDrainedJournalId() {
1146
+ return this.lastDrainedId;
1147
+ }
1148
+ };
1149
+ function readCardInventory(boardDir) {
1150
+ const inventoryPath = path.join(boardDir, INVENTORY_FILE);
1151
+ if (!fs.existsSync(inventoryPath)) return [];
1152
+ const lines = fs.readFileSync(inventoryPath, "utf-8").split("\n").filter((l) => l.trim());
1153
+ return lines.map((l) => JSON.parse(l));
1154
+ }
1155
+ function lookupCardPath(boardDir, cardId) {
1156
+ const entries = readCardInventory(boardDir);
1157
+ const entry = entries.find((e) => e.cardId === cardId);
1158
+ return entry?.cardFilePath ?? null;
1159
+ }
1160
+ function appendCardInventory(boardDir, entry) {
1161
+ const inventoryPath = path.join(boardDir, INVENTORY_FILE);
1162
+ fs.appendFileSync(inventoryPath, JSON.stringify(entry) + "\n");
1163
+ }
1164
+ function initBoard(dir) {
1165
+ const boardPath = path.join(dir, BOARD_FILE);
1166
+ if (fs.existsSync(boardPath)) {
1167
+ const envelope2 = JSON.parse(fs.readFileSync(boardPath, "utf-8"));
1168
+ restore(envelope2.graph);
1169
+ return "exists";
1170
+ }
1171
+ if (fs.existsSync(dir)) {
1172
+ const entries = fs.readdirSync(dir);
1173
+ if (entries.length > 0) {
1174
+ throw new Error(`Directory "${dir}" is not empty and has no valid ${BOARD_FILE}`);
1175
+ }
1176
+ }
1177
+ fs.mkdirSync(dir, { recursive: true });
1178
+ const live = createLiveGraph(EMPTY_CONFIG);
1179
+ const snap = snapshot(live);
1180
+ const envelope = { lastDrainedJournalId: "", graph: snap };
1181
+ fs.writeFileSync(boardPath, JSON.stringify(envelope, null, 2));
1182
+ return "created";
1183
+ }
1184
+ function loadBoardEnvelope(dir) {
1185
+ const raw = fs.readFileSync(path.join(dir, BOARD_FILE), "utf-8");
1186
+ return JSON.parse(raw);
1187
+ }
1188
+ function loadBoard(dir) {
1189
+ const envelope = loadBoardEnvelope(dir);
1190
+ return restore(envelope.graph);
1191
+ }
1192
+ function saveBoard(dir, rg, journal) {
1193
+ const snap = rg.snapshot();
1194
+ const envelope = {
1195
+ lastDrainedJournalId: journal.lastDrainedJournalId,
1196
+ graph: snap
1197
+ };
1198
+ fs.writeFileSync(path.join(dir, BOARD_FILE), JSON.stringify(envelope, null, 2));
1199
+ }
1200
+ function withBoardLock(boardDir, fn) {
1201
+ const boardPath = path.join(boardDir, BOARD_FILE);
1202
+ const release = lockSync(boardPath, { retries: { retries: 5, minTimeout: 100 } });
1203
+ try {
1204
+ return fn();
1205
+ } finally {
1206
+ release();
1207
+ }
1208
+ }
1209
+ function decodeCallbackToken2(token) {
1210
+ try {
1211
+ const payload = JSON.parse(Buffer.from(token, "base64url").toString());
1212
+ if (typeof payload?.t === "string") return { taskName: payload.t };
1213
+ return null;
1214
+ } catch {
1215
+ return null;
1216
+ }
1217
+ }
1218
+ function encodeSourceToken(payload) {
1219
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
1220
+ }
1221
+ function decodeSourceToken(token) {
1222
+ try {
1223
+ const p = JSON.parse(Buffer.from(token, "base64url").toString());
1224
+ if (typeof p?.cbk === "string" && typeof p?.cid === "string" && typeof p?.b === "string" && typeof p?.d === "string") {
1225
+ return p;
1226
+ }
1227
+ return null;
1228
+ } catch {
1229
+ return null;
1230
+ }
1231
+ }
1232
+ function runtimePath(boardDir, cardId) {
1233
+ return path.join(boardDir, `${cardId}.runtime.json`);
1234
+ }
1235
+ function readRuntimeState(boardDir, cardId) {
1236
+ const p = runtimePath(boardDir, cardId);
1237
+ if (!fs.existsSync(p)) return { _sources: {} };
1238
+ try {
1239
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
1240
+ } catch {
1241
+ return { _sources: {} };
1242
+ }
1243
+ }
1244
+ function writeRuntimeState(boardDir, cardId, state) {
1245
+ fs.writeFileSync(runtimePath(boardDir, cardId), JSON.stringify(state, null, 2));
1246
+ }
1247
+ function appendEventToJournal(boardDir, event) {
1248
+ const journalPath = path.join(boardDir, JOURNAL_FILE);
1249
+ const entry = { id: randomUUID(), event };
1250
+ fs.appendFileSync(journalPath, JSON.stringify(entry) + "\n", "utf-8");
1251
+ }
1252
+ function getUndrainedEntries(boardDir, lastDrainedId) {
1253
+ const journalPath = path.join(boardDir, JOURNAL_FILE);
1254
+ if (!fs.existsSync(journalPath)) return [];
1255
+ const content = fs.readFileSync(journalPath, "utf-8").trim();
1256
+ if (!content) return [];
1257
+ const entries = content.split("\n").map((l) => JSON.parse(l));
1258
+ if (!lastDrainedId) return entries;
1259
+ const idx = entries.findIndex((e) => e.id === lastDrainedId);
1260
+ return idx === -1 ? entries : entries.slice(idx + 1);
1261
+ }
1262
+ function determineLatestPendingAccumulated(boardDir) {
1263
+ const boardPath = path.join(boardDir, BOARD_FILE);
1264
+ if (!fs.existsSync(boardPath)) return 0;
1265
+ try {
1266
+ const envelope = loadBoardEnvelope(boardDir);
1267
+ return getUndrainedEntries(boardDir, envelope.lastDrainedJournalId).length;
1268
+ } catch {
1269
+ return 0;
1270
+ }
1271
+ }
1272
+ function shouldUseShellForCommand(cmd, forceShell) {
1273
+ if (typeof forceShell === "boolean") return forceShell;
1274
+ return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
1275
+ }
1276
+ function spawnDetachedCommand(cmd, args) {
1277
+ const child = process.platform === "win32" ? spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
1278
+ stdio: "ignore",
1279
+ windowsHide: true
1280
+ }) : spawn(cmd, args, {
1281
+ shell: false,
1282
+ detached: true,
1283
+ stdio: "ignore"
1284
+ });
1285
+ child.unref();
1286
+ }
1287
+ function execCommandSync(cmd, args, options) {
1288
+ const output = execFileSync(cmd, args, {
1289
+ shell: shouldUseShellForCommand(cmd, options?.shell),
1290
+ timeout: options?.timeout,
1291
+ encoding: options?.encoding,
1292
+ cwd: options?.cwd,
1293
+ windowsHide: true,
1294
+ env: options?.env
1295
+ });
1296
+ return typeof output === "string" ? output : output.toString("utf-8");
1297
+ }
1298
+ function execCommandAsync(cmd, args, callback) {
1299
+ execFile(
1300
+ cmd,
1301
+ args,
1302
+ { shell: shouldUseShellForCommand(cmd), encoding: "utf8", windowsHide: true },
1303
+ (err, stdout, stderr) => callback(err ?? null, stdout, stderr)
1304
+ );
1305
+ }
1306
+ function splitCommandLine(command) {
1307
+ const tokens = [];
1308
+ let current = "";
1309
+ let quote = null;
1310
+ for (const ch of command.trim()) {
1311
+ if (quote) {
1312
+ if (ch === quote) {
1313
+ quote = null;
1314
+ } else {
1315
+ current += ch;
1316
+ }
1317
+ continue;
1318
+ }
1319
+ if (ch === '"' || ch === "'") {
1320
+ quote = ch;
1321
+ continue;
1322
+ }
1323
+ if (/\s/.test(ch)) {
1324
+ if (current) {
1325
+ tokens.push(current);
1326
+ current = "";
1327
+ }
1328
+ continue;
1329
+ }
1330
+ current += ch;
1331
+ }
1332
+ if (quote) {
1333
+ throw new Error(`Unterminated quote in command: ${command}`);
1334
+ }
1335
+ if (current) tokens.push(current);
1336
+ return tokens;
1337
+ }
1338
+ function spawnDetachedProcessAccumulatedWorker(boardDir) {
1339
+ const { cmd, args: cliArgs } = getCliInvocation("process-accumulated-events", ["--rg", boardDir, "--inline-loop"]);
1340
+ try {
1341
+ spawnDetachedCommand(cmd, cliArgs);
1342
+ return true;
1343
+ } catch {
1344
+ return false;
1345
+ }
1346
+ }
1347
+ async function processAccumulatedEventsInlineLoop(boardDir, settleDelayMs = 50) {
1348
+ while (determineLatestPendingAccumulated(boardDir) > 0) {
1349
+ const ran = processAccumulatedEvents(boardDir);
1350
+ if (!ran) return false;
1351
+ await new Promise((resolve3) => setTimeout(resolve3, settleDelayMs));
1352
+ }
1353
+ return true;
1354
+ }
1355
+ function shouldAvoidDetachedProcessSpawn() {
1356
+ return process.env.BOARD_LIVE_CARDS_NO_SPAWN === "1";
1357
+ }
1358
+ function processAccumulatedEvents(boardDir) {
1359
+ const boardPath = path.join(boardDir, BOARD_FILE);
1360
+ let release;
1361
+ try {
1362
+ release = lockSync(boardPath, { retries: 0 });
1363
+ } catch {
1364
+ return false;
1365
+ }
1366
+ try {
1367
+ const { rg, journal } = createBoardReactiveGraph(boardDir);
1368
+ const undrained = journal.drain();
1369
+ rg.pushAll(undrained);
1370
+ saveBoard(boardDir, rg, journal);
1371
+ rg.dispose();
1372
+ return true;
1373
+ } finally {
1374
+ release();
1375
+ }
1376
+ }
1377
+ async function processAccumulatedEventsInfinitePass(boardDir, settleDelayMs = 50, options) {
1378
+ if (options?.inlineLoop || shouldAvoidDetachedProcessSpawn()) {
1379
+ return processAccumulatedEventsInlineLoop(boardDir, settleDelayMs);
1380
+ }
1381
+ return spawnDetachedProcessAccumulatedWorker(boardDir);
1382
+ }
1383
+ async function processAccumulatedEventsForced(boardDir, options) {
1384
+ processAccumulatedEvents(boardDir);
1385
+ await processAccumulatedEventsInfinitePass(boardDir, 50, options);
1386
+ }
1387
+ function liveCardToTaskConfig(card) {
1388
+ const requires = card.requires;
1389
+ const provides = card.provides ? card.provides.map((p) => p.bindTo) : [card.id];
1390
+ return {
1391
+ requires: requires && requires.length > 0 ? requires : void 0,
1392
+ provides,
1393
+ taskHandlers: ["card-handler"],
1394
+ description: card.meta?.title ?? card.id
1395
+ };
1396
+ }
1397
+ var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
1398
+ var REPO_ROOT = path.resolve(__dirname$1, "..", "..");
1399
+ var LOCAL_TSX_CLI = path.join(REPO_ROOT, "node_modules", "tsx", "dist", "cli.mjs");
1400
+ function getCliInvocation(command, args) {
1401
+ const jsPath = path.join(__dirname$1, "board-live-cards-cli.js");
1402
+ if (fs.existsSync(jsPath)) {
1403
+ return { cmd: process.execPath, args: [jsPath, command, ...args] };
1404
+ }
1405
+ const tsPath = path.join(__dirname$1, "board-live-cards-cli.ts");
1406
+ if (fs.existsSync(tsPath) && fs.existsSync(LOCAL_TSX_CLI)) {
1407
+ return { cmd: process.execPath, args: [LOCAL_TSX_CLI, tsPath, command, ...args] };
1408
+ }
1409
+ const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
1410
+ return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
1411
+ }
1412
+ function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
1413
+ const { cmd, args } = getCliInvocation("run-sources-internal", ["--card", cardPath, "--token", callbackToken, "--rg", boardDir]);
1414
+ const child = process.platform === "win32" ? spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
1415
+ stdio: "ignore",
1416
+ windowsHide: true
1417
+ }) : spawn(cmd, args, {
1418
+ shell: false,
1419
+ detached: true,
1420
+ stdio: "ignore"
1421
+ });
1422
+ let finished = false;
1423
+ const done = (err) => {
1424
+ if (finished) return;
1425
+ finished = true;
1426
+ callback(err);
1427
+ };
1428
+ child.on("error", (err) => done(err));
1429
+ child.unref();
1430
+ done(null);
1431
+ }
1432
+ function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
1433
+ const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
1434
+ execCommandAsync(cmd, args, (err, stdout, stderr) => {
1435
+ if (err) console.error(`[source-data-fetched] call failed:`, err.message);
1436
+ if (stdout) console.log(stdout.trim());
1437
+ if (stderr) console.error(stderr.trim());
1438
+ });
1439
+ }
1440
+ function invokeSourceDataFetchFailure(sourceToken, reason, callback) {
1441
+ const { cmd, args } = getCliInvocation("source-data-fetch-failure", ["--token", sourceToken, "--reason", reason]);
1442
+ execCommandAsync(cmd, args, (err) => callback(err));
1443
+ }
1444
+ function createBoardReactiveGraph(boardDir) {
1445
+ const envelope = loadBoardEnvelope(boardDir);
1446
+ const live = restore(envelope.graph);
1447
+ const journalPath = path.join(boardDir, JOURNAL_FILE);
1448
+ const journal = new BoardJournal(journalPath, envelope.lastDrainedJournalId);
1449
+ const handlers = {
1450
+ "card-handler": async (input) => {
1451
+ const cardPath = lookupCardPath(boardDir, input.nodeId);
1452
+ if (!cardPath) return "task-initiate-failure";
1453
+ const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
1454
+ const cardId = card.id;
1455
+ const cardState = card.state ?? {};
1456
+ const allSources = card.sources ?? [];
1457
+ const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
1458
+ const runtime = readRuntimeState(boardDir, cardId);
1459
+ let runtimeDirty = false;
1460
+ if (input.update) {
1461
+ const u = input.update;
1462
+ const bindTo = u.bindTo;
1463
+ if (!runtime._sources[bindTo]) runtime._sources[bindTo] = {};
1464
+ if (u.failure) {
1465
+ runtime._sources[bindTo].lastError = u.reason ?? "unknown";
1466
+ delete runtime._sources[bindTo].lastFetchedAt;
1467
+ runtimeDirty = true;
1468
+ console.log(`[card-handler] source "${bindTo}" fetch failed: ${runtime._sources[bindTo].lastError}`);
1469
+ } else {
1470
+ runtime._sources[bindTo].lastFetchedAt = u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1471
+ delete runtime._sources[bindTo].lastError;
1472
+ runtimeDirty = true;
1473
+ console.log(`[card-handler] source "${bindTo}" delivered \u2192 ${u.dest}`);
1474
+ }
1475
+ if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
1476
+ }
1477
+ const sourcesData = {};
1478
+ for (const src of allSources) {
1479
+ if (src.outputFile) {
1480
+ const filePath = path.join(boardDir, src.outputFile);
1481
+ if (fs.existsSync(filePath)) {
1482
+ const raw = fs.readFileSync(filePath, "utf-8").trim();
1483
+ try {
1484
+ sourcesData[src.bindTo] = JSON.parse(raw);
1485
+ } catch {
1486
+ sourcesData[src.bindTo] = raw;
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1491
+ const computeNode = {
1492
+ id: cardId,
1493
+ state: { ...cardState },
1494
+ requires: input.state ?? {},
1495
+ sources: allSources,
1496
+ compute: card.compute
1497
+ };
1498
+ if (card.compute) {
1499
+ await CardCompute.run(computeNode, { sourcesData });
1500
+ const cvPath = path.join(boardDir, `${cardId}.computed_values.json`);
1501
+ fs.writeFileSync(cvPath, JSON.stringify(computeNode.computed_values ?? {}, null, 2));
1502
+ }
1503
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1504
+ const undeliveredRequired = requiredSources.filter((s) => {
1505
+ if (!s.outputFile) return false;
1506
+ const entry = runtime._sources[s.bindTo];
1507
+ if (!entry?.lastRequestedAt) return true;
1508
+ if (!entry.lastFetchedAt) return true;
1509
+ return entry.lastFetchedAt <= entry.lastRequestedAt;
1510
+ });
1511
+ if (undeliveredRequired.length > 0) {
1512
+ let stampedAny = false;
1513
+ for (const src of undeliveredRequired) {
1514
+ const entry = runtime._sources[src.bindTo] ?? {};
1515
+ if (!entry.lastRequestedAt || entry.lastFetchedAt && entry.lastFetchedAt >= entry.lastRequestedAt) {
1516
+ entry.lastRequestedAt = now;
1517
+ runtime._sources[src.bindTo] = entry;
1518
+ stampedAny = true;
1519
+ }
1520
+ }
1521
+ if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
1522
+ invokeRunSources(boardDir, cardPath, input.callbackToken, (err) => {
1523
+ if (err) console.error(`[card-handler] ${input.nodeId}:`, err.message);
1524
+ });
1525
+ return "task-initiated";
1526
+ }
1527
+ const providesBindings = card.provides ?? [{ bindTo: cardId, src: `state.${cardId}` }];
1528
+ const data = {};
1529
+ for (const { bindTo, src } of providesBindings) {
1530
+ data[bindTo] = CardCompute.resolve(computeNode, src);
1531
+ }
1532
+ const undeliveredOptional = allSources.filter((s) => {
1533
+ if (s.optionalForCompletionGating !== true || !s.outputFile) return false;
1534
+ const entry = runtime._sources[s.bindTo];
1535
+ if (!entry?.lastRequestedAt) return true;
1536
+ if (!entry.lastFetchedAt) return true;
1537
+ return entry.lastFetchedAt <= entry.lastRequestedAt;
1538
+ });
1539
+ if (undeliveredOptional.length > 0) {
1540
+ invokeRunSources(boardDir, cardPath, input.callbackToken, (err) => {
1541
+ if (err) console.error(`[card-handler] ${input.nodeId}:`, err.message);
1542
+ });
1543
+ }
1544
+ appendEventToJournal(boardDir, {
1545
+ type: "task-completed",
1546
+ taskName: input.nodeId,
1547
+ data,
1548
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1549
+ });
1550
+ return "task-initiated";
1551
+ }
1552
+ };
1553
+ const rg = createReactiveGraph(live, { handlers });
1554
+ return { rg, journal };
1555
+ }
1556
+ function addSingleCardFromFile(dir, cardFile) {
1557
+ const absCardPath = path.resolve(cardFile);
1558
+ if (!fs.existsSync(absCardPath)) {
1559
+ console.error(`Card file not found: ${absCardPath}`);
1560
+ process.exit(1);
1561
+ }
1562
+ const card = JSON.parse(fs.readFileSync(absCardPath, "utf-8"));
1563
+ if (!card.id) {
1564
+ console.error('Card JSON must have an "id" field');
1565
+ process.exit(1);
1566
+ }
1567
+ const existing = readCardInventory(dir);
1568
+ if (existing.some((e) => e.cardId === card.id)) {
1569
+ console.error(`Card "${card.id}" already exists in inventory`);
1570
+ process.exit(1);
1571
+ }
1572
+ appendCardInventory(dir, {
1573
+ cardId: card.id,
1574
+ cardFilePath: absCardPath,
1575
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
1576
+ });
1577
+ const taskConfig = liveCardToTaskConfig(card);
1578
+ appendEventToJournal(dir, {
1579
+ type: "task-upsert",
1580
+ taskName: card.id,
1581
+ taskConfig,
1582
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1583
+ });
1584
+ console.log(`Card "${card.id}" added to board at ${path.resolve(dir)} (drain scheduled)`);
1585
+ console.log(` taskHandlers: [${taskConfig.taskHandlers?.join(", ") ?? ""}]`);
1586
+ console.log(` provides: [${taskConfig.provides.join(", ")}]`);
1587
+ if (taskConfig.requires) console.log(` requires: [${taskConfig.requires.join(", ")}]`);
1588
+ }
1589
+ function resolveCardGlobMatches(cardGlob) {
1590
+ const patterns = cardGlob.split(",").map((s) => s.trim()).filter(Boolean).map((p) => p.replace(/\\/g, "/"));
1591
+ const matches = fg.sync(patterns, {
1592
+ absolute: true,
1593
+ onlyFiles: true,
1594
+ unique: true,
1595
+ dot: false
1596
+ });
1597
+ return [...matches].sort((a, b) => a.localeCompare(b));
1598
+ }
1599
+ function cmdAddCards(args) {
1600
+ const rgIdx = args.indexOf("--rg");
1601
+ const cardIdx = args.indexOf("--card");
1602
+ const globIdx = args.indexOf("--card-glob");
1603
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1604
+ const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
1605
+ const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
1606
+ if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
1607
+ console.error("Usage: board-live-cards add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)");
1608
+ process.exit(1);
1609
+ }
1610
+ if (cardFile) {
1611
+ addSingleCardFromFile(dir, cardFile);
1612
+ } else {
1613
+ const matches = resolveCardGlobMatches(cardGlob);
1614
+ if (matches.length === 0) {
1615
+ console.error(`No card files matched glob: ${cardGlob}`);
1616
+ process.exit(1);
1617
+ }
1618
+ for (const match of matches) {
1619
+ addSingleCardFromFile(dir, match);
1620
+ }
1621
+ console.log(`Added ${matches.length} cards from glob: ${cardGlob}`);
1622
+ }
1623
+ void processAccumulatedEventsInfinitePass(dir);
1624
+ }
1625
+ function cmdInit(args) {
1626
+ const dir = args[0];
1627
+ if (!dir) {
1628
+ console.error("Usage: board-live-cards init <dir> [--task-executor <script>]");
1629
+ process.exit(1);
1630
+ }
1631
+ const teIdx = args.indexOf("--task-executor");
1632
+ const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
1633
+ const result = initBoard(dir);
1634
+ if (taskExecutor) {
1635
+ fs.writeFileSync(path.join(dir, ".task-executor"), taskExecutor, "utf-8");
1636
+ }
1637
+ if (result === "exists") {
1638
+ console.log(`Board already initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""}`);
1639
+ } else {
1640
+ console.log(`Board initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""}`);
1641
+ }
1642
+ }
1643
+ function cmdStatus(args) {
1644
+ const rgIdx = args.indexOf("--rg");
1645
+ const asJson = args.includes("--json");
1646
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1647
+ if (!dir) {
1648
+ console.error("Usage: board-live-cards status --rg <dir>");
1649
+ process.exit(1);
1650
+ }
1651
+ const live = loadBoard(dir);
1652
+ const taskState = live.state.tasks;
1653
+ const taskConfig = live.config.tasks;
1654
+ const cardNames = Object.keys(taskState);
1655
+ const sched = schedule(live);
1656
+ const statusCounts = {
1657
+ completed: 0,
1658
+ failed: 0,
1659
+ in_progress: 0,
1660
+ pending: 0,
1661
+ blocked: 0,
1662
+ unresolved: 0
1663
+ };
1664
+ const waitingByCard = /* @__PURE__ */ new Map();
1665
+ for (const p of sched.pending) waitingByCard.set(p.taskName, p.waitingOn);
1666
+ for (const u of sched.unresolved) waitingByCard.set(u.taskName, u.missingTokens);
1667
+ for (const b of sched.blocked) waitingByCard.set(b.taskName, b.failedTokens);
1668
+ const providersByToken = /* @__PURE__ */ new Map();
1669
+ const dependentsByToken = /* @__PURE__ */ new Map();
1670
+ for (const [name, cfg] of Object.entries(taskConfig)) {
1671
+ for (const token of cfg.provides ?? []) {
1672
+ const providers = providersByToken.get(token) ?? [];
1673
+ providers.push(name);
1674
+ providersByToken.set(token, providers);
1675
+ }
1676
+ for (const token of cfg.requires ?? []) {
1677
+ const dependents = dependentsByToken.get(token) ?? [];
1678
+ dependents.push(name);
1679
+ dependentsByToken.set(token, dependents);
1680
+ }
1681
+ }
1682
+ const cards = cardNames.sort().map((name) => {
1683
+ const state = taskState[name];
1684
+ const cfg = taskConfig[name] ?? { requires: [], provides: [] };
1685
+ if (state.status === "completed") statusCounts.completed += 1;
1686
+ else if (state.status === "failed") statusCounts.failed += 1;
1687
+ else if (state.status === "in-progress") statusCounts.in_progress += 1;
1688
+ const requires = cfg.requires ?? [];
1689
+ const provides = cfg.provides ?? [];
1690
+ const runtimeKeys = Object.keys(state.data ?? {}).sort();
1691
+ const requiresSatisfied = requires.filter((token) => live.state.availableOutputs.includes(token));
1692
+ const requiresMissing = requires.filter((token) => !live.state.availableOutputs.includes(token));
1693
+ const blockedBy = waitingByCard.get(name) ?? requiresMissing;
1694
+ const unblocks = /* @__PURE__ */ new Set();
1695
+ for (const token of provides) {
1696
+ for (const dependent of dependentsByToken.get(token) ?? []) {
1697
+ if (dependent !== name) unblocks.add(dependent);
1698
+ }
1699
+ }
1700
+ const lastFailureAt = state.failedAt;
1701
+ const error = state.error ? {
1702
+ message: state.error,
1703
+ code: "TASK_FAILED",
1704
+ at: lastFailureAt,
1705
+ source: "task-runtime"
1706
+ } : void 0;
1707
+ return {
1708
+ name,
1709
+ status: state.status,
1710
+ error,
1711
+ requires,
1712
+ requires_satisfied: requiresSatisfied,
1713
+ requires_missing: requiresMissing,
1714
+ provides_declared: provides,
1715
+ provides_runtime: runtimeKeys,
1716
+ blocked_by: blockedBy,
1717
+ unblocks: Array.from(unblocks).sort(),
1718
+ runtime: {
1719
+ attempt_count: state.executionCount ?? 0,
1720
+ restart_count: state.retryCount ?? 0,
1721
+ in_progress_since: state.status === "in-progress" ? state.startedAt ?? null : null,
1722
+ last_transition_at: state.lastUpdated ?? null,
1723
+ last_completed_at: state.completedAt ?? null,
1724
+ last_restarted_at: state.startedAt ?? null,
1725
+ status_age_ms: state.lastUpdated ? Math.max(0, Date.now() - Date.parse(state.lastUpdated)) : null
1726
+ }
1727
+ };
1728
+ });
1729
+ statusCounts.pending = sched.pending.length;
1730
+ statusCounts.blocked = sched.blocked.length;
1731
+ statusCounts.unresolved = sched.unresolved.length;
1732
+ const fanOut = cards.map((c) => ({ name: c.name, fanOut: c.unblocks.length })).sort((a, b) => b.fanOut - a.fanOut || a.name.localeCompare(b.name));
1733
+ const maxFanOut = fanOut.length > 0 ? fanOut[0] : { name: null, fanOut: 0 };
1734
+ const allRequires = /* @__PURE__ */ new Set();
1735
+ for (const cfg of Object.values(taskConfig)) {
1736
+ for (const r of cfg.requires ?? []) allRequires.add(r);
1737
+ }
1738
+ let orphanCards = 0;
1739
+ for (const [name, cfg] of Object.entries(taskConfig)) {
1740
+ const requiresNone = (cfg.requires ?? []).length === 0;
1741
+ const provides = cfg.provides ?? [];
1742
+ const feedsAny = provides.some((p) => (dependentsByToken.get(p) ?? []).some((d) => d !== name));
1743
+ if (requiresNone && !feedsAny) orphanCards += 1;
1744
+ }
1745
+ const statusObject = {
1746
+ schema_version: "v1",
1747
+ meta: {
1748
+ board: {
1749
+ path: path.resolve(dir)
1750
+ }
1751
+ },
1752
+ summary: {
1753
+ card_count: cardNames.length,
1754
+ completed: statusCounts.completed,
1755
+ eligible: sched.eligible.length,
1756
+ pending: statusCounts.pending,
1757
+ blocked: statusCounts.blocked,
1758
+ unresolved: statusCounts.unresolved,
1759
+ failed: statusCounts.failed,
1760
+ in_progress: statusCounts.in_progress,
1761
+ orphan_cards: orphanCards,
1762
+ topology: {
1763
+ edge_count: Array.from(allRequires).length,
1764
+ max_fan_out_card: maxFanOut.name,
1765
+ max_fan_out: maxFanOut.fanOut
1766
+ }
1767
+ },
1768
+ cards
1769
+ };
1770
+ if (asJson) {
1771
+ console.log(JSON.stringify(statusObject, null, 2));
1772
+ return;
1773
+ }
1774
+ console.log(`Board: ${statusObject.meta.board.path}`);
1775
+ console.log(`Tasks: ${statusObject.summary.card_count}`);
1776
+ console.log("");
1777
+ for (const card of statusObject.cards) {
1778
+ const dataKeys = card.provides_runtime.join(", ");
1779
+ console.log(` ${card.status.padEnd(12)} ${card.name}${dataKeys ? ` \u2014 [${dataKeys}]` : ""}`);
1780
+ }
1781
+ console.log("");
1782
+ console.log(`Schedule: ${statusObject.summary.eligible} eligible, ${statusObject.summary.pending} pending, ${statusObject.summary.blocked} blocked, ${statusObject.summary.unresolved} unresolved`);
1783
+ }
1784
+ function cmdTaskCompleted(args) {
1785
+ const rgIdx = args.indexOf("--rg");
1786
+ const tokenIdx = args.indexOf("--token");
1787
+ const dataIdx = args.indexOf("--data");
1788
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1789
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
1790
+ if (!dir || !token) {
1791
+ console.error("Usage: board-live-cards task-completed --rg <dir> --token <token> [--data <json>]");
1792
+ process.exit(1);
1793
+ }
1794
+ const decoded = decodeCallbackToken2(token);
1795
+ if (!decoded) {
1796
+ console.error("Invalid callback token");
1797
+ process.exit(1);
1798
+ }
1799
+ const data = dataIdx !== -1 ? JSON.parse(args[dataIdx + 1]) : {};
1800
+ appendEventToJournal(dir, {
1801
+ type: "task-completed",
1802
+ taskName: decoded.taskName,
1803
+ data,
1804
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1805
+ });
1806
+ void processAccumulatedEventsForced(dir);
1807
+ console.log("Task completed.");
1808
+ }
1809
+ function cmdTaskFailed(args) {
1810
+ const rgIdx = args.indexOf("--rg");
1811
+ const tokenIdx = args.indexOf("--token");
1812
+ const errorIdx = args.indexOf("--error");
1813
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1814
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
1815
+ const errorMsg = errorIdx !== -1 ? args[errorIdx + 1] : "unknown error";
1816
+ if (!dir || !token) {
1817
+ console.error("Usage: board-live-cards task-failed --rg <dir> --token <token> [--error <message>]");
1818
+ process.exit(1);
1819
+ }
1820
+ const decoded = decodeCallbackToken2(token);
1821
+ if (!decoded) {
1822
+ console.error("Invalid callback token");
1823
+ process.exit(1);
1824
+ }
1825
+ appendEventToJournal(dir, {
1826
+ type: "task-failed",
1827
+ taskName: decoded.taskName,
1828
+ error: errorMsg,
1829
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1830
+ });
1831
+ void processAccumulatedEventsForced(dir);
1832
+ console.log("Task failed.");
1833
+ }
1834
+ function cmdRemoveCard(args) {
1835
+ const rgIdx = args.indexOf("--rg");
1836
+ const idIdx = args.indexOf("--id");
1837
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1838
+ const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
1839
+ if (!dir || !cardId) {
1840
+ console.error("Usage: board-live-cards remove-card --rg <dir> --id <card-id>");
1841
+ process.exit(1);
1842
+ }
1843
+ appendEventToJournal(dir, {
1844
+ type: "task-removal",
1845
+ taskName: cardId,
1846
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1847
+ });
1848
+ void processAccumulatedEventsInfinitePass(dir);
1849
+ console.log(`Card "${cardId}" removed.`);
1850
+ }
1851
+ function cmdSourceDataFetched(args) {
1852
+ const tmpIdx = args.indexOf("--tmp");
1853
+ const tokenIdx = args.indexOf("--token");
1854
+ const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
1855
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
1856
+ if (!tmpFile || !token) {
1857
+ console.error("Usage: board-live-cards source-data-fetched --tmp <tmp-file> --token <sourceToken>");
1858
+ process.exit(1);
1859
+ }
1860
+ const payload = decodeSourceToken(token);
1861
+ if (!payload) {
1862
+ console.error("Invalid source token");
1863
+ process.exit(1);
1864
+ }
1865
+ const { cbk, rg, cid, b, d } = payload;
1866
+ const destPath = path.join(rg, d);
1867
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1868
+ fs.renameSync(tmpFile, destPath);
1869
+ console.log(`[source-data-fetched] ${cid}.${b} \u2192 ${d}`);
1870
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
1871
+ const cbkDecoded = decodeCallbackToken2(cbk);
1872
+ if (!cbkDecoded) {
1873
+ console.error("Invalid callback token embedded in source token");
1874
+ process.exit(1);
1875
+ }
1876
+ appendEventToJournal(rg, {
1877
+ type: "task-progress",
1878
+ taskName: cbkDecoded.taskName,
1879
+ update: { bindTo: b, fetchedAt, dest: d },
1880
+ timestamp: fetchedAt
1881
+ });
1882
+ void processAccumulatedEventsInfinitePass(rg);
1883
+ }
1884
+ function cmdSourceDataFetchFailure(args) {
1885
+ const tokenIdx = args.indexOf("--token");
1886
+ const reasonIdx = args.indexOf("--reason");
1887
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
1888
+ const reason = reasonIdx !== -1 ? args[reasonIdx + 1] : "unknown";
1889
+ if (!token) {
1890
+ console.error("Usage: board-live-cards source-data-fetch-failure --token <sourceToken> [--reason <msg>]");
1891
+ process.exit(1);
1892
+ }
1893
+ const payload = decodeSourceToken(token);
1894
+ if (!payload) {
1895
+ console.error("Invalid source token");
1896
+ process.exit(1);
1897
+ }
1898
+ const { cbk, rg, cid, b } = payload;
1899
+ console.log(`[source-data-fetch-failure] ${cid}.${b}: ${reason}`);
1900
+ const cbkDecoded = decodeCallbackToken2(cbk);
1901
+ if (!cbkDecoded) {
1902
+ console.error("Invalid callback token embedded in source token");
1903
+ process.exit(1);
1904
+ }
1905
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1906
+ appendEventToJournal(rg, {
1907
+ type: "task-progress",
1908
+ taskName: cbkDecoded.taskName,
1909
+ update: { bindTo: b, failure: true, reason },
1910
+ timestamp
1911
+ });
1912
+ void processAccumulatedEventsInfinitePass(rg);
1913
+ }
1914
+ function cmdRunSources(args) {
1915
+ const cardIdx = args.indexOf("--card");
1916
+ const tokenIdx = args.indexOf("--token");
1917
+ const rgIdx = args.indexOf("--rg");
1918
+ const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
1919
+ const callbackToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
1920
+ const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
1921
+ if (!cardFilePath || !callbackToken || !boardDir) {
1922
+ console.error("Usage: board-live-cards run-sources-internal --card <path> --token <token> --rg <dir>");
1923
+ process.exit(1);
1924
+ }
1925
+ const card = JSON.parse(fs.readFileSync(cardFilePath, "utf-8"));
1926
+ console.log(`[run-sources-internal] Processing card "${card.id}"`);
1927
+ const executorFile = path.join(boardDir, ".task-executor");
1928
+ const taskExecutor = fs.existsSync(executorFile) ? fs.readFileSync(executorFile, "utf-8").trim() : void 0;
1929
+ function runSource(src) {
1930
+ const sourceToken = encodeSourceToken({
1931
+ cbk: callbackToken,
1932
+ rg: boardDir,
1933
+ cid: card.id,
1934
+ b: src.bindTo,
1935
+ d: src.outputFile ?? ""
1936
+ });
1937
+ function reportFailure(reason) {
1938
+ invokeSourceDataFetchFailure(sourceToken, reason, (err) => {
1939
+ if (err) console.error(`[run-sources-internal] source-data-fetch-failure call failed:`, err.message);
1940
+ });
1941
+ }
1942
+ function reportFetched(outFile2) {
1943
+ invokeSourceDataFetched(sourceToken, outFile2);
1944
+ }
1945
+ if (taskExecutor) {
1946
+ if (!src.outputFile) {
1947
+ console.warn(`[run-sources-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
1948
+ reportFailure("no outputFile configured");
1949
+ return;
1950
+ }
1951
+ const inFile = path.join(os.tmpdir(), `card-source-in-${src.bindTo}-${Date.now()}.json`);
1952
+ const outFile2 = path.join(os.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
1953
+ const errFile = path.join(os.tmpdir(), `card-source-err-${src.bindTo}-${Date.now()}.txt`);
1954
+ const sourceForExecutor = { ...src, cwd: path.dirname(cardFilePath || ""), boardDir };
1955
+ fs.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
1956
+ console.log(`[run-sources-internal] task-executor: ${taskExecutor} run-source-fetch --in ${inFile} --out ${outFile2} --err ${errFile}`);
1957
+ try {
1958
+ execCommandSync(taskExecutor, ["run-source-fetch", "--in", inFile, "--out", outFile2, "--err", errFile], {
1959
+ shell: true,
1960
+ timeout: src.timeout ?? 12e4
1961
+ });
1962
+ } catch (err) {
1963
+ const reason = err.message ?? String(err);
1964
+ console.error(`[run-sources-internal] task-executor failed for source "${src.bindTo}":`, reason);
1965
+ reportFailure(reason);
1966
+ return;
1967
+ }
1968
+ if (fs.existsSync(outFile2)) {
1969
+ reportFetched(outFile2);
1970
+ } else {
1971
+ const errMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
1972
+ console.warn(`[run-sources-internal] source "${src.bindTo}": ${errMsg}`);
1973
+ reportFailure(errMsg);
1974
+ }
1975
+ return;
1976
+ }
1977
+ if (!src.outputFile) {
1978
+ console.warn(`[run-sources-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
1979
+ reportFailure("no outputFile configured");
1980
+ return;
1981
+ }
1982
+ const outFile = path.join(os.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
1983
+ if (!src.cli) {
1984
+ const errMsg = "source.cli is required for built-in source execution";
1985
+ console.warn(`[run-sources-internal] source "${src.bindTo}": ${errMsg}`);
1986
+ reportFailure(errMsg);
1987
+ return;
1988
+ }
1989
+ const timeout = src.timeout ?? 12e4;
1990
+ const sourceCwd = typeof src.cwd === "string" ? src.cwd : path.dirname(cardFilePath || "");
1991
+ const sourceBoardDir = typeof src.boardDir === "string" ? src.boardDir : boardDir;
1992
+ const cmdParts = splitCommandLine(src.cli);
1993
+ if (cmdParts.length === 0) {
1994
+ const errMsg = "source.cli command is empty";
1995
+ console.warn(`[run-sources-internal] source "${src.bindTo}": ${errMsg}`);
1996
+ reportFailure(errMsg);
1997
+ return;
1998
+ }
1999
+ const rawCmd = cmdParts[0];
2000
+ const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2001
+ const cliArgs = cmdParts.slice(1);
2002
+ let stdout;
2003
+ try {
2004
+ stdout = execCommandSync(cmd, cliArgs, {
2005
+ shell: false,
2006
+ encoding: "utf-8",
2007
+ timeout,
2008
+ cwd: sourceCwd,
2009
+ env: {
2010
+ ...process.env,
2011
+ ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
2012
+ }
2013
+ });
2014
+ } catch (err) {
2015
+ const reason = err.message ?? String(err);
2016
+ console.error(`[run-sources-internal] source fetch failed for source "${src.bindTo}":`, reason);
2017
+ reportFailure(reason);
2018
+ return;
2019
+ }
2020
+ fs.writeFileSync(outFile, stdout.trim(), "utf-8");
2021
+ reportFetched(outFile);
2022
+ }
2023
+ const sources = card.sources ?? [];
2024
+ for (const src of sources) {
2025
+ runSource(src);
2026
+ }
2027
+ }
2028
+ function cmdRunSourceFetch(args) {
2029
+ const inIdx = args.indexOf("--in");
2030
+ const outIdx = args.indexOf("--out");
2031
+ const errIdx = args.indexOf("--err");
2032
+ const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
2033
+ const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
2034
+ const errFile = errIdx !== -1 ? args[errIdx + 1] : void 0;
2035
+ if (!inFile || !outFile) {
2036
+ console.error("Usage: board-live-cards run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]");
2037
+ process.exit(1);
2038
+ }
2039
+ if (!fs.existsSync(inFile)) {
2040
+ const msg = `Input file not found: ${inFile}`;
2041
+ if (errFile) fs.writeFileSync(errFile, msg);
2042
+ console.error(`[run-source-fetch] ${msg}`);
2043
+ process.exit(1);
2044
+ }
2045
+ let source;
2046
+ try {
2047
+ const raw = fs.readFileSync(inFile, "utf-8");
2048
+ source = JSON.parse(raw);
2049
+ } catch (err) {
2050
+ const msg = `Failed to parse input file: ${err.message}`;
2051
+ if (errFile) fs.writeFileSync(errFile, msg);
2052
+ console.error(`[run-source-fetch] ${msg}`);
2053
+ process.exit(1);
2054
+ }
2055
+ if (!source.cli) {
2056
+ const msg = "Source definition missing cli field (board-live-cards built-in executor only understands source.cli)";
2057
+ if (errFile) fs.writeFileSync(errFile, msg);
2058
+ console.error(`[run-source-fetch] ${msg}`);
2059
+ process.exit(1);
2060
+ }
2061
+ console.log(`[run-source-fetch] executing: ${source.cli}`);
2062
+ const timeout = source.timeout ?? 12e4;
2063
+ const sourceCwd = typeof source.cwd === "string" ? source.cwd : process.cwd();
2064
+ const sourceBoardDir = typeof source.boardDir === "string" ? source.boardDir : void 0;
2065
+ const cmdParts = splitCommandLine(source.cli);
2066
+ if (cmdParts.length === 0) {
2067
+ const msg = "Source cli command is empty";
2068
+ if (errFile) fs.writeFileSync(errFile, msg);
2069
+ console.error(`[run-source-fetch] ${msg}`);
2070
+ process.exit(1);
2071
+ }
2072
+ const rawCmd = cmdParts[0];
2073
+ const cmd = /^(node|node\.exe)$/i.test(rawCmd) ? process.execPath : rawCmd;
2074
+ const cliArgs = cmdParts.slice(1);
2075
+ let stdout;
2076
+ try {
2077
+ stdout = execCommandSync(cmd, cliArgs, {
2078
+ shell: false,
2079
+ encoding: "utf-8",
2080
+ timeout,
2081
+ cwd: sourceCwd,
2082
+ env: {
2083
+ ...process.env,
2084
+ ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
2085
+ }
2086
+ });
2087
+ } catch (err) {
2088
+ const msg = err.message ?? String(err);
2089
+ console.error(`[run-source-fetch] cli failed: ${msg}`);
2090
+ if (errFile) fs.writeFileSync(errFile, msg);
2091
+ process.exit(1);
2092
+ }
2093
+ const result = stdout.trim();
2094
+ try {
2095
+ fs.writeFileSync(outFile, result);
2096
+ console.log(`[run-source-fetch] result written to ${outFile}`);
2097
+ } catch (err) {
2098
+ const msg = `Failed to write output file: ${err.message}`;
2099
+ console.error(`[run-source-fetch] ${msg}`);
2100
+ if (errFile) fs.writeFileSync(errFile, msg);
2101
+ process.exit(1);
2102
+ }
2103
+ }
2104
+ function cmdUpdateCard(args) {
2105
+ const rgIdx = args.indexOf("--rg");
2106
+ const idIdx = args.indexOf("--card-id");
2107
+ const restart = args.includes("--restart");
2108
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2109
+ const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
2110
+ if (!dir || !cardId) {
2111
+ console.error("Usage: board-live-cards update-card --rg <dir> --card-id <card-id> [--restart]");
2112
+ process.exit(1);
2113
+ }
2114
+ const cardPath = lookupCardPath(dir, cardId);
2115
+ if (!cardPath) {
2116
+ console.error(`Card "${cardId}" not found in inventory`);
2117
+ process.exit(1);
2118
+ }
2119
+ if (!fs.existsSync(cardPath)) {
2120
+ console.error(`Card file not found: ${cardPath}`);
2121
+ process.exit(1);
2122
+ }
2123
+ const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
2124
+ const taskConfig = liveCardToTaskConfig(card);
2125
+ appendEventToJournal(dir, {
2126
+ type: "task-upsert",
2127
+ taskName: cardId,
2128
+ taskConfig,
2129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2130
+ });
2131
+ if (restart) {
2132
+ appendEventToJournal(dir, {
2133
+ type: "task-restart",
2134
+ taskName: cardId,
2135
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2136
+ });
2137
+ }
2138
+ void processAccumulatedEventsInfinitePass(dir);
2139
+ console.log(`Card "${cardId}" updated${restart ? " (restarted)" : ""}.`);
2140
+ }
2141
+ async function cmdTryDrain(args) {
2142
+ const rgIdx = args.indexOf("--rg");
2143
+ const inlineLoop = args.includes("--inline-loop");
2144
+ const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2145
+ if (!boardDir) {
2146
+ console.error("Usage: board-live-cards process-accumulated-events --rg <dir>");
2147
+ process.exit(1);
2148
+ }
2149
+ await processAccumulatedEventsForced(boardDir, { inlineLoop });
2150
+ }
2151
+ function cmdRetrigger(args) {
2152
+ const rgIdx = args.indexOf("--rg");
2153
+ const taskIdx = args.indexOf("--task");
2154
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
2155
+ const taskName = taskIdx !== -1 ? args[taskIdx + 1] : void 0;
2156
+ if (!dir || !taskName) {
2157
+ console.error("Usage: board-live-cards retrigger --rg <dir> --task <task-name>");
2158
+ process.exit(1);
2159
+ }
2160
+ appendEventToJournal(dir, {
2161
+ type: "task-restart",
2162
+ taskName,
2163
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2164
+ });
2165
+ void processAccumulatedEventsInfinitePass(dir);
2166
+ console.log(`Task "${taskName}" retriggered.`);
2167
+ }
2168
+ async function cli(argv) {
2169
+ const cmd = argv[0];
2170
+ const rest = argv.slice(1);
2171
+ switch (cmd) {
2172
+ case "help":
2173
+ case "--help":
2174
+ case "-h":
2175
+ return cmdHelp();
2176
+ case "init":
2177
+ return cmdInit(rest);
2178
+ case "status":
2179
+ return cmdStatus(rest);
2180
+ case "add-cards":
2181
+ return cmdAddCards(rest);
2182
+ case "update-card":
2183
+ return cmdUpdateCard(rest);
2184
+ case "remove-card":
2185
+ return cmdRemoveCard(rest);
2186
+ case "retrigger":
2187
+ return cmdRetrigger(rest);
2188
+ case "task-completed":
2189
+ return cmdTaskCompleted(rest);
2190
+ case "task-failed":
2191
+ return cmdTaskFailed(rest);
2192
+ case "source-data-fetched":
2193
+ return cmdSourceDataFetched(rest);
2194
+ case "source-data-fetch-failure":
2195
+ return cmdSourceDataFetchFailure(rest);
2196
+ case "run-sources-internal":
2197
+ return cmdRunSources(rest);
2198
+ case "run-source-fetch":
2199
+ return cmdRunSourceFetch(rest);
2200
+ case "process-accumulated-events":
2201
+ return await cmdTryDrain(rest);
2202
+ default:
2203
+ console.error(`Unknown command: ${cmd ?? "(none)"}`);
2204
+ console.error("Run: board-live-cards help");
2205
+ process.exit(1);
2206
+ }
2207
+ }
2208
+ function cmdHelp() {
2209
+ console.log(`
2210
+ board-live-cards-cli \u2014 LiveCards board CLI
2211
+
2212
+ USAGE
2213
+ board-live-cards-cli <command> [options]
2214
+
2215
+ BOARD MANAGEMENT
2216
+ init <dir> [--task-executor <script>]
2217
+ Create a new board in <dir>.
2218
+ If --task-executor is given, writes <dir>/.task-executor with the script path.
2219
+ Re-running init on an existing board is safe; --task-executor updates the registration.
2220
+
2221
+ status --rg <dir> [--json]
2222
+ Print the current task status of every card in the board.
2223
+ --json emits a stable machine-readable status object.
2224
+
2225
+ CARD MANAGEMENT
2226
+ add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)
2227
+ Add one card or many cards from a glob and trigger processing.
2228
+ --card adds one JSON file.
2229
+ --card-glob adds all matching files in deterministic order.
2230
+ Example glob: "examples/browser/boards/portfolio-tracker/cards/*.json"
2231
+
2232
+ update-card --rg <dir> --card-id <card-id> [--restart]
2233
+ Re-read the card JSON from disk and patch the board.
2234
+ --restart clears the task so it re-triggers from scratch.
2235
+
2236
+ remove-card --rg <dir> --id <card-id>
2237
+ Remove a card and its task from the board.
2238
+
2239
+ retrigger --rg <dir> --task <task-name>
2240
+ Mark a task not-started and drain to re-trigger it.
2241
+
2242
+ TASK CALLBACKS (called by task executor scripts)
2243
+ task-completed --token <callbackToken> [--data <json>]
2244
+ Signal successful task completion with optional JSON result data.
2245
+
2246
+ task-failed --token <callbackToken> [--error <message>]
2247
+ Signal task failure with an optional error message.
2248
+
2249
+ SOURCE CALLBACKS (called internally by run-sources-internal)
2250
+ source-data-fetched --tmp <file> --token <sourceToken>
2251
+ Atomically rename <file> into the outputFile destination and record delivery
2252
+ in runtime.json. Appends a task-progress event to re-invoke the card handler.
2253
+
2254
+ source-data-fetch-failure --token <sourceToken> [--reason <message>]
2255
+ Record a source fetch failure in runtime.json and append a task-progress event.
2256
+
2257
+ INTERNAL COMMANDS
2258
+ process-accumulated-events --rg <dir>
2259
+ Executes forced drain for this board.
2260
+ This command is also used as the background relay worker.
2261
+ By default it schedules a detached worker and returns quickly.
2262
+ Internal workers run with --inline-loop to perform the settle loop.
2263
+
2264
+ Eventual-progress guarantee is relay-based (not per-call blocking guarantee):
2265
+ 1) at least one runner continues processing,
2266
+ 2) no crash/forced exit in relay window,
2267
+ 3) lock stays healthy,
2268
+ 4) event production eventually quiesces.
2269
+
2270
+ run-sources-internal-internal --card <card.json> --token <callbackToken> --rg <dir>
2271
+ Execute all source[] entries for a card, then report delivery or failure.
2272
+ (Internal command \u2014 invoked by the card-handler. Not intended for direct use.)
2273
+
2274
+ If <dir>/.task-executor exists, invokes it with run-source-fetch subcommand:
2275
+ <executor> run-source-fetch --in <source_json> --out <outfile> --err <errfile>
2276
+
2277
+ If no .task-executor is registered, uses board-live-cards built-in run-source-fetch.
2278
+
2279
+ run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
2280
+ Execute a source definition. Board-live-cards reads source.cli and executes it.
2281
+ Writes result to --out. Presence of --out after exit indicates success.
2282
+
2283
+ RUN-SOURCE-FETCH PROTOCOL
2284
+ External task-executors implement:
2285
+ <executor> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
2286
+
2287
+ INPUT: --in file contains the full sources[x] definition object
2288
+ OUTPUT: --out file is written with the result to signal success.
2289
+ --err file may be written to explain failure.
2290
+
2291
+ Exit code and --out presence determine success:
2292
+ Exit 0 + --out file present \u2192 source delivery recorded, card re-evaluated.
2293
+ Exit non-zero OR --out absent \u2192 source-data-fetch-failure recorded.
2294
+
2295
+ BOARD-LIVE-CARDS BUILT-IN EXECUTOR
2296
+ Understands source.cli field only:
2297
+ "sources": [{ "cli": "node ../fetch-prices.js", "bindTo": "prices", "outputFile": "prices.json" }]
2298
+
2299
+ The source.cli command is executed with:
2300
+ - Direct command invocation (no shell; quote-aware argument parsing)
2301
+ - Stdout is captured and delivered to the card as-is
2302
+ - Timeout from source.timeout (default 120s)
2303
+
2304
+ The source.cli command must:
2305
+ - Execute successfully (exit 0)
2306
+ - Write output to stdout
2307
+ - Complete within the timeout
2308
+
2309
+ The output format is the concern of the card's compute function to interpret.
2310
+
2311
+ External task-executors can interpret source definitions however they want.
2312
+
2313
+ EXAMPLES
2314
+ board-live-cards-cli init ./my-board
2315
+ board-live-cards-cli init ./my-board --task-executor ./executors/my-runner.py
2316
+ board-live-cards-cli add-cards --rg ./my-board --card cards/prices.json
2317
+ board-live-cards-cli status --rg ./my-board
2318
+ board-live-cards-cli retrigger --rg ./my-board --task price-fetch
2319
+ `.trimStart());
2320
+ }
2321
+ var isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
2322
+ if (isMain) {
2323
+ cli(process.argv.slice(2)).catch((err) => {
2324
+ const msg = err instanceof Error ? err.stack ?? err.message : String(err);
2325
+ console.error(msg);
2326
+ process.exit(1);
2327
+ });
2328
+ }
2329
+
2330
+ export { BoardJournal, appendCardInventory, appendEventToJournal, cli, createBoardReactiveGraph, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
2331
+ //# sourceMappingURL=board-live-cards-cli.js.map
2332
+ //# sourceMappingURL=board-live-cards-cli.js.map