yaml-flow 5.4.2 → 7.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 (252) hide show
  1. package/board-live-cards-cli.js +6 -6
  2. package/browser/asset-integrity.json +10 -0
  3. package/browser/board-livecards-client.js +2 -0
  4. package/browser/board-livecards-client.js.map +1 -0
  5. package/browser/board-livecards-localstorage.js +10 -0
  6. package/browser/board-livecards-localstorage.js.map +1 -0
  7. package/browser/board-livegraph-engine.js +2 -1676
  8. package/browser/board-livegraph-engine.js.map +1 -1
  9. package/browser/card-compute.js +28 -28
  10. package/browser/compute-jsonata.js +5 -0
  11. package/browser/compute-jsonata.js.map +1 -0
  12. package/browser/live-cards.js +561 -129
  13. package/browser/live-cards.schema.json +418 -132
  14. package/card-store.js +37 -0
  15. package/dist/batch/index.cjs +1 -108
  16. package/dist/batch/index.cjs.map +1 -1
  17. package/dist/batch/index.js +1 -106
  18. package/dist/batch/index.js.map +1 -1
  19. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  20. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  21. package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
  22. package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
  23. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  24. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  25. package/dist/board-livegraph-runtime/index.d.cts +12 -11
  26. package/dist/board-livegraph-runtime/index.d.ts +12 -11
  27. package/dist/board-livegraph-runtime/index.js +2 -1662
  28. package/dist/board-livegraph-runtime/index.js.map +1 -1
  29. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
  30. package/dist/card-compute/index.cjs +9 -7159
  31. package/dist/card-compute/index.cjs.map +1 -1
  32. package/dist/card-compute/index.d.cts +27 -1
  33. package/dist/card-compute/index.d.ts +27 -1
  34. package/dist/card-compute/index.js +9 -7145
  35. package/dist/card-compute/index.js.map +1 -1
  36. package/dist/card-compute/jsonata-sync.cjs +7623 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
  38. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  39. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
  40. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
  41. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
  42. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  43. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  44. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  45. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  46. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  47. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  48. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  49. package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
  50. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  51. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  52. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  53. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  54. package/dist/cli/node/artifacts-store-cli.js +11 -0
  55. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  56. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  57. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  58. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  59. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  60. package/dist/cli/node/board-live-cards-cli.js +15 -0
  61. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  62. package/dist/cli/node/card-store-cli.cjs +8 -0
  63. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  64. package/dist/cli/node/card-store-cli.d.cts +15 -0
  65. package/dist/cli/node/card-store-cli.d.ts +15 -0
  66. package/dist/cli/node/card-store-cli.js +8 -0
  67. package/dist/cli/node/card-store-cli.js.map +1 -0
  68. package/dist/cli/node/execution-adapter.cjs +3 -0
  69. package/dist/cli/node/execution-adapter.cjs.map +1 -0
  70. package/dist/cli/node/execution-adapter.d.cts +174 -0
  71. package/dist/cli/node/execution-adapter.d.ts +174 -0
  72. package/dist/cli/node/execution-adapter.js +3 -0
  73. package/dist/cli/node/execution-adapter.js.map +1 -0
  74. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  75. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  76. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  77. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  78. package/dist/cli/node/fs-board-adapter.js +14 -0
  79. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  80. package/dist/cli/node/jsonata-sync.cjs +7623 -0
  81. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  82. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  83. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  84. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  85. package/dist/cli/node/source-cli-task-executor.js +11 -0
  86. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  87. package/dist/config/index.cjs +1 -79
  88. package/dist/config/index.cjs.map +1 -1
  89. package/dist/config/index.js +1 -76
  90. package/dist/config/index.js.map +1 -1
  91. package/dist/continuous-event-graph/index.cjs +2 -2129
  92. package/dist/continuous-event-graph/index.cjs.map +1 -1
  93. package/dist/continuous-event-graph/index.d.cts +81 -5
  94. package/dist/continuous-event-graph/index.d.ts +81 -5
  95. package/dist/continuous-event-graph/index.js +2 -2088
  96. package/dist/continuous-event-graph/index.js.map +1 -1
  97. package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
  98. package/dist/event-graph/index.cjs +22 -8292
  99. package/dist/event-graph/index.cjs.map +1 -1
  100. package/dist/event-graph/index.js +22 -8237
  101. package/dist/event-graph/index.js.map +1 -1
  102. package/dist/execution-refs.cjs +3 -0
  103. package/dist/execution-refs.cjs.map +1 -0
  104. package/dist/execution-refs.d.cts +260 -0
  105. package/dist/execution-refs.d.ts +260 -0
  106. package/dist/execution-refs.js +3 -0
  107. package/dist/execution-refs.js.map +1 -0
  108. package/dist/index.cjs +29 -13221
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +2 -4
  111. package/dist/index.d.ts +2 -4
  112. package/dist/index.js +29 -13112
  113. package/dist/index.js.map +1 -1
  114. package/dist/inference/index.cjs +5 -617
  115. package/dist/inference/index.cjs.map +1 -1
  116. package/dist/inference/index.js +5 -610
  117. package/dist/inference/index.js.map +1 -1
  118. package/dist/jsonata-sync.cjs +7623 -0
  119. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  120. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  121. package/dist/server-runtime/index.cjs +9 -0
  122. package/dist/server-runtime/index.cjs.map +1 -0
  123. package/dist/server-runtime/index.d.cts +31 -0
  124. package/dist/server-runtime/index.d.ts +31 -0
  125. package/dist/server-runtime/index.js +9 -0
  126. package/dist/server-runtime/index.js.map +1 -0
  127. package/dist/server-runtime/jsonata-sync.cjs +7623 -0
  128. package/dist/step-machine/index.cjs +11 -7129
  129. package/dist/step-machine/index.cjs.map +1 -1
  130. package/dist/step-machine/index.js +11 -7113
  131. package/dist/step-machine/index.js.map +1 -1
  132. package/dist/step-machine-public/index.cjs +2 -0
  133. package/dist/step-machine-public/index.cjs.map +1 -0
  134. package/dist/step-machine-public/index.d.cts +159 -0
  135. package/dist/step-machine-public/index.d.ts +159 -0
  136. package/dist/step-machine-public/index.js +2 -0
  137. package/dist/step-machine-public/index.js.map +1 -0
  138. package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
  139. package/dist/storage-refs.cjs +10 -0
  140. package/dist/storage-refs.cjs.map +1 -0
  141. package/dist/storage-refs.d.cts +93 -0
  142. package/dist/storage-refs.d.ts +93 -0
  143. package/dist/storage-refs.js +10 -0
  144. package/dist/storage-refs.js.map +1 -0
  145. package/dist/stores/file.cjs +1 -114
  146. package/dist/stores/file.cjs.map +1 -1
  147. package/dist/stores/file.js +1 -112
  148. package/dist/stores/file.js.map +1 -1
  149. package/dist/stores/index.cjs +1 -231
  150. package/dist/stores/index.cjs.map +1 -1
  151. package/dist/stores/index.js +1 -227
  152. package/dist/stores/index.js.map +1 -1
  153. package/dist/stores/localStorage.cjs +1 -76
  154. package/dist/stores/localStorage.cjs.map +1 -1
  155. package/dist/stores/localStorage.js +1 -74
  156. package/dist/stores/localStorage.js.map +1 -1
  157. package/dist/stores/memory.cjs +1 -47
  158. package/dist/stores/memory.cjs.map +1 -1
  159. package/dist/stores/memory.js +1 -45
  160. package/dist/stores/memory.js.map +1 -1
  161. package/dist/types-B1ZRa4aI.d.ts +147 -0
  162. package/dist/types-BxEFcVK9.d.cts +147 -0
  163. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +291 -0
  164. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  165. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  166. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
  167. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  168. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
  169. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
  170. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
  171. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
  172. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +366 -0
  173. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  174. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  175. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
  176. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
  177. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
  178. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
  179. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
  180. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
  181. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
  182. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +9 -8
  183. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
  184. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
  185. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +107 -0
  186. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
  187. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
  188. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
  189. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  190. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  191. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  192. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  193. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
  194. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  195. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
  196. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  197. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  198. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
  199. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
  200. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
  201. package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
  202. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
  203. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
  204. package/examples/example-board/agent-instructions.md +11 -5
  205. package/examples/example-board/cards/_index.json +47 -0
  206. package/examples/example-board/cards/card-market-prices.json +33 -9
  207. package/examples/example-board/cards/card-my-identity.json +30 -6
  208. package/examples/example-board/cards/card-portfolio-action.json +24 -6
  209. package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
  210. package/examples/example-board/cards/card-portfolio-risks.json +24 -6
  211. package/examples/example-board/cards/card-portfolio-value.json +38 -10
  212. package/examples/example-board/cards/card-portfolio.json +57 -13
  213. package/examples/example-board/cards/card-rebalance-impact.json +22 -6
  214. package/examples/example-board/cards/card-rebalance-sim.json +66 -15
  215. package/examples/example-board/demo-chat-handler.js +14 -4
  216. package/examples/example-board/demo-server-config.json +1 -0
  217. package/examples/example-board/demo-server.js +366 -68
  218. package/examples/example-board/demo-shell-localstorage.html +774 -0
  219. package/examples/example-board/demo-shell-with-server.html +20 -37
  220. package/examples/example-board/demo-shell.html +5 -4
  221. package/examples/example-board/demo-task-executor.js +273 -275
  222. package/examples/index.html +0 -14
  223. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  224. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  225. package/package.json +46 -8
  226. package/schema/live-cards.schema.json +418 -132
  227. package/step-machine-cli.js +43 -310
  228. package/board-livecards-server-runtime.js +0 -1574
  229. package/browser/board-livecards-runtime-client.js +0 -263
  230. package/dist/cli/board-live-cards-cli.cjs +0 -10650
  231. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  232. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  233. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  234. package/dist/cli/board-live-cards-cli.js +0 -10598
  235. package/dist/cli/board-live-cards-cli.js.map +0 -1
  236. package/dist/journal-9HEgs7dU.d.ts +0 -28
  237. package/dist/journal-B-JCfQnh.d.cts +0 -28
  238. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  239. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  240. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  241. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  242. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  243. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  244. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  245. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  246. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  247. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  248. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  249. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
  250. package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
  251. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
  252. package/examples/example-board/demo-shell-browser.html +0 -674
@@ -32,7 +32,10 @@
32
32
  * "boardId": "<board id>", // e.g. "default"
33
33
  * "boardRuntimeDir": "<relative>", // e.g. "runtime"
34
34
  * "runtimeStatusDir": "<relative>", // e.g. "runtime-out"
35
- * "cardsDir": "<relative>" // e.g. "surface/tmp-cards"
35
+ * "cardsDir": "<relative>", // e.g. "surface/tmp-cards"
36
+ * "serverUrl": "<base url>", // optional; e.g. "http://127.0.0.1:7799"
37
+ * "boardLiveCardsCliJs":"<abs path>", // optional; path to board-live-cards-cli.js
38
+ * "stepMachineCliPath":"<abs path>" // optional; path to step-machine-cli.js
36
39
  * }
37
40
  *
38
41
  * Supported source kinds (based on custom fields in --in):
@@ -55,11 +58,15 @@
55
58
  import fs from 'node:fs';
56
59
  import path from 'node:path';
57
60
  import os from 'node:os';
58
- import crypto from 'node:crypto';
59
61
  import { execFileSync } from 'node:child_process';
60
- import { fileURLToPath } from 'node:url';
62
+ import { fileURLToPath, pathToFileURL } from 'node:url';
63
+ import { parseRef, blobStorageForRef, reportComplete, reportFailed } from 'yaml-flow/storage-refs';
64
+ import { loadStepFlow, createStepMachine, MemoryStore } from '../../dist/index.js';
65
+ import { buildStepHandlersForFlow } from '../../dist/step-machine-public/index.js';
66
+ import { invokeRefSync } from '../../dist/cli/node/execution-adapter.js';
61
67
 
62
68
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
69
+ const SOURCE_DEF_FLOWS_FILE = path.join(__dirname, 'source_def_flows.json');
63
70
 
64
71
  // ---------------------------------------------------------------------------
65
72
  // Mock data — used when a source has { mock: "key" }.
@@ -79,50 +86,6 @@ const MOCK_DB = {
79
86
  },
80
87
  };
81
88
 
82
- // ---------------------------------------------------------------------------
83
- // Simple file cache for url / url-list results.
84
- // Stored in os.tmpdir()/demo-executor-cache/<hash>.json
85
- // ---------------------------------------------------------------------------
86
- const CACHE_DIR = path.join(os.tmpdir(), 'demo-executor-cache');
87
- const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
88
-
89
- function cacheKey(str) {
90
- return crypto.createHash('sha1').update(str).digest('hex');
91
- }
92
-
93
- function readCache(key, ttlMs = CACHE_TTL_MS) {
94
- const file = path.join(CACHE_DIR, `${key}.json`);
95
- try {
96
- const stat = fs.statSync(file);
97
- if (Date.now() - stat.mtimeMs < ttlMs) {
98
- return JSON.parse(fs.readFileSync(file, 'utf-8'));
99
- }
100
- } catch {}
101
- return null;
102
- }
103
-
104
- // Shared single-URL fetch helper used by both url and url-list.
105
- // cacheTimeoutSec: override TTL in seconds (null → use CACHE_TTL_MS default).
106
- function doFetchApi(url, method, headers, cacheTimeoutSec, errFile) {
107
- const ttlMs = cacheTimeoutSec != null ? cacheTimeoutSec * 1000 : CACHE_TTL_MS;
108
- const k = cacheKey(`url:${method}:${url}`);
109
- const cached = readCache(k, ttlMs);
110
- if (cached) {
111
- console.warn(`[demo-task-executor] url: cache hit for ${url}`);
112
- return cached;
113
- }
114
- const data = curlFetchJson(url, method, headers);
115
- writeCache(k, data);
116
- return data;
117
- }
118
-
119
- function writeCache(key, value) {
120
- try {
121
- fs.mkdirSync(CACHE_DIR, { recursive: true });
122
- fs.writeFileSync(path.join(CACHE_DIR, `${key}.json`), JSON.stringify(value));
123
- } catch {}
124
- }
125
-
126
89
  function readJson(filePath) {
127
90
  return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
128
91
  }
@@ -156,26 +119,6 @@ const COPILOT_PROMPT_CONTEXT = {
156
119
  ].join('\n'),
157
120
  };
158
121
 
159
- /**
160
- * Fetch a URL using the system curl binary (synchronous, no Node event-loop handles).
161
- * Throws if curl exits non-zero (e.g. HTTP 4xx/5xx with -f, or network error).
162
- */
163
- function curlFetchJson(url, method, headers) {
164
- const bin = process.platform === 'win32' ? 'curl.exe' : 'curl';
165
- // -s : silent (no progress bar)
166
- // -S : show errors despite -s
167
- // -f : fail (non-zero exit) on HTTP 4xx/5xx
168
- // -L : follow redirects
169
- // --max-time 10 : hard timeout
170
- const args = ['-s', '-S', '-f', '-L', '--max-time', '10', '-X', method];
171
- for (const [k, v] of Object.entries(headers)) {
172
- args.push('-H', `${k}: ${v}`);
173
- }
174
- args.push(url);
175
- const raw = execFileSync(bin, args, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
176
- return JSON.parse(raw);
177
- }
178
-
179
122
  function resolveCopilotPrompt(sourceDef) {
180
123
  const cfg = sourceDef?.copilot && typeof sourceDef.copilot === 'object' ? sourceDef.copilot : {};
181
124
  const template = cfg.prompt_template ?? sourceDef.prompt_template;
@@ -245,6 +188,7 @@ function runCopilotViaWrapper(prompt, sourceDef, wrapperOutFile, sessionDir, cwd
245
188
  encoding: 'utf-8',
246
189
  stdio: ['ignore', 'pipe', 'pipe'],
247
190
  maxBuffer: 10 * 1024 * 1024,
191
+ windowsHide: true,
248
192
  });
249
193
  } finally {
250
194
  try { fs.unlinkSync(promptFile); } catch {}
@@ -264,205 +208,260 @@ function fail(msg, errFile) {
264
208
  process.exit(1);
265
209
  }
266
210
 
267
- function runSourceFetchSubcommand(argv) {
268
- const inIdx = argv.indexOf('--in');
269
- const outIdx = argv.indexOf('--out');
270
- const errIdx = argv.indexOf('--err');
271
- const extraIdx = argv.indexOf('--extra');
272
- const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
273
- const outFile = outIdx !== -1 ? argv[outIdx + 1] : undefined;
274
- const errFile = errIdx !== -1 ? argv[errIdx + 1] : undefined;
275
- const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
211
+ function loadSourceDefFlowsConfig() {
212
+ try {
213
+ return readJson(SOURCE_DEF_FLOWS_FILE);
214
+ } catch (err) {
215
+ fail(`Cannot read source flow registry at ${SOURCE_DEF_FLOWS_FILE}: ${String(err && err.message || err)}`);
216
+ }
217
+ }
276
218
 
277
- let extra = {};
278
- if (extraB64) {
279
- try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
280
- catch { console.warn('[demo-task-executor] bad --extra base64, ignoring'); }
219
+ function matchesDetectRule(sourceDef, detect) {
220
+ if (!detect || typeof detect !== 'object') return false;
221
+ if (typeof detect.field === 'string') {
222
+ return sourceDef[detect.field] !== undefined;
223
+ }
224
+ if (Array.isArray(detect.anyOfFields)) {
225
+ return detect.anyOfFields.some((field) => sourceDef[field] !== undefined);
281
226
  }
227
+ return false;
228
+ }
282
229
 
283
- if (!inFile || !outFile) {
284
- fail('Usage: run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]', errFile);
230
+ function resolveSourceKind(sourceDef, registry) {
231
+ const kinds = registry?.kinds && typeof registry.kinds === 'object' ? registry.kinds : {};
232
+ const order = Array.isArray(registry?.resolveOrder) ? registry.resolveOrder : Object.keys(kinds);
233
+ const matched = [];
234
+ for (const kind of order) {
235
+ const spec = kinds[kind];
236
+ if (!spec) continue;
237
+ if (matchesDetectRule(sourceDef, spec.detect)) {
238
+ matched.push(kind);
239
+ }
285
240
  }
286
241
 
287
- if (!fs.existsSync(inFile)) {
288
- fail(`Input file not found: ${inFile}`, errFile);
242
+ if (matched.length === 0) {
243
+ const knownKinds = Object.keys(kinds);
244
+ throw new Error(`No recognised source kind. Known kinds: ${knownKinds.join(', ')}`);
289
245
  }
246
+ if (matched.length > 1) {
247
+ throw new Error(`Multiple source kinds specified: [${matched.join(', ')}]. Use exactly one.`);
248
+ }
249
+ return matched[0];
250
+ }
290
251
 
291
- let sourceDef;
292
- try {
293
- sourceDef = readJson(inFile);
294
- } catch (err) {
295
- fail(`Cannot parse source file: ${String(err && err.message || err)}`, errFile);
296
- }
297
-
298
- let resultValue;
299
-
300
- if (sourceDef['url']) {
301
- // ---------------------------------------------------------------------------
302
- // url — single URL fetch via curl
303
- // {{key}} interpolation applied to url from _projections and optional args.
304
- // cacheTimeout: seconds to cache the response (default: CACHE_TTL_MS / 1000).
305
- // ---------------------------------------------------------------------------
306
- const cfg = sourceDef['url'];
307
- const method = (cfg.method || 'GET').toUpperCase();
308
- const headers = { ...(cfg.headers || {}) };
309
- const cacheTimeoutSec = cfg.cacheTimeout != null ? Number(cfg.cacheTimeout) : null;
310
-
311
- const fetchArgs = { ...(cfg.args || {}) };
312
- if (sourceDef.tickersFrom) {
313
- const dotIdx = sourceDef.tickersFrom.indexOf('.');
314
- if (dotIdx > 0) {
315
- const refKey = sourceDef.tickersFrom.slice(0, dotIdx);
316
- const fieldName = sourceDef.tickersFrom.slice(dotIdx + 1);
317
- const arr = sourceDef._projections?.[refKey];
318
- if (Array.isArray(arr)) {
319
- fetchArgs.tickers = arr.map(h => h[fieldName]).filter(Boolean).join(',');
320
- }
321
- }
252
+ async function executeStepMachineSourceFlow(context) {
253
+ const { kind, registry } = context;
254
+ const spec = registry?.kinds?.[kind];
255
+ if (!spec) {
256
+ throw new Error(`Missing flow registration for kind: ${kind}`);
257
+ }
258
+
259
+ const flowRef = spec.flow;
260
+ if (typeof flowRef !== 'string' || flowRef.length === 0) {
261
+ throw new Error(`Invalid or missing flow for kind: ${kind}`);
262
+ }
263
+
264
+ const flowPath = path.resolve(__dirname, flowRef);
265
+ const flow = await loadStepFlow(flowPath);
266
+
267
+ const invokeHttpRef = async (ref, args) => {
268
+ let rawUrl = ref.whatToRun;
269
+ try {
270
+ rawUrl = parseRef(ref.whatToRun).value;
271
+ } catch {
272
+ // Keep raw value when whatToRun is already a URL.
322
273
  }
323
- if (sourceDef.tickersFrom && !fetchArgs.tickers) {
324
- fail('url: tickersFrom resolved to empty list — skipping fetch', errFile);
274
+
275
+ const base = String(args?.extra?.serverUrl || 'http://127.0.0.1:7799').replace(/\/$/, '');
276
+ const resolvedUrl = /^https?:\/\//i.test(rawUrl)
277
+ ? rawUrl
278
+ : `${base}${rawUrl.startsWith('/') ? '' : '/'}${rawUrl}`;
279
+
280
+ let body = args;
281
+ const workiqCfg = args?.sourceDef?.workiq;
282
+ if (workiqCfg && typeof workiqCfg === 'object' && typeof workiqCfg.query_template === 'string') {
283
+ const interpolationContext = {
284
+ ...(args?.sourceDef?._projections || {}),
285
+ ...(workiqCfg.args || {}),
286
+ };
287
+ body = {
288
+ query: interpolatePrompt(workiqCfg.query_template, interpolationContext),
289
+ };
325
290
  }
326
- const urlContext = { ...(sourceDef._projections || {}), ...fetchArgs };
327
- const url = interpolatePrompt(cfg.url, urlContext);
291
+
292
+ const method = ref.howToRun === 'http:get' ? 'GET' : 'POST';
293
+ const response = await fetch(resolvedUrl, {
294
+ method,
295
+ headers: { 'Content-Type': 'application/json' },
296
+ ...(method === 'POST' ? { body: JSON.stringify(body) } : {}),
297
+ });
298
+
299
+ const text = await response.text();
300
+ let parsed;
328
301
  try {
329
- resultValue = doFetchApi(url, method, headers, cacheTimeoutSec, errFile);
330
- } catch (err) {
331
- fail(`url failed: ${err.message}`, errFile);
302
+ parsed = text ? JSON.parse(text) : {};
303
+ } catch {
304
+ parsed = { response: text };
332
305
  }
333
306
 
334
- } else if (sourceDef['url-list']) {
335
- // ---------------------------------------------------------------------------
336
- // url-list fan-out over a URL list, calling url logic per URL.
337
- // url_list must be a string[] pre-resolved in _projections.url_list.
338
- // cacheTimeout: seconds to cache each individual response.
339
- // ---------------------------------------------------------------------------
340
- const cfg = sourceDef['url-list'];
341
- const method = (cfg.method || 'GET').toUpperCase();
342
- const headers = { ...(cfg.headers || {}) };
343
- const cacheTimeoutSec = cfg.cacheTimeout != null ? Number(cfg.cacheTimeout) : null;
344
-
345
- const urlList = Array.isArray(sourceDef._projections?.url_list)
346
- ? sourceDef._projections.url_list : null;
347
-
348
- if (!urlList || urlList.length === 0) {
349
- fail('url-list: _projections.url_list must be a non-empty string array', errFile);
307
+ if (!response.ok) {
308
+ const msg = typeof parsed?.error === 'string' ? parsed.error : `HTTP ${response.status}`;
309
+ return { result: 'failure', data: { error: msg }, error: msg };
350
310
  }
351
311
 
352
- const results = [];
353
- for (const u of urlList) {
354
- try {
355
- results.push(doFetchApi(u, method, headers, cacheTimeoutSec, errFile));
356
- } catch (err) {
357
- fail(`url-list fetch failed for ${u}: ${err.message}`, errFile);
358
- }
312
+ if (typeof parsed?.error === 'string') {
313
+ return { result: 'failure', data: { error: parsed.error }, error: parsed.error };
359
314
  }
360
- resultValue = results;
361
315
 
362
- } else if (sourceDef.copilot || sourceDef.prompt_template) {
363
- const prompt = resolveCopilotPrompt(sourceDef);
364
- if (!prompt) {
365
- fail('Source definition missing copilot.prompt_template (or prompt_template)', errFile);
366
- }
316
+ return {
317
+ result: 'success',
318
+ data: {
319
+ resultValue: Object.prototype.hasOwnProperty.call(parsed, 'response') ? parsed.response : parsed,
320
+ },
321
+ };
322
+ };
367
323
 
368
- // Use boardSetupRoot (from --extra) as copilot working directory
369
- const copilotCwd = extra.boardSetupRoot || undefined;
370
-
371
- // On Windows, delegate entirely to copilot_wrapper.bat which handles:
372
- // - session management (--resume UUID for multi-turn continuity)
373
- // - noise/footer stripping, JSON extraction, agentic retry on bad shape
374
- // On non-Windows, fall back to a basic direct invocation (no retry).
375
- const wrapperPath = path.join(__dirname, 'scripts', 'copilot_wrapper.bat');
376
- const useWrapper = process.platform === 'win32' && fs.existsSync(wrapperPath);
377
-
378
- if (useWrapper) {
379
- // Session dir is stable across refreshes so --resume continues the conversation.
380
- const sessionDir = path.join(
381
- extra.boardSetupRoot || os.tmpdir(),
382
- 'copilot-sessions',
383
- String(sourceDef.bindTo || 'default').replace(/[^a-zA-Z0-9_-]/g, '_'),
384
- );
385
- const wrapperOutFile = outFile + '.wrapper-out.json';
386
- try {
387
- resultValue = runCopilotViaWrapper(prompt, sourceDef, wrapperOutFile, sessionDir, copilotCwd);
388
- } catch (err) {
389
- fail(`copilot invocation failed: ${String(err && err.message || err)}`, errFile);
390
- } finally {
391
- try { fs.unlinkSync(wrapperOutFile); } catch {}
392
- }
393
- } else {
394
- // Non-Windows fallback: call copilot directly via cmd.exe and do basic JSON extraction.
395
- let rawOutput = '';
396
- try {
397
- rawOutput = execFileSync('cmd.exe', ['/d', '/c', 'copilot --allow-all'], {
398
- input: String(prompt),
399
- encoding: 'utf-8',
400
- stdio: ['pipe', 'pipe', 'pipe'],
401
- maxBuffer: 10 * 1024 * 1024,
402
- ...(copilotCwd ? { cwd: copilotCwd } : {}),
403
- });
404
- } catch (err) {
405
- fail(`copilot invocation failed: ${String(err && err.message || err)}`, errFile);
406
- }
407
- // Basic JSON extraction: find first { or [ in output
408
- const firstBrace = rawOutput.indexOf('{');
409
- const firstBracket = rawOutput.indexOf('[');
410
- const jsonStart = (firstBrace === -1) ? firstBracket
411
- : (firstBracket === -1) ? firstBrace
412
- : Math.min(firstBrace, firstBracket);
413
- if (jsonStart !== -1) {
414
- try {
415
- const parsed = JSON.parse(rawOutput.slice(jsonStart));
416
- resultValue = (parsed && typeof parsed === 'object') ? parsed : rawOutput;
417
- } catch {
418
- resultValue = rawOutput;
419
- }
420
- } else {
421
- resultValue = rawOutput;
422
- }
324
+ const invoke = async (ref, args) => {
325
+ if (ref.howToRun === 'http:post' || ref.howToRun === 'http:get') {
326
+ return invokeHttpRef(ref, args);
423
327
  }
424
- } else if (sourceDef.workiq) {
425
- const cfg = typeof sourceDef.workiq === 'object' ? sourceDef.workiq : {};
426
- if (!cfg.query_template || typeof cfg.query_template !== 'string') {
427
- fail('Source definition missing workiq.query_template', errFile);
328
+ if (ref.howToRun === 'demo-local-module') {
329
+ const modulePath = path.resolve(__dirname, ref.whatToRun);
330
+ const mod = await import(pathToFileURL(modulePath).href);
331
+ if (typeof mod.execute !== 'function') {
332
+ throw new Error(`Flow module ${ref.whatToRun} must export execute(context)`);
333
+ }
334
+ return mod.execute(args);
428
335
  }
429
- const interpolationContext = { ...sourceDef._projections, ...(cfg.args ?? {}) };
430
- const query = interpolatePrompt(cfg.query_template, interpolationContext);
336
+ return invokeRefSync(ref, args, { cliDir: __dirname, cwd: process.cwd() });
337
+ };
431
338
 
432
- const wrapperPath = path.join(__dirname, 'scripts', 'workiq_wrapper.mjs');
433
- if (!fs.existsSync(wrapperPath)) {
434
- fail('workiq source kind requires workiq_wrapper.js in scripts/', errFile);
435
- }
339
+ const handlers = buildStepHandlersForFlow(flow, { invoke });
340
+ const machine = createStepMachine(flow, handlers, { store: new MemoryStore() });
341
+ const run = await machine.run({
342
+ ...context,
343
+ promptContext: COPILOT_PROMPT_CONTEXT,
344
+ executorDir: __dirname,
345
+ });
346
+
347
+ if (run.status !== 'completed') {
348
+ const reason = run.error?.message ?? run.intent ?? run.status;
349
+ throw new Error(`flow execution failed: ${reason}`);
350
+ }
351
+
352
+ if (run.intent !== 'success') {
353
+ const reason = typeof run.data?.error === 'string' ? run.data.error : `flow returned intent: ${run.intent}`;
354
+ throw new Error(reason);
355
+ }
356
+
357
+ return {
358
+ resultValue: run.data?.resultValue,
359
+ wroteOutputDirectly: !!run.data?.wroteOutputDirectly,
360
+ };
361
+ }
362
+
363
+ async function runSourceFetchSubcommand(argv) {
364
+ const inIdx = argv.indexOf('--in-ref');
365
+ const outIdx = argv.indexOf('--out-ref');
366
+ const errIdx = argv.indexOf('--err-ref');
367
+ const extraIdx = argv.indexOf('--extra');
368
+ const inRefStr = inIdx !== -1 ? argv[inIdx + 1] : undefined;
369
+ const outRefStr = outIdx !== -1 ? argv[outIdx + 1] : undefined;
370
+ const errRefStr = errIdx !== -1 ? argv[errIdx + 1] : undefined;
371
+ const extraB64 = extraIdx !== -1 ? argv[extraIdx + 1] : undefined;
372
+
373
+ let extra = {};
374
+ if (extraB64) {
375
+ try { extra = JSON.parse(Buffer.from(extraB64, 'base64').toString('utf-8')); }
376
+ catch { console.warn('[demo-task-executor] bad --extra base64, ignoring'); }
377
+ }
378
+
379
+ if (!inRefStr || !outRefStr) {
380
+ fail('Usage: run-source-fetch --in-ref <b64:<base64url(json)>> --out-ref <b64:<base64url(json)>> [--err-ref <b64:<base64url(json)>>]');
381
+ }
382
+
383
+ let inRef, outRef, errRef;
384
+ try {
385
+ inRef = parseRef(inRefStr);
386
+ outRef = parseRef(outRefStr);
387
+ if (errRefStr) errRef = parseRef(errRefStr);
388
+ } catch (e) {
389
+ fail(`invalid ref argument: ${e.message}`);
390
+ }
391
+
392
+ const inStorage = blobStorageForRef(inRef);
393
+ const outStorage = blobStorageForRef(outRef);
394
+ const errStorage = errRef ? blobStorageForRef(errRef) : undefined;
395
+
396
+ // Local error reporter — writes to errStorage and calls back to board if callback present.
397
+ const failRef = (msg, callback) => {
398
+ if (errStorage && errRef) { try { errStorage.write(errRef.value, msg); } catch {} }
399
+ console.error(`[demo-task-executor] ${msg}`);
400
+ if (callback) { try { reportFailed(callback, msg); } catch {} }
401
+ process.exit(1);
402
+ };
403
+
404
+ const rawIn = inStorage.read(inRef.value);
405
+ if (rawIn === null) {
406
+ failRef(`Input not found: ${inRefStr}`);
407
+ }
408
+
409
+ // Payload may be { source_def, callback } (new protocol) or raw source def (legacy).
410
+ let envelope;
411
+ try {
412
+ envelope = JSON.parse(rawIn);
413
+ } catch (err) {
414
+ failRef(`Cannot parse input: ${String(err && err.message || err)}`);
415
+ }
416
+
417
+ const callback = envelope.source_def ? envelope.callback : undefined;
418
+ let sourceDef;
419
+ try {
420
+ sourceDef = envelope.source_def ?? envelope;
421
+ } catch (err) {
422
+ failRef(`Cannot resolve source_def: ${String(err && err.message || err)}`, callback);
423
+ }
424
+
425
+ const registry = loadSourceDefFlowsConfig();
426
+ let kind;
427
+ try {
428
+ kind = resolveSourceKind(sourceDef, registry);
429
+ } catch (err) {
430
+ failRef(String(err && err.message || err), callback);
431
+ }
432
+
433
+ let flowResult;
434
+ try {
435
+ flowResult = await executeStepMachineSourceFlow({
436
+ kind,
437
+ registry,
438
+ sourceDef,
439
+ extra,
440
+ inRef,
441
+ outRef,
442
+ errRef,
443
+ mockDb: MOCK_DB,
444
+ });
445
+ } catch (err) {
446
+ const detail = (err && (err.stderr || err.stdout)) ? `\n${err.stderr || err.stdout}`.trimEnd() : '';
447
+ failRef(`${kind} invocation failed: ${String(err && err.message || err)}${detail}`, callback);
448
+ }
449
+
450
+ if (!flowResult?.wroteOutputDirectly) {
436
451
  try {
437
- execFileSync(process.execPath, [wrapperPath, outFile], {
438
- encoding: 'utf-8',
439
- stdio: ['inherit', 'pipe', 'pipe'],
440
- maxBuffer: 10 * 1024 * 1024,
441
- env: {
442
- ...process.env,
443
- WORKIQ_QUERY: query,
444
- ...(extra.serverUrl ? { WORKIQ_SERVER_URL: extra.serverUrl } : {}),
445
- },
446
- });
447
- return; // wrapper wrote directly to outFile
452
+ outStorage.write(outRef.value, JSON.stringify(flowResult?.resultValue, null, 2));
448
453
  } catch (err) {
449
- fail(`workiq invocation failed: ${String(err && err.message || err)}`, errFile);
454
+ failRef(`Cannot write output: ${String(err && err.message || err)}`, callback);
450
455
  }
451
- } else if (sourceDef.mock) {
452
- // MOCK_DB lookup — data hardcoded at the top of this file
453
- resultValue = MOCK_DB[sourceDef.mock];
454
- if (resultValue === undefined) {
455
- fail(`Key "${sourceDef.mock}" not found in MOCK_DB`, errFile);
456
- }
457
- } else {
458
- fail('Source definition has no recognised kind (url, url-list, copilot, workiq, mock)', errFile);
459
456
  }
460
457
 
461
- // Write result to --out as JSON payload, same contract as current mock mode.
462
- try {
463
- fs.writeFileSync(outFile, JSON.stringify(resultValue, null, 2));
464
- } catch (err) {
465
- fail(`Cannot write output file: ${String(err && err.message || err)}`, errFile);
458
+ if (callback) {
459
+ try {
460
+ reportComplete(callback, outRef);
461
+ } catch (err) {
462
+ console.error(`[demo-task-executor] reportComplete failed: ${String(err && err.message || err)}`);
463
+ process.exit(1);
464
+ }
466
465
  }
467
466
 
468
467
  }
@@ -493,30 +492,16 @@ function validateSourceDefSubcommand(argv) {
493
492
  }
494
493
 
495
494
  const errors = [];
495
+ const registry = loadSourceDefFlowsConfig();
496
496
 
497
- // Determine source kind and validate required fields
498
- const hasUrl = !!sourceDef['url'];
499
- const hasUrlList = !!sourceDef['url-list'];
500
- const hasCopilot = !!sourceDef.copilot;
501
- const hasPromptTemplate = typeof sourceDef.prompt_template === 'string';
502
- const hasWorkiq = !!sourceDef.workiq;
503
- const hasMock = sourceDef.mock !== undefined;
504
-
505
- const kindCount = [hasUrl, hasUrlList, hasCopilot || hasPromptTemplate, hasWorkiq, hasMock].filter(Boolean).length;
506
-
507
- if (kindCount === 0) {
508
- errors.push('No recognised source kind (url, url-list, copilot, workiq, mock). Add one of these fields.');
509
- } else if (kindCount > 1) {
510
- const kinds = [];
511
- if (hasUrl) kinds.push('url');
512
- if (hasUrlList) kinds.push('url-list');
513
- if (hasCopilot || hasPromptTemplate) kinds.push('copilot');
514
- if (hasWorkiq) kinds.push('workiq');
515
- if (hasMock) kinds.push('mock');
516
- errors.push(`Multiple source kinds specified: [${kinds.join(', ')}]. Use exactly one.`);
517
- }
518
-
519
- if (hasUrl) {
497
+ let kind = '';
498
+ try {
499
+ kind = resolveSourceKind(sourceDef, registry);
500
+ } catch (err) {
501
+ errors.push(String(err && err.message || err));
502
+ }
503
+
504
+ if (kind === 'url') {
520
505
  if (typeof sourceDef['url'] !== 'object') {
521
506
  errors.push('url must be an object.');
522
507
  } else if (!sourceDef['url'].url || typeof sourceDef['url'].url !== 'string') {
@@ -524,24 +509,24 @@ function validateSourceDefSubcommand(argv) {
524
509
  }
525
510
  }
526
511
 
527
- if (hasUrlList) {
512
+ if (kind === 'url-list') {
528
513
  if (typeof sourceDef['url-list'] !== 'object') {
529
514
  errors.push('url-list must be an object.');
530
515
  }
531
516
  // url_list is supplied via _projections at runtime — no static validation needed.
532
517
  }
533
518
 
534
- if (hasCopilot) {
519
+ if (kind === 'copilot') {
535
520
  if (typeof sourceDef.copilot !== 'object') {
536
- errors.push('copilot must be an object.');
537
- } else {
538
- if (!sourceDef.copilot.prompt_template && !hasPromptTemplate) {
539
- errors.push('copilot.prompt_template is required (or use top-level prompt_template).');
521
+ if (typeof sourceDef.prompt_template !== 'string') {
522
+ errors.push('copilot must be an object when prompt_template is not provided at top level.');
540
523
  }
524
+ } else if (!sourceDef.copilot.prompt_template && typeof sourceDef.prompt_template !== 'string') {
525
+ errors.push('copilot.prompt_template is required (or use top-level prompt_template).');
541
526
  }
542
527
  }
543
528
 
544
- if (hasWorkiq) {
529
+ if (kind === 'workiq') {
545
530
  if (typeof sourceDef.workiq !== 'object') {
546
531
  errors.push('workiq must be an object.');
547
532
  } else if (!sourceDef.workiq.query_template || typeof sourceDef.workiq.query_template !== 'string') {
@@ -549,7 +534,7 @@ function validateSourceDefSubcommand(argv) {
549
534
  }
550
535
  }
551
536
 
552
- if (hasMock) {
537
+ if (kind === 'mock') {
553
538
  if (typeof sourceDef.mock !== 'string') {
554
539
  errors.push('mock must be a string key.');
555
540
  }
@@ -650,21 +635,34 @@ const CAPABILITIES = {
650
635
  runtimeStatusDir: { type: 'string', description: 'Relative path to runtime-out dir.' },
651
636
  cardsDir: { type: 'string', description: 'Relative path to cards dir.' },
652
637
  serverUrl: { type: 'string', description: 'Base URL of the hosting server (e.g. http://127.0.0.1:7799). Used by source kinds that call server-side proxy endpoints.' },
638
+ boardLiveCardsCliJs: { type: 'string', description: 'Absolute path to board-live-cards-cli.js when configured by the runtime.' },
639
+ stepMachineCliPath: { type: 'string', description: 'Absolute path to step-machine-cli.js when configured by the runtime.' },
653
640
  },
654
641
  },
655
642
  };
656
643
 
657
644
  function describeCapabilities() {
658
- console.log(JSON.stringify(CAPABILITIES, null, 2));
645
+ const registry = loadSourceDefFlowsConfig();
646
+ const merged = {
647
+ ...CAPABILITIES,
648
+ sourceKinds: Object.fromEntries(
649
+ Object.entries(registry?.kinds || {}).map(([kind, spec]) => {
650
+ const existing = CAPABILITIES.sourceKinds[kind] || {};
651
+ const manifest = spec?.manifest && typeof spec.manifest === 'object' ? spec.manifest : {};
652
+ return [kind, { ...existing, ...manifest }];
653
+ }),
654
+ ),
655
+ };
656
+ console.log(JSON.stringify(merged, null, 2));
659
657
  }
660
658
 
661
659
  async function main() {
662
660
  const sub = process.argv[2];
663
661
  if (sub === 'run-source-fetch') {
664
- runSourceFetchSubcommand(process.argv.slice(3));
662
+ await runSourceFetchSubcommand(process.argv.slice(3));
665
663
  return;
666
664
  }
667
- if (sub === 'describe-capabilities') {
665
+ if (sub === 'describe' || sub === 'describe-capabilities') {
668
666
  describeCapabilities();
669
667
  return;
670
668
  }