yaml-flow 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/board-livegraph-runtime.js +1453 -0
- package/browser/board-livegraph-runtime.js.map +1 -0
- package/browser/card-compute.js +36 -17
- package/browser/live-cards.js +848 -109
- package/browser/live-cards.schema.json +46 -21
- package/dist/board-livegraph-runtime/index.cjs +1448 -0
- package/dist/board-livegraph-runtime/index.cjs.map +1 -0
- package/dist/board-livegraph-runtime/index.d.cts +101 -0
- package/dist/board-livegraph-runtime/index.d.ts +101 -0
- package/dist/board-livegraph-runtime/index.js +1441 -0
- package/dist/board-livegraph-runtime/index.js.map +1 -0
- package/dist/card-compute/index.cjs +159 -44
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +36 -11
- package/dist/card-compute/index.d.ts +36 -11
- package/dist/card-compute/index.js +156 -44
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +476 -105
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +8 -16
- package/dist/cli/board-live-cards-cli.d.ts +8 -16
- package/dist/cli/board-live-cards-cli.js +476 -106
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +74 -33
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +7 -23
- package/dist/continuous-event-graph/index.d.ts +7 -23
- package/dist/continuous-event-graph/index.js +73 -32
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +1440 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +1434 -56
- package/dist/index.js.map +1 -1
- package/dist/journal-DRfJiheM.d.cts +28 -0
- package/dist/journal-NLYuqege.d.ts +28 -0
- package/dist/{journal-B_2JnBMF.d.ts → live-cards-bridge-Or7fdEJV.d.ts} +5 -32
- package/dist/{journal-BJDjWb5Q.d.cts → live-cards-bridge-vGJ6tMzN.d.cts} +5 -32
- package/dist/schedule-CMcZe5Ny.d.ts +21 -0
- package/dist/schedule-CiucyCan.d.cts +21 -0
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +33 -5
- package/examples/browser/livecards-browser/index.html +37 -684
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +2 -2
- package/examples/example-board/board.yaml +23 -0
- package/examples/example-board/bootstrap_payload.json +1 -0
- package/examples/example-board/cards/card-chain-region-alert.json +39 -0
- package/examples/example-board/cards/card-chain-region-totals.json +26 -0
- package/examples/example-board/cards/card-chain-top-region.json +24 -0
- package/examples/example-board/cards/card-ex-actions.json +32 -0
- package/examples/example-board/cards/card-ex-chart.json +30 -0
- package/examples/example-board/cards/card-ex-filter.json +36 -0
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +59 -0
- package/examples/example-board/cards/card-ex-form.json +91 -0
- package/examples/example-board/cards/card-ex-list.json +22 -0
- package/examples/example-board/cards/card-ex-markdown.json +17 -0
- package/examples/example-board/cards/card-ex-metric.json +19 -0
- package/examples/example-board/cards/card-ex-narrative.json +36 -0
- package/examples/example-board/cards/card-ex-source-http.json +28 -0
- package/examples/example-board/cards/card-ex-source.json +21 -0
- package/examples/example-board/cards/card-ex-status.json +35 -0
- package/examples/example-board/cards/card-ex-table.json +30 -0
- package/examples/example-board/cards/card-ex-todo.json +29 -0
- package/examples/example-board/demo-chat-handler.js +69 -0
- package/examples/example-board/demo-server.js +87 -0
- package/examples/example-board/demo-shell-browser.html +806 -0
- package/examples/example-board/demo-shell-with-server.html +280 -0
- package/examples/example-board/demo-shell.html +62 -0
- package/examples/example-board/demo-task-executor.js +255 -0
- package/examples/example-board/mock.db +15 -0
- package/examples/example-board/reusable-board-runtime-client.js +265 -0
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +233 -0
- package/examples/example-board/reusable-server-runtime.js +1284 -0
- package/examples/index.html +16 -9
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +17 -17
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +23 -23
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/package.json +16 -2
- package/schema/card-runtime.schema.json +25 -0
- package/schema/live-cards.schema.json +46 -21
- package/browser/ingest-board.js +0 -296
- package/examples/ingest.js +0 -733
|
@@ -8,7 +8,7 @@ var child_process = require('child_process');
|
|
|
8
8
|
var url = require('url');
|
|
9
9
|
var fg = require('fast-glob');
|
|
10
10
|
var properLockfile = require('proper-lockfile');
|
|
11
|
-
var
|
|
11
|
+
var jsonata2 = require('jsonata');
|
|
12
12
|
require('ajv-formats');
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -36,7 +36,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
|
36
36
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
37
37
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
38
38
|
var fg__default = /*#__PURE__*/_interopDefault(fg);
|
|
39
|
-
var
|
|
39
|
+
var jsonata2__default = /*#__PURE__*/_interopDefault(jsonata2);
|
|
40
40
|
|
|
41
41
|
// src/cli/board-live-cards-cli.ts
|
|
42
42
|
|
|
@@ -667,9 +667,11 @@ var MemoryJournal = class {
|
|
|
667
667
|
return this.buffer.length;
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
|
+
|
|
671
|
+
// src/continuous-event-graph/reactive.ts
|
|
670
672
|
function computeDataHash(data) {
|
|
671
673
|
const json = stableStringify(data);
|
|
672
|
-
return
|
|
674
|
+
return fnv1a64Hex(json);
|
|
673
675
|
}
|
|
674
676
|
function stableStringify(value) {
|
|
675
677
|
if (value === null || value === void 0 || typeof value !== "object") {
|
|
@@ -682,13 +684,49 @@ function stableStringify(value) {
|
|
|
682
684
|
const keys = Object.keys(obj).sort();
|
|
683
685
|
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
684
686
|
}
|
|
687
|
+
function fnv1a64Hex(input) {
|
|
688
|
+
let hash = 0xcbf29ce484222325n;
|
|
689
|
+
const prime = 0x100000001b3n;
|
|
690
|
+
const mod = 0xffffffffffffffffn;
|
|
691
|
+
for (let i = 0; i < input.length; i++) {
|
|
692
|
+
hash ^= BigInt(input.charCodeAt(i));
|
|
693
|
+
hash = hash * prime & mod;
|
|
694
|
+
}
|
|
695
|
+
return hash.toString(16).padStart(16, "0");
|
|
696
|
+
}
|
|
697
|
+
function base64UrlEncode(input) {
|
|
698
|
+
if (typeof Buffer !== "undefined") {
|
|
699
|
+
return Buffer.from(input, "utf8").toString("base64url");
|
|
700
|
+
}
|
|
701
|
+
if (typeof btoa === "function") {
|
|
702
|
+
const bytes = new TextEncoder().encode(input);
|
|
703
|
+
let binary = "";
|
|
704
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
705
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
706
|
+
}
|
|
707
|
+
throw new Error("No base64 encoder available in this runtime");
|
|
708
|
+
}
|
|
709
|
+
function base64UrlDecode(input) {
|
|
710
|
+
if (typeof Buffer !== "undefined") {
|
|
711
|
+
return Buffer.from(input, "base64url").toString("utf8");
|
|
712
|
+
}
|
|
713
|
+
if (typeof atob === "function") {
|
|
714
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
715
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
716
|
+
const binary = atob(padded);
|
|
717
|
+
const bytes = new Uint8Array(binary.length);
|
|
718
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
719
|
+
return new TextDecoder().decode(bytes);
|
|
720
|
+
}
|
|
721
|
+
throw new Error("No base64 decoder available in this runtime");
|
|
722
|
+
}
|
|
685
723
|
function encodeCallbackToken(taskName) {
|
|
686
724
|
const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
|
|
687
|
-
return
|
|
725
|
+
return base64UrlEncode(payload);
|
|
688
726
|
}
|
|
689
727
|
function decodeCallbackToken(token) {
|
|
690
728
|
try {
|
|
691
|
-
const payload = JSON.parse(
|
|
729
|
+
const payload = JSON.parse(base64UrlDecode(token));
|
|
692
730
|
if (typeof payload?.t === "string") return { taskName: payload.t };
|
|
693
731
|
return null;
|
|
694
732
|
} catch {
|
|
@@ -965,18 +1003,18 @@ function deepSet(obj, path2, value) {
|
|
|
965
1003
|
}
|
|
966
1004
|
async function run(node, options) {
|
|
967
1005
|
if (!node?.compute?.length) return node;
|
|
968
|
-
if (!node.
|
|
1006
|
+
if (!node.card_data) node.card_data = {};
|
|
969
1007
|
node.computed_values = {};
|
|
970
1008
|
node._sourcesData = options?.sourcesData ?? {};
|
|
971
1009
|
const ctx = {
|
|
972
|
-
|
|
1010
|
+
card_data: node.card_data,
|
|
973
1011
|
requires: node.requires ?? {},
|
|
974
|
-
|
|
1012
|
+
fetched_sources: node._sourcesData,
|
|
975
1013
|
computed_values: node.computed_values
|
|
976
1014
|
};
|
|
977
1015
|
for (const step of node.compute) {
|
|
978
1016
|
try {
|
|
979
|
-
const val = await
|
|
1017
|
+
const val = await jsonata2__default.default(step.expr).evaluate(ctx);
|
|
980
1018
|
deepSet(node.computed_values, step.bindTo, val);
|
|
981
1019
|
ctx.computed_values = node.computed_values;
|
|
982
1020
|
} catch (err) {
|
|
@@ -987,16 +1025,16 @@ async function run(node, options) {
|
|
|
987
1025
|
}
|
|
988
1026
|
async function evalExpr(expr, node) {
|
|
989
1027
|
const ctx = {
|
|
990
|
-
|
|
1028
|
+
card_data: node.card_data ?? {},
|
|
991
1029
|
requires: node.requires ?? {},
|
|
992
|
-
|
|
1030
|
+
fetched_sources: node._sourcesData ?? {},
|
|
993
1031
|
computed_values: node.computed_values ?? {}
|
|
994
1032
|
};
|
|
995
|
-
return
|
|
1033
|
+
return jsonata2__default.default(expr).evaluate(ctx);
|
|
996
1034
|
}
|
|
997
1035
|
function resolve(node, path2) {
|
|
998
|
-
if (path2.startsWith("
|
|
999
|
-
return deepGet(node._sourcesData ?? {}, path2.slice("
|
|
1036
|
+
if (path2.startsWith("fetched_sources.")) {
|
|
1037
|
+
return deepGet(node._sourcesData ?? {}, path2.slice("fetched_sources.".length));
|
|
1000
1038
|
}
|
|
1001
1039
|
return deepGet(node, path2);
|
|
1002
1040
|
}
|
|
@@ -1016,8 +1054,7 @@ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
|
1016
1054
|
"markdown",
|
|
1017
1055
|
"custom"
|
|
1018
1056
|
]);
|
|
1019
|
-
var
|
|
1020
|
-
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "state", "compute", "sources"]);
|
|
1057
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "sources"]);
|
|
1021
1058
|
function validateNode(node) {
|
|
1022
1059
|
const errors = [];
|
|
1023
1060
|
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
@@ -1028,13 +1065,8 @@ function validateNode(node) {
|
|
|
1028
1065
|
for (const key of Object.keys(n)) {
|
|
1029
1066
|
if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
|
|
1030
1067
|
}
|
|
1031
|
-
if (n.
|
|
1032
|
-
errors.push("
|
|
1033
|
-
} else {
|
|
1034
|
-
const state = n.state;
|
|
1035
|
-
if (state.status != null && !VALID_STATUSES.has(state.status)) {
|
|
1036
|
-
errors.push(`state.status: must be one of: ${[...VALID_STATUSES].join(", ")}`);
|
|
1037
|
-
}
|
|
1068
|
+
if (n.card_data == null || typeof n.card_data !== "object" || Array.isArray(n.card_data)) {
|
|
1069
|
+
errors.push("card_data: required, must be an object");
|
|
1038
1070
|
}
|
|
1039
1071
|
if (n.meta != null) {
|
|
1040
1072
|
if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
|
|
@@ -1123,17 +1155,33 @@ function validateNode(node) {
|
|
|
1123
1155
|
}
|
|
1124
1156
|
return { ok: errors.length === 0, errors };
|
|
1125
1157
|
}
|
|
1158
|
+
function enrichSources(sources, context) {
|
|
1159
|
+
if (!sources || sources.length === 0) return [];
|
|
1160
|
+
return sources.map((src) => ({
|
|
1161
|
+
...src,
|
|
1162
|
+
_requires: context.requires ?? {},
|
|
1163
|
+
_sourcesData: context.sourcesData ?? {},
|
|
1164
|
+
_computed_values: context.computed_values ?? {}
|
|
1165
|
+
}));
|
|
1166
|
+
}
|
|
1126
1167
|
var CardCompute = {
|
|
1127
1168
|
run,
|
|
1128
1169
|
eval: evalExpr,
|
|
1129
1170
|
resolve,
|
|
1130
|
-
validate: validateNode
|
|
1171
|
+
validate: validateNode,
|
|
1172
|
+
enrichSources
|
|
1131
1173
|
};
|
|
1132
1174
|
|
|
1133
1175
|
// src/cli/board-live-cards-cli.ts
|
|
1134
1176
|
var BOARD_FILE = "board-graph.json";
|
|
1135
1177
|
var JOURNAL_FILE = "board-journal.jsonl";
|
|
1178
|
+
var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
|
|
1136
1179
|
var INVENTORY_FILE = "cards-inventory.jsonl";
|
|
1180
|
+
var RUNTIME_OUT_FILE = ".runtime-out";
|
|
1181
|
+
var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
|
|
1182
|
+
var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
|
|
1183
|
+
var RUNTIME_CARDS_DIR = "cards";
|
|
1184
|
+
var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
|
|
1137
1185
|
var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
|
|
1138
1186
|
var BoardJournal = class {
|
|
1139
1187
|
journalPath;
|
|
@@ -1188,7 +1236,34 @@ function lookupCardPath(boardDir, cardId) {
|
|
|
1188
1236
|
}
|
|
1189
1237
|
function appendCardInventory(boardDir, entry) {
|
|
1190
1238
|
const inventoryPath = path__namespace.join(boardDir, INVENTORY_FILE);
|
|
1191
|
-
|
|
1239
|
+
const normalized = { ...entry, cardFilePath: path__namespace.resolve(entry.cardFilePath) };
|
|
1240
|
+
fs__namespace.appendFileSync(inventoryPath, JSON.stringify(normalized) + "\n");
|
|
1241
|
+
}
|
|
1242
|
+
function buildCardInventoryIndex(boardDir) {
|
|
1243
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
1244
|
+
const byCardPath = /* @__PURE__ */ new Map();
|
|
1245
|
+
for (const entry of readCardInventory(boardDir)) {
|
|
1246
|
+
const normalizedPath = path__namespace.resolve(entry.cardFilePath);
|
|
1247
|
+
const normalizedEntry = {
|
|
1248
|
+
...entry,
|
|
1249
|
+
cardFilePath: normalizedPath
|
|
1250
|
+
};
|
|
1251
|
+
const existingById = byCardId.get(entry.cardId);
|
|
1252
|
+
if (existingById && existingById.cardFilePath !== normalizedPath) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`Inventory invariant violation: card id "${entry.cardId}" maps to multiple files: "${existingById.cardFilePath}" and "${normalizedPath}"`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
const existingByPath = byCardPath.get(normalizedPath);
|
|
1258
|
+
if (existingByPath && existingByPath.cardId !== entry.cardId) {
|
|
1259
|
+
throw new Error(
|
|
1260
|
+
`Inventory invariant violation: file "${normalizedPath}" maps to multiple ids: "${existingByPath.cardId}" and "${entry.cardId}"`
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
byCardId.set(entry.cardId, normalizedEntry);
|
|
1264
|
+
byCardPath.set(normalizedPath, normalizedEntry);
|
|
1265
|
+
}
|
|
1266
|
+
return { byCardId, byCardPath };
|
|
1192
1267
|
}
|
|
1193
1268
|
function initBoard(dir) {
|
|
1194
1269
|
const boardPath = path__namespace.join(dir, BOARD_FILE);
|
|
@@ -1224,7 +1299,63 @@ function saveBoard(dir, rg, journal) {
|
|
|
1224
1299
|
lastDrainedJournalId: journal.lastDrainedJournalId,
|
|
1225
1300
|
graph: snap
|
|
1226
1301
|
};
|
|
1227
|
-
|
|
1302
|
+
writeJsonAtomic(path__namespace.join(dir, BOARD_FILE), envelope);
|
|
1303
|
+
const live = restore(snap);
|
|
1304
|
+
const statusObject = buildBoardStatusObject(dir, live);
|
|
1305
|
+
writeJsonAtomic(resolveStatusSnapshotPath(dir), statusObject);
|
|
1306
|
+
}
|
|
1307
|
+
function runtimeOutConfigPath(boardDir) {
|
|
1308
|
+
return path__namespace.join(boardDir, RUNTIME_OUT_FILE);
|
|
1309
|
+
}
|
|
1310
|
+
function resolveConfiguredRuntimeOutDir(boardDir) {
|
|
1311
|
+
const cfgPath = runtimeOutConfigPath(boardDir);
|
|
1312
|
+
if (fs__namespace.existsSync(cfgPath)) {
|
|
1313
|
+
const configured = fs__namespace.readFileSync(cfgPath, "utf-8").trim();
|
|
1314
|
+
if (configured) {
|
|
1315
|
+
return path__namespace.isAbsolute(configured) ? configured : path__namespace.resolve(boardDir, configured);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
const defaultDir = path__namespace.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
|
|
1319
|
+
fs__namespace.writeFileSync(cfgPath, defaultDir, "utf-8");
|
|
1320
|
+
return defaultDir;
|
|
1321
|
+
}
|
|
1322
|
+
function configureRuntimeOutDir(boardDir, runtimeOut) {
|
|
1323
|
+
let resolved;
|
|
1324
|
+
if (runtimeOut) {
|
|
1325
|
+
resolved = path__namespace.isAbsolute(runtimeOut) ? runtimeOut : path__namespace.resolve(boardDir, runtimeOut);
|
|
1326
|
+
} else {
|
|
1327
|
+
resolved = path__namespace.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
|
|
1328
|
+
}
|
|
1329
|
+
fs__namespace.mkdirSync(resolved, { recursive: true });
|
|
1330
|
+
fs__namespace.writeFileSync(runtimeOutConfigPath(boardDir), resolved, "utf-8");
|
|
1331
|
+
return resolved;
|
|
1332
|
+
}
|
|
1333
|
+
function resolveStatusSnapshotPath(boardDir) {
|
|
1334
|
+
return path__namespace.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_STATUS_FILE);
|
|
1335
|
+
}
|
|
1336
|
+
function resolveComputedValuesPath(boardDir, cardId) {
|
|
1337
|
+
return path__namespace.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_CARDS_DIR, `${cardId}.computed.json`);
|
|
1338
|
+
}
|
|
1339
|
+
function resolveDataObjectsDirPath(boardDir) {
|
|
1340
|
+
return path__namespace.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_DATA_OBJECTS_DIR);
|
|
1341
|
+
}
|
|
1342
|
+
function toDataObjectFileName(token) {
|
|
1343
|
+
return token.replace(/[\\/]/g, "__");
|
|
1344
|
+
}
|
|
1345
|
+
function writeRuntimeDataObjects(boardDir, data) {
|
|
1346
|
+
for (const [token, payload] of Object.entries(data)) {
|
|
1347
|
+
if (!token) continue;
|
|
1348
|
+
const fileName = toDataObjectFileName(token);
|
|
1349
|
+
if (!fileName) continue;
|
|
1350
|
+
const filePath = path__namespace.join(resolveDataObjectsDirPath(boardDir), fileName);
|
|
1351
|
+
writeJsonAtomic(filePath, payload);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function writeJsonAtomic(filePath, payload) {
|
|
1355
|
+
fs__namespace.mkdirSync(path__namespace.dirname(filePath), { recursive: true });
|
|
1356
|
+
const tmpPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
1357
|
+
fs__namespace.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
1358
|
+
fs__namespace.renameSync(tmpPath, filePath);
|
|
1228
1359
|
}
|
|
1229
1360
|
function withBoardLock(boardDir, fn) {
|
|
1230
1361
|
const boardPath = path__namespace.join(boardDir, BOARD_FILE);
|
|
@@ -1302,15 +1433,63 @@ function shouldUseShellForCommand(cmd, forceShell) {
|
|
|
1302
1433
|
if (typeof forceShell === "boolean") return forceShell;
|
|
1303
1434
|
return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
|
|
1304
1435
|
}
|
|
1436
|
+
var _gitBashPath;
|
|
1437
|
+
var GIT_BASH_CACHE_FILE = path__namespace.join(os__namespace.tmpdir(), ".board-live-cards-git-bash-cache.json");
|
|
1438
|
+
function findGitBash() {
|
|
1439
|
+
if (_gitBashPath !== void 0) return _gitBashPath;
|
|
1440
|
+
if (process.platform !== "win32") return _gitBashPath = false;
|
|
1441
|
+
try {
|
|
1442
|
+
const cached = JSON.parse(fs__namespace.readFileSync(GIT_BASH_CACHE_FILE, "utf8"));
|
|
1443
|
+
if (cached.path === false || typeof cached.path === "string" && fs__namespace.existsSync(cached.path)) {
|
|
1444
|
+
return _gitBashPath = cached.path;
|
|
1445
|
+
}
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
const candidates = [
|
|
1449
|
+
process.env.SHELL,
|
|
1450
|
+
process.env.PROGRAMFILES && path__namespace.join(process.env.PROGRAMFILES, "Git", "usr", "bin", "bash.exe"),
|
|
1451
|
+
process.env.PROGRAMFILES && path__namespace.join(process.env.PROGRAMFILES, "Git", "bin", "bash.exe"),
|
|
1452
|
+
process.env["PROGRAMFILES(X86)"] && path__namespace.join(process.env["PROGRAMFILES(X86)"], "Git", "bin", "bash.exe"),
|
|
1453
|
+
process.env.LOCALAPPDATA && path__namespace.join(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")
|
|
1454
|
+
];
|
|
1455
|
+
for (const c of candidates) {
|
|
1456
|
+
if (c && /bash(\.exe)?$/i.test(c) && fs__namespace.existsSync(c)) {
|
|
1457
|
+
_gitBashPath = c;
|
|
1458
|
+
try {
|
|
1459
|
+
fs__namespace.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: c }));
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
return _gitBashPath;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
_gitBashPath = false;
|
|
1466
|
+
try {
|
|
1467
|
+
fs__namespace.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: false }));
|
|
1468
|
+
} catch {
|
|
1469
|
+
}
|
|
1470
|
+
return _gitBashPath;
|
|
1471
|
+
}
|
|
1472
|
+
function shellQuote(s) {
|
|
1473
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1474
|
+
}
|
|
1305
1475
|
function spawnDetachedCommand(cmd, args) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1476
|
+
if (process.platform === "win32") {
|
|
1477
|
+
const bash = findGitBash();
|
|
1478
|
+
if (bash) {
|
|
1479
|
+
const shellCmd = [cmd, ...args].map((a) => shellQuote(a.replace(/\\/g, "/"))).join(" ");
|
|
1480
|
+
const child3 = child_process.spawn(bash, ["-c", shellCmd], { detached: true, stdio: "ignore", windowsHide: true });
|
|
1481
|
+
child3.unref();
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const child2 = child_process.spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
|
|
1485
|
+
detached: true,
|
|
1486
|
+
stdio: "ignore",
|
|
1487
|
+
windowsHide: true
|
|
1488
|
+
});
|
|
1489
|
+
child2.unref();
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
const child = child_process.spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
1314
1493
|
child.unref();
|
|
1315
1494
|
}
|
|
1316
1495
|
function execCommandSync(cmd, args, options) {
|
|
@@ -1415,7 +1594,7 @@ async function processAccumulatedEventsForced(boardDir, options) {
|
|
|
1415
1594
|
}
|
|
1416
1595
|
function liveCardToTaskConfig(card) {
|
|
1417
1596
|
const requires = card.requires;
|
|
1418
|
-
const provides = card.provides
|
|
1597
|
+
const provides = card.provides?.map((p) => p.bindTo) ?? [];
|
|
1419
1598
|
return {
|
|
1420
1599
|
requires: requires && requires.length > 0 ? requires : void 0,
|
|
1421
1600
|
provides,
|
|
@@ -1440,23 +1619,23 @@ function getCliInvocation(command, args) {
|
|
|
1440
1619
|
}
|
|
1441
1620
|
function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
|
|
1442
1621
|
const { cmd, args } = getCliInvocation("run-sources-internal", ["--card", cardPath, "--token", callbackToken, "--rg", boardDir]);
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1622
|
+
try {
|
|
1623
|
+
spawnDetachedCommand(cmd, args);
|
|
1624
|
+
callback(null);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
function appendTaskExecutorLog(boardDir, hydratedSource) {
|
|
1630
|
+
try {
|
|
1631
|
+
const entry = {
|
|
1632
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1633
|
+
hydratedSource
|
|
1634
|
+
};
|
|
1635
|
+
fs__namespace.appendFileSync(path__namespace.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
|
|
1636
|
+
} catch (logErr) {
|
|
1637
|
+
console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
|
|
1638
|
+
}
|
|
1460
1639
|
}
|
|
1461
1640
|
function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
|
|
1462
1641
|
const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
|
|
@@ -1481,7 +1660,7 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1481
1660
|
if (!cardPath) return "task-initiate-failure";
|
|
1482
1661
|
const card = JSON.parse(fs__namespace.readFileSync(cardPath, "utf-8"));
|
|
1483
1662
|
const cardId = card.id;
|
|
1484
|
-
const cardState = card.
|
|
1663
|
+
const cardState = card.card_data ?? {};
|
|
1485
1664
|
const allSources = card.sources ?? [];
|
|
1486
1665
|
const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
|
|
1487
1666
|
const runtime = readRuntimeState(boardDir, cardId);
|
|
@@ -1517,18 +1696,32 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1517
1696
|
}
|
|
1518
1697
|
}
|
|
1519
1698
|
}
|
|
1699
|
+
const requires = {};
|
|
1700
|
+
for (const [token, taskData] of Object.entries(input.state ?? {})) {
|
|
1701
|
+
if (taskData !== null && typeof taskData === "object" && !Array.isArray(taskData)) {
|
|
1702
|
+
const unwrapped = taskData[token];
|
|
1703
|
+
requires[token] = unwrapped !== void 0 ? unwrapped : taskData;
|
|
1704
|
+
} else {
|
|
1705
|
+
requires[token] = taskData;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1520
1708
|
const computeNode = {
|
|
1521
1709
|
id: cardId,
|
|
1522
|
-
|
|
1523
|
-
requires
|
|
1710
|
+
card_data: { ...cardState },
|
|
1711
|
+
requires,
|
|
1524
1712
|
sources: allSources,
|
|
1525
1713
|
compute: card.compute
|
|
1526
1714
|
};
|
|
1715
|
+
computeNode._sourcesData = sourcesData;
|
|
1527
1716
|
if (card.compute) {
|
|
1528
1717
|
await CardCompute.run(computeNode, { sourcesData });
|
|
1529
|
-
const cvPath = path__namespace.join(boardDir, `${cardId}.computed_values.json`);
|
|
1530
|
-
fs__namespace.writeFileSync(cvPath, JSON.stringify(computeNode.computed_values ?? {}, null, 2));
|
|
1531
1718
|
}
|
|
1719
|
+
const cvPath = resolveComputedValuesPath(boardDir, cardId);
|
|
1720
|
+
writeJsonAtomic(cvPath, {
|
|
1721
|
+
schema_version: "v1",
|
|
1722
|
+
card_id: cardId,
|
|
1723
|
+
computed_values: computeNode.computed_values ?? {}
|
|
1724
|
+
});
|
|
1532
1725
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1533
1726
|
const undeliveredRequired = requiredSources.filter((s) => {
|
|
1534
1727
|
if (!s.outputFile) return false;
|
|
@@ -1548,16 +1741,40 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1548
1741
|
}
|
|
1549
1742
|
}
|
|
1550
1743
|
if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
|
|
1551
|
-
|
|
1552
|
-
|
|
1744
|
+
const enrichedCard = { ...card };
|
|
1745
|
+
const enrichedSources = CardCompute.enrichSources(
|
|
1746
|
+
Array.isArray(card.sources) ? card.sources : void 0,
|
|
1747
|
+
{
|
|
1748
|
+
requires,
|
|
1749
|
+
sourcesData,
|
|
1750
|
+
computed_values: computeNode.computed_values
|
|
1751
|
+
}
|
|
1752
|
+
);
|
|
1753
|
+
const sourceCwd = path__namespace.dirname(cardPath);
|
|
1754
|
+
enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
|
|
1755
|
+
...src,
|
|
1756
|
+
cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
|
|
1757
|
+
boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
|
|
1758
|
+
})) : enrichedSources;
|
|
1759
|
+
const enrichedCardPath = path__namespace.join(os__namespace.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
|
|
1760
|
+
fs__namespace.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
|
|
1761
|
+
invokeRunSources(boardDir, enrichedCardPath, input.callbackToken, (err) => {
|
|
1762
|
+
if (err) {
|
|
1763
|
+
console.error(`[card-handler] ${input.nodeId}:`, err.message);
|
|
1764
|
+
try {
|
|
1765
|
+
fs__namespace.unlinkSync(enrichedCardPath);
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1553
1769
|
});
|
|
1554
1770
|
return "task-initiated";
|
|
1555
1771
|
}
|
|
1556
|
-
const providesBindings = card.provides ?? [
|
|
1772
|
+
const providesBindings = card.provides ?? [];
|
|
1557
1773
|
const data = {};
|
|
1558
1774
|
for (const { bindTo, src } of providesBindings) {
|
|
1559
1775
|
data[bindTo] = CardCompute.resolve(computeNode, src);
|
|
1560
1776
|
}
|
|
1777
|
+
writeRuntimeDataObjects(boardDir, data);
|
|
1561
1778
|
const undeliveredOptional = allSources.filter((s) => {
|
|
1562
1779
|
if (s.optionalForCompletionGating !== true || !s.outputFile) return false;
|
|
1563
1780
|
const entry = runtime._sources[s.bindTo];
|
|
@@ -1585,18 +1802,20 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1585
1802
|
function addSingleCardFromFile(dir, cardFile) {
|
|
1586
1803
|
const absCardPath = path__namespace.resolve(cardFile);
|
|
1587
1804
|
if (!fs__namespace.existsSync(absCardPath)) {
|
|
1588
|
-
|
|
1589
|
-
|
|
1805
|
+
throw new Error(`Card file not found: ${absCardPath}`);
|
|
1806
|
+
}
|
|
1807
|
+
let card;
|
|
1808
|
+
try {
|
|
1809
|
+
card = JSON.parse(fs__namespace.readFileSync(absCardPath, "utf-8"));
|
|
1810
|
+
} catch (err) {
|
|
1811
|
+
throw new Error(`Failed to parse card file: ${absCardPath} - ${err instanceof Error ? err.message : String(err)}`);
|
|
1590
1812
|
}
|
|
1591
|
-
const card = JSON.parse(fs__namespace.readFileSync(absCardPath, "utf-8"));
|
|
1592
1813
|
if (!card.id) {
|
|
1593
|
-
|
|
1594
|
-
process.exit(1);
|
|
1814
|
+
throw new Error('Card JSON must have an "id" field');
|
|
1595
1815
|
}
|
|
1596
1816
|
const existing = readCardInventory(dir);
|
|
1597
1817
|
if (existing.some((e) => e.cardId === card.id)) {
|
|
1598
|
-
|
|
1599
|
-
process.exit(1);
|
|
1818
|
+
throw new Error(`Card "${card.id}" already exists in inventory`);
|
|
1600
1819
|
}
|
|
1601
1820
|
appendCardInventory(dir, {
|
|
1602
1821
|
cardId: card.id,
|
|
@@ -1623,7 +1842,7 @@ function resolveCardGlobMatches(cardGlob) {
|
|
|
1623
1842
|
unique: true,
|
|
1624
1843
|
dot: false
|
|
1625
1844
|
});
|
|
1626
|
-
return [...matches].sort((a, b) => a.localeCompare(b));
|
|
1845
|
+
return [...matches].map((m) => path__namespace.resolve(m)).sort((a, b) => a.localeCompare(b));
|
|
1627
1846
|
}
|
|
1628
1847
|
function cmdAddCards(args) {
|
|
1629
1848
|
const rgIdx = args.indexOf("--rg");
|
|
@@ -1633,16 +1852,14 @@ function cmdAddCards(args) {
|
|
|
1633
1852
|
const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
|
|
1634
1853
|
const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
|
|
1635
1854
|
if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
|
|
1636
|
-
|
|
1637
|
-
process.exit(1);
|
|
1855
|
+
throw new Error("Usage: board-live-cards add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)");
|
|
1638
1856
|
}
|
|
1639
1857
|
if (cardFile) {
|
|
1640
1858
|
addSingleCardFromFile(dir, cardFile);
|
|
1641
1859
|
} else {
|
|
1642
1860
|
const matches = resolveCardGlobMatches(cardGlob);
|
|
1643
1861
|
if (matches.length === 0) {
|
|
1644
|
-
|
|
1645
|
-
process.exit(1);
|
|
1862
|
+
throw new Error(`No card files matched glob: ${cardGlob}`);
|
|
1646
1863
|
}
|
|
1647
1864
|
for (const match of matches) {
|
|
1648
1865
|
addSingleCardFromFile(dir, match);
|
|
@@ -1654,30 +1871,34 @@ function cmdAddCards(args) {
|
|
|
1654
1871
|
function cmdInit(args) {
|
|
1655
1872
|
const dir = args[0];
|
|
1656
1873
|
if (!dir) {
|
|
1657
|
-
|
|
1658
|
-
process.exit(1);
|
|
1874
|
+
throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
|
|
1659
1875
|
}
|
|
1660
1876
|
const teIdx = args.indexOf("--task-executor");
|
|
1661
1877
|
const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
|
|
1878
|
+
const chIdx = args.indexOf("--chat-handler");
|
|
1879
|
+
const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
|
|
1880
|
+
const roIdx = args.indexOf("--runtime-out");
|
|
1881
|
+
const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
|
|
1882
|
+
if (roIdx !== -1 && !runtimeOut) {
|
|
1883
|
+
throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
|
|
1884
|
+
}
|
|
1662
1885
|
const result = initBoard(dir);
|
|
1663
1886
|
if (taskExecutor) {
|
|
1664
1887
|
fs__namespace.writeFileSync(path__namespace.join(dir, ".task-executor"), taskExecutor, "utf-8");
|
|
1665
1888
|
}
|
|
1889
|
+
if (chatHandler) {
|
|
1890
|
+
fs__namespace.writeFileSync(path__namespace.join(dir, ".chat-handler"), chatHandler, "utf-8");
|
|
1891
|
+
}
|
|
1892
|
+
const runtimeOutDir = configureRuntimeOutDir(dir, runtimeOut);
|
|
1893
|
+
const live = loadBoard(dir);
|
|
1894
|
+
writeJsonAtomic(resolveStatusSnapshotPath(dir), buildBoardStatusObject(dir, live));
|
|
1666
1895
|
if (result === "exists") {
|
|
1667
|
-
console.log(`Board already initialized at ${path__namespace.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""}`);
|
|
1896
|
+
console.log(`Board already initialized at ${path__namespace.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
|
|
1668
1897
|
} else {
|
|
1669
|
-
console.log(`Board initialized at ${path__namespace.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""}`);
|
|
1898
|
+
console.log(`Board initialized at ${path__namespace.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
|
|
1670
1899
|
}
|
|
1671
1900
|
}
|
|
1672
|
-
function
|
|
1673
|
-
const rgIdx = args.indexOf("--rg");
|
|
1674
|
-
const asJson = args.includes("--json");
|
|
1675
|
-
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
1676
|
-
if (!dir) {
|
|
1677
|
-
console.error("Usage: board-live-cards status --rg <dir>");
|
|
1678
|
-
process.exit(1);
|
|
1679
|
-
}
|
|
1680
|
-
const live = loadBoard(dir);
|
|
1901
|
+
function buildBoardStatusObject(dir, live) {
|
|
1681
1902
|
const taskState = live.state.tasks;
|
|
1682
1903
|
const taskConfig = live.config.tasks;
|
|
1683
1904
|
const cardNames = Object.keys(taskState);
|
|
@@ -1694,14 +1915,8 @@ function cmdStatus(args) {
|
|
|
1694
1915
|
for (const p of sched.pending) waitingByCard.set(p.taskName, p.waitingOn);
|
|
1695
1916
|
for (const u of sched.unresolved) waitingByCard.set(u.taskName, u.missingTokens);
|
|
1696
1917
|
for (const b of sched.blocked) waitingByCard.set(b.taskName, b.failedTokens);
|
|
1697
|
-
const providersByToken = /* @__PURE__ */ new Map();
|
|
1698
1918
|
const dependentsByToken = /* @__PURE__ */ new Map();
|
|
1699
1919
|
for (const [name, cfg] of Object.entries(taskConfig)) {
|
|
1700
|
-
for (const token of cfg.provides ?? []) {
|
|
1701
|
-
const providers = providersByToken.get(token) ?? [];
|
|
1702
|
-
providers.push(name);
|
|
1703
|
-
providersByToken.set(token, providers);
|
|
1704
|
-
}
|
|
1705
1920
|
for (const token of cfg.requires ?? []) {
|
|
1706
1921
|
const dependents = dependentsByToken.get(token) ?? [];
|
|
1707
1922
|
dependents.push(name);
|
|
@@ -1771,7 +1986,7 @@ function cmdStatus(args) {
|
|
|
1771
1986
|
const feedsAny = provides.some((p) => (dependentsByToken.get(p) ?? []).some((d) => d !== name));
|
|
1772
1987
|
if (requiresNone && !feedsAny) orphanCards += 1;
|
|
1773
1988
|
}
|
|
1774
|
-
|
|
1989
|
+
return {
|
|
1775
1990
|
schema_version: "v1",
|
|
1776
1991
|
meta: {
|
|
1777
1992
|
board: {
|
|
@@ -1796,6 +2011,23 @@ function cmdStatus(args) {
|
|
|
1796
2011
|
},
|
|
1797
2012
|
cards
|
|
1798
2013
|
};
|
|
2014
|
+
}
|
|
2015
|
+
function cmdStatus(args) {
|
|
2016
|
+
const rgIdx = args.indexOf("--rg");
|
|
2017
|
+
const asJson = args.includes("--json");
|
|
2018
|
+
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
2019
|
+
if (!dir) {
|
|
2020
|
+
console.error("Usage: board-live-cards status --rg <dir>");
|
|
2021
|
+
process.exit(1);
|
|
2022
|
+
}
|
|
2023
|
+
const statusOutPath = resolveStatusSnapshotPath(dir);
|
|
2024
|
+
let statusObject;
|
|
2025
|
+
if (fs__namespace.existsSync(statusOutPath)) {
|
|
2026
|
+
statusObject = JSON.parse(fs__namespace.readFileSync(statusOutPath, "utf-8"));
|
|
2027
|
+
} else {
|
|
2028
|
+
statusObject = buildBoardStatusObject(dir, loadBoard(dir));
|
|
2029
|
+
writeJsonAtomic(statusOutPath, statusObject);
|
|
2030
|
+
}
|
|
1799
2031
|
if (asJson) {
|
|
1800
2032
|
console.log(JSON.stringify(statusObject, null, 2));
|
|
1801
2033
|
return;
|
|
@@ -1826,6 +2058,7 @@ function cmdTaskCompleted(args) {
|
|
|
1826
2058
|
process.exit(1);
|
|
1827
2059
|
}
|
|
1828
2060
|
const data = dataIdx !== -1 ? JSON.parse(args[dataIdx + 1]) : {};
|
|
2061
|
+
writeRuntimeDataObjects(dir, data);
|
|
1829
2062
|
appendEventToJournal(dir, {
|
|
1830
2063
|
type: "task-completed",
|
|
1831
2064
|
taskName: decoded.taskName,
|
|
@@ -1952,6 +2185,12 @@ function cmdRunSources(args) {
|
|
|
1952
2185
|
process.exit(1);
|
|
1953
2186
|
}
|
|
1954
2187
|
const card = JSON.parse(fs__namespace.readFileSync(cardFilePath, "utf-8"));
|
|
2188
|
+
if (path__namespace.basename(cardFilePath).startsWith("card-enriched-")) {
|
|
2189
|
+
try {
|
|
2190
|
+
fs__namespace.unlinkSync(cardFilePath);
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
1955
2194
|
console.log(`[run-sources-internal] Processing card "${card.id}"`);
|
|
1956
2195
|
const executorFile = path__namespace.join(boardDir, ".task-executor");
|
|
1957
2196
|
const taskExecutor = fs__namespace.existsSync(executorFile) ? fs__namespace.readFileSync(executorFile, "utf-8").trim() : void 0;
|
|
@@ -1980,7 +2219,12 @@ function cmdRunSources(args) {
|
|
|
1980
2219
|
const inFile = path__namespace.join(os__namespace.tmpdir(), `card-source-in-${src.bindTo}-${Date.now()}.json`);
|
|
1981
2220
|
const outFile2 = path__namespace.join(os__namespace.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
|
|
1982
2221
|
const errFile = path__namespace.join(os__namespace.tmpdir(), `card-source-err-${src.bindTo}-${Date.now()}.txt`);
|
|
1983
|
-
const sourceForExecutor = {
|
|
2222
|
+
const sourceForExecutor = {
|
|
2223
|
+
...src,
|
|
2224
|
+
cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path__namespace.dirname(cardFilePath || ""),
|
|
2225
|
+
boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
|
|
2226
|
+
};
|
|
2227
|
+
appendTaskExecutorLog(boardDir, sourceForExecutor);
|
|
1984
2228
|
fs__namespace.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
|
|
1985
2229
|
console.log(`[run-sources-internal] task-executor: ${taskExecutor} run-source-fetch --in ${inFile} --out ${outFile2} --err ${errFile}`);
|
|
1986
2230
|
try {
|
|
@@ -2137,17 +2381,14 @@ function cmdUpdateCard(args) {
|
|
|
2137
2381
|
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
2138
2382
|
const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
|
|
2139
2383
|
if (!dir || !cardId) {
|
|
2140
|
-
|
|
2141
|
-
process.exit(1);
|
|
2384
|
+
throw new Error("Usage: board-live-cards update-card --rg <dir> --card-id <card-id> [--restart]");
|
|
2142
2385
|
}
|
|
2143
2386
|
const cardPath = lookupCardPath(dir, cardId);
|
|
2144
2387
|
if (!cardPath) {
|
|
2145
|
-
|
|
2146
|
-
process.exit(1);
|
|
2388
|
+
throw new Error(`Card "${cardId}" not found in inventory`);
|
|
2147
2389
|
}
|
|
2148
2390
|
if (!fs__namespace.existsSync(cardPath)) {
|
|
2149
|
-
|
|
2150
|
-
process.exit(1);
|
|
2391
|
+
throw new Error(`Card file not found: ${cardPath}`);
|
|
2151
2392
|
}
|
|
2152
2393
|
const card = JSON.parse(fs__namespace.readFileSync(cardPath, "utf-8"));
|
|
2153
2394
|
const taskConfig = liveCardToTaskConfig(card);
|
|
@@ -2167,6 +2408,121 @@ function cmdUpdateCard(args) {
|
|
|
2167
2408
|
void processAccumulatedEventsInfinitePass(dir);
|
|
2168
2409
|
console.log(`Card "${cardId}" updated${restart ? " (restarted)" : ""}.`);
|
|
2169
2410
|
}
|
|
2411
|
+
function cmdUpsertCard(args) {
|
|
2412
|
+
const rgIdx = args.indexOf("--rg");
|
|
2413
|
+
const cardIdx = args.indexOf("--card");
|
|
2414
|
+
const globIdx = args.indexOf("--card-glob");
|
|
2415
|
+
const cardIdIdx = args.indexOf("--card-id");
|
|
2416
|
+
const restart = args.includes("--restart");
|
|
2417
|
+
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
2418
|
+
const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
|
|
2419
|
+
const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
|
|
2420
|
+
const requestedCardId = cardIdIdx !== -1 ? args[cardIdIdx + 1] : void 0;
|
|
2421
|
+
if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
|
|
2422
|
+
console.error("Usage: board-live-cards upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]");
|
|
2423
|
+
process.exit(1);
|
|
2424
|
+
}
|
|
2425
|
+
if (cardGlob && requestedCardId) {
|
|
2426
|
+
console.error("Usage: --card-id may be used only with --card (single file), not with --card-glob");
|
|
2427
|
+
process.exit(1);
|
|
2428
|
+
}
|
|
2429
|
+
const cardFiles = cardFile ? [path__namespace.resolve(cardFile)] : resolveCardGlobMatches(cardGlob);
|
|
2430
|
+
if (!cardFile && cardFiles.length === 0) {
|
|
2431
|
+
console.error(`No card files matched glob: ${cardGlob}`);
|
|
2432
|
+
process.exit(1);
|
|
2433
|
+
}
|
|
2434
|
+
const idx = buildCardInventoryIndex(dir);
|
|
2435
|
+
const batchByCardId = /* @__PURE__ */ new Map();
|
|
2436
|
+
const batchByCardPath = /* @__PURE__ */ new Map();
|
|
2437
|
+
const plans = [];
|
|
2438
|
+
const logs = [];
|
|
2439
|
+
for (const absCardPath of cardFiles) {
|
|
2440
|
+
if (!fs__namespace.existsSync(absCardPath)) {
|
|
2441
|
+
console.error(`Card file not found: ${absCardPath}`);
|
|
2442
|
+
process.exit(1);
|
|
2443
|
+
}
|
|
2444
|
+
const card = JSON.parse(fs__namespace.readFileSync(absCardPath, "utf-8"));
|
|
2445
|
+
if (!card.id) {
|
|
2446
|
+
console.error(`Card JSON must have an "id" field (${absCardPath})`);
|
|
2447
|
+
process.exit(1);
|
|
2448
|
+
}
|
|
2449
|
+
if (requestedCardId && requestedCardId !== card.id) {
|
|
2450
|
+
console.error(
|
|
2451
|
+
`Card id mismatch: --card-id "${requestedCardId}" does not match file id "${card.id}" (${absCardPath})`
|
|
2452
|
+
);
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
const seenPathCardId = batchByCardPath.get(absCardPath);
|
|
2456
|
+
if (seenPathCardId && seenPathCardId !== card.id) {
|
|
2457
|
+
console.error(
|
|
2458
|
+
`Upsert rejected: file "${absCardPath}" appears multiple times in batch with conflicting ids ("${seenPathCardId}" vs "${card.id}")`
|
|
2459
|
+
);
|
|
2460
|
+
process.exit(1);
|
|
2461
|
+
}
|
|
2462
|
+
const seenCardPath = batchByCardId.get(card.id);
|
|
2463
|
+
if (seenCardPath && seenCardPath !== absCardPath) {
|
|
2464
|
+
console.error(
|
|
2465
|
+
`Upsert rejected: card id "${card.id}" appears multiple times in batch with conflicting files ("${seenCardPath}" vs "${absCardPath}")`
|
|
2466
|
+
);
|
|
2467
|
+
process.exit(1);
|
|
2468
|
+
}
|
|
2469
|
+
const existingById = idx.byCardId.get(card.id);
|
|
2470
|
+
const existingByPath = idx.byCardPath.get(absCardPath);
|
|
2471
|
+
if (existingByPath && existingByPath.cardId !== card.id) {
|
|
2472
|
+
console.error(
|
|
2473
|
+
`Upsert rejected: file "${absCardPath}" is already mapped to card id "${existingByPath.cardId}", cannot remap to "${card.id}"`
|
|
2474
|
+
);
|
|
2475
|
+
process.exit(1);
|
|
2476
|
+
}
|
|
2477
|
+
if (existingById && existingById.cardFilePath !== absCardPath) {
|
|
2478
|
+
console.error(
|
|
2479
|
+
`Upsert rejected: card id "${card.id}" is already mapped to file "${existingById.cardFilePath}", cannot remap to "${absCardPath}"`
|
|
2480
|
+
);
|
|
2481
|
+
process.exit(1);
|
|
2482
|
+
}
|
|
2483
|
+
batchByCardPath.set(absCardPath, card.id);
|
|
2484
|
+
batchByCardId.set(card.id, absCardPath);
|
|
2485
|
+
plans.push({
|
|
2486
|
+
card,
|
|
2487
|
+
absCardPath,
|
|
2488
|
+
isInsert: !existingById
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
for (const plan of plans) {
|
|
2492
|
+
const { card, absCardPath, isInsert } = plan;
|
|
2493
|
+
if (isInsert) {
|
|
2494
|
+
const newEntry = {
|
|
2495
|
+
cardId: card.id,
|
|
2496
|
+
cardFilePath: absCardPath,
|
|
2497
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2498
|
+
};
|
|
2499
|
+
appendCardInventory(dir, newEntry);
|
|
2500
|
+
idx.byCardId.set(card.id, newEntry);
|
|
2501
|
+
idx.byCardPath.set(absCardPath, newEntry);
|
|
2502
|
+
}
|
|
2503
|
+
const taskConfig = liveCardToTaskConfig(card);
|
|
2504
|
+
appendEventToJournal(dir, {
|
|
2505
|
+
type: "task-upsert",
|
|
2506
|
+
taskName: card.id,
|
|
2507
|
+
taskConfig,
|
|
2508
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2509
|
+
});
|
|
2510
|
+
if (restart) {
|
|
2511
|
+
appendEventToJournal(dir, {
|
|
2512
|
+
type: "task-restart",
|
|
2513
|
+
taskName: card.id,
|
|
2514
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
logs.push(`Card "${card.id}" ${isInsert ? "upserted (inserted)" : "upserted (updated)"}${restart ? " (restarted)" : ""}.`);
|
|
2518
|
+
}
|
|
2519
|
+
void processAccumulatedEventsInfinitePass(dir);
|
|
2520
|
+
if (cardGlob) {
|
|
2521
|
+
console.log(`Upserted ${cardFiles.length} cards from glob: ${cardGlob}${restart ? " (restarted)" : ""}`);
|
|
2522
|
+
} else {
|
|
2523
|
+
console.log(logs[0]);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2170
2526
|
async function cmdTryDrain(args) {
|
|
2171
2527
|
const rgIdx = args.indexOf("--rg");
|
|
2172
2528
|
const inlineLoop = args.includes("--inline-loop");
|
|
@@ -2210,6 +2566,8 @@ async function cli(argv) {
|
|
|
2210
2566
|
return cmdAddCards(rest);
|
|
2211
2567
|
case "update-card":
|
|
2212
2568
|
return cmdUpdateCard(rest);
|
|
2569
|
+
case "upsert-card":
|
|
2570
|
+
return cmdUpsertCard(rest);
|
|
2213
2571
|
case "remove-card":
|
|
2214
2572
|
return cmdRemoveCard(rest);
|
|
2215
2573
|
case "retrigger":
|
|
@@ -2229,9 +2587,7 @@ async function cli(argv) {
|
|
|
2229
2587
|
case "process-accumulated-events":
|
|
2230
2588
|
return await cmdTryDrain(rest);
|
|
2231
2589
|
default:
|
|
2232
|
-
|
|
2233
|
-
console.error("Run: board-live-cards help");
|
|
2234
|
-
process.exit(1);
|
|
2590
|
+
throw new Error(`Unknown command: ${cmd ?? "(none)"}`);
|
|
2235
2591
|
}
|
|
2236
2592
|
}
|
|
2237
2593
|
function cmdHelp() {
|
|
@@ -2242,14 +2598,18 @@ USAGE
|
|
|
2242
2598
|
board-live-cards-cli <command> [options]
|
|
2243
2599
|
|
|
2244
2600
|
BOARD MANAGEMENT
|
|
2245
|
-
init <dir> [--task-executor <script>]
|
|
2601
|
+
init <dir> [--task-executor <script>] [--runtime-out <dir>]
|
|
2246
2602
|
Create a new board in <dir>.
|
|
2247
2603
|
If --task-executor is given, writes <dir>/.task-executor with the script path.
|
|
2604
|
+
Writes <dir>/.runtime-out (default: <dir>/runtime-out).
|
|
2605
|
+
Published runtime files:
|
|
2606
|
+
<runtime-out>/board-livegraph-status.json
|
|
2607
|
+
<runtime-out>/cards/<card-id>.computed.json
|
|
2248
2608
|
Re-running init on an existing board is safe; --task-executor updates the registration.
|
|
2249
2609
|
|
|
2250
2610
|
status --rg <dir> [--json]
|
|
2251
|
-
|
|
2252
|
-
--json emits
|
|
2611
|
+
Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
|
|
2612
|
+
--json emits the stable machine-readable status object.
|
|
2253
2613
|
|
|
2254
2614
|
CARD MANAGEMENT
|
|
2255
2615
|
add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)
|
|
@@ -2262,6 +2622,16 @@ CARD MANAGEMENT
|
|
|
2262
2622
|
Re-read the card JSON from disk and patch the board.
|
|
2263
2623
|
--restart clears the task so it re-triggers from scratch.
|
|
2264
2624
|
|
|
2625
|
+
upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]
|
|
2626
|
+
Insert or update one or many cards.
|
|
2627
|
+
Enforces strict one-to-one mapping between card id and file path:
|
|
2628
|
+
- same id + same file path: update
|
|
2629
|
+
- new id + new file path: insert
|
|
2630
|
+
- id remap or file remap: rejected
|
|
2631
|
+
If --card-id is provided, it must match the id inside the file.
|
|
2632
|
+
--card-id is valid only with --card (single file), not with --card-glob.
|
|
2633
|
+
--restart clears the task so it re-triggers from scratch.
|
|
2634
|
+
|
|
2265
2635
|
remove-card --rg <dir> --id <card-id>
|
|
2266
2636
|
Remove a card and its task from the board.
|
|
2267
2637
|
|
|
@@ -2359,6 +2729,7 @@ if (isMain) {
|
|
|
2359
2729
|
exports.BoardJournal = BoardJournal;
|
|
2360
2730
|
exports.appendCardInventory = appendCardInventory;
|
|
2361
2731
|
exports.appendEventToJournal = appendEventToJournal;
|
|
2732
|
+
exports.buildCardInventoryIndex = buildCardInventoryIndex;
|
|
2362
2733
|
exports.cli = cli;
|
|
2363
2734
|
exports.createBoardReactiveGraph = createBoardReactiveGraph;
|
|
2364
2735
|
exports.decodeSourceToken = decodeSourceToken;
|