yaml-flow 6.0.0 → 7.1.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 (166) hide show
  1. package/board-live-cards-cli.js +4 -4
  2. package/browser/asset-integrity.json +3 -3
  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 -2
  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 +264 -151
  13. package/card-store.js +4 -4
  14. package/dist/{board-live-cards-public-CltXYgaY.d.cts → board-live-cards-public-5n1-syA3.d.cts} +8 -5
  15. package/dist/{board-live-cards-public-f-E-FAyp.d.ts → board-live-cards-public-CK_J8uv0.d.ts} +8 -5
  16. package/dist/board-livegraph-runtime/index.cjs +2 -2
  17. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  18. package/dist/board-livegraph-runtime/index.d.cts +11 -9
  19. package/dist/board-livegraph-runtime/index.d.ts +11 -9
  20. package/dist/board-livegraph-runtime/index.js +2 -2
  21. package/dist/board-livegraph-runtime/index.js.map +1 -1
  22. package/dist/board-livegraph-runtime/jsonata-sync.cjs +37 -1
  23. package/dist/card-compute/index.cjs +4 -4
  24. package/dist/card-compute/index.cjs.map +1 -1
  25. package/dist/card-compute/index.d.cts +5 -1
  26. package/dist/card-compute/index.d.ts +5 -1
  27. package/dist/card-compute/index.js +4 -4
  28. package/dist/card-compute/index.js.map +1 -1
  29. package/dist/card-compute/jsonata-sync.cjs +37 -1
  30. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -1
  31. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -1
  32. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +27 -14
  33. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +27 -14
  34. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -1
  35. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -1
  36. package/dist/cli/browser-api/card-store-browser-api.cjs +1 -1
  37. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -1
  38. package/dist/cli/browser-api/card-store-browser-api.js +1 -1
  39. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -1
  40. package/dist/cli/browser-api/jsonata-sync.cjs +37 -1
  41. package/dist/cli/node/artifacts-store-cli.cjs +8 -8
  42. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -1
  43. package/dist/cli/node/artifacts-store-cli.js +8 -8
  44. package/dist/cli/node/artifacts-store-cli.js.map +1 -1
  45. package/dist/cli/node/board-live-cards-cli.cjs +7 -7
  46. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -1
  47. package/dist/cli/node/board-live-cards-cli.js +7 -7
  48. package/dist/cli/node/board-live-cards-cli.js.map +1 -1
  49. package/dist/cli/node/card-store-cli.cjs +5 -5
  50. package/dist/cli/node/card-store-cli.cjs.map +1 -1
  51. package/dist/cli/node/card-store-cli.js +5 -5
  52. package/dist/cli/node/card-store-cli.js.map +1 -1
  53. package/dist/cli/node/execution-adapter.cjs +3 -0
  54. package/dist/cli/node/execution-adapter.cjs.map +1 -0
  55. package/dist/cli/node/execution-adapter.d.cts +174 -0
  56. package/dist/cli/node/execution-adapter.d.ts +174 -0
  57. package/dist/cli/node/execution-adapter.js +3 -0
  58. package/dist/cli/node/execution-adapter.js.map +1 -0
  59. package/dist/cli/node/fs-board-adapter.cjs +7 -7
  60. package/dist/cli/node/fs-board-adapter.cjs.map +1 -1
  61. package/dist/cli/node/fs-board-adapter.d.cts +2 -2
  62. package/dist/cli/node/fs-board-adapter.d.ts +2 -2
  63. package/dist/cli/node/fs-board-adapter.js +7 -7
  64. package/dist/cli/node/fs-board-adapter.js.map +1 -1
  65. package/dist/cli/node/jsonata-sync.cjs +37 -1
  66. package/dist/cli/node/source-cli-task-executor.cjs +4 -4
  67. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -1
  68. package/dist/cli/node/source-cli-task-executor.js +4 -4
  69. package/dist/cli/node/source-cli-task-executor.js.map +1 -1
  70. package/dist/continuous-event-graph/index.cjs +2 -2
  71. package/dist/continuous-event-graph/index.cjs.map +1 -1
  72. package/dist/continuous-event-graph/index.js +2 -2
  73. package/dist/continuous-event-graph/index.js.map +1 -1
  74. package/dist/continuous-event-graph/jsonata-sync.cjs +37 -1
  75. package/dist/execution-refs.cjs +2 -1
  76. package/dist/execution-refs.cjs.map +1 -1
  77. package/dist/execution-refs.d.cts +55 -12
  78. package/dist/execution-refs.d.ts +55 -12
  79. package/dist/execution-refs.js +2 -1
  80. package/dist/execution-refs.js.map +1 -1
  81. package/dist/index.cjs +10 -10
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.js +10 -10
  84. package/dist/index.js.map +1 -1
  85. package/dist/jsonata-sync.cjs +37 -1
  86. package/dist/server-runtime/index.cjs +9 -0
  87. package/dist/server-runtime/index.cjs.map +1 -0
  88. package/dist/server-runtime/index.d.cts +31 -0
  89. package/dist/server-runtime/index.d.ts +31 -0
  90. package/dist/server-runtime/index.js +9 -0
  91. package/dist/server-runtime/index.js.map +1 -0
  92. package/dist/server-runtime/jsonata-sync.cjs +7623 -0
  93. package/dist/step-machine-public/index.cjs +3 -0
  94. package/dist/step-machine-public/index.cjs.map +1 -0
  95. package/dist/step-machine-public/index.d.cts +166 -0
  96. package/dist/step-machine-public/index.d.ts +166 -0
  97. package/dist/step-machine-public/index.js +3 -0
  98. package/dist/step-machine-public/index.js.map +1 -0
  99. package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
  100. package/dist/storage-refs.cjs +2 -2
  101. package/dist/storage-refs.cjs.map +1 -1
  102. package/dist/storage-refs.d.cts +6 -6
  103. package/dist/storage-refs.d.ts +6 -6
  104. package/dist/storage-refs.js +2 -2
  105. package/dist/storage-refs.js.map +1 -1
  106. package/dist/types-CU3DjTKL.d.cts +147 -0
  107. package/dist/types-HGDTWIun.d.ts +147 -0
  108. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +9 -10
  109. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +370 -0
  110. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.py +398 -0
  111. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +9 -10
  112. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
  113. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
  114. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
  115. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +11 -10
  116. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +19 -4
  117. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +4 -8
  118. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -10
  119. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +8 -16
  120. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
  121. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
  122. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
  123. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +4 -8
  124. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +7 -16
  125. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
  126. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +13 -3
  127. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +2 -1
  128. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +2 -1
  129. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +2 -1
  130. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +20 -24
  131. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +0 -3
  132. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
  133. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
  134. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
  135. package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
  136. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
  137. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
  138. package/examples/example-board/agent-instructions.md +1 -1
  139. package/examples/example-board/cards/card-my-identity.json +30 -6
  140. package/examples/example-board/cards/card-portfolio-action.json +24 -6
  141. package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
  142. package/examples/example-board/cards/card-portfolio-risks.json +24 -6
  143. package/examples/example-board/cards/card-rebalance-impact.json +22 -6
  144. package/examples/example-board/cards/card-rebalance-sim.json +66 -15
  145. package/examples/example-board/cards/cardT-market-prices.json +80 -0
  146. package/examples/example-board/cards/{card-portfolio-value.json → cardT-portfolio-value.json} +38 -10
  147. package/examples/example-board/cards/cardT-portfolio.json +78 -0
  148. package/examples/example-board/demo-server-config.json +1 -1
  149. package/examples/example-board/demo-server.js +383 -69
  150. package/examples/example-board/demo-shell-localstorage.html +774 -0
  151. package/examples/example-board/demo-shell-with-server.html +18 -36
  152. package/examples/example-board/demo-shell.html +5 -4
  153. package/examples/example-board/demo-task-executor.js +213 -265
  154. package/package.json +15 -13
  155. package/step-machine-cli.js +43 -310
  156. package/board-livecards-server-runtime.js +0 -1513
  157. package/browser/board-livecards-runtime-client.js +0 -263
  158. package/dist/pycli/quickjs-board-runtime.global.js +0 -9
  159. package/dist/pycli/quickjs-board-runtime.global.js.map +0 -1
  160. package/dist/pycli/quickjs-step-machine-runtime.global.js +0 -5
  161. package/dist/pycli/quickjs-step-machine-runtime.global.js.map +0 -1
  162. package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
  163. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
  164. package/examples/example-board/cards/card-market-prices.json +0 -56
  165. package/examples/example-board/cards/card-portfolio.json +0 -44
  166. package/examples/example-board/demo-shell-browser.html +0 -675
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * portfolio-tracker-server.js
4
+ *
5
+ * Minimal single-board HTTP server for the portfolio-tracker example.
6
+ * Uses createSingleBoardServerRuntime from yaml-flow/server-runtime.
7
+ *
8
+ * Cards are seeded inline on first start (if the card store is empty).
9
+ * Task executor: portfolio-tracker-fetch-prices.js (mock-quotes source kind).
10
+ *
11
+ * Usage:
12
+ * node portfolio-tracker-server.js [--port 7800] [--reset]
13
+ *
14
+ * Endpoints (all under /api/board):
15
+ * GET /api/board/init-board
16
+ * GET /api/board/sse
17
+ * GET /api/board/board-status
18
+ * PATCH /api/board/cards/:id
19
+ * POST /api/board/cards/:id/actions
20
+ */
21
+
22
+ import http from 'node:http';
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import os from 'node:os';
26
+ import net from 'node:net';
27
+ import { spawn } from 'node:child_process';
28
+ import { fileURLToPath } from 'node:url';
29
+
30
+ import { createSingleBoardServerRuntime } from 'yaml-flow/server-runtime';
31
+ import {
32
+ createFsBoardPlatformAdapter,
33
+ parseRef,
34
+ serializeRef,
35
+ } from 'yaml-flow/board-live-cards-node';
36
+
37
+ const __filename = fileURLToPath(import.meta.url);
38
+ const __dirname = path.dirname(__filename);
39
+ const args = process.argv.slice(2);
40
+ const portIdx = args.indexOf('--port');
41
+ const PORT = portIdx >= 0 ? Number(args[portIdx + 1]) : 7800;
42
+ const RESET = args.includes('--reset');
43
+
44
+ // ── Paths ──────────────────────────────────────────────────────────────────────
45
+ const SETUP_DIR = path.join(os.tmpdir(), 'portfolio-tracker-server');
46
+ const RUNTIME_DIR = path.join(SETUP_DIR, 'runtime');
47
+ const CARDS_DIR = path.join(SETUP_DIR, 'cards');
48
+ const OUTPUTS_DIR = path.join(SETUP_DIR, 'outputs');
49
+ const FETCH_PRICES_JS = path.join(__dirname, 'portfolio-tracker-fetch-prices.js');
50
+
51
+ if (RESET && fs.existsSync(SETUP_DIR)) {
52
+ fs.rmSync(SETUP_DIR, { recursive: true, force: true });
53
+ console.log(`[portfolio-tracker-server] reset: wiped ${SETUP_DIR}`);
54
+ }
55
+ for (const d of [RUNTIME_DIR, CARDS_DIR, OUTPUTS_DIR]) {
56
+ fs.mkdirSync(d, { recursive: true });
57
+ }
58
+
59
+ // ── Card definitions ───────────────────────────────────────────────────────────
60
+ const INITIAL_HOLDINGS = [
61
+ { symbol: 'AAPL', qty: 50 },
62
+ { symbol: 'MSFT', qty: 30 },
63
+ ];
64
+
65
+ const INLINE_CARDS = [
66
+ {
67
+ id: 'portfolio-form',
68
+ meta: { title: 'Portfolio Holdings Form' },
69
+ provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
70
+ card_data: { holdings: INITIAL_HOLDINGS },
71
+ view: {
72
+ elements: [
73
+ { kind: 'table', label: 'Holdings',
74
+ data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } },
75
+ ],
76
+ },
77
+ },
78
+ {
79
+ id: 'price-fetch',
80
+ meta: { title: 'Fetch Market Prices' },
81
+ requires: ['holdings'],
82
+ provides: [{ bindTo: 'prices', ref: 'computed_values.prices' }],
83
+ card_data: {},
84
+ compute: [
85
+ {
86
+ bindTo: 'prices',
87
+ expr: '$merge($map(requires.holdings, function($h){ { $h.symbol: 100 } }))',
88
+ },
89
+ ],
90
+ view: {
91
+ elements: [
92
+ { kind: 'table', label: 'Market Prices',
93
+ data: { bind: 'computed_values.prices' } },
94
+ ],
95
+ },
96
+ },
97
+ {
98
+ id: 'holdings-table',
99
+ meta: { title: 'Holdings Table' },
100
+ requires: ['holdings', 'prices'],
101
+ provides: [{ bindTo: 'table', ref: 'computed_values.table' }],
102
+ card_data: {},
103
+ compute: [{
104
+ bindTo: 'table',
105
+ expr: '{ "rows": $map(requires.holdings, function($h) { { "symbol": $h.symbol, "qty": $h.qty, "price": $lookup(requires.prices, $h.symbol), "value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }',
106
+ }],
107
+ view: {
108
+ elements: [
109
+ { kind: 'table', label: 'Portfolio Positions',
110
+ data: { bind: 'computed_values.table.rows', columns: ['symbol', 'qty', 'price', 'value'] } },
111
+ ],
112
+ },
113
+ },
114
+ {
115
+ id: 'portfolio-value',
116
+ meta: { title: 'Portfolio Total Value' },
117
+ requires: ['table'],
118
+ provides: [{ bindTo: 'totalValue', ref: 'computed_values.totalValue' }],
119
+ card_data: {},
120
+ compute: [
121
+ { bindTo: 'totalValue', expr: '$sum(requires.table.rows.value)' },
122
+ ],
123
+ view: {
124
+ elements: [
125
+ { kind: 'metric', label: 'Total Portfolio Value',
126
+ data: { bind: 'computed_values.totalValue' } },
127
+ ],
128
+ },
129
+ },
130
+ ];
131
+
132
+ // ── Host adapters ──────────────────────────────────────────────────────────────
133
+ const NOTIFY_CHANNEL = `yaml-flow-pt-server-${process.pid}`;
134
+
135
+ function namedPipePath(name) {
136
+ if (process.platform === 'win32') return `\\\\.\\pipe\\${name}`;
137
+ return path.join(os.tmpdir(), `${name}.sock`);
138
+ }
139
+
140
+ function createNodeSpawnInvocationAdapter() {
141
+ return {
142
+ async invoke(ref, invokeArgs) {
143
+ if (ref.howToRun !== 'local-node') {
144
+ return { dispatched: false, error: `unsupported howToRun: ${ref.howToRun}` };
145
+ }
146
+ const whatToRun = String(ref.whatToRun || '');
147
+ let scriptPath = '';
148
+ if (whatToRun.startsWith('b64:')) {
149
+ try {
150
+ const parsed = parseRef(whatToRun);
151
+ if (parsed.kind === 'fs-path') scriptPath = parsed.value;
152
+ } catch {
153
+ scriptPath = '';
154
+ }
155
+ } else {
156
+ scriptPath = whatToRun;
157
+ }
158
+ if (!scriptPath) return { dispatched: false, error: 'no script path' };
159
+ const extra = Buffer.from(JSON.stringify(invokeArgs)).toString('base64');
160
+ try {
161
+ const proc = spawn(process.execPath, [
162
+ scriptPath,
163
+ '--boardId', String(invokeArgs.boardId || ''),
164
+ '--cardId', String(invokeArgs.cardId || ''),
165
+ '--extraEncJson', extra,
166
+ ], { stdio: 'ignore', windowsHide: true });
167
+ proc.unref();
168
+ return { dispatched: true };
169
+ } catch (err) {
170
+ return { dispatched: false, error: err?.message || String(err) };
171
+ }
172
+ },
173
+ };
174
+ }
175
+
176
+ function createNamedPipeNotificationTransport() {
177
+ return {
178
+ async subscribe(ref, onEvent) {
179
+ if (ref.kind !== 'named-pipe') return () => {};
180
+ const pipePath = ref.value;
181
+ if (process.platform !== 'win32' && fs.existsSync(pipePath)) {
182
+ try { fs.rmSync(pipePath, { force: true }); } catch { /* best-effort */ }
183
+ }
184
+ const server = net.createServer((socket) => {
185
+ let buf = '';
186
+ socket.on('data', (chunk) => {
187
+ buf += chunk.toString('utf-8');
188
+ while (true) {
189
+ const i = buf.indexOf('\n');
190
+ if (i < 0) break;
191
+ const line = buf.slice(0, i).trim();
192
+ buf = buf.slice(i + 1);
193
+ if (!line) continue;
194
+ try {
195
+ const msg = JSON.parse(line);
196
+ onEvent(msg?.notification ?? msg);
197
+ } catch { /* ignore malformed lines */ }
198
+ }
199
+ });
200
+ });
201
+ await new Promise((resolve, reject) => {
202
+ server.once('error', reject);
203
+ server.listen(pipePath, () => resolve());
204
+ });
205
+ return () => {
206
+ server.close();
207
+ if (process.platform !== 'win32') {
208
+ try { fs.rmSync(pipePath, { force: true }); } catch { /* best-effort */ }
209
+ }
210
+ };
211
+ },
212
+ };
213
+ }
214
+
215
+ // ── Board adapter ──────────────────────────────────────────────────────────────
216
+ // cliDir must point to the yaml-flow root so buildBoardCliInvocation finds
217
+ // board-live-cards-cli.js there (used to build selfRef for task executor callbacks).
218
+ // __dirname here is examples/browser/boards/portfolio-tracker — 4 levels up = yaml-flow root.
219
+ const YAML_FLOW_CLI_DIR = path.resolve(__dirname, '..', '..', '..', '..');
220
+ const baseRef = parseRef(serializeRef({ kind: 'fs-path', value: RUNTIME_DIR }));
221
+ const boardAdapter = createFsBoardPlatformAdapter(baseRef, YAML_FLOW_CLI_DIR, { notifyChannel: NOTIFY_CHANNEL });
222
+ // In the server context the drain loop is driven in-process.
223
+ boardAdapter.requestProcessAccumulated = () => {};
224
+
225
+ const cardStoreRef = serializeRef({ kind: 'fs-path', value: path.join(CARDS_DIR, 'cards') });
226
+ const outputsStoreRef = serializeRef({ kind: 'fs-path', value: path.join(OUTPUTS_DIR, '.outputs') });
227
+ const notifyRef = { kind: 'named-pipe', value: namedPipePath(NOTIFY_CHANNEL) };
228
+ const taskExecutorRef = {
229
+ howToRun: 'local-node',
230
+ whatToRun: serializeRef({ kind: 'fs-path', value: FETCH_PRICES_JS }),
231
+ meta: 'task-executor',
232
+ };
233
+
234
+ // ── Runtime ────────────────────────────────────────────────────────────────────
235
+ const runtime = createSingleBoardServerRuntime({
236
+ apiBasePath: '/api/board',
237
+ boardId: 'portfolio-tracker',
238
+ boards: [{
239
+ label: 'portfolio-tracker',
240
+ boardAdapter,
241
+ baseRef,
242
+ cardStoreRef,
243
+ outputsStoreRef,
244
+ notifyRef,
245
+ taskExecutorRef,
246
+ }],
247
+ invocationAdapter: createNodeSpawnInvocationAdapter(),
248
+ notificationTransport: createNamedPipeNotificationTransport(),
249
+ logger: { info: console.log, warn: console.warn, error: console.error },
250
+ serverUrl: `http://127.0.0.1:${PORT}`,
251
+ });
252
+
253
+ // ── Card store seeding ─────────────────────────────────────────────────────────
254
+ const existing = runtime.cardStore.get({});
255
+ const isEmpty = existing.status !== 'success' || !existing.data?.cards?.length;
256
+ if (isEmpty) {
257
+ runtime.cardStore.set({ body: INLINE_CARDS });
258
+ console.log(`[portfolio-tracker-server] seeded ${INLINE_CARDS.length} cards into card store`);
259
+ } else {
260
+ console.log(`[portfolio-tracker-server] card store already populated (${existing.data.cards.length} cards)`);
261
+ }
262
+
263
+ // ── HTTP server ────────────────────────────────────────────────────────────────
264
+ const CORS_HEADERS = {
265
+ 'Access-Control-Allow-Origin': '*',
266
+ 'Access-Control-Allow-Headers': 'content-type,x-file-name',
267
+ 'Access-Control-Allow-Methods': 'GET,POST,PATCH,OPTIONS',
268
+ };
269
+
270
+ const server = http.createServer((req, res) => {
271
+ const method = req.method || 'GET';
272
+ const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
273
+
274
+ if (method === 'OPTIONS') {
275
+ res.writeHead(204, CORS_HEADERS);
276
+ res.end();
277
+ return;
278
+ }
279
+
280
+ runtime.handleRuntimeApi(req, res, url).then((handled) => {
281
+ if (!handled) {
282
+ res.writeHead(404, { 'Content-Type': 'application/json' });
283
+ res.end(JSON.stringify({ error: 'Not found' }));
284
+ }
285
+ }).catch((err) => {
286
+ res.writeHead(500, { 'Content-Type': 'application/json' });
287
+ res.end(JSON.stringify({ error: String(err?.message || err) }));
288
+ });
289
+ });
290
+
291
+ server.listen(PORT, '127.0.0.1', () => {
292
+ console.log(`[portfolio-tracker-server] listening on http://127.0.0.1:${PORT}`);
293
+ console.log(`[portfolio-tracker-server] runtime dir: ${RUNTIME_DIR}`);
294
+ console.log(`[portfolio-tracker-server] endpoints:`);
295
+ console.log(` GET /api/board/init-board`);
296
+ console.log(` GET /api/board/sse`);
297
+ console.log(` GET /api/board/board-status`);
298
+ console.log(` PATCH /api/board/cards/:id`);
299
+ console.log(` POST /api/board/cards/:id/actions`);
300
+ });