yaml-flow 5.2.8 → 5.4.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 (38) hide show
  1. package/board-livecards-server-runtime.js +15 -66
  2. package/browser/board-livegraph-engine.js +4 -1
  3. package/browser/board-livegraph-engine.js.map +1 -1
  4. package/browser/card-compute.js +1 -1
  5. package/browser/live-cards.js +178 -144
  6. package/browser/live-cards.schema.json +1 -1
  7. package/dist/board-livegraph-runtime/index.cjs +4 -1
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.js +4 -1
  10. package/dist/board-livegraph-runtime/index.js.map +1 -1
  11. package/dist/card-compute/index.cjs +5 -1
  12. package/dist/card-compute/index.cjs.map +1 -1
  13. package/dist/card-compute/index.js +5 -1
  14. package/dist/card-compute/index.js.map +1 -1
  15. package/dist/cli/board-live-cards-cli.cjs +2416 -2113
  16. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  17. package/dist/cli/board-live-cards-cli.d.cts +59 -113
  18. package/dist/cli/board-live-cards-cli.d.ts +59 -113
  19. package/dist/cli/board-live-cards-cli.js +2413 -2109
  20. package/dist/cli/board-live-cards-cli.js.map +1 -1
  21. package/dist/continuous-event-graph/index.cjs +4 -1
  22. package/dist/continuous-event-graph/index.cjs.map +1 -1
  23. package/dist/continuous-event-graph/index.js +4 -1
  24. package/dist/continuous-event-graph/index.js.map +1 -1
  25. package/dist/index.cjs +5 -1
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +5 -1
  28. package/dist/index.js.map +1 -1
  29. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +4 -4
  30. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  31. package/examples/example-board/agent-instructions.md +4 -5
  32. package/examples/example-board/cards/card-rebalance-sim.json +13 -3
  33. package/examples/example-board/demo-server.js +77 -11
  34. package/examples/example-board/demo-shell-browser.html +4 -4
  35. package/examples/example-board/demo-shell-with-server.html +4 -4
  36. package/examples/example-board/demo-task-executor.js +22 -2
  37. package/package.json +1 -1
  38. package/schema/live-cards.schema.json +1 -1
@@ -1,13 +1,13 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
1
+ import * as fs7 from 'fs';
2
+ import * as os3 from 'os';
3
+ import * as path7 from 'path';
4
4
  import { randomUUID } from 'crypto';
5
5
  import { execFileSync, spawn, execFile } from 'child_process';
6
6
  import { fileURLToPath } from 'url';
7
7
  import fg from 'fast-glob';
8
8
  import { lockSync } from 'proper-lockfile';
9
- import jsonata2 from 'jsonata';
10
9
  import addFormats from 'ajv-formats';
10
+ import jsonata2 from 'jsonata';
11
11
 
12
12
  var __create = Object.create;
13
13
  var __defProp = Object.defineProperty;
@@ -2964,7 +2964,7 @@ var require_compile = __commonJS({
2964
2964
  const schOrFunc = root.refs[ref];
2965
2965
  if (schOrFunc)
2966
2966
  return schOrFunc;
2967
- let _sch = resolve3.call(this, root, ref);
2967
+ let _sch = resolve6.call(this, root, ref);
2968
2968
  if (_sch === void 0) {
2969
2969
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2970
2970
  const { schemaId } = this.opts;
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
2991
2991
  function sameSchemaEnv(s1, s2) {
2992
2992
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
2993
2993
  }
2994
- function resolve3(root, ref) {
2994
+ function resolve6(root, ref) {
2995
2995
  let sch;
2996
2996
  while (typeof (sch = this.refs[ref]) == "string")
2997
2997
  ref = sch;
@@ -3205,8 +3205,8 @@ var require_utils = __commonJS({
3205
3205
  }
3206
3206
  return ind;
3207
3207
  }
3208
- function removeDotSegments(path2) {
3209
- let input = path2;
3208
+ function removeDotSegments(path8) {
3209
+ let input = path8;
3210
3210
  const output = [];
3211
3211
  let nextSlash = -1;
3212
3212
  let len = 0;
@@ -3404,8 +3404,8 @@ var require_schemes = __commonJS({
3404
3404
  wsComponent.secure = void 0;
3405
3405
  }
3406
3406
  if (wsComponent.resourceName) {
3407
- const [path2, query] = wsComponent.resourceName.split("?");
3408
- wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3407
+ const [path8, query] = wsComponent.resourceName.split("?");
3408
+ wsComponent.path = path8 && path8 !== "/" ? path8 : void 0;
3409
3409
  wsComponent.query = query;
3410
3410
  wsComponent.resourceName = void 0;
3411
3411
  }
@@ -3563,7 +3563,7 @@ var require_fast_uri = __commonJS({
3563
3563
  }
3564
3564
  return uri;
3565
3565
  }
3566
- function resolve3(baseURI, relativeURI, options) {
3566
+ function resolve6(baseURI, relativeURI, options) {
3567
3567
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3568
3568
  const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
3569
3569
  schemelessOptions.skipEscape = true;
@@ -3790,7 +3790,7 @@ var require_fast_uri = __commonJS({
3790
3790
  var fastUri = {
3791
3791
  SCHEMES,
3792
3792
  normalize,
3793
- resolve: resolve3,
3793
+ resolve: resolve6,
3794
3794
  resolveComponent,
3795
3795
  equal,
3796
3796
  serialize,
@@ -7554,6 +7554,7 @@ var live_cards_schema_default = {
7554
7554
  "badge",
7555
7555
  "text",
7556
7556
  "markdown",
7557
+ "ref",
7557
7558
  "custom",
7558
7559
  "actions"
7559
7560
  ]
@@ -7739,25 +7740,25 @@ function parseRootPathNamespace(pathValue) {
7739
7740
  const match = ROOT_PATH_NAMESPACE_RE.exec(pathValue);
7740
7741
  return match ? match[1] : null;
7741
7742
  }
7742
- function validateJsonataExprWithNamespaces(expr, path2, allowedNamespaces, errors) {
7743
+ function validateJsonataExprWithNamespaces(expr, path8, allowedNamespaces, errors) {
7743
7744
  try {
7744
7745
  jsonata2(expr);
7745
7746
  } catch (err) {
7746
7747
  const message = err instanceof Error ? err.message : String(err);
7747
- errors.push(`${path2}: invalid JSONata expression (${message})`);
7748
+ errors.push(`${path8}: invalid JSONata expression (${message})`);
7748
7749
  return;
7749
7750
  }
7750
7751
  const usedNamespaces = referencedNamespaces(expr);
7751
7752
  for (const namespace of usedNamespaces) {
7752
7753
  if (!allowedNamespaces.has(namespace)) {
7753
- errors.push(`${path2}: disallowed namespace "${namespace}" in expression`);
7754
+ errors.push(`${path8}: disallowed namespace "${namespace}" in expression`);
7754
7755
  }
7755
7756
  }
7756
7757
  }
7757
- function walkViewPathReferences(value, path2, errors) {
7758
+ function walkViewPathReferences(value, path8, errors) {
7758
7759
  if (Array.isArray(value)) {
7759
7760
  value.forEach((entry, index) => {
7760
- walkViewPathReferences(entry, `${path2}/${index}`, errors);
7761
+ walkViewPathReferences(entry, `${path8}/${index}`, errors);
7761
7762
  });
7762
7763
  return;
7763
7764
  }
@@ -7765,14 +7766,14 @@ function walkViewPathReferences(value, path2, errors) {
7765
7766
  const rootNamespace = parseRootPathNamespace(value);
7766
7767
  if (!rootNamespace) return;
7767
7768
  if (!(/* @__PURE__ */ new Set(["card_data", "requires", "fetched_sources", "computed_values"])).has(rootNamespace)) {
7768
- errors.push(`${path2}: disallowed namespace "${rootNamespace}" in view reference`);
7769
+ errors.push(`${path8}: disallowed namespace "${rootNamespace}" in view reference`);
7769
7770
  }
7770
7771
  return;
7771
7772
  }
7772
7773
  if (!value || typeof value !== "object") return;
7773
7774
  const record = value;
7774
7775
  for (const [key, next] of Object.entries(record)) {
7775
- walkViewPathReferences(next, `${path2}/${key}`, errors);
7776
+ walkViewPathReferences(next, `${path8}/${key}`, errors);
7776
7777
  }
7777
7778
  }
7778
7779
  function getValidator() {
@@ -7786,8 +7787,8 @@ function validateLiveCardSchema(node) {
7786
7787
  const validate = getValidator();
7787
7788
  const valid = validate(node);
7788
7789
  const errors = (validate.errors ?? []).map((e) => {
7789
- const path2 = e.instancePath || "/";
7790
- return `${path2}: ${e.message ?? "unknown error"}`;
7790
+ const path8 = e.instancePath || "/";
7791
+ return `${path8}: ${e.message ?? "unknown error"}`;
7791
7792
  });
7792
7793
  if (node && typeof node === "object" && !Array.isArray(node)) {
7793
7794
  const source_defs = node.source_defs;
@@ -7886,1095 +7887,1696 @@ function validateLiveCardDefinition(node) {
7886
7887
  if (!runtime.ok) return { ok: false, errors: runtime.errors };
7887
7888
  return { ok: true, errors: [] };
7888
7889
  }
7889
-
7890
- // src/card-compute/index.ts
7891
- function deepGet(obj, path2) {
7892
- if (!path2 || !obj) return void 0;
7893
- const parts = path2.split(".");
7894
- let cur = obj;
7895
- for (let i = 0; i < parts.length; i++) {
7896
- if (cur == null) return void 0;
7897
- cur = cur[parts[i]];
7898
- }
7899
- return cur;
7900
- }
7901
- function deepSet(obj, path2, value) {
7902
- const parts = path2.split(".");
7903
- let cur = obj;
7904
- for (let i = 0; i < parts.length - 1; i++) {
7905
- if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
7906
- cur = cur[parts[i]];
7907
- }
7908
- cur[parts[parts.length - 1]] = value;
7909
- }
7910
- async function run(node, options) {
7911
- if (!node?.compute?.length) return node;
7912
- if (!node.card_data) node.card_data = {};
7913
- node.computed_values = {};
7914
- node._sourcesData = options?.sourcesData ?? {};
7915
- const ctx = {
7916
- card_data: node.card_data,
7917
- requires: node.requires ?? {},
7918
- fetched_sources: node._sourcesData,
7919
- computed_values: node.computed_values
7920
- };
7921
- for (const step of node.compute) {
7922
- try {
7923
- const val = await jsonata2(step.expr).evaluate(ctx);
7924
- deepSet(node.computed_values, step.bindTo, val);
7925
- ctx.computed_values = node.computed_values;
7926
- } catch (err) {
7927
- console.error(`CardCompute.run error on "${node.id ?? "?"}.${step.bindTo}":`, err);
7890
+ function createBoardCommandHandlers(deps) {
7891
+ function cmdInit(args) {
7892
+ const dir = args[0];
7893
+ if (!dir) {
7894
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
7895
+ }
7896
+ const teIdx = args.indexOf("--task-executor");
7897
+ const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
7898
+ const chIdx = args.indexOf("--chat-handler");
7899
+ const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
7900
+ const iaIdx = args.indexOf("--inference-adapter");
7901
+ const inferenceAdapter = iaIdx !== -1 ? args[iaIdx + 1] : void 0;
7902
+ const roIdx = args.indexOf("--runtime-out");
7903
+ const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
7904
+ if (roIdx !== -1 && !runtimeOut) {
7905
+ throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
7906
+ }
7907
+ const result = deps.initBoard(dir);
7908
+ if (taskExecutor) {
7909
+ const teExtraIdx = args.indexOf("--task-executor-extra");
7910
+ let teExtra;
7911
+ if (teExtraIdx !== -1 && args[teExtraIdx + 1]) {
7912
+ try {
7913
+ teExtra = JSON.parse(args[teExtraIdx + 1]);
7914
+ } catch {
7915
+ }
7916
+ }
7917
+ const teConfig = { command: taskExecutor, ...teExtra ? { extra: teExtra } : {} };
7918
+ fs7.writeFileSync(path7.join(dir, ".task-executor"), JSON.stringify(teConfig, null, 2), "utf-8");
7928
7919
  }
7929
- }
7930
- return node;
7931
- }
7932
- async function evalExpr(expr, node) {
7933
- const ctx = {
7934
- card_data: node.card_data ?? {},
7935
- requires: node.requires ?? {},
7936
- fetched_sources: node._sourcesData ?? {},
7937
- computed_values: node.computed_values ?? {}
7938
- };
7939
- return jsonata2(expr).evaluate(ctx);
7940
- }
7941
- function resolve(node, path2) {
7942
- if (path2.startsWith("fetched_sources.")) {
7943
- return deepGet(node._sourcesData ?? {}, path2.slice("fetched_sources.".length));
7944
- }
7945
- return deepGet(node, path2);
7946
- }
7947
- var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
7948
- "metric",
7949
- "table",
7950
- "chart",
7951
- "form",
7952
- "filter",
7953
- "list",
7954
- "notes",
7955
- "todo",
7956
- "alert",
7957
- "narrative",
7958
- "badge",
7959
- "text",
7960
- "markdown",
7961
- "custom"
7962
- ]);
7963
- var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "source_defs"]);
7964
- function validateNode(node) {
7965
- const errors = [];
7966
- if (!node || typeof node !== "object" || Array.isArray(node)) {
7967
- return { ok: false, errors: ["Node must be a non-null object"] };
7968
- }
7969
- const n = node;
7970
- if (typeof n.id !== "string" || !n.id) errors.push("id: required, must be a non-empty string");
7971
- for (const key of Object.keys(n)) {
7972
- if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
7973
- }
7974
- if (n.card_data == null || typeof n.card_data !== "object" || Array.isArray(n.card_data)) {
7975
- errors.push("card_data: required, must be an object");
7976
- }
7977
- if (n.meta != null) {
7978
- if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
7979
- errors.push("meta: must be an object");
7980
- } else {
7981
- const meta = n.meta;
7982
- if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
7983
- if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
7920
+ if (chatHandler) {
7921
+ fs7.writeFileSync(path7.join(dir, ".chat-handler"), chatHandler, "utf-8");
7984
7922
  }
7985
- }
7986
- if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
7987
- if (n.provides != null) {
7988
- if (!Array.isArray(n.provides)) {
7989
- errors.push("provides: must be an array of { bindTo, ref } bindings");
7990
- } else {
7991
- n.provides.forEach((p, i) => {
7992
- if (!p || typeof p !== "object" || Array.isArray(p)) {
7993
- errors.push(`provides[${i}]: must be an object with bindTo and ref`);
7994
- } else {
7995
- const b = p;
7996
- if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
7997
- if (typeof b.ref !== "string" || !b.ref) errors.push(`provides[${i}]: missing required "ref" string`);
7998
- }
7999
- });
7923
+ if (inferenceAdapter) {
7924
+ fs7.writeFileSync(path7.join(dir, ".inference-adapter"), inferenceAdapter, "utf-8");
8000
7925
  }
8001
- }
8002
- if (n.compute != null) {
8003
- if (!Array.isArray(n.compute)) {
8004
- errors.push("compute: must be an array of compute steps");
7926
+ const runtimeOutDir = deps.configureRuntimeOutDir(dir, runtimeOut);
7927
+ const live = deps.loadBoard(dir);
7928
+ deps.writeJsonAtomic(deps.resolveStatusSnapshotPath(dir), deps.buildBoardStatusObject(dir, live));
7929
+ if (result === "exists") {
7930
+ console.log(`Board already initialized at ${path7.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
8005
7931
  } else {
8006
- n.compute.forEach((step, i) => {
8007
- if (!step || typeof step !== "object" || Array.isArray(step)) {
8008
- errors.push(`compute[${i}]: must be a compute step object`);
8009
- } else {
8010
- const s = step;
8011
- if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`compute[${i}]: missing required "bindTo" property`);
8012
- if (typeof s.expr !== "string" || !s.expr) errors.push(`compute[${i}]: missing required "expr" string (JSONata expression)`);
8013
- }
8014
- });
7932
+ console.log(`Board initialized at ${path7.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
8015
7933
  }
8016
7934
  }
8017
- if (n.source_defs != null) {
8018
- if (!Array.isArray(n.source_defs)) {
8019
- errors.push("source_defs: must be an array");
8020
- } else {
8021
- const bindTos = /* @__PURE__ */ new Set();
8022
- const outputFiles = /* @__PURE__ */ new Set();
8023
- n.source_defs.forEach((src, i) => {
8024
- if (!src || typeof src !== "object" || Array.isArray(src)) {
8025
- errors.push(`source_defs[${i}]: must be an object`);
8026
- } else {
8027
- const s = src;
8028
- if (typeof s.bindTo !== "string" || !s.bindTo) {
8029
- errors.push(`source_defs[${i}]: missing required "bindTo" property`);
8030
- } else {
8031
- if (bindTos.has(s.bindTo)) {
8032
- errors.push(`source_defs[${i}]: bindTo "${s.bindTo}" is not unique across source_defs`);
8033
- }
8034
- bindTos.add(s.bindTo);
8035
- }
8036
- if (typeof s.outputFile !== "string" || !s.outputFile) {
8037
- errors.push(`source_defs[${i}]: missing required "outputFile" property`);
8038
- } else {
8039
- if (outputFiles.has(s.outputFile)) {
8040
- errors.push(`source_defs[${i}]: outputFile "${s.outputFile}" is not unique across source_defs`);
8041
- }
8042
- outputFiles.add(s.outputFile);
8043
- }
8044
- if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
8045
- errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);
8046
- }
8047
- }
8048
- });
7935
+ function cmdStatus(args) {
7936
+ const rgIdx = args.indexOf("--rg");
7937
+ const asJson = args.includes("--json");
7938
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
7939
+ if (!dir) {
7940
+ console.error("Usage: board-live-cards status --rg <dir>");
7941
+ process.exit(1);
8049
7942
  }
8050
- }
8051
- if (n.view != null) {
8052
- if (typeof n.view !== "object" || Array.isArray(n.view)) {
8053
- errors.push("view: must be an object");
7943
+ const statusOutPath = deps.resolveStatusSnapshotPath(dir);
7944
+ let statusObject;
7945
+ if (fs7.existsSync(statusOutPath)) {
7946
+ statusObject = JSON.parse(fs7.readFileSync(statusOutPath, "utf-8"));
8054
7947
  } else {
8055
- const view = n.view;
8056
- if (!Array.isArray(view.elements) || view.elements.length === 0) {
8057
- errors.push("view.elements: required, must be a non-empty array");
8058
- } else {
8059
- view.elements.forEach((elem, i) => {
8060
- if (!elem || typeof elem !== "object") {
8061
- errors.push(`view.elements[${i}]: must be an object`);
8062
- return;
8063
- }
8064
- if (!elem.kind || typeof elem.kind !== "string") {
8065
- errors.push(`view.elements[${i}].kind: required, must be a string`);
8066
- } else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
8067
- errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
8068
- }
8069
- if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
8070
- errors.push(`view.elements[${i}].data: must be an object`);
8071
- }
8072
- });
8073
- }
8074
- if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) errors.push("view.layout: must be an object");
8075
- if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) errors.push("view.features: must be an object");
7948
+ statusObject = deps.buildBoardStatusObject(dir, deps.loadBoard(dir));
7949
+ deps.writeJsonAtomic(statusOutPath, statusObject);
8076
7950
  }
8077
- }
8078
- return { ok: errors.length === 0, errors };
8079
- }
8080
- async function enrichSources(source_defs, context) {
8081
- if (!source_defs || source_defs.length === 0) return [];
8082
- const evalCtx = {
8083
- card_data: context.card_data ?? {},
8084
- requires: context.requires ?? {}
8085
- };
8086
- return Promise.all(
8087
- source_defs.map(async (src) => {
8088
- const _projections = {};
8089
- if (src.projections && typeof src.projections === "object" && !Array.isArray(src.projections)) {
8090
- for (const [key, expr] of Object.entries(src.projections)) {
8091
- if (typeof expr === "string" && expr.trim().length > 0) {
7951
+ if (asJson) {
7952
+ console.log(JSON.stringify(statusObject, null, 2));
7953
+ return;
7954
+ }
7955
+ console.log(`Board: ${statusObject.meta.board.path}`);
7956
+ console.log(`Tasks: ${statusObject.summary.card_count}`);
7957
+ console.log("");
7958
+ for (const card of statusObject.cards) {
7959
+ const dataKeys = card.provides_runtime.join(", ");
7960
+ console.log(` ${card.status.padEnd(12)} ${card.name}${dataKeys ? ` \u2014 [${dataKeys}]` : ""}`);
7961
+ }
7962
+ console.log("");
7963
+ console.log(`Schedule: ${statusObject.summary.eligible} eligible, ${statusObject.summary.pending} pending, ${statusObject.summary.blocked} blocked, ${statusObject.summary.unresolved} unresolved`);
7964
+ }
7965
+ function cmdValidateCard(args) {
7966
+ const cardIdx = args.indexOf("--card");
7967
+ const globIdx = args.indexOf("--card-glob");
7968
+ const rgIdx = args.indexOf("--rg");
7969
+ const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
7970
+ const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
7971
+ const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
7972
+ if (!cardFile && !cardGlob || cardFile && cardGlob) {
7973
+ throw new Error("Usage: board-live-cards validate-card (--card <card.json> | --card-glob <glob>) [--rg <boardDir>]");
7974
+ }
7975
+ let teConfig;
7976
+ if (boardDir) {
7977
+ teConfig = deps.readTaskExecutorConfig(boardDir);
7978
+ if (!teConfig) {
7979
+ throw new Error(`--rg specified but no .task-executor found in ${boardDir}`);
7980
+ }
7981
+ }
7982
+ const files = cardFile ? [path7.resolve(cardFile)] : deps.resolveCardGlobMatches(cardGlob);
7983
+ if (files.length === 0) {
7984
+ throw new Error(`No card files matched glob: ${cardGlob}`);
7985
+ }
7986
+ let failures = 0;
7987
+ for (const f of files) {
7988
+ const label = path7.relative(process.cwd(), f) || f;
7989
+ if (!fs7.existsSync(f)) {
7990
+ console.error(`FAIL ${label}: file not found`);
7991
+ failures++;
7992
+ continue;
7993
+ }
7994
+ let card;
7995
+ try {
7996
+ card = JSON.parse(fs7.readFileSync(f, "utf-8"));
7997
+ } catch (err) {
7998
+ console.error(`FAIL ${label}: invalid JSON \u2014 ${err instanceof Error ? err.message : String(err)}`);
7999
+ failures++;
8000
+ continue;
8001
+ }
8002
+ const result = deps.validateLiveCardDefinition(card);
8003
+ const sourceErrors = [];
8004
+ if (teConfig && Array.isArray(card.source_defs)) {
8005
+ for (const src of card.source_defs) {
8006
+ const bindTo = typeof src.bindTo === "string" ? src.bindTo : "(unknown)";
8007
+ const tmpFile = path7.join(os3.tmpdir(), `validate-src-${bindTo}-${Date.now()}.json`);
8008
+ try {
8009
+ fs7.writeFileSync(tmpFile, JSON.stringify(src), "utf-8");
8010
+ let stdout;
8092
8011
  try {
8093
- _projections[key] = await jsonata2(expr).evaluate(evalCtx);
8012
+ stdout = deps.execCommandSync(teConfig.command, ["validate-source-def", "--in", tmpFile], { shell: true, timeout: 1e4 });
8013
+ } catch (execErr) {
8014
+ stdout = typeof execErr?.stdout === "string" ? execErr.stdout : Buffer.isBuffer(execErr?.stdout) ? execErr.stdout.toString("utf-8") : "";
8015
+ if (!stdout.trim()) {
8016
+ sourceErrors.push(`source "${bindTo}": executor validate-source-def failed \u2014 ${execErr instanceof Error ? execErr.message : String(execErr)}`);
8017
+ continue;
8018
+ }
8019
+ }
8020
+ const parsed = JSON.parse(stdout.trim());
8021
+ if (!parsed.ok && Array.isArray(parsed.errors)) {
8022
+ for (const error of parsed.errors) {
8023
+ sourceErrors.push(`source "${bindTo}": ${error}`);
8024
+ }
8025
+ }
8026
+ } catch (err) {
8027
+ sourceErrors.push(`source "${bindTo}": executor validate-source-def failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
8028
+ } finally {
8029
+ try {
8030
+ fs7.unlinkSync(tmpFile);
8094
8031
  } catch {
8095
- _projections[key] = void 0;
8096
8032
  }
8097
8033
  }
8098
8034
  }
8099
8035
  }
8100
- return { ...src, _projections };
8101
- })
8102
- );
8103
- }
8104
- var CardCompute = {
8105
- run,
8106
- eval: evalExpr,
8107
- resolve,
8108
- validate: validateNode,
8109
- enrichSources
8110
- };
8111
-
8112
- // src/cli/board-live-cards-cli.ts
8113
- var BOARD_FILE = "board-graph.json";
8114
- var JOURNAL_FILE = "board-journal.jsonl";
8115
- var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
8116
- var INFERENCE_ADAPTER_LOG_FILE = "inference-adapter.jsonl";
8117
- var INVENTORY_FILE = "cards-inventory.jsonl";
8118
- var RUNTIME_OUT_FILE = ".runtime-out";
8119
- var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
8120
- var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
8121
- var RUNTIME_CARDS_DIR = "cards";
8122
- var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
8123
- var INFERENCE_ADAPTER_FILE = ".inference-adapter";
8124
- var TASK_EXECUTOR_FILE = ".task-executor";
8125
- var DEFAULT_TASK_COMPLETION_RULE = "all_required_sources_fetched";
8126
- function readTaskExecutorConfig(boardDir) {
8127
- const executorFile = path.join(boardDir, TASK_EXECUTOR_FILE);
8128
- if (!fs.existsSync(executorFile)) return void 0;
8129
- const raw = fs.readFileSync(executorFile, "utf-8").trim();
8130
- if (!raw) return void 0;
8131
- try {
8132
- const parsed = JSON.parse(raw);
8133
- if (parsed && typeof parsed === "object" && typeof parsed.command === "string") {
8134
- return parsed;
8036
+ const allErrors = [...result.errors, ...sourceErrors];
8037
+ if (allErrors.length === 0) {
8038
+ console.log(`OK ${label}`);
8039
+ } else {
8040
+ console.error(`FAIL ${label}:`);
8041
+ for (const error of allErrors) {
8042
+ console.error(` ${error}`);
8043
+ }
8044
+ failures++;
8045
+ }
8135
8046
  }
8136
- } catch {
8047
+ if (failures > 0) {
8048
+ throw new Error(`${failures} of ${files.length} card(s) failed validation.`);
8049
+ }
8050
+ console.log(`
8051
+ ${files.length} card(s) passed validation.`);
8137
8052
  }
8138
- return { command: raw };
8053
+ function cmdRemoveCard(args) {
8054
+ const rgIdx = args.indexOf("--rg");
8055
+ const idIdx = args.indexOf("--id");
8056
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8057
+ const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
8058
+ if (!dir || !cardId) {
8059
+ console.error("Usage: board-live-cards remove-card --rg <dir> --id <card-id>");
8060
+ process.exit(1);
8061
+ }
8062
+ deps.appendEventToJournal(dir, {
8063
+ type: "task-removal",
8064
+ taskName: cardId,
8065
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8066
+ });
8067
+ void deps.processAccumulatedEventsInfinitePass(dir);
8068
+ console.log(`Card "${cardId}" removed.`);
8069
+ }
8070
+ function cmdRetrigger(args) {
8071
+ const rgIdx = args.indexOf("--rg");
8072
+ const taskIdx = args.indexOf("--task");
8073
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8074
+ const taskName = taskIdx !== -1 ? args[taskIdx + 1] : void 0;
8075
+ if (!dir || !taskName) {
8076
+ console.error("Usage: board-live-cards retrigger --rg <dir> --task <task-name>");
8077
+ process.exit(1);
8078
+ }
8079
+ deps.appendEventToJournal(dir, {
8080
+ type: "task-restart",
8081
+ taskName,
8082
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8083
+ });
8084
+ void deps.processAccumulatedEventsInfinitePass(dir);
8085
+ console.log(`Task "${taskName}" retriggered.`);
8086
+ }
8087
+ return {
8088
+ cmdInit,
8089
+ cmdStatus,
8090
+ cmdValidateCard,
8091
+ cmdRemoveCard,
8092
+ cmdRetrigger
8093
+ };
8139
8094
  }
8140
- var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
8141
- var BoardJournal = class {
8142
- journalPath;
8143
- lastDrainedId;
8144
- constructor(journalPath, lastDrainedJournalId) {
8145
- this.journalPath = journalPath;
8146
- this.lastDrainedId = lastDrainedJournalId;
8147
- }
8148
- append(event) {
8149
- const entry = { id: randomUUID(), event };
8150
- fs.appendFileSync(this.journalPath, JSON.stringify(entry) + "\n", "utf-8");
8151
- }
8152
- drain() {
8153
- if (!fs.existsSync(this.journalPath)) return [];
8154
- const content = fs.readFileSync(this.journalPath, "utf-8").trim();
8155
- if (!content) return [];
8156
- const entries = content.split("\n").map((l) => JSON.parse(l));
8157
- let startIdx = 0;
8158
- if (this.lastDrainedId) {
8159
- const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
8160
- if (drainedIdx !== -1) startIdx = drainedIdx + 1;
8095
+ function createCallbackCommandHandlers(deps) {
8096
+ function cmdTaskCompleted(args) {
8097
+ const rgIdx = args.indexOf("--rg");
8098
+ const tokenIdx = args.indexOf("--token");
8099
+ const dataIdx = args.indexOf("--data");
8100
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8101
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8102
+ if (!dir || !token) {
8103
+ console.error("Usage: board-live-cards task-completed --rg <dir> --token <token> [--data <json>]");
8104
+ process.exit(1);
8161
8105
  }
8162
- const undrained = entries.slice(startIdx);
8163
- if (undrained.length > 0) {
8164
- this.lastDrainedId = undrained[undrained.length - 1].id;
8106
+ const decoded = deps.decodeCallbackToken(token);
8107
+ if (!decoded) {
8108
+ console.error("Invalid callback token");
8109
+ process.exit(1);
8165
8110
  }
8166
- return undrained.map((e) => e.event);
8167
- }
8168
- get size() {
8169
- if (!fs.existsSync(this.journalPath)) return 0;
8170
- const content = fs.readFileSync(this.journalPath, "utf-8").trim();
8171
- if (!content) return 0;
8172
- const entries = content.split("\n").map((l) => JSON.parse(l));
8173
- if (!this.lastDrainedId) return entries.length;
8174
- const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
8175
- return drainedIdx === -1 ? entries.length : entries.length - drainedIdx - 1;
8176
- }
8177
- get lastDrainedJournalId() {
8178
- return this.lastDrainedId;
8179
- }
8180
- };
8181
- function readCardInventory(boardDir) {
8182
- const inventoryPath = path.join(boardDir, INVENTORY_FILE);
8183
- if (!fs.existsSync(inventoryPath)) return [];
8184
- const lines = fs.readFileSync(inventoryPath, "utf-8").split("\n").filter((l) => l.trim());
8185
- return lines.map((l) => JSON.parse(l));
8186
- }
8187
- function lookupCardPath(boardDir, cardId) {
8188
- const entries = readCardInventory(boardDir);
8189
- const entry = entries.find((e) => e.cardId === cardId);
8190
- return entry?.cardFilePath ?? null;
8191
- }
8192
- function appendCardInventory(boardDir, entry) {
8193
- const inventoryPath = path.join(boardDir, INVENTORY_FILE);
8194
- const normalized = { ...entry, cardFilePath: path.resolve(entry.cardFilePath) };
8195
- fs.appendFileSync(inventoryPath, JSON.stringify(normalized) + "\n");
8196
- }
8197
- function buildCardInventoryIndex(boardDir) {
8198
- const byCardId = /* @__PURE__ */ new Map();
8199
- const byCardPath = /* @__PURE__ */ new Map();
8200
- for (const entry of readCardInventory(boardDir)) {
8201
- const normalizedPath = path.resolve(entry.cardFilePath);
8202
- const normalizedEntry = {
8203
- ...entry,
8204
- cardFilePath: normalizedPath
8205
- };
8206
- const existingById = byCardId.get(entry.cardId);
8207
- if (existingById && existingById.cardFilePath !== normalizedPath) {
8208
- throw new Error(
8209
- `Inventory invariant violation: card id "${entry.cardId}" maps to multiple files: "${existingById.cardFilePath}" and "${normalizedPath}"`
8210
- );
8111
+ const data = dataIdx !== -1 ? JSON.parse(args[dataIdx + 1]) : {};
8112
+ deps.writeRuntimeDataObjects(dir, data);
8113
+ deps.appendEventToJournal(dir, {
8114
+ type: "task-completed",
8115
+ taskName: decoded.taskName,
8116
+ data,
8117
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8118
+ });
8119
+ void deps.processAccumulatedEventsForced(dir);
8120
+ console.log("Task completed.");
8121
+ }
8122
+ function cmdTaskFailed(args) {
8123
+ const rgIdx = args.indexOf("--rg");
8124
+ const tokenIdx = args.indexOf("--token");
8125
+ const errorIdx = args.indexOf("--error");
8126
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8127
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8128
+ const errorMsg = errorIdx !== -1 ? args[errorIdx + 1] : "unknown error";
8129
+ if (!dir || !token) {
8130
+ console.error("Usage: board-live-cards task-failed --rg <dir> --token <token> [--error <message>]");
8131
+ process.exit(1);
8211
8132
  }
8212
- const existingByPath = byCardPath.get(normalizedPath);
8213
- if (existingByPath && existingByPath.cardId !== entry.cardId) {
8214
- throw new Error(
8215
- `Inventory invariant violation: file "${normalizedPath}" maps to multiple ids: "${existingByPath.cardId}" and "${entry.cardId}"`
8216
- );
8133
+ const decoded = deps.decodeCallbackToken(token);
8134
+ if (!decoded) {
8135
+ console.error("Invalid callback token");
8136
+ process.exit(1);
8217
8137
  }
8218
- byCardId.set(entry.cardId, normalizedEntry);
8219
- byCardPath.set(normalizedPath, normalizedEntry);
8220
- }
8221
- return { byCardId, byCardPath };
8222
- }
8223
- function initBoard(dir) {
8224
- const boardPath = path.join(dir, BOARD_FILE);
8225
- if (fs.existsSync(boardPath)) {
8226
- const envelope2 = JSON.parse(fs.readFileSync(boardPath, "utf-8"));
8227
- restore(envelope2.graph);
8228
- return "exists";
8229
- }
8230
- if (fs.existsSync(dir)) {
8231
- const entries = fs.readdirSync(dir);
8232
- if (entries.length > 0) {
8233
- throw new Error(`Directory "${dir}" is not empty and has no valid ${BOARD_FILE}`);
8138
+ deps.appendEventToJournal(dir, {
8139
+ type: "task-failed",
8140
+ taskName: decoded.taskName,
8141
+ error: errorMsg,
8142
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8143
+ });
8144
+ void deps.processAccumulatedEventsForced(dir);
8145
+ console.log("Task failed.");
8146
+ }
8147
+ function cmdSourceDataFetched(args) {
8148
+ const tmpIdx = args.indexOf("--tmp");
8149
+ const tokenIdx = args.indexOf("--token");
8150
+ const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
8151
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8152
+ if (!tmpFile || !token) {
8153
+ console.error("Usage: board-live-cards source-data-fetched --tmp <tmp-file> --token <sourceToken>");
8154
+ process.exit(1);
8155
+ }
8156
+ const payload = deps.decodeSourceToken(token);
8157
+ if (!payload) {
8158
+ console.error("Invalid source token");
8159
+ process.exit(1);
8160
+ }
8161
+ const { cbk, rg, cid, b, d, cs } = payload;
8162
+ const destPath = path7.join(rg, cid, d);
8163
+ fs7.mkdirSync(path7.dirname(destPath), { recursive: true });
8164
+ fs7.renameSync(tmpFile, destPath);
8165
+ console.log(`[source-data-fetched] ${cid}.${b} -> ${cid}/${d}`);
8166
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
8167
+ const cbkDecoded = deps.decodeCallbackToken(cbk);
8168
+ if (!cbkDecoded) {
8169
+ console.error("Invalid callback token embedded in source token");
8170
+ process.exit(1);
8171
+ }
8172
+ deps.appendEventToJournal(rg, {
8173
+ type: "task-progress",
8174
+ taskName: cbkDecoded.taskName,
8175
+ update: { bindTo: b, outputFile: d, fetchedAt, sourceChecksum: cs },
8176
+ timestamp: fetchedAt
8177
+ });
8178
+ void deps.processAccumulatedEventsInfinitePass(rg);
8179
+ }
8180
+ function cmdSourceDataFetchFailure(args) {
8181
+ const tokenIdx = args.indexOf("--token");
8182
+ const reasonIdx = args.indexOf("--reason");
8183
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8184
+ const reason = reasonIdx !== -1 ? args[reasonIdx + 1] : "unknown";
8185
+ if (!token) {
8186
+ console.error("Usage: board-live-cards source-data-fetch-failure --token <sourceToken> [--reason <msg>]");
8187
+ process.exit(1);
8188
+ }
8189
+ const payload = deps.decodeSourceToken(token);
8190
+ if (!payload) {
8191
+ console.error("Invalid source token");
8192
+ process.exit(1);
8193
+ }
8194
+ const { cbk, rg, cid, b, d, cs } = payload;
8195
+ console.log(`[source-data-fetch-failure] ${cid}.${b}: ${reason}`);
8196
+ const cbkDecoded = deps.decodeCallbackToken(cbk);
8197
+ if (!cbkDecoded) {
8198
+ console.error("Invalid callback token embedded in source token");
8199
+ process.exit(1);
8200
+ }
8201
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8202
+ deps.appendEventToJournal(rg, {
8203
+ type: "task-progress",
8204
+ taskName: cbkDecoded.taskName,
8205
+ update: { bindTo: b, outputFile: d, failure: true, reason, sourceChecksum: cs },
8206
+ timestamp
8207
+ });
8208
+ void deps.processAccumulatedEventsInfinitePass(rg);
8209
+ }
8210
+ function cmdTaskProgress(args) {
8211
+ const rgIdx = args.indexOf("--rg");
8212
+ const tokenIdx = args.indexOf("--token");
8213
+ const updateIdx = args.indexOf("--update");
8214
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8215
+ const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8216
+ const updateJson = updateIdx !== -1 ? args[updateIdx + 1] : "{}";
8217
+ if (!dir || !token) {
8218
+ console.error("Usage: board-live-cards task-progress --rg <dir> --token <token> [--update <json>]");
8219
+ process.exit(1);
8220
+ }
8221
+ const decoded = deps.decodeCallbackToken(token);
8222
+ if (!decoded) {
8223
+ console.error("Invalid callback token");
8224
+ process.exit(1);
8234
8225
  }
8226
+ const update = updateJson ? JSON.parse(updateJson) : {};
8227
+ deps.appendEventToJournal(dir, {
8228
+ type: "task-progress",
8229
+ taskName: decoded.taskName,
8230
+ update,
8231
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8232
+ });
8233
+ void deps.processAccumulatedEventsInfinitePass(dir);
8235
8234
  }
8236
- fs.mkdirSync(dir, { recursive: true });
8237
- const live = createLiveGraph(EMPTY_CONFIG);
8238
- const snap = snapshot(live);
8239
- const envelope = { lastDrainedJournalId: "", graph: snap };
8240
- fs.writeFileSync(boardPath, JSON.stringify(envelope, null, 2));
8241
- return "created";
8242
- }
8243
- function loadBoardEnvelope(dir) {
8244
- const raw = fs.readFileSync(path.join(dir, BOARD_FILE), "utf-8");
8245
- return JSON.parse(raw);
8246
- }
8247
- function loadBoard(dir) {
8248
- const envelope = loadBoardEnvelope(dir);
8249
- return restore(envelope.graph);
8250
- }
8251
- function saveBoard(dir, rg, journal) {
8252
- const snap = rg.snapshot();
8253
- const envelope = {
8254
- lastDrainedJournalId: journal.lastDrainedJournalId,
8255
- graph: snap
8235
+ return {
8236
+ cmdTaskCompleted,
8237
+ cmdTaskFailed,
8238
+ cmdTaskProgress,
8239
+ cmdSourceDataFetched,
8240
+ cmdSourceDataFetchFailure
8256
8241
  };
8257
- writeJsonAtomic(path.join(dir, BOARD_FILE), envelope);
8258
- const live = restore(snap);
8259
- const statusObject = buildBoardStatusObject(dir, live);
8260
- writeJsonAtomic(resolveStatusSnapshotPath(dir), statusObject);
8261
8242
  }
8262
- function runtimeOutConfigPath(boardDir) {
8263
- return path.join(boardDir, RUNTIME_OUT_FILE);
8264
- }
8265
- function resolveConfiguredRuntimeOutDir(boardDir) {
8266
- const cfgPath = runtimeOutConfigPath(boardDir);
8267
- if (fs.existsSync(cfgPath)) {
8268
- const configured = fs.readFileSync(cfgPath, "utf-8").trim();
8269
- if (configured) {
8270
- return path.isAbsolute(configured) ? configured : path.resolve(boardDir, configured);
8243
+ function createNonCoreCommandHandlers(deps) {
8244
+ function cmdRunSourceFetch(args) {
8245
+ const inIdx = args.indexOf("--in");
8246
+ const outIdx = args.indexOf("--out");
8247
+ const errIdx = args.indexOf("--err");
8248
+ const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
8249
+ const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
8250
+ const errFile = errIdx !== -1 ? args[errIdx + 1] : void 0;
8251
+ if (!inFile || !outFile) {
8252
+ console.error("Usage: board-live-cards run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]");
8253
+ process.exit(1);
8254
+ }
8255
+ if (!fs7.existsSync(inFile)) {
8256
+ const msg = `Input file not found: ${inFile}`;
8257
+ if (errFile) fs7.writeFileSync(errFile, msg);
8258
+ console.error(`[run-source-fetch] ${msg}`);
8259
+ process.exit(1);
8260
+ }
8261
+ let source;
8262
+ try {
8263
+ const raw = fs7.readFileSync(inFile, "utf-8");
8264
+ source = JSON.parse(raw);
8265
+ } catch (err) {
8266
+ const msg = `Failed to parse input file: ${err.message}`;
8267
+ if (errFile) fs7.writeFileSync(errFile, msg);
8268
+ console.error(`[run-source-fetch] ${msg}`);
8269
+ process.exit(1);
8270
+ }
8271
+ if (!source.cli) {
8272
+ const msg = "Source definition missing cli field (board-live-cards built-in executor only understands source.cli)";
8273
+ if (errFile) fs7.writeFileSync(errFile, msg);
8274
+ console.error(`[run-source-fetch] ${msg}`);
8275
+ process.exit(1);
8276
+ }
8277
+ console.log(`[run-source-fetch] executing: ${source.cli}`);
8278
+ const timeout = source.timeout ?? 12e4;
8279
+ const sourceCwd = typeof source.cwd === "string" ? source.cwd : process.cwd();
8280
+ const sourceBoardDir = typeof source.boardDir === "string" ? source.boardDir : void 0;
8281
+ const cmdParts = deps.splitCommandLine(source.cli);
8282
+ if (cmdParts.length === 0) {
8283
+ const msg = "Source cli command is empty";
8284
+ if (errFile) fs7.writeFileSync(errFile, msg);
8285
+ console.error(`[run-source-fetch] ${msg}`);
8286
+ process.exit(1);
8287
+ }
8288
+ const rawCmd = cmdParts[0];
8289
+ const { cmd, args: cliArgs } = deps.resolveCommandInvocation(rawCmd, cmdParts.slice(1));
8290
+ let stdout;
8291
+ try {
8292
+ stdout = deps.execCommandSync(cmd, cliArgs, {
8293
+ shell: false,
8294
+ encoding: "utf-8",
8295
+ timeout,
8296
+ cwd: sourceCwd,
8297
+ env: {
8298
+ ...process.env,
8299
+ ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
8300
+ }
8301
+ });
8302
+ } catch (err) {
8303
+ const msg = err.message ?? String(err);
8304
+ console.error(`[run-source-fetch] cli failed: ${msg}`);
8305
+ if (errFile) fs7.writeFileSync(errFile, msg);
8306
+ process.exit(1);
8307
+ }
8308
+ const result = stdout.trim();
8309
+ try {
8310
+ fs7.writeFileSync(outFile, result);
8311
+ console.log(`[run-source-fetch] result written to ${outFile}`);
8312
+ } catch (err) {
8313
+ const msg = `Failed to write output file: ${err.message}`;
8314
+ console.error(`[run-source-fetch] ${msg}`);
8315
+ if (errFile) fs7.writeFileSync(errFile, msg);
8316
+ process.exit(1);
8271
8317
  }
8272
8318
  }
8273
- const defaultDir = path.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
8274
- fs.writeFileSync(cfgPath, defaultDir, "utf-8");
8275
- return defaultDir;
8276
- }
8277
- function configureRuntimeOutDir(boardDir, runtimeOut) {
8278
- let resolved;
8279
- if (runtimeOut) {
8280
- resolved = path.isAbsolute(runtimeOut) ? runtimeOut : path.resolve(boardDir, runtimeOut);
8281
- } else {
8282
- resolved = path.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
8283
- }
8284
- fs.mkdirSync(resolved, { recursive: true });
8285
- fs.writeFileSync(runtimeOutConfigPath(boardDir), resolved, "utf-8");
8286
- return resolved;
8287
- }
8288
- function resolveStatusSnapshotPath(boardDir) {
8289
- return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_STATUS_FILE);
8290
- }
8291
- function resolveComputedValuesPath(boardDir, cardId) {
8292
- return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_CARDS_DIR, `${cardId}.computed.json`);
8293
- }
8294
- function resolveDataObjectsDirPath(boardDir) {
8295
- return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_DATA_OBJECTS_DIR);
8296
- }
8297
- function toDataObjectFileName(token) {
8298
- return token.replace(/[\\/]/g, "__");
8299
- }
8300
- function writeRuntimeDataObjects(boardDir, data) {
8301
- for (const [token, payload] of Object.entries(data)) {
8302
- if (!token) continue;
8303
- const fileName = toDataObjectFileName(token);
8304
- if (!fileName) continue;
8305
- const filePath = path.join(resolveDataObjectsDirPath(boardDir), fileName);
8306
- writeJsonAtomic(filePath, payload);
8307
- }
8308
- }
8309
- function writeJsonAtomic(filePath, payload) {
8310
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
8311
- const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
8312
- fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
8313
- fs.renameSync(tmpPath, filePath);
8314
- }
8315
- function withBoardLock(boardDir, fn) {
8316
- const boardPath = path.join(boardDir, BOARD_FILE);
8317
- const release = lockSync(boardPath, { retries: { retries: 5, minTimeout: 100 } });
8318
- try {
8319
- return fn();
8320
- } finally {
8321
- release();
8319
+ async function cmdProbeSource(args) {
8320
+ const cardIdx = args.indexOf("--card");
8321
+ const sourceIdxArg = args.indexOf("--source-idx");
8322
+ const sourceBindArg = args.indexOf("--source-bind");
8323
+ const mockProjectionsIdx = args.indexOf("--mock-projections");
8324
+ const rgIdx = args.indexOf("--rg");
8325
+ const outIdx = args.indexOf("--out");
8326
+ const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
8327
+ const sourceIdxVal = sourceIdxArg !== -1 ? parseInt(args[sourceIdxArg + 1], 10) : 0;
8328
+ const sourceBindVal = sourceBindArg !== -1 ? args[sourceBindArg + 1] : void 0;
8329
+ const mockProjectionsRaw = mockProjectionsIdx !== -1 ? args[mockProjectionsIdx + 1] : void 0;
8330
+ const boardDirArg = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8331
+ const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
8332
+ if (!cardFilePath) {
8333
+ console.error("Usage: board-live-cards probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>] [--mock-projections <json>] [--rg <boardDir>] [--out <result.json>]");
8334
+ process.exit(1);
8335
+ }
8336
+ let card;
8337
+ try {
8338
+ card = JSON.parse(fs7.readFileSync(path7.resolve(cardFilePath), "utf-8"));
8339
+ } catch (e) {
8340
+ console.error(`[probe-source] Cannot read card: ${e.message}`);
8341
+ process.exit(1);
8342
+ }
8343
+ const source_defs = card.source_defs ?? [];
8344
+ if (source_defs.length === 0) {
8345
+ console.error(`[probe-source] Card "${card.id}" has no source_defs`);
8346
+ process.exit(1);
8347
+ }
8348
+ let sourceIdx;
8349
+ if (sourceBindVal) {
8350
+ sourceIdx = source_defs.findIndex((s) => s.bindTo === sourceBindVal);
8351
+ if (sourceIdx === -1) {
8352
+ console.error(`[probe-source] No source with bindTo="${sourceBindVal}" in card "${card.id}"`);
8353
+ process.exit(1);
8354
+ }
8355
+ } else {
8356
+ sourceIdx = sourceIdxVal;
8357
+ if (isNaN(sourceIdx) || sourceIdx < 0 || sourceIdx >= source_defs.length) {
8358
+ console.error(`[probe-source] --source-idx ${sourceIdxVal} out of range (card has ${source_defs.length} source(s))`);
8359
+ process.exit(1);
8360
+ }
8361
+ }
8362
+ const sourceDef = source_defs[sourceIdx];
8363
+ const cardDir = path7.resolve(path7.dirname(cardFilePath));
8364
+ const boardDir = boardDirArg ? path7.resolve(boardDirArg) : cardDir;
8365
+ let mockProjections = {};
8366
+ if (mockProjectionsRaw) {
8367
+ const raw = mockProjectionsRaw.startsWith("@") ? fs7.readFileSync(path7.resolve(mockProjectionsRaw.slice(1)), "utf-8") : mockProjectionsRaw;
8368
+ try {
8369
+ mockProjections = JSON.parse(raw);
8370
+ } catch (e) {
8371
+ console.error(`[probe-source] --mock-projections is not valid JSON: ${e.message}`);
8372
+ process.exit(1);
8373
+ }
8374
+ }
8375
+ const teConfig = deps.readTaskExecutorConfig(boardDir);
8376
+ const taskExecutor = teConfig?.command;
8377
+ const taskExecutorExtraB64 = teConfig?.extra ? Buffer.from(JSON.stringify(teConfig.extra)).toString("base64") : void 0;
8378
+ const inPayload = {
8379
+ ...sourceDef,
8380
+ cwd: typeof sourceDef.cwd === "string" && sourceDef.cwd ? sourceDef.cwd : cardDir,
8381
+ boardDir: typeof sourceDef.boardDir === "string" && sourceDef.boardDir ? sourceDef.boardDir : boardDir,
8382
+ _projections: mockProjections
8383
+ };
8384
+ let sourceKind = "unknown";
8385
+ if (taskExecutor) {
8386
+ try {
8387
+ const capRaw = deps.execCommandSync(taskExecutor, ["describe-capabilities"], {
8388
+ shell: true,
8389
+ timeout: 8e3,
8390
+ encoding: "utf-8"
8391
+ });
8392
+ const caps = JSON.parse(String(capRaw));
8393
+ const knownKinds = caps?.sourceKinds ? Object.keys(caps.sourceKinds) : [];
8394
+ const defKeys = new Set(Object.keys(sourceDef));
8395
+ sourceKind = knownKinds.find((k) => defKeys.has(k)) ?? "unknown";
8396
+ } catch {
8397
+ }
8398
+ }
8399
+ console.log(`[probe-source] card: ${card.id}`);
8400
+ console.log(`[probe-source] source[${sourceIdx}]: bindTo="${sourceDef.bindTo}" kind=${sourceKind}`);
8401
+ console.log(`[probe-source] _projections: ${JSON.stringify(mockProjections)}`);
8402
+ console.log(`[probe-source] executor: ${taskExecutor ?? "built-in (source.cli only)"}`);
8403
+ console.log("[probe-source] running fetch...");
8404
+ const ts = Date.now();
8405
+ const inFile = path7.join(os3.tmpdir(), `probe-in-${sourceDef.bindTo}-${ts}.json`);
8406
+ const tmpOut = path7.join(os3.tmpdir(), `probe-out-${sourceDef.bindTo}-${ts}.json`);
8407
+ const errFile = path7.join(os3.tmpdir(), `probe-err-${sourceDef.bindTo}-${ts}.txt`);
8408
+ fs7.writeFileSync(inFile, JSON.stringify(inPayload, null, 2), "utf-8");
8409
+ let passed = false;
8410
+ let errorMsg;
8411
+ let resultRaw;
8412
+ try {
8413
+ if (taskExecutor) {
8414
+ const executorArgs = ["run-source-fetch", "--in", inFile, "--out", tmpOut, "--err", errFile];
8415
+ if (taskExecutorExtraB64) executorArgs.push("--extra", taskExecutorExtraB64);
8416
+ deps.execCommandSync(taskExecutor, executorArgs, {
8417
+ shell: true,
8418
+ timeout: sourceDef.timeout ?? 3e4
8419
+ });
8420
+ } else {
8421
+ if (!inPayload.cli) {
8422
+ throw new Error("No task-executor registered and source has no cli field \u2014 cannot probe with built-in executor");
8423
+ }
8424
+ const cmdParts = deps.splitCommandLine(inPayload.cli);
8425
+ const rawCmd = cmdParts[0];
8426
+ const { cmd, args: cliArgs } = deps.resolveCommandInvocation(rawCmd, cmdParts.slice(1));
8427
+ const stdout = deps.execCommandSync(cmd, cliArgs, {
8428
+ shell: false,
8429
+ encoding: "utf-8",
8430
+ timeout: sourceDef.timeout ?? 3e4,
8431
+ cwd: inPayload.cwd
8432
+ });
8433
+ fs7.writeFileSync(tmpOut, String(stdout).trim(), "utf-8");
8434
+ }
8435
+ passed = fs7.existsSync(tmpOut);
8436
+ if (passed) {
8437
+ resultRaw = fs7.readFileSync(tmpOut, "utf-8");
8438
+ } else {
8439
+ errorMsg = fs7.existsSync(errFile) ? fs7.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
8440
+ }
8441
+ } catch (e) {
8442
+ errorMsg = e.message ?? String(e);
8443
+ if (!errorMsg && fs7.existsSync(errFile)) {
8444
+ errorMsg = fs7.readFileSync(errFile, "utf-8").trim();
8445
+ }
8446
+ }
8447
+ for (const f of [inFile, errFile]) {
8448
+ try {
8449
+ fs7.unlinkSync(f);
8450
+ } catch {
8451
+ }
8452
+ }
8453
+ if (passed && resultRaw !== void 0) {
8454
+ const resultSize = resultRaw.length;
8455
+ const sample = resultRaw.slice(0, 300);
8456
+ console.log("[probe-source] STATUS: PROBE_PASS");
8457
+ console.log(`[probe-source] result size: ${resultSize} bytes`);
8458
+ console.log(`[probe-source] sample: ${sample}${resultSize > 300 ? "..." : ""}`);
8459
+ if (outFile) {
8460
+ fs7.writeFileSync(path7.resolve(outFile), resultRaw);
8461
+ console.log(`[probe-source] result written to: ${outFile}`);
8462
+ } else {
8463
+ try {
8464
+ fs7.unlinkSync(tmpOut);
8465
+ } catch {
8466
+ }
8467
+ }
8468
+ } else {
8469
+ console.log("[probe-source] STATUS: PROBE_FAIL");
8470
+ if (errorMsg) console.log(`[probe-source] error: ${errorMsg}`);
8471
+ try {
8472
+ if (fs7.existsSync(tmpOut)) fs7.unlinkSync(tmpOut);
8473
+ } catch {
8474
+ }
8475
+ }
8476
+ const summary = {
8477
+ status: passed ? "PROBE_PASS" : "PROBE_FAIL",
8478
+ cardId: card.id,
8479
+ sourceIdx,
8480
+ bindTo: sourceDef.bindTo,
8481
+ sourceKind,
8482
+ mockProjectionsKeys: Object.keys(mockProjections),
8483
+ resultSizeBytes: resultRaw !== void 0 ? resultRaw.length : 0,
8484
+ error: errorMsg ?? void 0
8485
+ };
8486
+ console.log(`[probe-source:result] ${JSON.stringify(summary)}`);
8487
+ process.exit(passed ? 0 : 1);
8488
+ }
8489
+ function cmdDescribeTaskExecutorCapabilities(args) {
8490
+ const rgIdx = args.indexOf("--rg");
8491
+ const boardDir = rgIdx !== -1 ? path7.resolve(args[rgIdx + 1]) : void 0;
8492
+ if (!boardDir) {
8493
+ console.error("Usage: board-live-cards describe-task-executor-capabilities --rg <dir>");
8494
+ process.exit(1);
8495
+ }
8496
+ const teConfig = deps.readTaskExecutorConfig(boardDir);
8497
+ if (!teConfig) {
8498
+ console.error(`[describe-task-executor-capabilities] No .task-executor registered in ${boardDir}`);
8499
+ process.exit(1);
8500
+ }
8501
+ try {
8502
+ const stdout = deps.execCommandSync(teConfig.command, ["describe-capabilities"], {
8503
+ shell: true,
8504
+ timeout: 1e4,
8505
+ encoding: "utf-8"
8506
+ });
8507
+ process.stdout.write(String(stdout));
8508
+ if (!String(stdout).endsWith("\n")) process.stdout.write("\n");
8509
+ } catch (e) {
8510
+ console.error(`[describe-task-executor-capabilities] Executor failed: ${e.message ?? e}`);
8511
+ process.exit(1);
8512
+ }
8322
8513
  }
8323
- }
8324
- function decodeCallbackToken2(token) {
8325
- try {
8326
- const payload = JSON.parse(Buffer.from(token, "base64url").toString());
8327
- if (typeof payload?.t === "string") return { taskName: payload.t };
8328
- return null;
8329
- } catch {
8330
- return null;
8514
+ function cmdHelp() {
8515
+ console.log(`
8516
+ board-live-cards-cli \u2014 LiveCards board CLI
8517
+
8518
+ USAGE
8519
+ board-live-cards-cli <command> [options]
8520
+
8521
+ BOARD MANAGEMENT
8522
+ init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]
8523
+ Create a new board in <dir>.
8524
+ If --task-executor is given, writes <dir>/.task-executor with the script path.
8525
+ If --chat-handler is given, writes <dir>/.chat-handler with the script path.
8526
+ If --inference-adapter is given, writes <dir>/.inference-adapter with the script path.
8527
+ Writes <dir>/.runtime-out (default: <dir>/runtime-out).
8528
+ Published runtime files:
8529
+ <runtime-out>/board-livegraph-status.json
8530
+ <runtime-out>/cards/<card-id>.computed.json
8531
+ Re-running init on an existing board is safe; handler registrations are updated.
8532
+
8533
+ status --rg <dir> [--json]
8534
+ Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
8535
+ --json emits the stable machine-readable status object.
8536
+
8537
+ CARD MANAGEMENT
8538
+ upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]
8539
+ Insert or update one or many cards.
8540
+ Enforces strict one-to-one mapping between card id and file path:
8541
+ - same id + same file path: update
8542
+ - new id + new file path: insert
8543
+ - id remap or file remap: rejected
8544
+ If --card-id is provided, it must match the id inside the file.
8545
+ --card-id is valid only with --card (single file), not with --card-glob.
8546
+ --restart clears the task so it re-triggers from scratch.
8547
+
8548
+ validate-card (--card <card.json> | --card-glob <glob>) [--rg <boardDir>]
8549
+ Validate one or many card JSON files without adding them to a board.
8550
+ Checks JSON Schema structure, runtime expression syntax, and provides.ref namespaces.
8551
+ When --rg is provided, also invokes the board's task executor validate-source-def
8552
+ subcommand to structurally validate each source definition against supported kinds.
8553
+ Exits with code 1 if any card fails validation.
8554
+
8555
+ remove-card --rg <dir> --id <card-id>
8556
+ Remove a card and its task from the board.
8557
+
8558
+ retrigger --rg <dir> --task <task-name>
8559
+ Mark a task not-started and drain to re-trigger it.
8560
+
8561
+ TASK CALLBACKS (called by task executor scripts)
8562
+ task-completed --token <callbackToken> [--data <json>]
8563
+ Signal successful task completion with optional JSON result data.
8564
+
8565
+ task-failed --token <callbackToken> [--error <message>]
8566
+ Signal task failure with an optional error message.
8567
+
8568
+ task-progress --rg <dir> --token <callbackToken> [--update <json>]
8569
+ Signal task progress with optional update payload (for waiting on more evidence, etc.).
8570
+
8571
+ SOURCE CALLBACKS (called internally by run-sourcedefs-internal)
8572
+ source-data-fetched --tmp <file> --token <sourceToken>
8573
+ Atomically rename <file> into the outputFile destination and record delivery
8574
+ via journal events. Appends a task-progress event to re-invoke the card handler.
8575
+
8576
+ source-data-fetch-failure --token <sourceToken> [--reason <message>]
8577
+ Record a source fetch failure via journal events and append a task-progress event.
8578
+
8579
+ INTERNAL COMMANDS
8580
+ process-accumulated-events --rg <dir>
8581
+ Executes forced drain for this board.
8582
+ This command is also used as the background relay worker.
8583
+ By default it schedules a detached worker and returns quickly.
8584
+ Internal workers run with --inline-loop to perform the settle loop.
8585
+
8586
+ Eventual-progress guarantee is relay-based (not per-call blocking guarantee):
8587
+ 1) at least one runner continues processing,
8588
+ 2) no crash/forced exit in relay window,
8589
+ 3) lock stays healthy,
8590
+ 4) event production eventually quiesces.
8591
+
8592
+ run-sourcedefs-internal --card <card.json> --token <callbackToken> --rg <dir>
8593
+ Execute all source[] entries for a card, then report delivery or failure.
8594
+ (Internal command \u2014 invoked by the card-handler. Not intended for direct use.)
8595
+
8596
+ If <dir>/.task-executor exists, invokes it with run-source-fetch subcommand:
8597
+ <executor> run-source-fetch --in <source_json> --out <outfile> --err <errfile>
8598
+
8599
+ If no .task-executor is registered, uses board-live-cards built-in run-source-fetch.
8600
+
8601
+ run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
8602
+ Execute a source definition. Board-live-cards reads source.cli and executes it.
8603
+ Writes result to --out. Presence of --out after exit indicates success.
8604
+
8605
+ describe-task-executor-capabilities --rg <dir>
8606
+ Invoke the registered task-executor's describe-capabilities subcommand and
8607
+ print its capabilities JSON to stdout. Requires a .task-executor file in <dir>.
8608
+
8609
+ probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>]
8610
+ [--mock-projections <json>] [--rg <boardDir>] [--out <result.json>]
8611
+ Validate that a card source can be fetched successfully.
8612
+ Reads the card file, extracts the chosen source (default: index 0), builds the
8613
+ run-source-fetch --in payload with the supplied _projections data, invokes the
8614
+ registered task-executor (or built-in executor for source.cli), and reports pass/fail.
8615
+ --mock-projections: JSON string (or @file.json) providing pre-resolved _projections values
8616
+ the source needs. Craft the minimal payload that exercises the
8617
+ source \u2014 e.g. '{"holdings":[{"ticker":"AAPL","quantity":10}]}'.
8618
+ If omitted, _projections is passed as empty ({}).
8619
+ --source-idx: 0-based index into card.source_defs[]. Default: 0.
8620
+ --source-bind: Select source by its bindTo name instead of index.
8621
+ --rg: Board directory used to find .task-executor. Defaults to the
8622
+ directory containing the card file.
8623
+ --out: Optional path to write the raw fetch result JSON.
8624
+ Prints a structured report ending with a [probe-source:result] JSON line.
8625
+ Exits 0 on PROBE_PASS, 1 on PROBE_FAIL.
8626
+
8627
+ run-inference-internal --in <input.json> --token <inferenceToken>
8628
+ Execute inference via registered .inference-adapter and forward result to inference-done.
8629
+ inferenceToken encodes boardDir (rg), cardId (cid), callbackToken (cbk), checksum (cs).
8630
+ (Internal command \u2014 invoked by the card-handler when custom completion rule is used.)
8631
+
8632
+ inference-done --tmp <result.json> --token <inferenceToken>
8633
+ Persist llm_task_completion_inference on the card and append a task-progress event.
8634
+ Reads boardDir/callbackToken/checksum from decoded inferenceToken; deletes --tmp file after reading.
8635
+ (Internal command \u2014 invoked by run-inference-internal.)
8636
+
8637
+ RUN-SOURCE-FETCH PROTOCOL
8638
+ External task-executors implement:
8639
+ <executor> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
8640
+
8641
+ INPUT: --in file contains the full source_defs[x] definition object
8642
+ OUTPUT: --out file is written with the result to signal success.
8643
+ --err file may be written to explain failure.
8644
+
8645
+ Exit code and --out presence determine success:
8646
+ Exit 0 + --out file present \u2192 source delivery recorded, card re-evaluated.
8647
+ Exit non-zero OR --out absent \u2192 source-data-fetch-failure recorded.
8648
+
8649
+ BOARD-LIVE-CARDS BUILT-IN EXECUTOR
8650
+ Understands source.cli field only:
8651
+ "source_defs": [{ "cli": "node ../fetch-prices.js", "bindTo": "prices", "outputFile": "prices.json" }]
8652
+
8653
+ The source.cli command is executed with:
8654
+ - Direct command invocation (no shell; quote-aware argument parsing)
8655
+ - Stdout is captured and delivered to the card as-is
8656
+ - Timeout from source.timeout (default 120s)
8657
+
8658
+ The source.cli command must:
8659
+ - Execute successfully (exit 0)
8660
+ - Write output to stdout
8661
+ - Complete within the timeout
8662
+
8663
+ The output format is the concern of the card's compute function to interpret.
8664
+
8665
+ External task-executors can interpret source definitions however they want.
8666
+
8667
+ EXAMPLES
8668
+ board-live-cards-cli init ./my-board
8669
+ board-live-cards-cli init ./my-board --task-executor ./executors/my-runner.py
8670
+ board-live-cards-cli upsert-card --rg ./my-board --card cards/prices.json
8671
+ board-live-cards-cli status --rg ./my-board
8672
+ board-live-cards-cli retrigger --rg ./my-board --task price-fetch
8673
+ board-live-cards-cli probe-source --card cards/card-market-prices.json --source-idx 0 --rg ./my-board --mock-projections '{"holdings":[{"ticker":"AAPL","quantity":10}]}'
8674
+ `.trimStart());
8331
8675
  }
8676
+ return {
8677
+ cmdHelp,
8678
+ cmdRunSourceFetch,
8679
+ cmdProbeSource,
8680
+ cmdDescribeTaskExecutorCapabilities
8681
+ };
8332
8682
  }
8333
- function encodeSourceToken(payload) {
8334
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
8335
- }
8336
- function decodeSourceToken(token) {
8337
- try {
8338
- const p = JSON.parse(Buffer.from(token, "base64url").toString());
8339
- if (typeof p?.cbk === "string" && typeof p?.cid === "string" && typeof p?.b === "string" && typeof p?.d === "string") {
8340
- return p;
8683
+ function createCardCommandHandlers(deps) {
8684
+ function cmdUpsertCard(args) {
8685
+ const rgIdx = args.indexOf("--rg");
8686
+ const cardIdx = args.indexOf("--card");
8687
+ const globIdx = args.indexOf("--card-glob");
8688
+ const cardIdIdx = args.indexOf("--card-id");
8689
+ const restart = args.includes("--restart");
8690
+ const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8691
+ const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
8692
+ const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
8693
+ const requestedCardId = cardIdIdx !== -1 ? args[cardIdIdx + 1] : void 0;
8694
+ if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
8695
+ console.error("Usage: board-live-cards upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]");
8696
+ process.exit(1);
8697
+ }
8698
+ if (cardGlob && requestedCardId) {
8699
+ console.error("Usage: --card-id may be used only with --card (single file), not with --card-glob");
8700
+ process.exit(1);
8701
+ }
8702
+ const cardFiles = cardFile ? [path7.resolve(cardFile)] : deps.resolveCardGlobMatches(cardGlob);
8703
+ if (!cardFile && cardFiles.length === 0) {
8704
+ console.error(`No card files matched glob: ${cardGlob}`);
8705
+ process.exit(1);
8706
+ }
8707
+ const idx = deps.buildCardInventoryIndex(dir);
8708
+ const batchByCardId = /* @__PURE__ */ new Map();
8709
+ const batchByCardPath = /* @__PURE__ */ new Map();
8710
+ const plans = [];
8711
+ const logs = [];
8712
+ for (const absCardPath of cardFiles) {
8713
+ if (!fs7.existsSync(absCardPath)) {
8714
+ console.error(`Card file not found: ${absCardPath}`);
8715
+ process.exit(1);
8716
+ }
8717
+ const card = JSON.parse(fs7.readFileSync(absCardPath, "utf-8"));
8718
+ if (!card.id) {
8719
+ console.error(`Card JSON must have an "id" field (${absCardPath})`);
8720
+ process.exit(1);
8721
+ }
8722
+ if (requestedCardId && requestedCardId !== card.id) {
8723
+ console.error(
8724
+ `Card id mismatch: --card-id "${requestedCardId}" does not match file id "${card.id}" (${absCardPath})`
8725
+ );
8726
+ process.exit(1);
8727
+ }
8728
+ const seenPathCardId = batchByCardPath.get(absCardPath);
8729
+ if (seenPathCardId && seenPathCardId !== card.id) {
8730
+ console.error(
8731
+ `Upsert rejected: file "${absCardPath}" appears multiple times in batch with conflicting ids ("${seenPathCardId}" vs "${card.id}")`
8732
+ );
8733
+ process.exit(1);
8734
+ }
8735
+ const seenCardPath = batchByCardId.get(card.id);
8736
+ if (seenCardPath && seenCardPath !== absCardPath) {
8737
+ console.error(
8738
+ `Upsert rejected: card id "${card.id}" appears multiple times in batch with conflicting files ("${seenCardPath}" vs "${absCardPath}")`
8739
+ );
8740
+ process.exit(1);
8741
+ }
8742
+ const existingById = idx.byCardId.get(card.id);
8743
+ const existingByPath = idx.byCardPath.get(absCardPath);
8744
+ if (existingByPath && existingByPath.cardId !== card.id) {
8745
+ console.error(
8746
+ `Upsert rejected: file "${absCardPath}" is already mapped to card id "${existingByPath.cardId}", cannot remap to "${card.id}"`
8747
+ );
8748
+ process.exit(1);
8749
+ }
8750
+ if (existingById && existingById.cardFilePath !== absCardPath) {
8751
+ console.error(
8752
+ `Upsert rejected: card id "${card.id}" is already mapped to file "${existingById.cardFilePath}", cannot remap to "${absCardPath}"`
8753
+ );
8754
+ process.exit(1);
8755
+ }
8756
+ batchByCardPath.set(absCardPath, card.id);
8757
+ batchByCardId.set(card.id, absCardPath);
8758
+ plans.push({
8759
+ card,
8760
+ absCardPath,
8761
+ isInsert: !existingById
8762
+ });
8763
+ }
8764
+ for (const plan of plans) {
8765
+ const { card, absCardPath, isInsert } = plan;
8766
+ if (isInsert) {
8767
+ const newEntry = {
8768
+ cardId: card.id,
8769
+ cardFilePath: absCardPath,
8770
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
8771
+ };
8772
+ deps.appendCardInventory(dir, newEntry);
8773
+ idx.byCardId.set(card.id, newEntry);
8774
+ idx.byCardPath.set(absCardPath, newEntry);
8775
+ }
8776
+ const taskConfig = deps.liveCardToTaskConfig(card);
8777
+ deps.appendEventToJournal(dir, {
8778
+ type: "task-upsert",
8779
+ taskName: card.id,
8780
+ taskConfig,
8781
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8782
+ });
8783
+ if (restart) {
8784
+ deps.appendEventToJournal(dir, {
8785
+ type: "task-restart",
8786
+ taskName: card.id,
8787
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8788
+ });
8789
+ }
8790
+ logs.push(`Card "${card.id}" ${isInsert ? "upserted (inserted)" : "upserted (updated)"}${restart ? " (restarted)" : ""}.`);
8791
+ }
8792
+ void deps.processAccumulatedEventsInfinitePass(dir);
8793
+ if (cardGlob) {
8794
+ console.log(`Upserted ${cardFiles.length} cards from glob: ${cardGlob}${restart ? " (restarted)" : ""}`);
8795
+ } else {
8796
+ console.log(logs[0]);
8341
8797
  }
8342
- return null;
8343
- } catch {
8344
- return null;
8345
- }
8346
- }
8347
- function markRequested(entry, requestedAt) {
8348
- entry.lastRequestedAt = requestedAt;
8349
- }
8350
- function markFetchFailed(entry, reason) {
8351
- entry.lastError = reason;
8352
- delete entry.lastFetchedAt;
8353
- }
8354
- function markFetchCompleted(entry, fetchedAt) {
8355
- entry.lastFetchedAt = fetchedAt;
8356
- delete entry.lastError;
8357
- }
8358
- function isSourceInFlight(entry) {
8359
- if (!entry?.lastRequestedAt) return false;
8360
- return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;
8361
- }
8362
- function decideSourceAction(entry, queueRequestedAt) {
8363
- if (!entry?.lastRequestedAt) return "dispatch";
8364
- const inFlight = isSourceInFlight(entry);
8365
- if (inFlight) return "in-flight";
8366
- if (!entry.lastFetchedAt) return "dispatch";
8367
- if (entry.lastFetchedAt < queueRequestedAt) return "dispatch";
8368
- return "idle";
8369
- }
8370
- function nextEntryAfterFetchDelivery(entry, fetchedAt) {
8371
- const next = { ...entry };
8372
- markFetchCompleted(next, fetchedAt);
8373
- return next;
8374
- }
8375
- function nextEntryAfterFetchFailure(entry, reason) {
8376
- const next = { ...entry };
8377
- markFetchFailed(next, reason);
8378
- return next;
8379
- }
8380
- function runtimePath(boardDir, cardId) {
8381
- return path.join(boardDir, cardId, "runtime.json");
8382
- }
8383
- function readRuntimeState(boardDir, cardId) {
8384
- const p = runtimePath(boardDir, cardId);
8385
- if (!fs.existsSync(p)) return { _sources: {} };
8386
- try {
8387
- return JSON.parse(fs.readFileSync(p, "utf-8"));
8388
- } catch {
8389
- return { _sources: {} };
8390
8798
  }
8799
+ return { cmdUpsertCard };
8391
8800
  }
8392
- function writeRuntimeState(boardDir, cardId, state) {
8393
- const p = runtimePath(boardDir, cardId);
8394
- fs.mkdirSync(path.dirname(p), { recursive: true });
8395
- fs.writeFileSync(p, JSON.stringify(state, null, 2));
8396
- }
8397
- function appendEventToJournal(boardDir, event) {
8398
- const journalPath = path.join(boardDir, JOURNAL_FILE);
8399
- const entry = { id: randomUUID(), event };
8400
- fs.appendFileSync(journalPath, JSON.stringify(entry) + "\n", "utf-8");
8401
- }
8402
- function getUndrainedEntries(boardDir, lastDrainedId) {
8403
- const journalPath = path.join(boardDir, JOURNAL_FILE);
8404
- if (!fs.existsSync(journalPath)) return [];
8405
- const content = fs.readFileSync(journalPath, "utf-8").trim();
8406
- if (!content) return [];
8407
- const entries = content.split("\n").map((l) => JSON.parse(l));
8408
- if (!lastDrainedId) return entries;
8409
- const idx = entries.findIndex((e) => e.id === lastDrainedId);
8410
- return idx === -1 ? entries : entries.slice(idx + 1);
8411
- }
8412
- function determineLatestPendingAccumulated(boardDir) {
8413
- const boardPath = path.join(boardDir, BOARD_FILE);
8414
- if (!fs.existsSync(boardPath)) return 0;
8415
- try {
8416
- const envelope = loadBoardEnvelope(boardDir);
8417
- return getUndrainedEntries(boardDir, envelope.lastDrainedJournalId).length;
8418
- } catch {
8419
- return 0;
8801
+ function createExecutionCommandHandlers(deps) {
8802
+ function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
8803
+ const { cmd, args } = deps.getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
8804
+ deps.execCommandAsync(cmd, args, (err, stdout, stderr) => {
8805
+ if (err) console.error(`[source-data-fetched] call failed:`, err.message);
8806
+ if (stdout) console.log(stdout.trim());
8807
+ if (stderr) console.error(stderr.trim());
8808
+ });
8420
8809
  }
8421
- }
8422
- function shouldUseShellForCommand(cmd, forceShell) {
8423
- if (typeof forceShell === "boolean") return forceShell;
8424
- return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
8425
- }
8426
- var _gitBashPath;
8427
- var GIT_BASH_CACHE_FILE = path.join(os.tmpdir(), ".board-live-cards-git-bash-cache.json");
8428
- function findGitBash() {
8429
- if (_gitBashPath !== void 0) return _gitBashPath;
8430
- if (process.platform !== "win32") return _gitBashPath = false;
8431
- try {
8432
- const cached = JSON.parse(fs.readFileSync(GIT_BASH_CACHE_FILE, "utf8"));
8433
- if (cached.path === false || typeof cached.path === "string" && fs.existsSync(cached.path)) {
8434
- return _gitBashPath = cached.path;
8810
+ function invokeSourceDataFetchFailure(sourceToken, reason, callback) {
8811
+ const { cmd, args } = deps.getCliInvocation("source-data-fetch-failure", ["--token", sourceToken, "--reason", reason]);
8812
+ deps.execCommandAsync(cmd, args, (err) => callback(err));
8813
+ }
8814
+ function cmdRunSources(args) {
8815
+ const cardIdx = args.indexOf("--card");
8816
+ const tokenIdx = args.indexOf("--token");
8817
+ const rgIdx = args.indexOf("--rg");
8818
+ const sourceChecksumsIdx = args.indexOf("--source-checksums");
8819
+ const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
8820
+ const callbackToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8821
+ const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
8822
+ const sourceChecksumsJson = sourceChecksumsIdx !== -1 ? args[sourceChecksumsIdx + 1] : void 0;
8823
+ const sourceChecksums = sourceChecksumsJson ? JSON.parse(sourceChecksumsJson) : void 0;
8824
+ if (!cardFilePath || !callbackToken || !boardDir) {
8825
+ console.error("Usage: board-live-cards run-sourcedefs-internal --card <path> --token <token> --rg <dir> [--source-checksums <json>]");
8826
+ process.exit(1);
8435
8827
  }
8436
- } catch {
8437
- }
8438
- const candidates = [
8439
- process.env.SHELL,
8440
- process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, "Git", "usr", "bin", "bash.exe"),
8441
- process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, "Git", "bin", "bash.exe"),
8442
- process.env["PROGRAMFILES(X86)"] && path.join(process.env["PROGRAMFILES(X86)"], "Git", "bin", "bash.exe"),
8443
- process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")
8444
- ];
8445
- for (const c of candidates) {
8446
- if (c && /bash(\.exe)?$/i.test(c) && fs.existsSync(c)) {
8447
- _gitBashPath = c;
8828
+ const card = JSON.parse(fs7.readFileSync(cardFilePath, "utf-8"));
8829
+ if (path7.basename(cardFilePath).startsWith("card-enriched-")) {
8448
8830
  try {
8449
- fs.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: c }));
8831
+ fs7.unlinkSync(cardFilePath);
8450
8832
  } catch {
8451
8833
  }
8452
- return _gitBashPath;
8834
+ }
8835
+ console.log(`[run-sourcedefs-internal] Processing card "${card.id}"`);
8836
+ const teConfig = deps.readTaskExecutorConfig(boardDir);
8837
+ const taskExecutor = teConfig?.command;
8838
+ const taskExecutorExtraB64 = teConfig?.extra ? Buffer.from(JSON.stringify(teConfig.extra)).toString("base64") : void 0;
8839
+ function runSource(src) {
8840
+ const sourceChecksumForInvoke = src.outputFile ? sourceChecksums?.[src.outputFile] : void 0;
8841
+ const sourceToken = deps.encodeSourceToken({
8842
+ cbk: callbackToken,
8843
+ rg: boardDir,
8844
+ cid: card.id,
8845
+ b: src.bindTo,
8846
+ d: src.outputFile ?? "",
8847
+ cs: sourceChecksumForInvoke
8848
+ });
8849
+ function reportFailure(reason) {
8850
+ invokeSourceDataFetchFailure(sourceToken, reason, (err) => {
8851
+ if (err) console.error(`[run-sourcedefs-internal] source-data-fetch-failure call failed:`, err.message);
8852
+ });
8853
+ }
8854
+ function reportFetched(outFile2) {
8855
+ invokeSourceDataFetched(sourceToken, outFile2);
8856
+ }
8857
+ if (taskExecutor) {
8858
+ if (!src.outputFile) {
8859
+ console.warn(`[run-sourcedefs-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
8860
+ reportFailure("no outputFile configured");
8861
+ return;
8862
+ }
8863
+ const inFile = path7.join(os3.tmpdir(), `card-source-in-${src.bindTo}-${Date.now()}.json`);
8864
+ const outFile2 = path7.join(os3.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
8865
+ const errFile = path7.join(os3.tmpdir(), `card-source-err-${src.bindTo}-${Date.now()}.txt`);
8866
+ const sourceForExecutor = {
8867
+ ...src,
8868
+ cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path7.dirname(cardFilePath || ""),
8869
+ boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
8870
+ };
8871
+ deps.appendTaskExecutorLog(boardDir, sourceForExecutor, "external-task-executor");
8872
+ fs7.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
8873
+ const executorArgs = ["run-source-fetch", "--in", inFile, "--out", outFile2, "--err", errFile];
8874
+ if (taskExecutorExtraB64) executorArgs.push("--extra", taskExecutorExtraB64);
8875
+ console.log(`[run-sourcedefs-internal] task-executor: ${taskExecutor} ${executorArgs.join(" ")}`);
8876
+ try {
8877
+ deps.execCommandSync(taskExecutor, executorArgs, {
8878
+ shell: true,
8879
+ timeout: src.timeout ?? 12e4
8880
+ });
8881
+ } catch (err) {
8882
+ const reason = err.message ?? String(err);
8883
+ console.error(`[run-sourcedefs-internal] task-executor failed for source "${src.bindTo}":`, reason);
8884
+ reportFailure(reason);
8885
+ return;
8886
+ }
8887
+ if (fs7.existsSync(outFile2)) {
8888
+ reportFetched(outFile2);
8889
+ } else {
8890
+ const errMsg = fs7.existsSync(errFile) ? fs7.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
8891
+ console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
8892
+ reportFailure(errMsg);
8893
+ }
8894
+ return;
8895
+ }
8896
+ if (!src.outputFile) {
8897
+ console.warn(`[run-sourcedefs-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
8898
+ reportFailure("no outputFile configured");
8899
+ return;
8900
+ }
8901
+ const outFile = path7.join(os3.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
8902
+ if (!src.cli) {
8903
+ const errMsg = "source.cli is required for built-in source execution";
8904
+ console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
8905
+ reportFailure(errMsg);
8906
+ return;
8907
+ }
8908
+ const timeout = src.timeout ?? 12e4;
8909
+ const sourceCwd = typeof src.cwd === "string" ? src.cwd : path7.dirname(cardFilePath || "");
8910
+ const sourceBoardDir = typeof src.boardDir === "string" ? src.boardDir : boardDir;
8911
+ const sourceForBuiltInExecutor = {
8912
+ ...src,
8913
+ cwd: sourceCwd,
8914
+ boardDir: sourceBoardDir
8915
+ };
8916
+ deps.appendTaskExecutorLog(boardDir, sourceForBuiltInExecutor, "built-in-run-source-fetch");
8917
+ const cmdParts = deps.splitCommandLine(src.cli);
8918
+ if (cmdParts.length === 0) {
8919
+ const errMsg = "source.cli command is empty";
8920
+ console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
8921
+ reportFailure(errMsg);
8922
+ return;
8923
+ }
8924
+ const rawCmd = cmdParts[0];
8925
+ const { cmd, args: cliArgs } = deps.resolveCommandInvocation(rawCmd, cmdParts.slice(1));
8926
+ let stdout;
8927
+ try {
8928
+ stdout = deps.execCommandSync(cmd, cliArgs, {
8929
+ shell: false,
8930
+ encoding: "utf-8",
8931
+ timeout,
8932
+ cwd: sourceCwd,
8933
+ env: {
8934
+ ...process.env,
8935
+ ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
8936
+ }
8937
+ });
8938
+ } catch (err) {
8939
+ const reason = err.message ?? String(err);
8940
+ console.error(`[run-sourcedefs-internal] source fetch failed for source "${src.bindTo}":`, reason);
8941
+ reportFailure(reason);
8942
+ return;
8943
+ }
8944
+ fs7.writeFileSync(outFile, stdout.trim(), "utf-8");
8945
+ reportFetched(outFile);
8946
+ }
8947
+ const source_defs = card.source_defs ?? [];
8948
+ for (const src of source_defs) {
8949
+ runSource(src);
8453
8950
  }
8454
8951
  }
8455
- _gitBashPath = false;
8456
- try {
8457
- fs.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: false }));
8458
- } catch {
8459
- }
8460
- return _gitBashPath;
8461
- }
8462
- function shellQuote(s) {
8463
- return "'" + s.replace(/'/g, "'\\''") + "'";
8464
- }
8465
- function spawnDetachedCommand(cmd, args) {
8466
- if (process.platform === "win32") {
8467
- const bash = findGitBash();
8468
- if (bash) {
8469
- const shellCmd = [cmd, ...args].map((a) => shellQuote(a.replace(/\\/g, "/"))).join(" ");
8470
- const child3 = spawn(bash, ["-c", shellCmd], { detached: true, stdio: "ignore", windowsHide: true });
8471
- child3.unref();
8952
+ function cmdRunInference(args) {
8953
+ const inIdx = args.indexOf("--in");
8954
+ const tokenIdx = args.indexOf("--token");
8955
+ const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
8956
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
8957
+ if (!inFile || !inferenceToken) {
8958
+ console.error("Usage: board-live-cards run-inference-internal --in <input.json> --token <inference-token>");
8959
+ process.exit(1);
8960
+ }
8961
+ const decodedToken = deps.decodeSourceToken(inferenceToken);
8962
+ if (!decodedToken) {
8963
+ console.error("Invalid inference token");
8964
+ process.exit(1);
8965
+ }
8966
+ const callbackToken = decodedToken.cbk;
8967
+ const boardDir = decodedToken.rg;
8968
+ const cbkDecoded = deps.decodeCallbackToken(callbackToken);
8969
+ if (!cbkDecoded) {
8970
+ console.error("Invalid callback token embedded in inference token");
8971
+ process.exit(1);
8972
+ }
8973
+ function spawnInferenceDone(tmpFile) {
8974
+ const { cmd, args: cliArgs } = deps.getCliInvocation("inference-done", ["--tmp", tmpFile, "--token", inferenceToken]);
8975
+ deps.spawnDetachedCommand(cmd, cliArgs);
8976
+ }
8977
+ function spawnInferenceDoneError(reason) {
8978
+ const tmpFile = path7.join(os3.tmpdir(), `card-inference-err-${Date.now()}.json`);
8979
+ fs7.writeFileSync(tmpFile, JSON.stringify({ isTaskCompleted: false, reason }), "utf-8");
8980
+ spawnInferenceDone(tmpFile);
8981
+ }
8982
+ if (!fs7.existsSync(inFile)) {
8983
+ spawnInferenceDoneError(`inference input not found: ${inFile}`);
8472
8984
  return;
8473
8985
  }
8474
- const child2 = spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
8475
- detached: true,
8476
- stdio: "ignore",
8477
- windowsHide: true
8478
- });
8479
- child2.unref();
8480
- return;
8481
- }
8482
- const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
8483
- child.unref();
8484
- }
8485
- function execCommandSync(cmd, args, options) {
8486
- const output = execFileSync(cmd, args, {
8487
- shell: shouldUseShellForCommand(cmd, options?.shell),
8488
- timeout: options?.timeout,
8489
- encoding: options?.encoding,
8490
- cwd: options?.cwd,
8491
- windowsHide: true,
8492
- env: options?.env
8493
- });
8494
- return typeof output === "string" ? output : output.toString("utf-8");
8495
- }
8496
- function execCommandAsync(cmd, args, callback) {
8497
- execFile(
8498
- cmd,
8499
- args,
8500
- { shell: shouldUseShellForCommand(cmd), encoding: "utf8", windowsHide: true },
8501
- (err, stdout, stderr) => callback(err ?? null, stdout, stderr)
8502
- );
8503
- }
8504
- function splitCommandLine(command) {
8505
- const tokens = [];
8506
- let current = "";
8507
- let quote = null;
8508
- for (const ch of command.trim()) {
8509
- if (quote) {
8510
- if (ch === quote) {
8511
- quote = null;
8512
- } else {
8513
- current += ch;
8514
- }
8515
- continue;
8986
+ const adapterFile = path7.join(boardDir, deps.INFERENCE_ADAPTER_FILE);
8987
+ const inferenceAdapter = fs7.existsSync(adapterFile) ? fs7.readFileSync(adapterFile, "utf-8").trim() : void 0;
8988
+ if (!inferenceAdapter) {
8989
+ spawnInferenceDoneError(`inference adapter is not configured (${deps.INFERENCE_ADAPTER_FILE})`);
8990
+ return;
8991
+ }
8992
+ const outFile = path7.join(os3.tmpdir(), `card-inference-out-${Date.now()}.json`);
8993
+ const errFile = path7.join(os3.tmpdir(), `card-inference-err-${Date.now()}.txt`);
8994
+ const adapterParts = deps.splitCommandLine(inferenceAdapter);
8995
+ if (adapterParts.length === 0) {
8996
+ spawnInferenceDoneError("inference adapter command is empty");
8997
+ return;
8516
8998
  }
8517
- if (ch === '"' || ch === "'") {
8518
- quote = ch;
8519
- continue;
8999
+ const adapterRawCmd = adapterParts[0];
9000
+ const adapterRawArgs = adapterParts.slice(1);
9001
+ const { cmd: adapterCmd, args: adapterArgsPrefix } = deps.resolveCommandInvocation(adapterRawCmd, adapterRawArgs);
9002
+ const adapterArgs = [...adapterArgsPrefix, "run-inference", "--in", inFile, "--out", outFile, "--err", errFile];
9003
+ try {
9004
+ deps.execCommandSync(adapterCmd, adapterArgs, {
9005
+ shell: false,
9006
+ timeout: 12e4,
9007
+ cwd: boardDir,
9008
+ env: {
9009
+ ...process.env,
9010
+ BOARD_DIR: boardDir
9011
+ }
9012
+ });
9013
+ } catch (err) {
9014
+ const reason = err.message ?? String(err);
9015
+ spawnInferenceDoneError(reason);
9016
+ return;
8520
9017
  }
8521
- if (/\s/.test(ch)) {
8522
- if (current) {
8523
- tokens.push(current);
8524
- current = "";
8525
- }
8526
- continue;
9018
+ if (!fs7.existsSync(outFile)) {
9019
+ const errMsg = fs7.existsSync(errFile) ? fs7.readFileSync(errFile, "utf-8").trim() : "inference adapter produced no output file";
9020
+ spawnInferenceDoneError(errMsg);
9021
+ return;
8527
9022
  }
8528
- current += ch;
9023
+ spawnInferenceDone(outFile);
8529
9024
  }
8530
- if (quote) {
8531
- throw new Error(`Unterminated quote in command: ${command}`);
9025
+ function cmdInferenceDone(args) {
9026
+ const tmpIdx = args.indexOf("--tmp");
9027
+ const tokenIdx = args.indexOf("--token");
9028
+ const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
9029
+ const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9030
+ if (!tmpFile || !inferenceToken) {
9031
+ console.error("Usage: board-live-cards inference-done --tmp <result.json> --token <inference-token>");
9032
+ process.exit(1);
9033
+ }
9034
+ const decodedToken = deps.decodeSourceToken(inferenceToken);
9035
+ if (!decodedToken) {
9036
+ console.error("Invalid inference token");
9037
+ process.exit(1);
9038
+ }
9039
+ const { cbk: callbackToken, rg: dir, cs: inputChecksum } = decodedToken;
9040
+ const decoded = deps.decodeCallbackToken(callbackToken);
9041
+ if (!decoded) {
9042
+ console.error("Invalid callback token embedded in inference token");
9043
+ process.exit(1);
9044
+ }
9045
+ const taskName = decoded.taskName;
9046
+ const cardPath = deps.lookupCardPath(dir, taskName);
9047
+ if (!cardPath) {
9048
+ console.error(`Card file for task "${taskName}" not found in inventory`);
9049
+ process.exit(1);
9050
+ }
9051
+ let result = {};
9052
+ if (fs7.existsSync(tmpFile)) {
9053
+ try {
9054
+ result = JSON.parse(fs7.readFileSync(tmpFile, "utf-8").trim());
9055
+ } catch (err) {
9056
+ result = { isTaskCompleted: false, reason: `failed to parse inference result: ${err instanceof Error ? err.message : String(err)}` };
9057
+ }
9058
+ try {
9059
+ fs7.unlinkSync(tmpFile);
9060
+ } catch {
9061
+ }
9062
+ } else {
9063
+ result = { isTaskCompleted: false, reason: `inference result file not found: ${tmpFile}` };
9064
+ }
9065
+ const isTaskCompletedFlag = result.isTaskCompleted === true;
9066
+ const inferenceCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
9067
+ const card = JSON.parse(fs7.readFileSync(cardPath, "utf-8"));
9068
+ if (!card.card_data) card.card_data = {};
9069
+ const cardData = card.card_data;
9070
+ const existingInference = cardData.llm_task_completion_inference && typeof cardData.llm_task_completion_inference === "object" ? cardData.llm_task_completion_inference : {};
9071
+ cardData.llm_task_completion_inference = {
9072
+ ...existingInference,
9073
+ isTaskCompleted: isTaskCompletedFlag,
9074
+ reason: typeof result.reason === "string" ? result.reason : "",
9075
+ evidence: typeof result.evidence === "string" ? result.evidence : "",
9076
+ inferenceCompletedAt
9077
+ };
9078
+ fs7.writeFileSync(cardPath, JSON.stringify(card, null, 2), "utf-8");
9079
+ const runtimePath = path7.join(dir, taskName, "runtime.json");
9080
+ let runtime = { _sources: {} };
9081
+ if (fs7.existsSync(runtimePath)) {
9082
+ try {
9083
+ runtime = JSON.parse(fs7.readFileSync(runtimePath, "utf-8"));
9084
+ } catch {
9085
+ }
9086
+ }
9087
+ const inferenceEntry = runtime._inferenceEntry ?? {};
9088
+ runtime._inferenceEntry = deps.nextEntryAfterFetchDelivery(inferenceEntry, inferenceCompletedAt);
9089
+ fs7.mkdirSync(path7.dirname(runtimePath), { recursive: true });
9090
+ fs7.writeFileSync(runtimePath, JSON.stringify(runtime, null, 2), "utf-8");
9091
+ deps.appendEventToJournal(dir, {
9092
+ type: "task-progress",
9093
+ taskName,
9094
+ update: {
9095
+ kind: "inference-done",
9096
+ isTaskCompleted: isTaskCompletedFlag,
9097
+ inputChecksum
9098
+ },
9099
+ timestamp: inferenceCompletedAt
9100
+ });
9101
+ void deps.processAccumulatedEventsInfinitePass(dir);
9102
+ }
9103
+ async function cmdTryDrain(args) {
9104
+ const rgIdx = args.indexOf("--rg");
9105
+ const inlineLoop = args.includes("--inline-loop");
9106
+ const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9107
+ if (!boardDir) {
9108
+ console.error("Usage: board-live-cards process-accumulated-events --rg <dir>");
9109
+ process.exit(1);
9110
+ }
9111
+ await deps.processAccumulatedEventsForced(boardDir, { inlineLoop });
8532
9112
  }
8533
- if (current) tokens.push(current);
8534
- return tokens;
9113
+ return { cmdRunSources, cmdRunInference, cmdInferenceDone, cmdTryDrain };
8535
9114
  }
8536
- function resolveCommandInvocation(rawCmd, rawArgs) {
8537
- if (/^(node|node\.exe)$/i.test(rawCmd)) {
8538
- return { cmd: process.execPath, args: rawArgs };
8539
- }
8540
- if (/\.m?js$/i.test(rawCmd)) {
8541
- return { cmd: process.execPath, args: [rawCmd, ...rawArgs] };
9115
+ function deepGet(obj, path8) {
9116
+ if (!path8 || !obj) return void 0;
9117
+ const parts = path8.split(".");
9118
+ let cur = obj;
9119
+ for (let i = 0; i < parts.length; i++) {
9120
+ if (cur == null) return void 0;
9121
+ cur = cur[parts[i]];
8542
9122
  }
8543
- return { cmd: rawCmd, args: rawArgs };
9123
+ return cur;
8544
9124
  }
8545
- function spawnDetachedProcessAccumulatedWorker(boardDir) {
8546
- const { cmd, args: cliArgs } = getCliInvocation("process-accumulated-events", ["--rg", boardDir, "--inline-loop"]);
8547
- try {
8548
- spawnDetachedCommand(cmd, cliArgs);
8549
- return true;
8550
- } catch {
8551
- return false;
9125
+ function deepSet(obj, path8, value) {
9126
+ const parts = path8.split(".");
9127
+ let cur = obj;
9128
+ for (let i = 0; i < parts.length - 1; i++) {
9129
+ if (cur[parts[i]] == null || typeof cur[parts[i]] !== "object") cur[parts[i]] = {};
9130
+ cur = cur[parts[i]];
8552
9131
  }
9132
+ cur[parts[parts.length - 1]] = value;
8553
9133
  }
8554
- async function processAccumulatedEventsInlineLoop(boardDir, settleDelayMs = 50) {
8555
- while (determineLatestPendingAccumulated(boardDir) > 0) {
8556
- const ran = await processAccumulatedEvents(boardDir);
8557
- if (!ran) return false;
8558
- await new Promise((resolve3) => setTimeout(resolve3, settleDelayMs));
9134
+ async function run(node, options) {
9135
+ if (!node?.compute?.length) return node;
9136
+ if (!node.card_data) node.card_data = {};
9137
+ node.computed_values = {};
9138
+ node._sourcesData = options?.sourcesData ?? {};
9139
+ const ctx = {
9140
+ card_data: node.card_data,
9141
+ requires: node.requires ?? {},
9142
+ fetched_sources: node._sourcesData,
9143
+ computed_values: node.computed_values
9144
+ };
9145
+ for (const step of node.compute) {
9146
+ try {
9147
+ const val = await jsonata2(step.expr).evaluate(ctx);
9148
+ deepSet(node.computed_values, step.bindTo, val);
9149
+ ctx.computed_values = node.computed_values;
9150
+ } catch (err) {
9151
+ console.error(`CardCompute.run error on "${node.id ?? "?"}.${step.bindTo}":`, err);
9152
+ }
8559
9153
  }
8560
- return true;
8561
- }
8562
- function shouldAvoidDetachedProcessSpawn() {
8563
- return process.env.BOARD_LIVE_CARDS_NO_SPAWN === "1";
9154
+ return node;
8564
9155
  }
8565
- async function processAccumulatedEvents(boardDir) {
8566
- const boardPath = path.join(boardDir, BOARD_FILE);
8567
- let release;
8568
- try {
8569
- release = lockSync(boardPath, { retries: 0 });
8570
- } catch {
8571
- return false;
8572
- }
8573
- try {
8574
- const { rg, journal } = createBoardReactiveGraph(boardDir);
8575
- const undrained = journal.drain();
8576
- rg.pushAll(undrained);
8577
- await rg.dispose({ wait: true });
8578
- saveBoard(boardDir, rg, journal);
8579
- return true;
8580
- } finally {
8581
- release();
8582
- }
9156
+ async function evalExpr(expr, node) {
9157
+ const ctx = {
9158
+ card_data: node.card_data ?? {},
9159
+ requires: node.requires ?? {},
9160
+ fetched_sources: node._sourcesData ?? {},
9161
+ computed_values: node.computed_values ?? {}
9162
+ };
9163
+ return jsonata2(expr).evaluate(ctx);
8583
9164
  }
8584
- async function processAccumulatedEventsInfinitePass(boardDir, settleDelayMs = 50, options) {
8585
- if (options?.inlineLoop || shouldAvoidDetachedProcessSpawn()) {
8586
- return processAccumulatedEventsInlineLoop(boardDir, settleDelayMs);
9165
+ function resolve4(node, path8) {
9166
+ if (path8.startsWith("fetched_sources.")) {
9167
+ return deepGet(node._sourcesData ?? {}, path8.slice("fetched_sources.".length));
8587
9168
  }
8588
- return spawnDetachedProcessAccumulatedWorker(boardDir);
8589
- }
8590
- async function processAccumulatedEventsForced(boardDir, options) {
8591
- await processAccumulatedEvents(boardDir);
8592
- await processAccumulatedEventsInfinitePass(boardDir, 50, options);
8593
- }
8594
- function liveCardToTaskConfig(card) {
8595
- const requires = card.requires;
8596
- const provides = card.provides?.map((p) => p.bindTo) ?? [];
8597
- return {
8598
- requires: requires && requires.length > 0 ? requires : void 0,
8599
- provides,
8600
- taskHandlers: ["card-handler"],
8601
- description: card.meta?.title ?? card.id
8602
- };
9169
+ return deepGet(node, path8);
8603
9170
  }
8604
- var __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
8605
- var REPO_ROOT = path.resolve(__dirname$1, "..", "..");
8606
- var LOCAL_TSX_CLI = path.join(REPO_ROOT, "node_modules", "tsx", "dist", "cli.mjs");
8607
- function getCliInvocation(command, args) {
8608
- const jsPath = path.join(__dirname$1, "board-live-cards-cli.js");
8609
- if (fs.existsSync(jsPath)) {
8610
- return { cmd: process.execPath, args: [jsPath, command, ...args] };
9171
+ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
9172
+ "metric",
9173
+ "table",
9174
+ "editable-table",
9175
+ "chart",
9176
+ "form",
9177
+ "filter",
9178
+ "list",
9179
+ "notes",
9180
+ "todo",
9181
+ "alert",
9182
+ "narrative",
9183
+ "badge",
9184
+ "text",
9185
+ "markdown",
9186
+ "ref",
9187
+ "custom",
9188
+ "actions"
9189
+ ]);
9190
+ var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "source_defs"]);
9191
+ function validateNode(node) {
9192
+ const errors = [];
9193
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
9194
+ return { ok: false, errors: ["Node must be a non-null object"] };
8611
9195
  }
8612
- const tsPath = path.join(__dirname$1, "board-live-cards-cli.ts");
8613
- if (fs.existsSync(tsPath) && fs.existsSync(LOCAL_TSX_CLI)) {
8614
- return { cmd: process.execPath, args: [LOCAL_TSX_CLI, tsPath, command, ...args] };
9196
+ const n = node;
9197
+ if (typeof n.id !== "string" || !n.id) errors.push("id: required, must be a non-empty string");
9198
+ for (const key of Object.keys(n)) {
9199
+ if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
8615
9200
  }
8616
- const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
8617
- return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
8618
- }
8619
- function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
8620
- const args = ["--card", cardPath, "--token", callbackToken, "--rg", boardDir];
8621
- const { cmd, args: cmdArgs } = getCliInvocation("run-sourcedefs-internal", args);
8622
- try {
8623
- spawnDetachedCommand(cmd, cmdArgs);
8624
- callback(null);
8625
- } catch (err) {
8626
- callback(err instanceof Error ? err : new Error(String(err)));
9201
+ if (n.card_data == null || typeof n.card_data !== "object" || Array.isArray(n.card_data)) {
9202
+ errors.push("card_data: required, must be an object");
8627
9203
  }
8628
- }
8629
- function invokeRunInference(boardDir, cardId, inputFile, callbackToken, checksum, callback) {
8630
- const inferenceToken = encodeSourceToken({ cbk: callbackToken, rg: boardDir, cid: cardId, b: "", d: "", cs: checksum });
8631
- const { cmd, args } = getCliInvocation("run-inference-internal", ["--in", inputFile, "--token", inferenceToken]);
8632
- try {
8633
- spawnDetachedCommand(cmd, args);
8634
- callback(null);
8635
- } catch (err) {
8636
- callback(err instanceof Error ? err : new Error(String(err)));
9204
+ if (n.meta != null) {
9205
+ if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
9206
+ errors.push("meta: must be an object");
9207
+ } else {
9208
+ const meta = n.meta;
9209
+ if (meta.title != null && typeof meta.title !== "string") errors.push("meta.title: must be a string");
9210
+ if (meta.tags != null && !Array.isArray(meta.tags)) errors.push("meta.tags: must be an array");
9211
+ }
8637
9212
  }
8638
- }
8639
- function appendTaskExecutorLog(boardDir, hydratedSource, mode) {
8640
- try {
8641
- const entry = {
8642
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8643
- mode,
8644
- hydratedSource
8645
- };
8646
- fs.appendFileSync(path.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
8647
- } catch (logErr) {
8648
- console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
9213
+ if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
9214
+ if (n.provides != null) {
9215
+ if (!Array.isArray(n.provides)) {
9216
+ errors.push("provides: must be an array of { bindTo, ref } bindings");
9217
+ } else {
9218
+ n.provides.forEach((p, i) => {
9219
+ if (!p || typeof p !== "object" || Array.isArray(p)) {
9220
+ errors.push(`provides[${i}]: must be an object with bindTo and ref`);
9221
+ } else {
9222
+ const b = p;
9223
+ if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
9224
+ if (typeof b.ref !== "string" || !b.ref) errors.push(`provides[${i}]: missing required "ref" string`);
9225
+ }
9226
+ });
9227
+ }
8649
9228
  }
8650
- }
8651
- function appendInferenceAdapterLog(boardDir, cardId, payload) {
8652
- try {
8653
- const entry = {
8654
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8655
- cardId,
8656
- payload
8657
- };
8658
- fs.appendFileSync(path.join(boardDir, INFERENCE_ADAPTER_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
8659
- } catch (logErr) {
8660
- console.error(`[inference-adapter-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
9229
+ if (n.compute != null) {
9230
+ if (!Array.isArray(n.compute)) {
9231
+ errors.push("compute: must be an array of compute steps");
9232
+ } else {
9233
+ n.compute.forEach((step, i) => {
9234
+ if (!step || typeof step !== "object" || Array.isArray(step)) {
9235
+ errors.push(`compute[${i}]: must be a compute step object`);
9236
+ } else {
9237
+ const s = step;
9238
+ if (typeof s.bindTo !== "string" || !s.bindTo) errors.push(`compute[${i}]: missing required "bindTo" property`);
9239
+ if (typeof s.expr !== "string" || !s.expr) errors.push(`compute[${i}]: missing required "expr" string (JSONata expression)`);
9240
+ }
9241
+ });
9242
+ }
8661
9243
  }
8662
- }
8663
- function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
8664
- const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
8665
- execCommandAsync(cmd, args, (err, stdout, stderr) => {
8666
- if (err) console.error(`[source-data-fetched] call failed:`, err.message);
8667
- if (stdout) console.log(stdout.trim());
8668
- if (stderr) console.error(stderr.trim());
8669
- });
8670
- }
8671
- function invokeSourceDataFetchFailure(sourceToken, reason, callback) {
8672
- const { cmd, args } = getCliInvocation("source-data-fetch-failure", ["--token", sourceToken, "--reason", reason]);
8673
- execCommandAsync(cmd, args, (err) => callback(err));
8674
- }
8675
- function createBoardReactiveGraph(boardDir) {
8676
- const envelope = loadBoardEnvelope(boardDir);
8677
- const live = restore(envelope.graph);
8678
- const journalPath = path.join(boardDir, JOURNAL_FILE);
8679
- const journal = new BoardJournal(journalPath, envelope.lastDrainedJournalId);
8680
- const handlers = {
8681
- "card-handler": async (input) => {
8682
- const cardPath = lookupCardPath(boardDir, input.nodeId);
8683
- if (!cardPath) return "task-initiate-failure";
8684
- const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
8685
- const cardId = card.id;
8686
- const cardState = card.card_data ?? {};
8687
- const allSources = card.source_defs ?? [];
8688
- const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
8689
- const runtime = readRuntimeState(boardDir, cardId);
8690
- let runtimeDirty = false;
8691
- const currentExecutionCount = input.taskState?.executionCount ?? 0;
8692
- if (typeof runtime._lastExecutionCount === "number" && runtime._lastExecutionCount !== currentExecutionCount) {
8693
- runtime._sources = {};
8694
- runtime._inferenceEntry = void 0;
8695
- }
8696
- if (runtime._lastExecutionCount !== currentExecutionCount) {
8697
- runtime._lastExecutionCount = currentExecutionCount;
8698
- runtimeDirty = true;
8699
- }
8700
- if (input.update) {
8701
- const u = input.update;
8702
- const outputFile = u.outputFile;
8703
- if (outputFile) {
8704
- if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
8705
- const entry = runtime._sources[outputFile];
8706
- if (u.failure) {
8707
- runtime._sources[outputFile] = nextEntryAfterFetchFailure(entry, u.reason ?? "unknown");
8708
- runtimeDirty = true;
9244
+ if (n.source_defs != null) {
9245
+ if (!Array.isArray(n.source_defs)) {
9246
+ errors.push("source_defs: must be an array");
9247
+ } else {
9248
+ const bindTos = /* @__PURE__ */ new Set();
9249
+ const outputFiles = /* @__PURE__ */ new Set();
9250
+ n.source_defs.forEach((src, i) => {
9251
+ if (!src || typeof src !== "object" || Array.isArray(src)) {
9252
+ errors.push(`source_defs[${i}]: must be an object`);
9253
+ } else {
9254
+ const s = src;
9255
+ if (typeof s.bindTo !== "string" || !s.bindTo) {
9256
+ errors.push(`source_defs[${i}]: missing required "bindTo" property`);
9257
+ } else {
9258
+ if (bindTos.has(s.bindTo)) {
9259
+ errors.push(`source_defs[${i}]: bindTo "${s.bindTo}" is not unique across source_defs`);
9260
+ }
9261
+ bindTos.add(s.bindTo);
9262
+ }
9263
+ if (typeof s.outputFile !== "string" || !s.outputFile) {
9264
+ errors.push(`source_defs[${i}]: missing required "outputFile" property`);
8709
9265
  } else {
8710
- runtime._sources[outputFile] = nextEntryAfterFetchDelivery(
8711
- entry,
8712
- u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString()
8713
- );
8714
- runtimeDirty = true;
9266
+ if (outputFiles.has(s.outputFile)) {
9267
+ errors.push(`source_defs[${i}]: outputFile "${s.outputFile}" is not unique across source_defs`);
9268
+ }
9269
+ outputFiles.add(s.outputFile);
9270
+ }
9271
+ if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
9272
+ errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);
8715
9273
  }
8716
- if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
8717
9274
  }
9275
+ });
9276
+ }
9277
+ }
9278
+ if (n.view != null) {
9279
+ if (typeof n.view !== "object" || Array.isArray(n.view)) {
9280
+ errors.push("view: must be an object");
9281
+ } else {
9282
+ const view = n.view;
9283
+ if (!Array.isArray(view.elements) || view.elements.length === 0) {
9284
+ errors.push("view.elements: required, must be a non-empty array");
9285
+ } else {
9286
+ view.elements.forEach((elem, i) => {
9287
+ if (!elem || typeof elem !== "object") {
9288
+ errors.push(`view.elements[${i}]: must be an object`);
9289
+ return;
9290
+ }
9291
+ if (!elem.kind || typeof elem.kind !== "string") {
9292
+ errors.push(`view.elements[${i}].kind: required, must be a string`);
9293
+ } else if (!VALID_ELEMENT_KINDS.has(elem.kind)) {
9294
+ errors.push(`view.elements[${i}].kind: unknown kind "${elem.kind}". Valid: ${[...VALID_ELEMENT_KINDS].join(", ")}`);
9295
+ }
9296
+ if (elem.data != null && (typeof elem.data !== "object" || Array.isArray(elem.data))) {
9297
+ errors.push(`view.elements[${i}].data: must be an object`);
9298
+ }
9299
+ });
8718
9300
  }
8719
- const sourcesData = {};
8720
- for (const src of allSources) {
8721
- if (src.outputFile) {
8722
- const filePath = path.join(boardDir, cardId, src.outputFile);
8723
- if (fs.existsSync(filePath)) {
8724
- const raw = fs.readFileSync(filePath, "utf-8").trim();
9301
+ if (view.layout != null && (typeof view.layout !== "object" || Array.isArray(view.layout))) errors.push("view.layout: must be an object");
9302
+ if (view.features != null && (typeof view.features !== "object" || Array.isArray(view.features))) errors.push("view.features: must be an object");
9303
+ }
9304
+ }
9305
+ return { ok: errors.length === 0, errors };
9306
+ }
9307
+ async function enrichSources(source_defs, context) {
9308
+ if (!source_defs || source_defs.length === 0) return [];
9309
+ const evalCtx = {
9310
+ card_data: context.card_data ?? {},
9311
+ requires: context.requires ?? {}
9312
+ };
9313
+ return Promise.all(
9314
+ source_defs.map(async (src) => {
9315
+ const _projections = {};
9316
+ if (src.projections && typeof src.projections === "object" && !Array.isArray(src.projections)) {
9317
+ for (const [key, expr] of Object.entries(src.projections)) {
9318
+ if (typeof expr === "string" && expr.trim().length > 0) {
8725
9319
  try {
8726
- sourcesData[src.bindTo] = JSON.parse(raw);
9320
+ _projections[key] = await jsonata2(expr).evaluate(evalCtx);
8727
9321
  } catch {
8728
- sourcesData[src.bindTo] = raw;
9322
+ _projections[key] = void 0;
8729
9323
  }
8730
9324
  }
8731
9325
  }
8732
9326
  }
8733
- const requires = {};
8734
- for (const [token, taskData] of Object.entries(input.state ?? {})) {
8735
- if (taskData !== null && typeof taskData === "object" && !Array.isArray(taskData)) {
8736
- const unwrapped = taskData[token];
8737
- requires[token] = unwrapped !== void 0 ? unwrapped : taskData;
9327
+ return { ...src, _projections };
9328
+ })
9329
+ );
9330
+ }
9331
+ var CardCompute = {
9332
+ run,
9333
+ eval: evalExpr,
9334
+ resolve: resolve4,
9335
+ validate: validateNode,
9336
+ enrichSources
9337
+ };
9338
+
9339
+ // src/cli/board-live-cards-lib-types.ts
9340
+ function isSourceInFlight(entry) {
9341
+ if (!entry?.lastRequestedAt) return false;
9342
+ return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;
9343
+ }
9344
+ function decideSourceAction(entry, queueRequestedAt) {
9345
+ if (!entry?.lastRequestedAt) return "dispatch";
9346
+ const inFlight = isSourceInFlight(entry);
9347
+ if (inFlight) return "in-flight";
9348
+ if (!entry.lastFetchedAt) return "dispatch";
9349
+ if (entry.lastFetchedAt < queueRequestedAt) return "dispatch";
9350
+ return "idle";
9351
+ }
9352
+ function nextEntryAfterFetchDelivery(entry, fetchedAt) {
9353
+ const next = { ...entry, lastFetchedAt: fetchedAt };
9354
+ delete next.lastError;
9355
+ return next;
9356
+ }
9357
+ function nextEntryAfterFetchFailure(entry, reason) {
9358
+ const next = { ...entry, lastError: reason };
9359
+ delete next.lastFetchedAt;
9360
+ return next;
9361
+ }
9362
+
9363
+ // src/cli/board-live-cards-lib-card-handler.ts
9364
+ var DEFAULT_TASK_COMPLETION_RULE = "all_required_sources_fetched";
9365
+ function createCardHandlerFn(boardDir, adapters) {
9366
+ return async (input) => {
9367
+ const cardPath = adapters.cardStore.lookupCardPath(boardDir, input.nodeId);
9368
+ if (!cardPath) return "task-initiate-failure";
9369
+ const card = adapters.cardStore.readCard(cardPath);
9370
+ if (!card) return "task-initiate-failure";
9371
+ const cardId = card.id;
9372
+ const cardState = card.card_data ?? {};
9373
+ const allSources = card.source_defs ?? [];
9374
+ const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
9375
+ const session = adapters.runtimeStore.openSession(boardDir, cardId);
9376
+ const currentExecutionCount = input.taskState?.executionCount ?? 0;
9377
+ const lastExecCount = session.getLastExecutionCount();
9378
+ if (typeof lastExecCount === "number" && lastExecCount !== currentExecutionCount) {
9379
+ session.resetSources();
9380
+ session.resetInferenceEntry();
9381
+ }
9382
+ if (lastExecCount !== currentExecutionCount) {
9383
+ session.setLastExecutionCount(currentExecutionCount);
9384
+ }
9385
+ if (input.update) {
9386
+ const u = input.update;
9387
+ const outputFile = u.outputFile;
9388
+ if (outputFile) {
9389
+ const entry = session.getSourceEntry(outputFile);
9390
+ if (u.failure) {
9391
+ session.setSourceEntry(outputFile, nextEntryAfterFetchFailure(entry, u.reason ?? "unknown"));
8738
9392
  } else {
8739
- requires[token] = taskData;
9393
+ session.setSourceEntry(outputFile, nextEntryAfterFetchDelivery(
9394
+ entry,
9395
+ u.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString()
9396
+ ));
8740
9397
  }
9398
+ session.flush();
8741
9399
  }
8742
- const computeNode = {
8743
- id: cardId,
8744
- card_data: { ...cardState },
8745
- requires,
8746
- source_defs: allSources,
8747
- compute: card.compute
8748
- };
8749
- computeNode._sourcesData = sourcesData;
8750
- if (card.compute) {
8751
- await CardCompute.run(computeNode, { sourcesData });
8752
- }
8753
- const cvPath = resolveComputedValuesPath(boardDir, cardId);
8754
- writeJsonAtomic(cvPath, {
8755
- schema_version: "v1",
8756
- card_id: cardId,
8757
- computed_values: computeNode.computed_values ?? {}
8758
- });
8759
- const enrichedCard = { ...card };
8760
- const enrichedSources = await CardCompute.enrichSources(
8761
- Array.isArray(card.source_defs) ? card.source_defs : void 0,
8762
- {
8763
- card_data: card.card_data,
8764
- requires,
8765
- sourcesData,
8766
- computed_values: computeNode.computed_values
9400
+ }
9401
+ const sourcesData = {};
9402
+ for (const src of allSources) {
9403
+ if (src.outputFile) {
9404
+ const content = adapters.cardStore.readSourceFileContent(boardDir, cardId, src.outputFile);
9405
+ if (content !== null) {
9406
+ sourcesData[src.bindTo] = content;
8767
9407
  }
8768
- );
8769
- const sourceCwd = path.dirname(cardPath);
8770
- enrichedCard.source_defs = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
8771
- ...src,
8772
- cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
8773
- boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
8774
- })) : enrichedSources;
8775
- const enrichedByOutput = /* @__PURE__ */ new Map();
8776
- for (const src of Array.isArray(enrichedCard.source_defs) ? enrichedCard.source_defs : []) {
9408
+ }
9409
+ }
9410
+ const requires = {};
9411
+ for (const [token, taskData] of Object.entries(input.state ?? {})) {
9412
+ if (taskData !== null && typeof taskData === "object" && !Array.isArray(taskData)) {
9413
+ const unwrapped = taskData[token];
9414
+ requires[token] = unwrapped !== void 0 ? unwrapped : taskData;
9415
+ } else {
9416
+ requires[token] = taskData;
9417
+ }
9418
+ }
9419
+ const computeNode = {
9420
+ id: cardId,
9421
+ card_data: { ...cardState },
9422
+ requires,
9423
+ source_defs: allSources,
9424
+ compute: card.compute
9425
+ };
9426
+ computeNode._sourcesData = sourcesData;
9427
+ if (card.compute) {
9428
+ await CardCompute.run(computeNode, { sourcesData });
9429
+ }
9430
+ adapters.outputStore.writeComputedValues(boardDir, cardId, computeNode.computed_values ?? {});
9431
+ const enrichedCard = { ...card };
9432
+ const enrichedSources = await CardCompute.enrichSources(
9433
+ Array.isArray(card.source_defs) ? card.source_defs : void 0,
9434
+ {
9435
+ card_data: card.card_data,
9436
+ requires,
9437
+ sourcesData,
9438
+ computed_values: computeNode.computed_values
9439
+ }
9440
+ );
9441
+ const sourceCwd = cardPath.replace(/[\\/][^\\/]*$/, "");
9442
+ enrichedCard.source_defs = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
9443
+ ...src,
9444
+ cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
9445
+ boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
9446
+ })) : enrichedSources;
9447
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9448
+ const runQueuedAt = input.update ? void 0 : now;
9449
+ const undeliveredRequired = requiredSources.filter((s) => {
9450
+ const outputFile = s.outputFile;
9451
+ if (typeof outputFile !== "string" || !outputFile) return true;
9452
+ let entry = session.getSourceEntry(outputFile);
9453
+ if (runQueuedAt) {
9454
+ entry = { ...entry, queueRequestedAt: runQueuedAt };
9455
+ session.setSourceEntry(outputFile, entry);
9456
+ }
9457
+ const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;
9458
+ const action = decideSourceAction(entry, qrt);
9459
+ if (action === "in-flight") return false;
9460
+ return action === "dispatch";
9461
+ });
9462
+ session.flush();
9463
+ if (undeliveredRequired.length > 0) {
9464
+ let stampedAny = false;
9465
+ for (const src of undeliveredRequired) {
8777
9466
  const outputFile = src.outputFile;
8778
- if (typeof outputFile === "string" && outputFile) {
8779
- enrichedByOutput.set(outputFile, src);
8780
- }
9467
+ if (typeof outputFile !== "string" || !outputFile) continue;
9468
+ const entry = session.getSourceEntry(outputFile);
9469
+ session.setSourceEntry(outputFile, { ...entry, lastRequestedAt: now });
9470
+ stampedAny = true;
9471
+ }
9472
+ if (stampedAny) session.flush();
9473
+ if (!stampedAny) return "task-initiated";
9474
+ const result = await adapters.invocationAdapter.requestSourceFetch(
9475
+ boardDir,
9476
+ enrichedCard,
9477
+ input.callbackToken
9478
+ );
9479
+ if (!result.dispatched && result.error) {
9480
+ console.error(`[card-handler] ${input.nodeId}: source fetch dispatch failed: ${result.error}`);
8781
9481
  }
8782
- const now = (/* @__PURE__ */ new Date()).toISOString();
8783
- const runQueuedAt = input.update ? void 0 : now;
8784
- const undeliveredRequired = requiredSources.filter((s) => {
8785
- const outputFile = s.outputFile;
8786
- if (typeof outputFile !== "string" || !outputFile) return true;
8787
- if (!runtime._sources[outputFile]) runtime._sources[outputFile] = {};
8788
- const entry = runtime._sources[outputFile];
9482
+ return "task-initiated";
9483
+ }
9484
+ const providesBindings = card.provides ?? [];
9485
+ const data = {};
9486
+ for (const { bindTo, ref } of providesBindings) {
9487
+ data[bindTo] = CardCompute.resolve(computeNode, ref);
9488
+ }
9489
+ const completionRule = typeof card.when_is_task_completed === "string" && card.when_is_task_completed.trim() ? card.when_is_task_completed.trim() : DEFAULT_TASK_COMPLETION_RULE;
9490
+ const cardData = card.card_data;
9491
+ const llmCompletion = cardData?.llm_task_completion_inference ?? {};
9492
+ const isLlmTaskCompleted = llmCompletion.isTaskCompleted === true;
9493
+ const inferenceEntry = session.getInferenceEntry();
9494
+ const inferenceRequestedAt = typeof inferenceEntry.lastRequestedAt === "string" ? inferenceEntry.lastRequestedAt : void 0;
9495
+ const inferenceCompletedAt = typeof llmCompletion.inferenceCompletedAt === "string" ? llmCompletion.inferenceCompletedAt : void 0;
9496
+ const inferencePending = !!inferenceRequestedAt && (!inferenceCompletedAt || inferenceCompletedAt < inferenceRequestedAt);
9497
+ const latestRequiredSourceFetchedAt = requiredSources.reduce((latest, src) => {
9498
+ const fetchedAt = session.getSourceEntry(src.outputFile).lastFetchedAt;
9499
+ if (typeof fetchedAt !== "string") return latest;
9500
+ if (!latest || fetchedAt > latest) return fetchedAt;
9501
+ return latest;
9502
+ }, void 0);
9503
+ const shouldRequestInference = !inferenceRequestedAt || !inferenceCompletedAt || !!latestRequiredSourceFetchedAt && latestRequiredSourceFetchedAt > inferenceCompletedAt;
9504
+ if (completionRule !== DEFAULT_TASK_COMPLETION_RULE) {
9505
+ if (isLlmTaskCompleted) ; else if (inferencePending) {
9506
+ return "task-initiated";
9507
+ } else if (!shouldRequestInference) {
9508
+ return "task-initiated";
9509
+ } else {
9510
+ const inferencePayload = {
9511
+ cardId,
9512
+ taskName: input.nodeId,
9513
+ completionRule,
9514
+ context: {
9515
+ requires,
9516
+ sourcesData,
9517
+ computed_values: computeNode.computed_values ?? {},
9518
+ provides: data,
9519
+ card_data: computeNode.card_data ?? {}
9520
+ }
9521
+ };
9522
+ let updatedInferenceEntry = { ...inferenceEntry };
8789
9523
  if (runQueuedAt) {
8790
- entry.queueRequestedAt = runQueuedAt;
8791
- runtimeDirty = true;
9524
+ updatedInferenceEntry = { ...updatedInferenceEntry, queueRequestedAt: runQueuedAt };
9525
+ session.setInferenceEntry(updatedInferenceEntry);
8792
9526
  }
8793
- const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;
8794
- const action = decideSourceAction(entry, qrt);
8795
- if (action === "in-flight") return false;
8796
- return action === "dispatch";
8797
- });
8798
- if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
8799
- if (undeliveredRequired.length > 0) {
8800
- let stampedAny = false;
8801
- for (const src of undeliveredRequired) {
8802
- const outputFile = src.outputFile;
8803
- if (typeof outputFile !== "string" || !outputFile) continue;
8804
- const entry = runtime._sources[outputFile] ?? {};
8805
- markRequested(entry, now);
8806
- runtime._sources[outputFile] = entry;
8807
- stampedAny = true;
8808
- }
8809
- if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
8810
- if (!stampedAny) return "task-initiated";
8811
- const enrichedCardPath = path.join(os.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
8812
- fs.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
8813
- invokeRunSources(boardDir, enrichedCardPath, input.callbackToken, (err) => {
8814
- if (err) {
8815
- console.error(`[card-handler] ${input.nodeId}:`, err.message);
8816
- try {
8817
- fs.unlinkSync(enrichedCardPath);
8818
- } catch {
8819
- }
8820
- }
8821
- });
8822
- return "task-initiated";
8823
- }
8824
- const providesBindings = card.provides ?? [];
8825
- const data = {};
8826
- for (const { bindTo, ref } of providesBindings) {
8827
- data[bindTo] = CardCompute.resolve(computeNode, ref);
8828
- }
8829
- const completionRule = typeof card.when_is_task_completed === "string" && card.when_is_task_completed.trim() ? card.when_is_task_completed.trim() : DEFAULT_TASK_COMPLETION_RULE;
8830
- const cardData = card.card_data;
8831
- const llmCompletion = cardData?.llm_task_completion_inference ?? {};
8832
- const isLlmTaskCompleted = llmCompletion.isTaskCompleted === true;
8833
- const inferenceEntry = runtime._inferenceEntry ?? {};
8834
- const inferenceRequestedAt = typeof inferenceEntry.lastRequestedAt === "string" ? inferenceEntry.lastRequestedAt : void 0;
8835
- const inferenceCompletedAt = typeof llmCompletion.inferenceCompletedAt === "string" ? llmCompletion.inferenceCompletedAt : void 0;
8836
- const inferencePending = !!inferenceRequestedAt && (!inferenceCompletedAt || inferenceCompletedAt < inferenceRequestedAt);
8837
- const latestRequiredSourceFetchedAt = requiredSources.reduce((latest, src) => {
8838
- const fetchedAt = runtime._sources[src.outputFile]?.lastFetchedAt;
8839
- if (typeof fetchedAt !== "string") return latest;
8840
- if (!latest || fetchedAt > latest) return fetchedAt;
8841
- return latest;
8842
- }, void 0);
8843
- const shouldRequestInference = !inferenceRequestedAt || !inferenceCompletedAt || !!latestRequiredSourceFetchedAt && latestRequiredSourceFetchedAt > inferenceCompletedAt;
8844
- if (completionRule !== DEFAULT_TASK_COMPLETION_RULE) {
8845
- if (isLlmTaskCompleted) ; else if (inferencePending) {
9527
+ const inferenceQrt = updatedInferenceEntry.queueRequestedAt ?? updatedInferenceEntry.lastRequestedAt ?? now;
9528
+ const inferenceAction = decideSourceAction(updatedInferenceEntry, inferenceQrt);
9529
+ if (inferenceAction === "in-flight") {
9530
+ session.flush();
8846
9531
  return "task-initiated";
8847
- } else if (!shouldRequestInference) {
9532
+ }
9533
+ if (inferenceAction === "idle") {
8848
9534
  return "task-initiated";
8849
- } else {
8850
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
8851
- const inferencePayload = {
8852
- cardId,
9535
+ }
9536
+ adapters.outputStore.appendInferenceLog(boardDir, cardId, inferencePayload);
9537
+ session.setInferenceEntry({ ...updatedInferenceEntry, lastRequestedAt: now });
9538
+ session.flush();
9539
+ const inferenceResult = await adapters.invocationAdapter.requestInference(
9540
+ boardDir,
9541
+ cardId,
9542
+ inferencePayload,
9543
+ input.callbackToken
9544
+ );
9545
+ if (!inferenceResult.dispatched) {
9546
+ const failedAt = (/* @__PURE__ */ new Date()).toISOString();
9547
+ adapters.inputStore.appendEvent(boardDir, {
9548
+ type: "task-failed",
8853
9549
  taskName: input.nodeId,
8854
- completionRule,
8855
- context: {
8856
- requires,
8857
- sourcesData,
8858
- computed_values: computeNode.computed_values ?? {},
8859
- provides: data,
8860
- card_data: computeNode.card_data ?? {}
8861
- }
8862
- };
8863
- if (runQueuedAt) {
8864
- inferenceEntry.queueRequestedAt = runQueuedAt;
8865
- runtimeDirty = true;
8866
- }
8867
- const inferenceQrt = inferenceEntry.queueRequestedAt ?? inferenceEntry.lastRequestedAt ?? now2;
8868
- const inferenceAction = decideSourceAction(inferenceEntry, inferenceQrt);
8869
- if (inferenceAction === "in-flight") {
8870
- runtime._inferenceEntry = inferenceEntry;
8871
- if (runtimeDirty) writeRuntimeState(boardDir, cardId, runtime);
8872
- return "task-initiated";
8873
- }
8874
- if (inferenceAction === "idle") {
8875
- return "task-initiated";
8876
- }
8877
- const inferenceInFile = path.join(os.tmpdir(), `card-inference-${cardId}-${Date.now()}.json`);
8878
- fs.writeFileSync(inferenceInFile, JSON.stringify(inferencePayload, null, 2), "utf-8");
8879
- appendInferenceAdapterLog(boardDir, cardId, inferencePayload);
8880
- markRequested(inferenceEntry, now2);
8881
- runtime._inferenceEntry = inferenceEntry;
8882
- runtimeDirty = true;
8883
- invokeRunInference(boardDir, cardId, inferenceInFile, input.callbackToken, void 0, (err) => {
8884
- if (err) {
8885
- console.error(`[card-handler] ${input.nodeId}:`, err.message);
8886
- const failedAt = (/* @__PURE__ */ new Date()).toISOString();
8887
- appendEventToJournal(boardDir, {
8888
- type: "task-failed",
8889
- taskName: input.nodeId,
8890
- error: err.message,
8891
- timestamp: failedAt
8892
- });
8893
- }
9550
+ error: inferenceResult.error ?? "inference dispatch failed",
9551
+ timestamp: failedAt
8894
9552
  });
8895
- return "task-initiated";
8896
9553
  }
9554
+ return "task-initiated";
8897
9555
  }
8898
- writeRuntimeDataObjects(boardDir, data);
8899
- const undeliveredOptional = allSources.filter((s) => {
8900
- if (s.optionalForCompletionGating !== true) return false;
8901
- const entry = runtime._sources[s.outputFile];
8902
- if (!entry?.lastRequestedAt) return true;
8903
- if (!entry.lastFetchedAt) return true;
8904
- return entry.lastFetchedAt <= entry.lastRequestedAt;
8905
- });
8906
- if (undeliveredOptional.length > 0) {
8907
- invokeRunSources(boardDir, cardPath, input.callbackToken, (err) => {
8908
- if (err) console.error(`[card-handler] ${input.nodeId}:`, err.message);
8909
- });
8910
- }
8911
- appendEventToJournal(boardDir, {
8912
- type: "task-completed",
8913
- taskName: input.nodeId,
8914
- data,
8915
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8916
- });
8917
- return "task-initiated";
8918
9556
  }
8919
- };
8920
- const rg = createReactiveGraph(live, { handlers });
8921
- return { rg, journal };
8922
- }
8923
- function resolveCardGlobMatches(cardGlob) {
8924
- const patterns = cardGlob.split(",").map((s) => s.trim()).filter(Boolean).map((p) => p.replace(/\\/g, "/"));
8925
- const matches = fg.sync(patterns, {
8926
- absolute: true,
8927
- onlyFiles: true,
8928
- unique: true,
8929
- dot: false
8930
- });
8931
- return [...matches].map((m) => path.resolve(m)).sort((a, b) => a.localeCompare(b));
8932
- }
8933
- function cmdInit(args) {
8934
- const dir = args[0];
8935
- if (!dir) {
8936
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
8937
- }
8938
- const teIdx = args.indexOf("--task-executor");
8939
- const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
8940
- const chIdx = args.indexOf("--chat-handler");
8941
- const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
8942
- const iaIdx = args.indexOf("--inference-adapter");
8943
- const inferenceAdapter = iaIdx !== -1 ? args[iaIdx + 1] : void 0;
8944
- const roIdx = args.indexOf("--runtime-out");
8945
- const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
8946
- if (roIdx !== -1 && !runtimeOut) {
8947
- throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]");
8948
- }
8949
- const result = initBoard(dir);
8950
- if (taskExecutor) {
8951
- const teExtraIdx = args.indexOf("--task-executor-extra");
8952
- let teExtra;
8953
- if (teExtraIdx !== -1 && args[teExtraIdx + 1]) {
8954
- try {
8955
- teExtra = JSON.parse(args[teExtraIdx + 1]);
8956
- } catch {
8957
- }
9557
+ adapters.outputStore.writeDataObjects(boardDir, data);
9558
+ const undeliveredOptional = allSources.filter((s) => {
9559
+ if (s.optionalForCompletionGating !== true) return false;
9560
+ const entry = session.getSourceEntry(s.outputFile);
9561
+ if (!entry.lastRequestedAt) return true;
9562
+ if (!entry.lastFetchedAt) return true;
9563
+ return entry.lastFetchedAt <= entry.lastRequestedAt;
9564
+ });
9565
+ if (undeliveredOptional.length > 0) {
9566
+ adapters.invocationAdapter.requestSourceFetch(boardDir, enrichedCard, input.callbackToken).catch((err) => console.error(`[card-handler] ${input.nodeId}: optional source fetch:`, err));
8958
9567
  }
8959
- const teConfig = { command: taskExecutor, ...teExtra ? { extra: teExtra } : {} };
8960
- fs.writeFileSync(path.join(dir, TASK_EXECUTOR_FILE), JSON.stringify(teConfig, null, 2), "utf-8");
8961
- }
8962
- if (chatHandler) {
8963
- fs.writeFileSync(path.join(dir, ".chat-handler"), chatHandler, "utf-8");
8964
- }
8965
- if (inferenceAdapter) {
8966
- fs.writeFileSync(path.join(dir, INFERENCE_ADAPTER_FILE), inferenceAdapter, "utf-8");
8967
- }
8968
- const runtimeOutDir = configureRuntimeOutDir(dir, runtimeOut);
8969
- const live = loadBoard(dir);
8970
- writeJsonAtomic(resolveStatusSnapshotPath(dir), buildBoardStatusObject(dir, live));
8971
- if (result === "exists") {
8972
- console.log(`Board already initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
8973
- } else {
8974
- console.log(`Board initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
8975
- }
9568
+ adapters.inputStore.appendEvent(boardDir, {
9569
+ type: "task-completed",
9570
+ taskName: input.nodeId,
9571
+ data,
9572
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9573
+ });
9574
+ return "task-initiated";
9575
+ };
8976
9576
  }
8977
- function buildBoardStatusObject(dir, live) {
9577
+
9578
+ // src/cli/board-live-cards-lib-board-status.ts
9579
+ function buildBoardStatusObject(boardPath, live) {
8978
9580
  const taskState = live.state.tasks;
8979
9581
  const taskConfig = live.config.tasks;
8980
9582
  const cardNames = Object.keys(taskState);
@@ -9058,15 +9660,15 @@ function buildBoardStatusObject(dir, live) {
9058
9660
  let orphanCards = 0;
9059
9661
  for (const [name, cfg] of Object.entries(taskConfig)) {
9060
9662
  const requiresNone = (cfg.requires ?? []).length === 0;
9061
- const provides = cfg.provides ?? [];
9062
- const feedsAny = provides.some((p) => (dependentsByToken.get(p) ?? []).some((d) => d !== name));
9663
+ const providesList = cfg.provides ?? [];
9664
+ const feedsAny = providesList.some((p) => (dependentsByToken.get(p) ?? []).some((d) => d !== name));
9063
9665
  if (requiresNone && !feedsAny) orphanCards += 1;
9064
9666
  }
9065
9667
  return {
9066
9668
  schema_version: "v1",
9067
9669
  meta: {
9068
9670
  board: {
9069
- path: path.resolve(dir)
9671
+ path: boardPath
9070
9672
  }
9071
9673
  },
9072
9674
  summary: {
@@ -9088,1193 +9690,895 @@ function buildBoardStatusObject(dir, live) {
9088
9690
  cards
9089
9691
  };
9090
9692
  }
9091
- function cmdStatus(args) {
9092
- const rgIdx = args.indexOf("--rg");
9093
- const asJson = args.includes("--json");
9094
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9095
- if (!dir) {
9096
- console.error("Usage: board-live-cards status --rg <dir>");
9097
- process.exit(1);
9693
+ var NodeRuntimeStoreSession = class {
9694
+ constructor(boardDir, cardId) {
9695
+ this.boardDir = boardDir;
9696
+ this.cardId = cardId;
9697
+ this.state = NodeRuntimeInternalStore.readState(boardDir, cardId);
9098
9698
  }
9099
- const statusOutPath = resolveStatusSnapshotPath(dir);
9100
- let statusObject;
9101
- if (fs.existsSync(statusOutPath)) {
9102
- statusObject = JSON.parse(fs.readFileSync(statusOutPath, "utf-8"));
9103
- } else {
9104
- statusObject = buildBoardStatusObject(dir, loadBoard(dir));
9105
- writeJsonAtomic(statusOutPath, statusObject);
9699
+ boardDir;
9700
+ cardId;
9701
+ state;
9702
+ dirty = false;
9703
+ getSourceEntry(outputFile) {
9704
+ return { ...this.state._sources[outputFile] ?? {} };
9106
9705
  }
9107
- if (asJson) {
9108
- console.log(JSON.stringify(statusObject, null, 2));
9109
- return;
9706
+ setSourceEntry(outputFile, entry) {
9707
+ this.state._sources[outputFile] = entry;
9708
+ this.dirty = true;
9709
+ }
9710
+ resetSources() {
9711
+ this.state._sources = {};
9712
+ this.dirty = true;
9713
+ }
9714
+ getInferenceEntry() {
9715
+ return { ...this.state._inferenceEntry ?? {} };
9716
+ }
9717
+ setInferenceEntry(entry) {
9718
+ this.state._inferenceEntry = entry;
9719
+ this.dirty = true;
9720
+ }
9721
+ resetInferenceEntry() {
9722
+ this.state._inferenceEntry = void 0;
9723
+ this.dirty = true;
9110
9724
  }
9111
- console.log(`Board: ${statusObject.meta.board.path}`);
9112
- console.log(`Tasks: ${statusObject.summary.card_count}`);
9113
- console.log("");
9114
- for (const card of statusObject.cards) {
9115
- const dataKeys = card.provides_runtime.join(", ");
9116
- console.log(` ${card.status.padEnd(12)} ${card.name}${dataKeys ? ` \u2014 [${dataKeys}]` : ""}`);
9725
+ getLastExecutionCount() {
9726
+ return this.state._lastExecutionCount;
9117
9727
  }
9118
- console.log("");
9119
- console.log(`Schedule: ${statusObject.summary.eligible} eligible, ${statusObject.summary.pending} pending, ${statusObject.summary.blocked} blocked, ${statusObject.summary.unresolved} unresolved`);
9728
+ setLastExecutionCount(count) {
9729
+ this.state._lastExecutionCount = count;
9730
+ this.dirty = true;
9731
+ }
9732
+ flush() {
9733
+ if (!this.dirty) return;
9734
+ NodeRuntimeInternalStore.writeState(this.boardDir, this.cardId, this.state);
9735
+ this.dirty = false;
9736
+ }
9737
+ };
9738
+ var NodeRuntimeInternalStore = class {
9739
+ openSession(boardDir, cardId) {
9740
+ return new NodeRuntimeStoreSession(boardDir, cardId);
9741
+ }
9742
+ static readState(boardDir, cardId) {
9743
+ const p = path7.join(boardDir, cardId, "runtime.json");
9744
+ if (!fs7.existsSync(p)) return { _sources: {} };
9745
+ try {
9746
+ return JSON.parse(fs7.readFileSync(p, "utf-8"));
9747
+ } catch {
9748
+ return { _sources: {} };
9749
+ }
9750
+ }
9751
+ static writeState(boardDir, cardId, state) {
9752
+ const p = path7.join(boardDir, cardId, "runtime.json");
9753
+ fs7.mkdirSync(path7.dirname(p), { recursive: true });
9754
+ fs7.writeFileSync(p, JSON.stringify(state, null, 2));
9755
+ }
9756
+ };
9757
+ function createNodeRuntimeStore() {
9758
+ return new NodeRuntimeInternalStore();
9120
9759
  }
9121
- function cmdTaskCompleted(args) {
9122
- const rgIdx = args.indexOf("--rg");
9123
- const tokenIdx = args.indexOf("--token");
9124
- const dataIdx = args.indexOf("--data");
9125
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9126
- const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9127
- if (!dir || !token) {
9128
- console.error("Usage: board-live-cards task-completed --rg <dir> --token <token> [--data <json>]");
9129
- process.exit(1);
9760
+ function writeJsonAtomic(filePath, payload) {
9761
+ fs7.mkdirSync(path7.dirname(filePath), { recursive: true });
9762
+ const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
9763
+ fs7.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
9764
+ fs7.renameSync(tmpPath, filePath);
9765
+ }
9766
+ var NodeOutputStore = class {
9767
+ constructor(resolveComputedValuesPath2, resolveDataObjectsDirPath2, inferenceAdapterLogFile) {
9768
+ this.resolveComputedValuesPath = resolveComputedValuesPath2;
9769
+ this.resolveDataObjectsDirPath = resolveDataObjectsDirPath2;
9770
+ this.inferenceAdapterLogFile = inferenceAdapterLogFile;
9771
+ }
9772
+ resolveComputedValuesPath;
9773
+ resolveDataObjectsDirPath;
9774
+ inferenceAdapterLogFile;
9775
+ writeComputedValues(boardDir, cardId, values) {
9776
+ writeJsonAtomic(this.resolveComputedValuesPath(boardDir, cardId), {
9777
+ schema_version: "v1",
9778
+ card_id: cardId,
9779
+ computed_values: values
9780
+ });
9130
9781
  }
9131
- const decoded = decodeCallbackToken2(token);
9132
- if (!decoded) {
9133
- console.error("Invalid callback token");
9134
- process.exit(1);
9782
+ writeDataObjects(boardDir, data) {
9783
+ for (const [token, payload] of Object.entries(data)) {
9784
+ if (!token) continue;
9785
+ const fileName = token.replace(/[\\/]/g, "__");
9786
+ if (!fileName) continue;
9787
+ writeJsonAtomic(path7.join(this.resolveDataObjectsDirPath(boardDir), fileName), payload);
9788
+ }
9135
9789
  }
9136
- const data = dataIdx !== -1 ? JSON.parse(args[dataIdx + 1]) : {};
9137
- writeRuntimeDataObjects(dir, data);
9138
- appendEventToJournal(dir, {
9139
- type: "task-completed",
9140
- taskName: decoded.taskName,
9141
- data,
9142
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9143
- });
9144
- void processAccumulatedEventsForced(dir);
9145
- console.log("Task completed.");
9790
+ appendInferenceLog(boardDir, cardId, payload) {
9791
+ try {
9792
+ const entry = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), cardId, payload };
9793
+ fs7.appendFileSync(
9794
+ path7.join(boardDir, this.inferenceAdapterLogFile),
9795
+ JSON.stringify(entry) + "\n",
9796
+ "utf-8"
9797
+ );
9798
+ } catch (logErr) {
9799
+ console.error(`[inference-adapter-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
9800
+ }
9801
+ }
9802
+ };
9803
+ function createNodeOutputStore(resolveComputedValuesPath2, resolveDataObjectsDirPath2, inferenceAdapterLogFile) {
9804
+ return new NodeOutputStore(resolveComputedValuesPath2, resolveDataObjectsDirPath2, inferenceAdapterLogFile);
9146
9805
  }
9147
- function cmdTaskFailed(args) {
9148
- const rgIdx = args.indexOf("--rg");
9149
- const tokenIdx = args.indexOf("--token");
9150
- const errorIdx = args.indexOf("--error");
9151
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9152
- const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9153
- const errorMsg = errorIdx !== -1 ? args[errorIdx + 1] : "unknown error";
9154
- if (!dir || !token) {
9155
- console.error("Usage: board-live-cards task-failed --rg <dir> --token <token> [--error <message>]");
9156
- process.exit(1);
9806
+ var NodeInputStore = class {
9807
+ constructor(journalFile) {
9808
+ this.journalFile = journalFile;
9157
9809
  }
9158
- const decoded = decodeCallbackToken2(token);
9159
- if (!decoded) {
9160
- console.error("Invalid callback token");
9161
- process.exit(1);
9810
+ journalFile;
9811
+ appendEvent(boardDir, event) {
9812
+ const journalPath = path7.join(boardDir, this.journalFile);
9813
+ const entry = { id: randomUUID(), event };
9814
+ fs7.appendFileSync(journalPath, JSON.stringify(entry) + "\n", "utf-8");
9162
9815
  }
9163
- appendEventToJournal(dir, {
9164
- type: "task-failed",
9165
- taskName: decoded.taskName,
9166
- error: errorMsg,
9167
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9168
- });
9169
- void processAccumulatedEventsForced(dir);
9170
- console.log("Task failed.");
9816
+ };
9817
+ function createNodeInputStore(journalFile) {
9818
+ return new NodeInputStore(journalFile);
9171
9819
  }
9172
- function cmdValidateCard(args) {
9173
- const cardIdx = args.indexOf("--card");
9174
- const globIdx = args.indexOf("--card-glob");
9175
- const rgIdx = args.indexOf("--rg");
9176
- const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
9177
- const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
9178
- const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9179
- if (!cardFile && !cardGlob || cardFile && cardGlob) {
9180
- throw new Error("Usage: board-live-cards validate-card (--card <card.json> | --card-glob <glob>) [--rg <boardDir>]");
9181
- }
9182
- let teConfig;
9183
- if (boardDir) {
9184
- teConfig = readTaskExecutorConfig(boardDir);
9185
- if (!teConfig) {
9186
- throw new Error(`--rg specified but no .task-executor found in ${boardDir}`);
9820
+ function shouldSuppressSpawn() {
9821
+ return process.env.BOARD_LIVE_CARDS_NO_SPAWN === "1";
9822
+ }
9823
+ function getCliInvocationPath(cliDir) {
9824
+ const jsPath = path7.join(cliDir, "board-live-cards-cli.js");
9825
+ if (fs7.existsSync(jsPath)) {
9826
+ return { cmd: process.execPath, args: [jsPath] };
9827
+ }
9828
+ const tsPath = path7.join(cliDir, "board-live-cards-cli.ts");
9829
+ const localTsx = path7.join(cliDir, "..", "..", "node_modules", ".bin", "tsx");
9830
+ if (fs7.existsSync(tsPath) && fs7.existsSync(localTsx)) {
9831
+ return { cmd: process.execPath, args: [localTsx, tsPath] };
9832
+ }
9833
+ return null;
9834
+ }
9835
+ function buildCliInvocation(cliDir, command, args) {
9836
+ const found = getCliInvocationPath(cliDir);
9837
+ if (found) {
9838
+ return { cmd: found.cmd, args: [...found.args, command, ...args] };
9839
+ }
9840
+ const tsPath = path7.join(cliDir, "board-live-cards-cli.ts");
9841
+ const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
9842
+ return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
9843
+ }
9844
+ var _gitBashCache;
9845
+ var GIT_BASH_CACHE_FILE = path7.join(os3.tmpdir(), ".board-live-cards-git-bash-path.json");
9846
+ function findGitBash() {
9847
+ if (_gitBashCache !== void 0) return _gitBashCache;
9848
+ try {
9849
+ const cached = JSON.parse(fs7.readFileSync(GIT_BASH_CACHE_FILE, "utf-8"));
9850
+ if (typeof cached?.path === "string" || cached?.path === false) {
9851
+ _gitBashCache = cached.path;
9852
+ return _gitBashCache;
9853
+ }
9854
+ } catch {
9855
+ }
9856
+ const candidates = [
9857
+ "C:\\Program Files\\Git\\bin\\bash.exe",
9858
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe"
9859
+ ];
9860
+ for (const c of candidates) {
9861
+ if (fs7.existsSync(c)) {
9862
+ _gitBashCache = c;
9863
+ try {
9864
+ fs7.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: c }));
9865
+ } catch {
9866
+ }
9867
+ return c;
9868
+ }
9869
+ }
9870
+ _gitBashCache = false;
9871
+ try {
9872
+ fs7.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: false }));
9873
+ } catch {
9874
+ }
9875
+ return false;
9876
+ }
9877
+ function shellQuote(s) {
9878
+ return "'" + s.replace(/'/g, "'\\''") + "'";
9879
+ }
9880
+ function spawnDetached(cmd, args) {
9881
+ if (process.platform === "win32") {
9882
+ const bash = findGitBash();
9883
+ if (bash) {
9884
+ const shellCmd = [cmd, ...args].map((a) => shellQuote(a.replace(/\\/g, "/"))).join(" ");
9885
+ const child3 = spawn(bash, ["-c", shellCmd], { detached: true, stdio: "ignore", windowsHide: true });
9886
+ child3.unref();
9887
+ return;
9888
+ }
9889
+ const child2 = spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
9890
+ detached: true,
9891
+ stdio: "ignore",
9892
+ windowsHide: true
9893
+ });
9894
+ child2.unref();
9895
+ return;
9896
+ }
9897
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
9898
+ child.unref();
9899
+ }
9900
+ var NodeInvocationAdapter = class {
9901
+ constructor(cliDir, encodeSourceToken2) {
9902
+ this.cliDir = cliDir;
9903
+ this.encodeSourceToken = encodeSourceToken2;
9904
+ }
9905
+ cliDir;
9906
+ encodeSourceToken;
9907
+ async requestSourceFetch(boardDir, enrichedCard, callbackToken) {
9908
+ if (shouldSuppressSpawn()) {
9909
+ return { dispatched: false, invocationId: void 0 };
9910
+ }
9911
+ try {
9912
+ const cardId = enrichedCard.id ?? "unknown";
9913
+ const enrichedCardPath = path7.join(os3.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
9914
+ fs7.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
9915
+ const args = ["--card", enrichedCardPath, "--token", callbackToken, "--rg", boardDir];
9916
+ const { cmd, args: cmdArgs } = buildCliInvocation(this.cliDir, "run-sourcedefs-internal", args);
9917
+ const invocationId = randomUUID();
9918
+ spawnDetached(cmd, cmdArgs);
9919
+ return { dispatched: true, invocationId };
9920
+ } catch (err) {
9921
+ return { dispatched: false, error: err instanceof Error ? err.message : String(err) };
9187
9922
  }
9188
9923
  }
9189
- const files = cardFile ? [path.resolve(cardFile)] : resolveCardGlobMatches(cardGlob);
9190
- if (files.length === 0) {
9191
- throw new Error(`No card files matched glob: ${cardGlob}`);
9192
- }
9193
- let failures = 0;
9194
- for (const f of files) {
9195
- const label = path.relative(process.cwd(), f) || f;
9196
- if (!fs.existsSync(f)) {
9197
- console.error(`FAIL ${label}: file not found`);
9198
- failures++;
9199
- continue;
9924
+ async requestInference(boardDir, cardId, inferencePayload, callbackToken) {
9925
+ if (shouldSuppressSpawn()) {
9926
+ return { dispatched: false, invocationId: void 0 };
9200
9927
  }
9201
- let card;
9202
9928
  try {
9203
- card = JSON.parse(fs.readFileSync(f, "utf-8"));
9929
+ const inferenceInFile = path7.join(os3.tmpdir(), `card-inference-${cardId}-${Date.now()}.json`);
9930
+ fs7.writeFileSync(inferenceInFile, JSON.stringify(inferencePayload, null, 2), "utf-8");
9931
+ const inferenceToken = this.encodeSourceToken({
9932
+ cbk: callbackToken,
9933
+ rg: boardDir,
9934
+ cid: cardId,
9935
+ b: "",
9936
+ d: "",
9937
+ cs: void 0
9938
+ });
9939
+ const { cmd, args } = buildCliInvocation(
9940
+ this.cliDir,
9941
+ "run-inference-internal",
9942
+ ["--in", inferenceInFile, "--token", inferenceToken]
9943
+ );
9944
+ const invocationId = randomUUID();
9945
+ spawnDetached(cmd, args);
9946
+ return { dispatched: true, invocationId };
9204
9947
  } catch (err) {
9205
- console.error(`FAIL ${label}: invalid JSON \u2014 ${err instanceof Error ? err.message : String(err)}`);
9206
- failures++;
9207
- continue;
9948
+ return { dispatched: false, error: err instanceof Error ? err.message : String(err) };
9208
9949
  }
9209
- const result = validateLiveCardDefinition(card);
9210
- const sourceErrors = [];
9211
- if (teConfig && Array.isArray(card.source_defs)) {
9212
- for (const src of card.source_defs) {
9213
- const bindTo = typeof src.bindTo === "string" ? src.bindTo : "(unknown)";
9214
- const tmpFile = path.join(os.tmpdir(), `validate-src-${bindTo}-${Date.now()}.json`);
9215
- try {
9216
- fs.writeFileSync(tmpFile, JSON.stringify(src), "utf-8");
9217
- let stdout;
9218
- try {
9219
- stdout = execCommandSync(teConfig.command, ["validate-source-def", "--in", tmpFile], { shell: true, timeout: 1e4 });
9220
- } catch (execErr) {
9221
- stdout = typeof execErr?.stdout === "string" ? execErr.stdout : Buffer.isBuffer(execErr?.stdout) ? execErr.stdout.toString("utf-8") : "";
9222
- if (!stdout.trim()) {
9223
- sourceErrors.push(`source "${bindTo}": executor validate-source-def failed \u2014 ${execErr instanceof Error ? execErr.message : String(execErr)}`);
9224
- continue;
9225
- }
9226
- }
9227
- const parsed = JSON.parse(stdout.trim());
9228
- if (!parsed.ok && Array.isArray(parsed.errors)) {
9229
- for (const e of parsed.errors) {
9230
- sourceErrors.push(`source "${bindTo}": ${e}`);
9231
- }
9232
- }
9233
- } catch (err) {
9234
- sourceErrors.push(`source "${bindTo}": executor validate-source-def failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
9235
- } finally {
9236
- try {
9237
- fs.unlinkSync(tmpFile);
9238
- } catch {
9239
- }
9240
- }
9241
- }
9950
+ }
9951
+ };
9952
+ function createNodeInvocationAdapter(cliDir, encodeSourceToken2) {
9953
+ return new NodeInvocationAdapter(cliDir, encodeSourceToken2);
9954
+ }
9955
+ var NodeCardStore = class {
9956
+ constructor(lookupCardPathFn) {
9957
+ this.lookupCardPathFn = lookupCardPathFn;
9958
+ }
9959
+ lookupCardPathFn;
9960
+ readCard(cardPath) {
9961
+ try {
9962
+ return JSON.parse(fs7.readFileSync(cardPath, "utf-8"));
9963
+ } catch {
9964
+ return null;
9242
9965
  }
9243
- const allErrors = [...result.errors, ...sourceErrors];
9244
- if (allErrors.length === 0) {
9245
- console.log(`OK ${label}`);
9246
- } else {
9247
- console.error(`FAIL ${label}:`);
9248
- for (const e of allErrors) {
9249
- console.error(` ${e}`);
9966
+ }
9967
+ readSourceFileContent(boardDir, cardId, outputFile) {
9968
+ const filePath = path7.join(boardDir, cardId, outputFile);
9969
+ if (!fs7.existsSync(filePath)) return null;
9970
+ try {
9971
+ const raw = fs7.readFileSync(filePath, "utf-8").trim();
9972
+ try {
9973
+ return JSON.parse(raw);
9974
+ } catch {
9975
+ return raw;
9250
9976
  }
9251
- failures++;
9977
+ } catch {
9978
+ return null;
9252
9979
  }
9253
9980
  }
9254
- if (failures > 0) {
9255
- throw new Error(`${failures} of ${files.length} card(s) failed validation.`);
9256
- } else {
9257
- console.log(`
9258
- ${files.length} card(s) passed validation.`);
9981
+ lookupCardPath(boardDir, nodeId) {
9982
+ return this.lookupCardPathFn(boardDir, nodeId);
9259
9983
  }
9984
+ };
9985
+ function createNodeCardStore(lookupCardPath2) {
9986
+ return new NodeCardStore(lookupCardPath2);
9260
9987
  }
9261
- function cmdRemoveCard(args) {
9262
- const rgIdx = args.indexOf("--rg");
9263
- const idIdx = args.indexOf("--id");
9264
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9265
- const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
9266
- if (!dir || !cardId) {
9267
- console.error("Usage: board-live-cards remove-card --rg <dir> --id <card-id>");
9268
- process.exit(1);
9988
+
9989
+ // src/cli/board-live-cards-cli.ts
9990
+ var BOARD_FILE = "board-graph.json";
9991
+ var JOURNAL_FILE = "board-journal.jsonl";
9992
+ var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
9993
+ var INFERENCE_ADAPTER_LOG_FILE = "inference-adapter.jsonl";
9994
+ var INVENTORY_FILE = "cards-inventory.jsonl";
9995
+ var RUNTIME_OUT_FILE = ".runtime-out";
9996
+ var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
9997
+ var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
9998
+ var RUNTIME_CARDS_DIR = "cards";
9999
+ var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
10000
+ var INFERENCE_ADAPTER_FILE = ".inference-adapter";
10001
+ var TASK_EXECUTOR_FILE = ".task-executor";
10002
+ function readTaskExecutorConfig(boardDir) {
10003
+ const executorFile = path7.join(boardDir, TASK_EXECUTOR_FILE);
10004
+ if (!fs7.existsSync(executorFile)) return void 0;
10005
+ const raw = fs7.readFileSync(executorFile, "utf-8").trim();
10006
+ if (!raw) return void 0;
10007
+ try {
10008
+ const parsed = JSON.parse(raw);
10009
+ if (parsed && typeof parsed === "object" && typeof parsed.command === "string") {
10010
+ return parsed;
10011
+ }
10012
+ } catch {
9269
10013
  }
9270
- appendEventToJournal(dir, {
9271
- type: "task-removal",
9272
- taskName: cardId,
9273
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9274
- });
9275
- void processAccumulatedEventsInfinitePass(dir);
9276
- console.log(`Card "${cardId}" removed.`);
10014
+ return { command: raw };
9277
10015
  }
9278
- function cmdSourceDataFetched(args) {
9279
- const tmpIdx = args.indexOf("--tmp");
9280
- const tokenIdx = args.indexOf("--token");
9281
- const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
9282
- const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9283
- if (!tmpFile || !token) {
9284
- console.error("Usage: board-live-cards source-data-fetched --tmp <tmp-file> --token <sourceToken>");
9285
- process.exit(1);
10016
+ var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
10017
+ var BoardJournal = class {
10018
+ journalPath;
10019
+ lastDrainedId;
10020
+ constructor(journalPath, lastDrainedJournalId) {
10021
+ this.journalPath = journalPath;
10022
+ this.lastDrainedId = lastDrainedJournalId;
9286
10023
  }
9287
- const payload = decodeSourceToken(token);
9288
- if (!payload) {
9289
- console.error("Invalid source token");
9290
- process.exit(1);
10024
+ append(event) {
10025
+ const entry = { id: randomUUID(), event };
10026
+ fs7.appendFileSync(this.journalPath, JSON.stringify(entry) + "\n", "utf-8");
9291
10027
  }
9292
- const { cbk, rg, cid, b, d, cs } = payload;
9293
- const destPath = path.join(rg, cid, d);
9294
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
9295
- fs.renameSync(tmpFile, destPath);
9296
- console.log(`[source-data-fetched] ${cid}.${b} \u2192 ${cid}/${d}`);
9297
- const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
9298
- const cbkDecoded = decodeCallbackToken2(cbk);
9299
- if (!cbkDecoded) {
9300
- console.error("Invalid callback token embedded in source token");
9301
- process.exit(1);
10028
+ drain() {
10029
+ if (!fs7.existsSync(this.journalPath)) return [];
10030
+ const content = fs7.readFileSync(this.journalPath, "utf-8").trim();
10031
+ if (!content) return [];
10032
+ const entries = content.split("\n").map((l) => JSON.parse(l));
10033
+ let startIdx = 0;
10034
+ if (this.lastDrainedId) {
10035
+ const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
10036
+ if (drainedIdx !== -1) startIdx = drainedIdx + 1;
10037
+ }
10038
+ const undrained = entries.slice(startIdx);
10039
+ if (undrained.length > 0) {
10040
+ this.lastDrainedId = undrained[undrained.length - 1].id;
10041
+ }
10042
+ return undrained.map((e) => e.event);
9302
10043
  }
9303
- appendEventToJournal(rg, {
9304
- type: "task-progress",
9305
- taskName: cbkDecoded.taskName,
9306
- update: { bindTo: b, outputFile: d, fetchedAt, sourceChecksum: cs },
9307
- timestamp: fetchedAt
9308
- });
9309
- void processAccumulatedEventsInfinitePass(rg);
9310
- }
9311
- function cmdSourceDataFetchFailure(args) {
9312
- const tokenIdx = args.indexOf("--token");
9313
- const reasonIdx = args.indexOf("--reason");
9314
- const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9315
- const reason = reasonIdx !== -1 ? args[reasonIdx + 1] : "unknown";
9316
- if (!token) {
9317
- console.error("Usage: board-live-cards source-data-fetch-failure --token <sourceToken> [--reason <msg>]");
9318
- process.exit(1);
10044
+ get size() {
10045
+ if (!fs7.existsSync(this.journalPath)) return 0;
10046
+ const content = fs7.readFileSync(this.journalPath, "utf-8").trim();
10047
+ if (!content) return 0;
10048
+ const entries = content.split("\n").map((l) => JSON.parse(l));
10049
+ if (!this.lastDrainedId) return entries.length;
10050
+ const drainedIdx = entries.findIndex((e) => e.id === this.lastDrainedId);
10051
+ return drainedIdx === -1 ? entries.length : entries.length - drainedIdx - 1;
9319
10052
  }
9320
- const payload = decodeSourceToken(token);
9321
- if (!payload) {
9322
- console.error("Invalid source token");
9323
- process.exit(1);
10053
+ get lastDrainedJournalId() {
10054
+ return this.lastDrainedId;
9324
10055
  }
9325
- const { cbk, rg, cid, b, d, cs } = payload;
9326
- console.log(`[source-data-fetch-failure] ${cid}.${b}: ${reason}`);
9327
- const cbkDecoded = decodeCallbackToken2(cbk);
9328
- if (!cbkDecoded) {
9329
- console.error("Invalid callback token embedded in source token");
9330
- process.exit(1);
10056
+ };
10057
+ function readCardInventory(boardDir) {
10058
+ const inventoryPath = path7.join(boardDir, INVENTORY_FILE);
10059
+ if (!fs7.existsSync(inventoryPath)) return [];
10060
+ const lines = fs7.readFileSync(inventoryPath, "utf-8").split("\n").filter((l) => l.trim());
10061
+ return lines.map((l) => JSON.parse(l));
10062
+ }
10063
+ function lookupCardPath(boardDir, cardId) {
10064
+ const entries = readCardInventory(boardDir);
10065
+ const entry = entries.find((e) => e.cardId === cardId);
10066
+ return entry?.cardFilePath ?? null;
10067
+ }
10068
+ function appendCardInventory(boardDir, entry) {
10069
+ const inventoryPath = path7.join(boardDir, INVENTORY_FILE);
10070
+ const normalized = { ...entry, cardFilePath: path7.resolve(entry.cardFilePath) };
10071
+ fs7.appendFileSync(inventoryPath, JSON.stringify(normalized) + "\n");
10072
+ }
10073
+ function buildCardInventoryIndex(boardDir) {
10074
+ const byCardId = /* @__PURE__ */ new Map();
10075
+ const byCardPath = /* @__PURE__ */ new Map();
10076
+ for (const entry of readCardInventory(boardDir)) {
10077
+ const normalizedPath = path7.resolve(entry.cardFilePath);
10078
+ const normalizedEntry = {
10079
+ ...entry,
10080
+ cardFilePath: normalizedPath
10081
+ };
10082
+ const existingById = byCardId.get(entry.cardId);
10083
+ if (existingById && existingById.cardFilePath !== normalizedPath) {
10084
+ throw new Error(
10085
+ `Inventory invariant violation: card id "${entry.cardId}" maps to multiple files: "${existingById.cardFilePath}" and "${normalizedPath}"`
10086
+ );
10087
+ }
10088
+ const existingByPath = byCardPath.get(normalizedPath);
10089
+ if (existingByPath && existingByPath.cardId !== entry.cardId) {
10090
+ throw new Error(
10091
+ `Inventory invariant violation: file "${normalizedPath}" maps to multiple ids: "${existingByPath.cardId}" and "${entry.cardId}"`
10092
+ );
10093
+ }
10094
+ byCardId.set(entry.cardId, normalizedEntry);
10095
+ byCardPath.set(normalizedPath, normalizedEntry);
9331
10096
  }
9332
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
9333
- appendEventToJournal(rg, {
9334
- type: "task-progress",
9335
- taskName: cbkDecoded.taskName,
9336
- update: { bindTo: b, outputFile: d, failure: true, reason, sourceChecksum: cs },
9337
- timestamp
9338
- });
9339
- void processAccumulatedEventsInfinitePass(rg);
10097
+ return { byCardId, byCardPath };
9340
10098
  }
9341
- function cmdRunSources(args) {
9342
- const cardIdx = args.indexOf("--card");
9343
- const tokenIdx = args.indexOf("--token");
9344
- const rgIdx = args.indexOf("--rg");
9345
- const sourceChecksumsIdx = args.indexOf("--source-checksums");
9346
- const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
9347
- const callbackToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9348
- const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9349
- const sourceChecksumsJson = sourceChecksumsIdx !== -1 ? args[sourceChecksumsIdx + 1] : void 0;
9350
- const sourceChecksums = sourceChecksumsJson ? JSON.parse(sourceChecksumsJson) : void 0;
9351
- if (!cardFilePath || !callbackToken || !boardDir) {
9352
- console.error("Usage: board-live-cards run-sourcedefs-internal --card <path> --token <token> --rg <dir> [--source-checksums <json>]");
9353
- process.exit(1);
10099
+ function initBoard(dir) {
10100
+ const boardPath = path7.join(dir, BOARD_FILE);
10101
+ if (fs7.existsSync(boardPath)) {
10102
+ const envelope2 = JSON.parse(fs7.readFileSync(boardPath, "utf-8"));
10103
+ restore(envelope2.graph);
10104
+ return "exists";
9354
10105
  }
9355
- const card = JSON.parse(fs.readFileSync(cardFilePath, "utf-8"));
9356
- if (path.basename(cardFilePath).startsWith("card-enriched-")) {
9357
- try {
9358
- fs.unlinkSync(cardFilePath);
9359
- } catch {
10106
+ if (fs7.existsSync(dir)) {
10107
+ const entries = fs7.readdirSync(dir);
10108
+ if (entries.length > 0) {
10109
+ throw new Error(`Directory "${dir}" is not empty and has no valid ${BOARD_FILE}`);
9360
10110
  }
9361
10111
  }
9362
- console.log(`[run-sourcedefs-internal] Processing card "${card.id}"`);
9363
- const teConfig = readTaskExecutorConfig(boardDir);
9364
- const taskExecutor = teConfig?.command;
9365
- const taskExecutorExtraB64 = teConfig?.extra ? Buffer.from(JSON.stringify(teConfig.extra)).toString("base64") : void 0;
9366
- function runSource(src) {
9367
- const sourceChecksumForInvoke = src.outputFile ? sourceChecksums?.[src.outputFile] : void 0;
9368
- const sourceToken = encodeSourceToken({
9369
- cbk: callbackToken,
9370
- rg: boardDir,
9371
- cid: card.id,
9372
- b: src.bindTo,
9373
- d: src.outputFile ?? "",
9374
- cs: sourceChecksumForInvoke
9375
- });
9376
- function reportFailure(reason) {
9377
- invokeSourceDataFetchFailure(sourceToken, reason, (err) => {
9378
- if (err) console.error(`[run-sourcedefs-internal] source-data-fetch-failure call failed:`, err.message);
9379
- });
9380
- }
9381
- function reportFetched(outFile2) {
9382
- invokeSourceDataFetched(sourceToken, outFile2);
9383
- }
9384
- if (taskExecutor) {
9385
- if (!src.outputFile) {
9386
- console.warn(`[run-sourcedefs-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
9387
- reportFailure("no outputFile configured");
9388
- return;
9389
- }
9390
- const inFile = path.join(os.tmpdir(), `card-source-in-${src.bindTo}-${Date.now()}.json`);
9391
- const outFile2 = path.join(os.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
9392
- const errFile = path.join(os.tmpdir(), `card-source-err-${src.bindTo}-${Date.now()}.txt`);
9393
- const sourceForExecutor = {
9394
- ...src,
9395
- cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path.dirname(cardFilePath || ""),
9396
- boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
9397
- };
9398
- appendTaskExecutorLog(boardDir, sourceForExecutor, "external-task-executor");
9399
- fs.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
9400
- const executorArgs = ["run-source-fetch", "--in", inFile, "--out", outFile2, "--err", errFile];
9401
- if (taskExecutorExtraB64) executorArgs.push("--extra", taskExecutorExtraB64);
9402
- console.log(`[run-sourcedefs-internal] task-executor: ${taskExecutor} ${executorArgs.join(" ")}`);
9403
- try {
9404
- execCommandSync(taskExecutor, executorArgs, {
9405
- shell: true,
9406
- timeout: src.timeout ?? 12e4
9407
- });
9408
- } catch (err) {
9409
- const reason = err.message ?? String(err);
9410
- console.error(`[run-sourcedefs-internal] task-executor failed for source "${src.bindTo}":`, reason);
9411
- reportFailure(reason);
9412
- return;
9413
- }
9414
- if (fs.existsSync(outFile2)) {
9415
- reportFetched(outFile2);
9416
- } else {
9417
- const errMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
9418
- console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
9419
- reportFailure(errMsg);
9420
- }
9421
- return;
9422
- }
9423
- if (!src.outputFile) {
9424
- console.warn(`[run-sourcedefs-internal] source "${src.bindTo}" has no outputFile configured \u2014 cannot deliver`);
9425
- reportFailure("no outputFile configured");
9426
- return;
9427
- }
9428
- const outFile = path.join(os.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
9429
- if (!src.cli) {
9430
- const errMsg = "source.cli is required for built-in source execution";
9431
- console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
9432
- reportFailure(errMsg);
9433
- return;
9434
- }
9435
- const timeout = src.timeout ?? 12e4;
9436
- const sourceCwd = typeof src.cwd === "string" ? src.cwd : path.dirname(cardFilePath || "");
9437
- const sourceBoardDir = typeof src.boardDir === "string" ? src.boardDir : boardDir;
9438
- const sourceForBuiltInExecutor = {
9439
- ...src,
9440
- cwd: sourceCwd,
9441
- boardDir: sourceBoardDir
9442
- };
9443
- appendTaskExecutorLog(boardDir, sourceForBuiltInExecutor, "built-in-run-source-fetch");
9444
- const cmdParts = splitCommandLine(src.cli);
9445
- if (cmdParts.length === 0) {
9446
- const errMsg = "source.cli command is empty";
9447
- console.warn(`[run-sourcedefs-internal] source "${src.bindTo}": ${errMsg}`);
9448
- reportFailure(errMsg);
9449
- return;
9450
- }
9451
- const rawCmd = cmdParts[0];
9452
- const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
9453
- let stdout;
9454
- try {
9455
- stdout = execCommandSync(cmd, cliArgs, {
9456
- shell: false,
9457
- encoding: "utf-8",
9458
- timeout,
9459
- cwd: sourceCwd,
9460
- env: {
9461
- ...process.env,
9462
- ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
9463
- }
9464
- });
9465
- } catch (err) {
9466
- const reason = err.message ?? String(err);
9467
- console.error(`[run-sourcedefs-internal] source fetch failed for source "${src.bindTo}":`, reason);
9468
- reportFailure(reason);
9469
- return;
10112
+ fs7.mkdirSync(dir, { recursive: true });
10113
+ const live = createLiveGraph(EMPTY_CONFIG);
10114
+ const snap = snapshot(live);
10115
+ const envelope = { lastDrainedJournalId: "", graph: snap };
10116
+ fs7.writeFileSync(boardPath, JSON.stringify(envelope, null, 2));
10117
+ return "created";
10118
+ }
10119
+ function loadBoardEnvelope(dir) {
10120
+ const raw = fs7.readFileSync(path7.join(dir, BOARD_FILE), "utf-8");
10121
+ return JSON.parse(raw);
10122
+ }
10123
+ function loadBoard(dir) {
10124
+ const envelope = loadBoardEnvelope(dir);
10125
+ return restore(envelope.graph);
10126
+ }
10127
+ function saveBoard(dir, rg, journal) {
10128
+ const snap = rg.snapshot();
10129
+ const envelope = {
10130
+ lastDrainedJournalId: journal.lastDrainedJournalId,
10131
+ graph: snap
10132
+ };
10133
+ writeJsonAtomic2(path7.join(dir, BOARD_FILE), envelope);
10134
+ const live = restore(snap);
10135
+ const statusObject = buildBoardStatusObject(path7.resolve(dir), live);
10136
+ writeJsonAtomic2(resolveStatusSnapshotPath(dir), statusObject);
10137
+ }
10138
+ function runtimeOutConfigPath(boardDir) {
10139
+ return path7.join(boardDir, RUNTIME_OUT_FILE);
10140
+ }
10141
+ function resolveConfiguredRuntimeOutDir(boardDir) {
10142
+ const cfgPath = runtimeOutConfigPath(boardDir);
10143
+ if (fs7.existsSync(cfgPath)) {
10144
+ const configured = fs7.readFileSync(cfgPath, "utf-8").trim();
10145
+ if (configured) {
10146
+ return path7.isAbsolute(configured) ? configured : path7.resolve(boardDir, configured);
9470
10147
  }
9471
- fs.writeFileSync(outFile, stdout.trim(), "utf-8");
9472
- reportFetched(outFile);
9473
10148
  }
9474
- const source_defs = card.source_defs ?? [];
9475
- for (const src of source_defs) {
9476
- runSource(src);
10149
+ const defaultDir = path7.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
10150
+ fs7.writeFileSync(cfgPath, defaultDir, "utf-8");
10151
+ return defaultDir;
10152
+ }
10153
+ function configureRuntimeOutDir(boardDir, runtimeOut) {
10154
+ let resolved;
10155
+ if (runtimeOut) {
10156
+ resolved = path7.isAbsolute(runtimeOut) ? runtimeOut : path7.resolve(boardDir, runtimeOut);
10157
+ } else {
10158
+ resolved = path7.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
10159
+ }
10160
+ fs7.mkdirSync(resolved, { recursive: true });
10161
+ fs7.writeFileSync(runtimeOutConfigPath(boardDir), resolved, "utf-8");
10162
+ return resolved;
10163
+ }
10164
+ function resolveStatusSnapshotPath(boardDir) {
10165
+ return path7.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_STATUS_FILE);
10166
+ }
10167
+ function resolveComputedValuesPath(boardDir, cardId) {
10168
+ return path7.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_CARDS_DIR, `${cardId}.computed.json`);
10169
+ }
10170
+ function resolveDataObjectsDirPath(boardDir) {
10171
+ return path7.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_DATA_OBJECTS_DIR);
10172
+ }
10173
+ function toDataObjectFileName(token) {
10174
+ return token.replace(/[\\/]/g, "__");
10175
+ }
10176
+ function writeRuntimeDataObjects(boardDir, data) {
10177
+ for (const [token, payload] of Object.entries(data)) {
10178
+ if (!token) continue;
10179
+ const fileName = toDataObjectFileName(token);
10180
+ if (!fileName) continue;
10181
+ const filePath = path7.join(resolveDataObjectsDirPath(boardDir), fileName);
10182
+ writeJsonAtomic2(filePath, payload);
10183
+ }
10184
+ }
10185
+ function writeJsonAtomic2(filePath, payload) {
10186
+ fs7.mkdirSync(path7.dirname(filePath), { recursive: true });
10187
+ const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
10188
+ fs7.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
10189
+ fs7.renameSync(tmpPath, filePath);
10190
+ }
10191
+ function withBoardLock(boardDir, fn) {
10192
+ const boardPath = path7.join(boardDir, BOARD_FILE);
10193
+ const release = lockSync(boardPath, { retries: { retries: 5, minTimeout: 100 } });
10194
+ try {
10195
+ return fn();
10196
+ } finally {
10197
+ release();
10198
+ }
10199
+ }
10200
+ function decodeCallbackToken2(token) {
10201
+ try {
10202
+ const payload = JSON.parse(Buffer.from(token, "base64url").toString());
10203
+ if (typeof payload?.t === "string") return { taskName: payload.t };
10204
+ return null;
10205
+ } catch {
10206
+ return null;
9477
10207
  }
9478
10208
  }
9479
- function cmdTaskProgress(args) {
9480
- const rgIdx = args.indexOf("--rg");
9481
- const tokenIdx = args.indexOf("--token");
9482
- const updateIdx = args.indexOf("--update");
9483
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9484
- const token = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9485
- const updateJson = updateIdx !== -1 ? args[updateIdx + 1] : "{}";
9486
- if (!dir || !token) {
9487
- console.error("Usage: board-live-cards task-progress --rg <dir> --token <token> [--update <json>]");
9488
- process.exit(1);
9489
- }
9490
- const decoded = decodeCallbackToken2(token);
9491
- if (!decoded) {
9492
- console.error("Invalid callback token");
9493
- process.exit(1);
9494
- }
9495
- const update = updateJson ? JSON.parse(updateJson) : {};
9496
- appendEventToJournal(dir, {
9497
- type: "task-progress",
9498
- taskName: decoded.taskName,
9499
- update,
9500
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9501
- });
9502
- void processAccumulatedEventsInfinitePass(dir);
10209
+ function encodeSourceToken(payload) {
10210
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
9503
10211
  }
9504
- function cmdRunInference(args) {
9505
- const inIdx = args.indexOf("--in");
9506
- const tokenIdx = args.indexOf("--token");
9507
- const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
9508
- const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9509
- if (!inFile || !inferenceToken) {
9510
- console.error("Usage: board-live-cards run-inference-internal --in <input.json> --token <inference-token>");
9511
- process.exit(1);
9512
- }
9513
- const decodedToken = decodeSourceToken(inferenceToken);
9514
- if (!decodedToken) {
9515
- console.error("Invalid inference token");
9516
- process.exit(1);
9517
- }
9518
- const callbackToken = decodedToken.cbk;
9519
- const boardDir = decodedToken.rg;
9520
- const cbkDecoded = decodeCallbackToken2(callbackToken);
9521
- if (!cbkDecoded) {
9522
- console.error("Invalid callback token embedded in inference token");
9523
- process.exit(1);
9524
- }
9525
- function spawnInferenceDone(tmpFile) {
9526
- const { cmd, args: cliArgs } = getCliInvocation("inference-done", ["--tmp", tmpFile, "--token", inferenceToken]);
9527
- spawnDetachedCommand(cmd, cliArgs);
9528
- }
9529
- function spawnInferenceDoneError(reason) {
9530
- const tmpFile = path.join(os.tmpdir(), `card-inference-err-${Date.now()}.json`);
9531
- fs.writeFileSync(tmpFile, JSON.stringify({ isTaskCompleted: false, reason }), "utf-8");
9532
- spawnInferenceDone(tmpFile);
10212
+ function decodeSourceToken(token) {
10213
+ try {
10214
+ const p = JSON.parse(Buffer.from(token, "base64url").toString());
10215
+ if (typeof p?.cbk === "string" && typeof p?.cid === "string" && typeof p?.b === "string" && typeof p?.d === "string") {
10216
+ return p;
10217
+ }
10218
+ return null;
10219
+ } catch {
10220
+ return null;
9533
10221
  }
9534
- if (!fs.existsSync(inFile)) {
9535
- spawnInferenceDoneError(`inference input not found: ${inFile}`);
9536
- return;
10222
+ }
10223
+ function appendEventToJournal(boardDir, event) {
10224
+ const journalPath = path7.join(boardDir, JOURNAL_FILE);
10225
+ const entry = { id: randomUUID(), event };
10226
+ fs7.appendFileSync(journalPath, JSON.stringify(entry) + "\n", "utf-8");
10227
+ }
10228
+ function getUndrainedEntries(boardDir, lastDrainedId) {
10229
+ const journalPath = path7.join(boardDir, JOURNAL_FILE);
10230
+ if (!fs7.existsSync(journalPath)) return [];
10231
+ const content = fs7.readFileSync(journalPath, "utf-8").trim();
10232
+ if (!content) return [];
10233
+ const entries = content.split("\n").map((l) => JSON.parse(l));
10234
+ if (!lastDrainedId) return entries;
10235
+ const idx = entries.findIndex((e) => e.id === lastDrainedId);
10236
+ return idx === -1 ? entries : entries.slice(idx + 1);
10237
+ }
10238
+ function determineLatestPendingAccumulated(boardDir) {
10239
+ const boardPath = path7.join(boardDir, BOARD_FILE);
10240
+ if (!fs7.existsSync(boardPath)) return 0;
10241
+ try {
10242
+ const envelope = loadBoardEnvelope(boardDir);
10243
+ return getUndrainedEntries(boardDir, envelope.lastDrainedJournalId).length;
10244
+ } catch {
10245
+ return 0;
9537
10246
  }
9538
- const adapterFile = path.join(boardDir, INFERENCE_ADAPTER_FILE);
9539
- const inferenceAdapter = fs.existsSync(adapterFile) ? fs.readFileSync(adapterFile, "utf-8").trim() : void 0;
9540
- if (!inferenceAdapter) {
9541
- spawnInferenceDoneError(`inference adapter is not configured (${INFERENCE_ADAPTER_FILE})`);
9542
- return;
10247
+ }
10248
+ function shouldUseShellForCommand(cmd, forceShell) {
10249
+ if (typeof forceShell === "boolean") return forceShell;
10250
+ return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
10251
+ }
10252
+ var _gitBashPath;
10253
+ var GIT_BASH_CACHE_FILE2 = path7.join(os3.tmpdir(), ".board-live-cards-git-bash-cache.json");
10254
+ function findGitBash2() {
10255
+ if (_gitBashPath !== void 0) return _gitBashPath;
10256
+ if (process.platform !== "win32") return _gitBashPath = false;
10257
+ try {
10258
+ const cached = JSON.parse(fs7.readFileSync(GIT_BASH_CACHE_FILE2, "utf8"));
10259
+ if (cached.path === false || typeof cached.path === "string" && fs7.existsSync(cached.path)) {
10260
+ return _gitBashPath = cached.path;
10261
+ }
10262
+ } catch {
9543
10263
  }
9544
- const outFile = path.join(os.tmpdir(), `card-inference-out-${Date.now()}.json`);
9545
- const errFile = path.join(os.tmpdir(), `card-inference-err-${Date.now()}.txt`);
9546
- const adapterParts = splitCommandLine(inferenceAdapter);
9547
- if (adapterParts.length === 0) {
9548
- spawnInferenceDoneError("inference adapter command is empty");
9549
- return;
10264
+ const candidates = [
10265
+ process.env.SHELL,
10266
+ process.env.PROGRAMFILES && path7.join(process.env.PROGRAMFILES, "Git", "usr", "bin", "bash.exe"),
10267
+ process.env.PROGRAMFILES && path7.join(process.env.PROGRAMFILES, "Git", "bin", "bash.exe"),
10268
+ process.env["PROGRAMFILES(X86)"] && path7.join(process.env["PROGRAMFILES(X86)"], "Git", "bin", "bash.exe"),
10269
+ process.env.LOCALAPPDATA && path7.join(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")
10270
+ ];
10271
+ for (const c of candidates) {
10272
+ if (c && /bash(\.exe)?$/i.test(c) && fs7.existsSync(c)) {
10273
+ _gitBashPath = c;
10274
+ try {
10275
+ fs7.writeFileSync(GIT_BASH_CACHE_FILE2, JSON.stringify({ path: c }));
10276
+ } catch {
10277
+ }
10278
+ return _gitBashPath;
10279
+ }
9550
10280
  }
9551
- const adapterRawCmd = adapterParts[0];
9552
- const adapterRawArgs = adapterParts.slice(1);
9553
- const { cmd: adapterCmd, args: adapterArgsPrefix } = resolveCommandInvocation(adapterRawCmd, adapterRawArgs);
9554
- const adapterArgs = [...adapterArgsPrefix, "run-inference", "--in", inFile, "--out", outFile, "--err", errFile];
10281
+ _gitBashPath = false;
9555
10282
  try {
9556
- execCommandSync(adapterCmd, adapterArgs, {
9557
- shell: false,
9558
- timeout: 12e4,
9559
- cwd: boardDir,
9560
- env: {
9561
- ...process.env,
9562
- BOARD_DIR: boardDir
9563
- }
9564
- });
9565
- } catch (err) {
9566
- const reason = err.message ?? String(err);
9567
- spawnInferenceDoneError(reason);
9568
- return;
10283
+ fs7.writeFileSync(GIT_BASH_CACHE_FILE2, JSON.stringify({ path: false }));
10284
+ } catch {
9569
10285
  }
9570
- if (!fs.existsSync(outFile)) {
9571
- const errMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "inference adapter produced no output file";
9572
- spawnInferenceDoneError(errMsg);
10286
+ return _gitBashPath;
10287
+ }
10288
+ function shellQuote2(s) {
10289
+ return "'" + s.replace(/'/g, "'\\''") + "'";
10290
+ }
10291
+ function spawnDetachedCommand(cmd, args) {
10292
+ if (process.platform === "win32") {
10293
+ const bash = findGitBash2();
10294
+ if (bash) {
10295
+ const shellCmd = [cmd, ...args].map((a) => shellQuote2(a.replace(/\\/g, "/"))).join(" ");
10296
+ const child3 = spawn(bash, ["-c", shellCmd], { detached: true, stdio: "ignore", windowsHide: true });
10297
+ child3.unref();
10298
+ return;
10299
+ }
10300
+ const child2 = spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
10301
+ detached: true,
10302
+ stdio: "ignore",
10303
+ windowsHide: true
10304
+ });
10305
+ child2.unref();
9573
10306
  return;
9574
10307
  }
9575
- spawnInferenceDone(outFile);
10308
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
10309
+ child.unref();
9576
10310
  }
9577
- function cmdInferenceDone(args) {
9578
- const tmpIdx = args.indexOf("--tmp");
9579
- const tokenIdx = args.indexOf("--token");
9580
- const tmpFile = tmpIdx !== -1 ? args[tmpIdx + 1] : void 0;
9581
- const inferenceToken = tokenIdx !== -1 ? args[tokenIdx + 1] : void 0;
9582
- if (!tmpFile || !inferenceToken) {
9583
- console.error("Usage: board-live-cards inference-done --tmp <result.json> --token <inference-token>");
9584
- process.exit(1);
9585
- }
9586
- const decodedToken = decodeSourceToken(inferenceToken);
9587
- if (!decodedToken) {
9588
- console.error("Invalid inference token");
9589
- process.exit(1);
9590
- }
9591
- const { cbk: callbackToken, rg: dir, cs: inputChecksum } = decodedToken;
9592
- const decoded = decodeCallbackToken2(callbackToken);
9593
- if (!decoded) {
9594
- console.error("Invalid callback token embedded in inference token");
9595
- process.exit(1);
9596
- }
9597
- const taskName = decoded.taskName;
9598
- const cardPath = lookupCardPath(dir, taskName);
9599
- if (!cardPath) {
9600
- console.error(`Card file for task "${taskName}" not found in inventory`);
9601
- process.exit(1);
9602
- }
9603
- let result = {};
9604
- if (fs.existsSync(tmpFile)) {
9605
- try {
9606
- result = JSON.parse(fs.readFileSync(tmpFile, "utf-8").trim());
9607
- } catch (err) {
9608
- result = { isTaskCompleted: false, reason: `failed to parse inference result: ${err instanceof Error ? err.message : String(err)}` };
10311
+ function execCommandSync(cmd, args, options) {
10312
+ const output = execFileSync(cmd, args, {
10313
+ shell: shouldUseShellForCommand(cmd, options?.shell),
10314
+ timeout: options?.timeout,
10315
+ encoding: options?.encoding,
10316
+ cwd: options?.cwd,
10317
+ windowsHide: true,
10318
+ env: options?.env
10319
+ });
10320
+ return typeof output === "string" ? output : output.toString("utf-8");
10321
+ }
10322
+ function execCommandAsync(cmd, args, callback) {
10323
+ execFile(
10324
+ cmd,
10325
+ args,
10326
+ { shell: shouldUseShellForCommand(cmd), encoding: "utf8", windowsHide: true },
10327
+ (err, stdout, stderr) => callback(err ?? null, stdout, stderr)
10328
+ );
10329
+ }
10330
+ function splitCommandLine(command) {
10331
+ const tokens = [];
10332
+ let current = "";
10333
+ let quote = null;
10334
+ for (const ch of command.trim()) {
10335
+ if (quote) {
10336
+ if (ch === quote) {
10337
+ quote = null;
10338
+ } else {
10339
+ current += ch;
10340
+ }
10341
+ continue;
9609
10342
  }
9610
- try {
9611
- fs.unlinkSync(tmpFile);
9612
- } catch {
10343
+ if (ch === '"' || ch === "'") {
10344
+ quote = ch;
10345
+ continue;
9613
10346
  }
9614
- } else {
9615
- result = { isTaskCompleted: false, reason: `inference result file not found: ${tmpFile}` };
9616
- }
9617
- const isTaskCompletedFlag = result.isTaskCompleted === true;
9618
- const inferenceCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
9619
- const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
9620
- if (!card.card_data) card.card_data = {};
9621
- const cardData = card.card_data;
9622
- const existingInference = cardData.llm_task_completion_inference && typeof cardData.llm_task_completion_inference === "object" ? cardData.llm_task_completion_inference : {};
9623
- cardData.llm_task_completion_inference = {
9624
- ...existingInference,
9625
- isTaskCompleted: isTaskCompletedFlag,
9626
- reason: typeof result.reason === "string" ? result.reason : "",
9627
- evidence: typeof result.evidence === "string" ? result.evidence : "",
9628
- inferenceCompletedAt
9629
- };
9630
- fs.writeFileSync(cardPath, JSON.stringify(card, null, 2), "utf-8");
9631
- const runtimePath2 = path.join(dir, taskName, "runtime.json");
9632
- let runtime = { _sources: {} };
9633
- if (fs.existsSync(runtimePath2)) {
9634
- try {
9635
- runtime = JSON.parse(fs.readFileSync(runtimePath2, "utf-8"));
9636
- } catch {
10347
+ if (/\s/.test(ch)) {
10348
+ if (current) {
10349
+ tokens.push(current);
10350
+ current = "";
10351
+ }
10352
+ continue;
9637
10353
  }
10354
+ current += ch;
9638
10355
  }
9639
- const inferenceEntry = runtime._inferenceEntry ?? {};
9640
- runtime._inferenceEntry = nextEntryAfterFetchDelivery(inferenceEntry, inferenceCompletedAt);
9641
- fs.mkdirSync(path.dirname(runtimePath2), { recursive: true });
9642
- fs.writeFileSync(runtimePath2, JSON.stringify(runtime, null, 2), "utf-8");
9643
- appendEventToJournal(dir, {
9644
- type: "task-progress",
9645
- taskName,
9646
- update: {
9647
- kind: "inference-done",
9648
- isTaskCompleted: isTaskCompletedFlag,
9649
- inputChecksum
9650
- },
9651
- timestamp: inferenceCompletedAt
9652
- });
9653
- void processAccumulatedEventsInfinitePass(dir);
10356
+ if (quote) {
10357
+ throw new Error(`Unterminated quote in command: ${command}`);
10358
+ }
10359
+ if (current) tokens.push(current);
10360
+ return tokens;
9654
10361
  }
9655
- function cmdRunSourceFetch(args) {
9656
- const inIdx = args.indexOf("--in");
9657
- const outIdx = args.indexOf("--out");
9658
- const errIdx = args.indexOf("--err");
9659
- const inFile = inIdx !== -1 ? args[inIdx + 1] : void 0;
9660
- const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
9661
- const errFile = errIdx !== -1 ? args[errIdx + 1] : void 0;
9662
- if (!inFile || !outFile) {
9663
- console.error("Usage: board-live-cards run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]");
9664
- process.exit(1);
10362
+ function resolveCommandInvocation(rawCmd, rawArgs) {
10363
+ if (/^(node|node\.exe)$/i.test(rawCmd)) {
10364
+ return { cmd: process.execPath, args: rawArgs };
9665
10365
  }
9666
- if (!fs.existsSync(inFile)) {
9667
- const msg = `Input file not found: ${inFile}`;
9668
- if (errFile) fs.writeFileSync(errFile, msg);
9669
- console.error(`[run-source-fetch] ${msg}`);
9670
- process.exit(1);
10366
+ if (/\.m?js$/i.test(rawCmd)) {
10367
+ return { cmd: process.execPath, args: [rawCmd, ...rawArgs] };
9671
10368
  }
9672
- let source;
10369
+ return { cmd: rawCmd, args: rawArgs };
10370
+ }
10371
+ function spawnDetachedProcessAccumulatedWorker(boardDir) {
10372
+ const { cmd, args: cliArgs } = getCliInvocation("process-accumulated-events", ["--rg", boardDir, "--inline-loop"]);
9673
10373
  try {
9674
- const raw = fs.readFileSync(inFile, "utf-8");
9675
- source = JSON.parse(raw);
9676
- } catch (err) {
9677
- const msg = `Failed to parse input file: ${err.message}`;
9678
- if (errFile) fs.writeFileSync(errFile, msg);
9679
- console.error(`[run-source-fetch] ${msg}`);
9680
- process.exit(1);
9681
- }
9682
- if (!source.cli) {
9683
- const msg = "Source definition missing cli field (board-live-cards built-in executor only understands source.cli)";
9684
- if (errFile) fs.writeFileSync(errFile, msg);
9685
- console.error(`[run-source-fetch] ${msg}`);
9686
- process.exit(1);
10374
+ spawnDetachedCommand(cmd, cliArgs);
10375
+ return true;
10376
+ } catch {
10377
+ return false;
9687
10378
  }
9688
- console.log(`[run-source-fetch] executing: ${source.cli}`);
9689
- const timeout = source.timeout ?? 12e4;
9690
- const sourceCwd = typeof source.cwd === "string" ? source.cwd : process.cwd();
9691
- const sourceBoardDir = typeof source.boardDir === "string" ? source.boardDir : void 0;
9692
- const cmdParts = splitCommandLine(source.cli);
9693
- if (cmdParts.length === 0) {
9694
- const msg = "Source cli command is empty";
9695
- if (errFile) fs.writeFileSync(errFile, msg);
9696
- console.error(`[run-source-fetch] ${msg}`);
9697
- process.exit(1);
10379
+ }
10380
+ async function processAccumulatedEventsInlineLoop(boardDir, settleDelayMs = 50) {
10381
+ while (determineLatestPendingAccumulated(boardDir) > 0) {
10382
+ const ran = await processAccumulatedEvents(boardDir);
10383
+ if (!ran) return false;
10384
+ await new Promise((resolve6) => setTimeout(resolve6, settleDelayMs));
9698
10385
  }
9699
- const rawCmd = cmdParts[0];
9700
- const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
9701
- let stdout;
10386
+ return true;
10387
+ }
10388
+ function shouldAvoidDetachedProcessSpawn() {
10389
+ return process.env.BOARD_LIVE_CARDS_NO_SPAWN === "1";
10390
+ }
10391
+ async function processAccumulatedEvents(boardDir) {
10392
+ const boardPath = path7.join(boardDir, BOARD_FILE);
10393
+ const cliDir = path7.resolve(__dirname$1, "..", "..");
10394
+ let release;
9702
10395
  try {
9703
- stdout = execCommandSync(cmd, cliArgs, {
9704
- shell: false,
9705
- encoding: "utf-8",
9706
- timeout,
9707
- cwd: sourceCwd,
9708
- env: {
9709
- ...process.env,
9710
- ...sourceBoardDir ? { BOARD_DIR: sourceBoardDir } : {}
9711
- }
9712
- });
9713
- } catch (err) {
9714
- const msg = err.message ?? String(err);
9715
- console.error(`[run-source-fetch] cli failed: ${msg}`);
9716
- if (errFile) fs.writeFileSync(errFile, msg);
9717
- process.exit(1);
10396
+ release = lockSync(boardPath, { retries: 0 });
10397
+ } catch {
10398
+ return false;
9718
10399
  }
9719
- const result = stdout.trim();
9720
10400
  try {
9721
- fs.writeFileSync(outFile, result);
9722
- console.log(`[run-source-fetch] result written to ${outFile}`);
9723
- } catch (err) {
9724
- const msg = `Failed to write output file: ${err.message}`;
9725
- console.error(`[run-source-fetch] ${msg}`);
9726
- if (errFile) fs.writeFileSync(errFile, msg);
9727
- process.exit(1);
10401
+ const cardHandlerAdapters = {
10402
+ cardStore: createNodeCardStore(lookupCardPath),
10403
+ runtimeStore: createNodeRuntimeStore(),
10404
+ outputStore: createNodeOutputStore(resolveComputedValuesPath, resolveDataObjectsDirPath, INFERENCE_ADAPTER_LOG_FILE),
10405
+ inputStore: createNodeInputStore(JOURNAL_FILE),
10406
+ invocationAdapter: createNodeInvocationAdapter(cliDir, encodeSourceToken)
10407
+ };
10408
+ const envelope = loadBoardEnvelope(boardDir);
10409
+ const live = restore(envelope.graph);
10410
+ const journal = new BoardJournal(path7.join(boardDir, JOURNAL_FILE), envelope.lastDrainedJournalId);
10411
+ const rg = createReactiveGraph(live, { handlers: { "card-handler": createCardHandlerFn(boardDir, cardHandlerAdapters) } });
10412
+ const undrained = journal.drain();
10413
+ rg.pushAll(undrained);
10414
+ await rg.dispose({ wait: true });
10415
+ saveBoard(boardDir, rg, journal);
10416
+ return true;
10417
+ } finally {
10418
+ release();
9728
10419
  }
9729
10420
  }
9730
- function cmdUpsertCard(args) {
9731
- const rgIdx = args.indexOf("--rg");
9732
- const cardIdx = args.indexOf("--card");
9733
- const globIdx = args.indexOf("--card-glob");
9734
- const cardIdIdx = args.indexOf("--card-id");
9735
- const restart = args.includes("--restart");
9736
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9737
- const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
9738
- const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
9739
- const requestedCardId = cardIdIdx !== -1 ? args[cardIdIdx + 1] : void 0;
9740
- if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
9741
- console.error("Usage: board-live-cards upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]");
9742
- process.exit(1);
9743
- }
9744
- if (cardGlob && requestedCardId) {
9745
- console.error("Usage: --card-id may be used only with --card (single file), not with --card-glob");
9746
- process.exit(1);
9747
- }
9748
- const cardFiles = cardFile ? [path.resolve(cardFile)] : resolveCardGlobMatches(cardGlob);
9749
- if (!cardFile && cardFiles.length === 0) {
9750
- console.error(`No card files matched glob: ${cardGlob}`);
9751
- process.exit(1);
9752
- }
9753
- const idx = buildCardInventoryIndex(dir);
9754
- const batchByCardId = /* @__PURE__ */ new Map();
9755
- const batchByCardPath = /* @__PURE__ */ new Map();
9756
- const plans = [];
9757
- const logs = [];
9758
- for (const absCardPath of cardFiles) {
9759
- if (!fs.existsSync(absCardPath)) {
9760
- console.error(`Card file not found: ${absCardPath}`);
9761
- process.exit(1);
9762
- }
9763
- const card = JSON.parse(fs.readFileSync(absCardPath, "utf-8"));
9764
- if (!card.id) {
9765
- console.error(`Card JSON must have an "id" field (${absCardPath})`);
9766
- process.exit(1);
9767
- }
9768
- if (requestedCardId && requestedCardId !== card.id) {
9769
- console.error(
9770
- `Card id mismatch: --card-id "${requestedCardId}" does not match file id "${card.id}" (${absCardPath})`
9771
- );
9772
- process.exit(1);
9773
- }
9774
- const seenPathCardId = batchByCardPath.get(absCardPath);
9775
- if (seenPathCardId && seenPathCardId !== card.id) {
9776
- console.error(
9777
- `Upsert rejected: file "${absCardPath}" appears multiple times in batch with conflicting ids ("${seenPathCardId}" vs "${card.id}")`
9778
- );
9779
- process.exit(1);
9780
- }
9781
- const seenCardPath = batchByCardId.get(card.id);
9782
- if (seenCardPath && seenCardPath !== absCardPath) {
9783
- console.error(
9784
- `Upsert rejected: card id "${card.id}" appears multiple times in batch with conflicting files ("${seenCardPath}" vs "${absCardPath}")`
9785
- );
9786
- process.exit(1);
9787
- }
9788
- const existingById = idx.byCardId.get(card.id);
9789
- const existingByPath = idx.byCardPath.get(absCardPath);
9790
- if (existingByPath && existingByPath.cardId !== card.id) {
9791
- console.error(
9792
- `Upsert rejected: file "${absCardPath}" is already mapped to card id "${existingByPath.cardId}", cannot remap to "${card.id}"`
9793
- );
9794
- process.exit(1);
9795
- }
9796
- if (existingById && existingById.cardFilePath !== absCardPath) {
9797
- console.error(
9798
- `Upsert rejected: card id "${card.id}" is already mapped to file "${existingById.cardFilePath}", cannot remap to "${absCardPath}"`
9799
- );
9800
- process.exit(1);
9801
- }
9802
- batchByCardPath.set(absCardPath, card.id);
9803
- batchByCardId.set(card.id, absCardPath);
9804
- plans.push({
9805
- card,
9806
- absCardPath,
9807
- isInsert: !existingById
9808
- });
10421
+ async function processAccumulatedEventsInfinitePass(boardDir, settleDelayMs = 50, options) {
10422
+ if (options?.inlineLoop || shouldAvoidDetachedProcessSpawn()) {
10423
+ return processAccumulatedEventsInlineLoop(boardDir, settleDelayMs);
9809
10424
  }
9810
- for (const plan of plans) {
9811
- const { card, absCardPath, isInsert } = plan;
9812
- if (isInsert) {
9813
- const newEntry = {
9814
- cardId: card.id,
9815
- cardFilePath: absCardPath,
9816
- addedAt: (/* @__PURE__ */ new Date()).toISOString()
9817
- };
9818
- appendCardInventory(dir, newEntry);
9819
- idx.byCardId.set(card.id, newEntry);
9820
- idx.byCardPath.set(absCardPath, newEntry);
9821
- }
9822
- const taskConfig = liveCardToTaskConfig(card);
9823
- appendEventToJournal(dir, {
9824
- type: "task-upsert",
9825
- taskName: card.id,
9826
- taskConfig,
9827
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9828
- });
9829
- if (restart) {
9830
- appendEventToJournal(dir, {
9831
- type: "task-restart",
9832
- taskName: card.id,
9833
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9834
- });
9835
- }
9836
- logs.push(`Card "${card.id}" ${isInsert ? "upserted (inserted)" : "upserted (updated)"}${restart ? " (restarted)" : ""}.`);
10425
+ return spawnDetachedProcessAccumulatedWorker(boardDir);
10426
+ }
10427
+ async function processAccumulatedEventsForced(boardDir, options) {
10428
+ await processAccumulatedEvents(boardDir);
10429
+ await processAccumulatedEventsInfinitePass(boardDir, 50, options);
10430
+ }
10431
+ function liveCardToTaskConfig(card) {
10432
+ const requires = card.requires;
10433
+ const provides = card.provides?.map((p) => p.bindTo) ?? [];
10434
+ return {
10435
+ requires: requires && requires.length > 0 ? requires : void 0,
10436
+ provides,
10437
+ taskHandlers: ["card-handler"],
10438
+ description: card.meta?.title ?? card.id
10439
+ };
10440
+ }
10441
+ var __dirname$1 = path7.dirname(fileURLToPath(import.meta.url));
10442
+ var REPO_ROOT = path7.resolve(__dirname$1, "..", "..");
10443
+ var LOCAL_TSX_CLI = path7.join(REPO_ROOT, "node_modules", "tsx", "dist", "cli.mjs");
10444
+ function getCliInvocation(command, args) {
10445
+ const jsPath = path7.join(__dirname$1, "board-live-cards-cli.js");
10446
+ if (fs7.existsSync(jsPath)) {
10447
+ return { cmd: process.execPath, args: [jsPath, command, ...args] };
9837
10448
  }
9838
- void processAccumulatedEventsInfinitePass(dir);
9839
- if (cardGlob) {
9840
- console.log(`Upserted ${cardFiles.length} cards from glob: ${cardGlob}${restart ? " (restarted)" : ""}`);
9841
- } else {
9842
- console.log(logs[0]);
10449
+ const tsPath = path7.join(__dirname$1, "board-live-cards-cli.ts");
10450
+ if (fs7.existsSync(tsPath) && fs7.existsSync(LOCAL_TSX_CLI)) {
10451
+ return { cmd: process.execPath, args: [LOCAL_TSX_CLI, tsPath, command, ...args] };
9843
10452
  }
10453
+ const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
10454
+ return { cmd: npxCmd, args: ["tsx", tsPath, command, ...args] };
9844
10455
  }
9845
- async function cmdTryDrain(args) {
9846
- const rgIdx = args.indexOf("--rg");
9847
- const inlineLoop = args.includes("--inline-loop");
9848
- const boardDir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9849
- if (!boardDir) {
9850
- console.error("Usage: board-live-cards process-accumulated-events --rg <dir>");
9851
- process.exit(1);
10456
+ function appendTaskExecutorLog(boardDir, hydratedSource, mode) {
10457
+ try {
10458
+ const entry = {
10459
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10460
+ mode,
10461
+ hydratedSource
10462
+ };
10463
+ fs7.appendFileSync(path7.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
10464
+ } catch (logErr) {
10465
+ console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
9852
10466
  }
9853
- await processAccumulatedEventsForced(boardDir, { inlineLoop });
9854
10467
  }
9855
- function cmdRetrigger(args) {
9856
- const rgIdx = args.indexOf("--rg");
9857
- const taskIdx = args.indexOf("--task");
9858
- const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9859
- const taskName = taskIdx !== -1 ? args[taskIdx + 1] : void 0;
9860
- if (!dir || !taskName) {
9861
- console.error("Usage: board-live-cards retrigger --rg <dir> --task <task-name>");
9862
- process.exit(1);
9863
- }
9864
- appendEventToJournal(dir, {
9865
- type: "task-restart",
9866
- taskName,
9867
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
10468
+ function resolveCardGlobMatches(cardGlob) {
10469
+ const patterns = cardGlob.split(",").map((s) => s.trim()).filter(Boolean).map((p) => p.replace(/\\/g, "/"));
10470
+ const matches = fg.sync(patterns, {
10471
+ absolute: true,
10472
+ onlyFiles: true,
10473
+ unique: true,
10474
+ dot: false
9868
10475
  });
9869
- void processAccumulatedEventsInfinitePass(dir);
9870
- console.log(`Task "${taskName}" retriggered.`);
10476
+ return [...matches].map((m) => path7.resolve(m)).sort((a, b) => a.localeCompare(b));
9871
10477
  }
9872
10478
  async function cli(argv) {
10479
+ const boardCommandHandlers = createBoardCommandHandlers({
10480
+ initBoard,
10481
+ configureRuntimeOutDir,
10482
+ loadBoard,
10483
+ writeJsonAtomic: writeJsonAtomic2,
10484
+ resolveStatusSnapshotPath,
10485
+ buildBoardStatusObject: (dir, live) => buildBoardStatusObject(path7.resolve(dir), live),
10486
+ readTaskExecutorConfig,
10487
+ resolveCardGlobMatches,
10488
+ validateLiveCardDefinition,
10489
+ execCommandSync,
10490
+ appendEventToJournal,
10491
+ processAccumulatedEventsInfinitePass
10492
+ });
10493
+ const callbackCommandHandlers = createCallbackCommandHandlers({
10494
+ decodeCallbackToken: decodeCallbackToken2,
10495
+ decodeSourceToken,
10496
+ writeRuntimeDataObjects,
10497
+ appendEventToJournal,
10498
+ processAccumulatedEventsForced,
10499
+ processAccumulatedEventsInfinitePass
10500
+ });
10501
+ const nonCoreCommandHandlers = createNonCoreCommandHandlers({
10502
+ readTaskExecutorConfig,
10503
+ execCommandSync,
10504
+ splitCommandLine,
10505
+ resolveCommandInvocation
10506
+ });
10507
+ const cardCommandHandlers = createCardCommandHandlers({
10508
+ resolveCardGlobMatches,
10509
+ buildCardInventoryIndex,
10510
+ appendCardInventory,
10511
+ liveCardToTaskConfig,
10512
+ appendEventToJournal,
10513
+ processAccumulatedEventsInfinitePass
10514
+ });
10515
+ const executionCommandHandlers = createExecutionCommandHandlers({
10516
+ INFERENCE_ADAPTER_FILE,
10517
+ readTaskExecutorConfig,
10518
+ execCommandSync,
10519
+ execCommandAsync,
10520
+ splitCommandLine,
10521
+ resolveCommandInvocation,
10522
+ encodeSourceToken,
10523
+ decodeSourceToken,
10524
+ decodeCallbackToken: decodeCallbackToken2,
10525
+ spawnDetachedCommand,
10526
+ getCliInvocation,
10527
+ appendTaskExecutorLog,
10528
+ appendEventToJournal,
10529
+ processAccumulatedEventsInfinitePass,
10530
+ processAccumulatedEventsForced,
10531
+ lookupCardPath,
10532
+ nextEntryAfterFetchDelivery
10533
+ });
9873
10534
  const cmd = argv[0];
9874
10535
  const rest = argv.slice(1);
9875
10536
  switch (cmd) {
9876
10537
  case "help":
9877
10538
  case "--help":
9878
10539
  case "-h":
9879
- return cmdHelp();
10540
+ return nonCoreCommandHandlers.cmdHelp();
9880
10541
  case "init":
9881
- return cmdInit(rest);
10542
+ return boardCommandHandlers.cmdInit(rest);
9882
10543
  case "status":
9883
- return cmdStatus(rest);
10544
+ return boardCommandHandlers.cmdStatus(rest);
9884
10545
  case "upsert-card":
9885
- return cmdUpsertCard(rest);
10546
+ return cardCommandHandlers.cmdUpsertCard(rest);
9886
10547
  case "validate-card":
9887
- return cmdValidateCard(rest);
10548
+ return boardCommandHandlers.cmdValidateCard(rest);
9888
10549
  case "remove-card":
9889
- return cmdRemoveCard(rest);
10550
+ return boardCommandHandlers.cmdRemoveCard(rest);
9890
10551
  case "retrigger":
9891
- return cmdRetrigger(rest);
10552
+ return boardCommandHandlers.cmdRetrigger(rest);
9892
10553
  case "task-completed":
9893
- return cmdTaskCompleted(rest);
10554
+ return callbackCommandHandlers.cmdTaskCompleted(rest);
9894
10555
  case "task-failed":
9895
- return cmdTaskFailed(rest);
10556
+ return callbackCommandHandlers.cmdTaskFailed(rest);
9896
10557
  case "task-progress":
9897
- return cmdTaskProgress(rest);
10558
+ return callbackCommandHandlers.cmdTaskProgress(rest);
9898
10559
  case "source-data-fetched":
9899
- return cmdSourceDataFetched(rest);
10560
+ return callbackCommandHandlers.cmdSourceDataFetched(rest);
9900
10561
  case "source-data-fetch-failure":
9901
- return cmdSourceDataFetchFailure(rest);
10562
+ return callbackCommandHandlers.cmdSourceDataFetchFailure(rest);
9902
10563
  case "run-sourcedefs-internal":
9903
- return cmdRunSources(rest);
10564
+ return executionCommandHandlers.cmdRunSources(rest);
9904
10565
  case "run-inference-internal":
9905
- return cmdRunInference(rest);
10566
+ return executionCommandHandlers.cmdRunInference(rest);
9906
10567
  case "inference-done":
9907
- return cmdInferenceDone(rest);
10568
+ return executionCommandHandlers.cmdInferenceDone(rest);
9908
10569
  case "run-source-fetch":
9909
- return cmdRunSourceFetch(rest);
10570
+ return nonCoreCommandHandlers.cmdRunSourceFetch(rest);
9910
10571
  case "probe-source":
9911
- return await cmdProbeSource(rest);
10572
+ return await nonCoreCommandHandlers.cmdProbeSource(rest);
9912
10573
  case "describe-task-executor-capabilities":
9913
- return cmdDescribeTaskExecutorCapabilities(rest);
10574
+ return nonCoreCommandHandlers.cmdDescribeTaskExecutorCapabilities(rest);
9914
10575
  case "process-accumulated-events":
9915
- return await cmdTryDrain(rest);
10576
+ return await executionCommandHandlers.cmdTryDrain(rest);
9916
10577
  default:
9917
10578
  throw new Error(`Unknown command: ${cmd ?? "(none)"}`);
9918
10579
  }
9919
10580
  }
9920
- async function cmdProbeSource(args) {
9921
- const cardIdx = args.indexOf("--card");
9922
- const sourceIdxArg = args.indexOf("--source-idx");
9923
- const sourceBindArg = args.indexOf("--source-bind");
9924
- const mockProjectionsIdx = args.indexOf("--mock-projections");
9925
- const rgIdx = args.indexOf("--rg");
9926
- const outIdx = args.indexOf("--out");
9927
- const cardFilePath = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
9928
- const sourceIdxVal = sourceIdxArg !== -1 ? parseInt(args[sourceIdxArg + 1], 10) : 0;
9929
- const sourceBindVal = sourceBindArg !== -1 ? args[sourceBindArg + 1] : void 0;
9930
- const mockProjectionsRaw = mockProjectionsIdx !== -1 ? args[mockProjectionsIdx + 1] : void 0;
9931
- const boardDirArg = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
9932
- const outFile = outIdx !== -1 ? args[outIdx + 1] : void 0;
9933
- if (!cardFilePath) {
9934
- console.error("Usage: board-live-cards probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>] [--mock-projections <json>] [--rg <boardDir>] [--out <result.json>]");
9935
- process.exit(1);
9936
- }
9937
- let card;
9938
- try {
9939
- card = JSON.parse(fs.readFileSync(path.resolve(cardFilePath), "utf-8"));
9940
- } catch (e) {
9941
- console.error(`[probe-source] Cannot read card: ${e.message}`);
9942
- process.exit(1);
9943
- }
9944
- const source_defs = card.source_defs ?? [];
9945
- if (source_defs.length === 0) {
9946
- console.error(`[probe-source] Card "${card.id}" has no source_defs`);
9947
- process.exit(1);
9948
- }
9949
- let sourceIdx;
9950
- if (sourceBindVal) {
9951
- sourceIdx = source_defs.findIndex((s) => s.bindTo === sourceBindVal);
9952
- if (sourceIdx === -1) {
9953
- console.error(`[probe-source] No source with bindTo="${sourceBindVal}" in card "${card.id}"`);
9954
- process.exit(1);
9955
- }
9956
- } else {
9957
- sourceIdx = sourceIdxVal;
9958
- if (isNaN(sourceIdx) || sourceIdx < 0 || sourceIdx >= source_defs.length) {
9959
- console.error(`[probe-source] --source-idx ${sourceIdxVal} out of range (card has ${source_defs.length} source(s))`);
9960
- process.exit(1);
9961
- }
9962
- }
9963
- const sourceDef = source_defs[sourceIdx];
9964
- const cardDir = path.resolve(path.dirname(cardFilePath));
9965
- const boardDir = boardDirArg ? path.resolve(boardDirArg) : cardDir;
9966
- let mockProjections = {};
9967
- if (mockProjectionsRaw) {
9968
- const raw = mockProjectionsRaw.startsWith("@") ? fs.readFileSync(path.resolve(mockProjectionsRaw.slice(1)), "utf-8") : mockProjectionsRaw;
9969
- try {
9970
- mockProjections = JSON.parse(raw);
9971
- } catch (e) {
9972
- console.error(`[probe-source] --mock-projections is not valid JSON: ${e.message}`);
9973
- process.exit(1);
9974
- }
9975
- }
9976
- const teConfig = readTaskExecutorConfig(boardDir);
9977
- const taskExecutor = teConfig?.command;
9978
- const taskExecutorExtraB64 = teConfig?.extra ? Buffer.from(JSON.stringify(teConfig.extra)).toString("base64") : void 0;
9979
- const inPayload = {
9980
- ...sourceDef,
9981
- cwd: typeof sourceDef.cwd === "string" && sourceDef.cwd ? sourceDef.cwd : cardDir,
9982
- boardDir: typeof sourceDef.boardDir === "string" && sourceDef.boardDir ? sourceDef.boardDir : boardDir,
9983
- _projections: mockProjections
9984
- };
9985
- let sourceKind = "unknown";
9986
- if (taskExecutor) {
9987
- try {
9988
- const capRaw = execCommandSync(taskExecutor, ["describe-capabilities"], {
9989
- shell: true,
9990
- timeout: 8e3,
9991
- encoding: "utf-8"
9992
- });
9993
- const caps = JSON.parse(String(capRaw));
9994
- const knownKinds = caps?.sourceKinds ? Object.keys(caps.sourceKinds) : [];
9995
- const defKeys = new Set(Object.keys(sourceDef));
9996
- sourceKind = knownKinds.find((k) => defKeys.has(k)) ?? "unknown";
9997
- } catch {
9998
- }
9999
- }
10000
- console.log(`[probe-source] card: ${card.id}`);
10001
- console.log(`[probe-source] source[${sourceIdx}]: bindTo="${sourceDef.bindTo}" kind=${sourceKind}`);
10002
- console.log(`[probe-source] _projections: ${JSON.stringify(mockProjections)}`);
10003
- console.log(`[probe-source] executor: ${taskExecutor ?? "built-in (source.cli only)"}`);
10004
- console.log(`[probe-source] running fetch...`);
10005
- const ts = Date.now();
10006
- const inFile = path.join(os.tmpdir(), `probe-in-${sourceDef.bindTo}-${ts}.json`);
10007
- const tmpOut = path.join(os.tmpdir(), `probe-out-${sourceDef.bindTo}-${ts}.json`);
10008
- const errFile = path.join(os.tmpdir(), `probe-err-${sourceDef.bindTo}-${ts}.txt`);
10009
- fs.writeFileSync(inFile, JSON.stringify(inPayload, null, 2), "utf-8");
10010
- let passed = false;
10011
- let errorMsg;
10012
- let resultRaw;
10013
- try {
10014
- if (taskExecutor) {
10015
- const executorArgs = ["run-source-fetch", "--in", inFile, "--out", tmpOut, "--err", errFile];
10016
- if (taskExecutorExtraB64) executorArgs.push("--extra", taskExecutorExtraB64);
10017
- execCommandSync(taskExecutor, executorArgs, {
10018
- shell: true,
10019
- timeout: sourceDef.timeout ?? 3e4
10020
- });
10021
- } else {
10022
- if (!inPayload.cli) {
10023
- throw new Error("No task-executor registered and source has no cli field \u2014 cannot probe with built-in executor");
10024
- }
10025
- const cmdParts = splitCommandLine(inPayload.cli);
10026
- const rawCmd = cmdParts[0];
10027
- const { cmd, args: cliArgs } = resolveCommandInvocation(rawCmd, cmdParts.slice(1));
10028
- const stdout = execCommandSync(cmd, cliArgs, {
10029
- shell: false,
10030
- encoding: "utf-8",
10031
- timeout: sourceDef.timeout ?? 3e4,
10032
- cwd: inPayload.cwd
10033
- });
10034
- fs.writeFileSync(tmpOut, stdout.trim(), "utf-8");
10035
- }
10036
- passed = fs.existsSync(tmpOut);
10037
- if (passed) {
10038
- resultRaw = fs.readFileSync(tmpOut, "utf-8");
10039
- } else {
10040
- errorMsg = fs.existsSync(errFile) ? fs.readFileSync(errFile, "utf-8").trim() : "executor produced no output file";
10041
- }
10042
- } catch (e) {
10043
- errorMsg = e.message ?? String(e);
10044
- if (!errorMsg && fs.existsSync(errFile)) {
10045
- errorMsg = fs.readFileSync(errFile, "utf-8").trim();
10046
- }
10047
- }
10048
- for (const f of [inFile, errFile]) {
10049
- try {
10050
- fs.unlinkSync(f);
10051
- } catch {
10052
- }
10053
- }
10054
- if (passed && resultRaw !== void 0) {
10055
- const resultSize = resultRaw.length;
10056
- const sample = resultRaw.slice(0, 300);
10057
- console.log(`[probe-source] STATUS: PROBE_PASS`);
10058
- console.log(`[probe-source] result size: ${resultSize} bytes`);
10059
- console.log(`[probe-source] sample: ${sample}${resultSize > 300 ? "..." : ""}`);
10060
- if (outFile) {
10061
- fs.writeFileSync(path.resolve(outFile), resultRaw);
10062
- console.log(`[probe-source] result written to: ${outFile}`);
10063
- } else {
10064
- try {
10065
- fs.unlinkSync(tmpOut);
10066
- } catch {
10067
- }
10068
- }
10069
- } else {
10070
- console.log(`[probe-source] STATUS: PROBE_FAIL`);
10071
- if (errorMsg) console.log(`[probe-source] error: ${errorMsg}`);
10072
- try {
10073
- if (fs.existsSync(tmpOut)) fs.unlinkSync(tmpOut);
10074
- } catch {
10075
- }
10076
- }
10077
- const summary = {
10078
- status: passed ? "PROBE_PASS" : "PROBE_FAIL",
10079
- cardId: card.id,
10080
- sourceIdx,
10081
- bindTo: sourceDef.bindTo,
10082
- sourceKind,
10083
- mockProjectionsKeys: Object.keys(mockProjections),
10084
- resultSizeBytes: resultRaw !== void 0 ? resultRaw.length : 0,
10085
- error: errorMsg ?? void 0
10086
- };
10087
- console.log(`[probe-source:result] ${JSON.stringify(summary)}`);
10088
- process.exit(passed ? 0 : 1);
10089
- }
10090
- function cmdDescribeTaskExecutorCapabilities(args) {
10091
- const rgIdx = args.indexOf("--rg");
10092
- const boardDir = rgIdx !== -1 ? path.resolve(args[rgIdx + 1]) : void 0;
10093
- if (!boardDir) {
10094
- console.error("Usage: board-live-cards describe-task-executor-capabilities --rg <dir>");
10095
- process.exit(1);
10096
- }
10097
- const teConfig = readTaskExecutorConfig(boardDir);
10098
- if (!teConfig) {
10099
- console.error(`[describe-task-executor-capabilities] No .task-executor registered in ${boardDir}`);
10100
- process.exit(1);
10101
- }
10102
- try {
10103
- const stdout = execCommandSync(teConfig.command, ["describe-capabilities"], {
10104
- shell: true,
10105
- timeout: 1e4,
10106
- encoding: "utf-8"
10107
- });
10108
- process.stdout.write(String(stdout));
10109
- if (!String(stdout).endsWith("\n")) process.stdout.write("\n");
10110
- } catch (e) {
10111
- console.error(`[describe-task-executor-capabilities] Executor failed: ${e.message ?? e}`);
10112
- process.exit(1);
10113
- }
10114
- }
10115
- function cmdHelp() {
10116
- console.log(`
10117
- board-live-cards-cli \u2014 LiveCards board CLI
10118
-
10119
- USAGE
10120
- board-live-cards-cli <command> [options]
10121
-
10122
- BOARD MANAGEMENT
10123
- init <dir> [--task-executor <script>] [--chat-handler <script>] [--inference-adapter <script>] [--runtime-out <dir>]
10124
- Create a new board in <dir>.
10125
- If --task-executor is given, writes <dir>/.task-executor with the script path.
10126
- If --chat-handler is given, writes <dir>/.chat-handler with the script path.
10127
- If --inference-adapter is given, writes <dir>/.inference-adapter with the script path.
10128
- Writes <dir>/.runtime-out (default: <dir>/runtime-out).
10129
- Published runtime files:
10130
- <runtime-out>/board-livegraph-status.json
10131
- <runtime-out>/cards/<card-id>.computed.json
10132
- Re-running init on an existing board is safe; handler registrations are updated.
10133
-
10134
- status --rg <dir> [--json]
10135
- Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
10136
- --json emits the stable machine-readable status object.
10137
-
10138
- CARD MANAGEMENT
10139
- upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]
10140
- Insert or update one or many cards.
10141
- Enforces strict one-to-one mapping between card id and file path:
10142
- - same id + same file path: update
10143
- - new id + new file path: insert
10144
- - id remap or file remap: rejected
10145
- If --card-id is provided, it must match the id inside the file.
10146
- --card-id is valid only with --card (single file), not with --card-glob.
10147
- --restart clears the task so it re-triggers from scratch.
10148
-
10149
- validate-card (--card <card.json> | --card-glob <glob>) [--rg <boardDir>]
10150
- Validate one or many card JSON files without adding them to a board.
10151
- Checks JSON Schema structure, runtime expression syntax, and provides.ref namespaces.
10152
- When --rg is provided, also invokes the board's task executor validate-source-def
10153
- subcommand to structurally validate each source definition against supported kinds.
10154
- Exits with code 1 if any card fails validation.
10155
-
10156
- remove-card --rg <dir> --id <card-id>
10157
- Remove a card and its task from the board.
10158
-
10159
- retrigger --rg <dir> --task <task-name>
10160
- Mark a task not-started and drain to re-trigger it.
10161
-
10162
- TASK CALLBACKS (called by task executor scripts)
10163
- task-completed --token <callbackToken> [--data <json>]
10164
- Signal successful task completion with optional JSON result data.
10165
-
10166
- task-failed --token <callbackToken> [--error <message>]
10167
- Signal task failure with an optional error message.
10168
-
10169
- task-progress --rg <dir> --token <callbackToken> [--update <json>]
10170
- Signal task progress with optional update payload (for waiting on more evidence, etc.).
10171
-
10172
- SOURCE CALLBACKS (called internally by run-sourcedefs-internal)
10173
- source-data-fetched --tmp <file> --token <sourceToken>
10174
- Atomically rename <file> into the outputFile destination and record delivery
10175
- via journal events. Appends a task-progress event to re-invoke the card handler.
10176
-
10177
- source-data-fetch-failure --token <sourceToken> [--reason <message>]
10178
- Record a source fetch failure via journal events and append a task-progress event.
10179
-
10180
- INTERNAL COMMANDS
10181
- process-accumulated-events --rg <dir>
10182
- Executes forced drain for this board.
10183
- This command is also used as the background relay worker.
10184
- By default it schedules a detached worker and returns quickly.
10185
- Internal workers run with --inline-loop to perform the settle loop.
10186
-
10187
- Eventual-progress guarantee is relay-based (not per-call blocking guarantee):
10188
- 1) at least one runner continues processing,
10189
- 2) no crash/forced exit in relay window,
10190
- 3) lock stays healthy,
10191
- 4) event production eventually quiesces.
10192
-
10193
- run-sourcedefs-internal --card <card.json> --token <callbackToken> --rg <dir>
10194
- Execute all source[] entries for a card, then report delivery or failure.
10195
- (Internal command \u2014 invoked by the card-handler. Not intended for direct use.)
10196
-
10197
- If <dir>/.task-executor exists, invokes it with run-source-fetch subcommand:
10198
- <executor> run-source-fetch --in <source_json> --out <outfile> --err <errfile>
10199
-
10200
- If no .task-executor is registered, uses board-live-cards built-in run-source-fetch.
10201
-
10202
- run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
10203
- Execute a source definition. Board-live-cards reads source.cli and executes it.
10204
- Writes result to --out. Presence of --out after exit indicates success.
10205
-
10206
- describe-task-executor-capabilities --rg <dir>
10207
- Invoke the registered task-executor's describe-capabilities subcommand and
10208
- print its capabilities JSON to stdout. Requires a .task-executor file in <dir>.
10209
-
10210
- probe-source --card <card.json> [--source-idx <n>] [--source-bind <name>]
10211
- [--mock-projections <json>] [--rg <boardDir>] [--out <result.json>]
10212
- Validate that a card source can be fetched successfully.
10213
- Reads the card file, extracts the chosen source (default: index 0), builds the
10214
- run-source-fetch --in payload with the supplied _projections data, invokes the
10215
- registered task-executor (or built-in executor for source.cli), and reports pass/fail.
10216
- --mock-projections: JSON string (or @file.json) providing pre-resolved _projections values
10217
- the source needs. Craft the minimal payload that exercises the
10218
- source \u2014 e.g. '{"holdings":[{"ticker":"AAPL","quantity":10}]}'.
10219
- If omitted, _projections is passed as empty ({}).
10220
- --source-idx: 0-based index into card.source_defs[]. Default: 0.
10221
- --source-bind: Select source by its bindTo name instead of index.
10222
- --rg: Board directory used to find .task-executor. Defaults to the
10223
- directory containing the card file.
10224
- --out: Optional path to write the raw fetch result JSON.
10225
- Prints a structured report ending with a [probe-source:result] JSON line.
10226
- Exits 0 on PROBE_PASS, 1 on PROBE_FAIL.
10227
-
10228
- run-inference-internal --in <input.json> --token <inferenceToken>
10229
- Execute inference via registered .inference-adapter and forward result to inference-done.
10230
- inferenceToken encodes boardDir (rg), cardId (cid), callbackToken (cbk), checksum (cs).
10231
- (Internal command \u2014 invoked by the card-handler when custom completion rule is used.)
10232
-
10233
- inference-done --tmp <result.json> --token <inferenceToken>
10234
- Persist llm_task_completion_inference on the card and append a task-progress event.
10235
- Reads boardDir/callbackToken/checksum from decoded inferenceToken; deletes --tmp file after reading.
10236
- (Internal command \u2014 invoked by run-inference-internal.)
10237
-
10238
- RUN-SOURCE-FETCH PROTOCOL
10239
- External task-executors implement:
10240
- <executor> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
10241
-
10242
- INPUT: --in file contains the full source_defs[x] definition object
10243
- OUTPUT: --out file is written with the result to signal success.
10244
- --err file may be written to explain failure.
10245
-
10246
- Exit code and --out presence determine success:
10247
- Exit 0 + --out file present \u2192 source delivery recorded, card re-evaluated.
10248
- Exit non-zero OR --out absent \u2192 source-data-fetch-failure recorded.
10249
-
10250
- BOARD-LIVE-CARDS BUILT-IN EXECUTOR
10251
- Understands source.cli field only:
10252
- "source_defs": [{ "cli": "node ../fetch-prices.js", "bindTo": "prices", "outputFile": "prices.json" }]
10253
-
10254
- The source.cli command is executed with:
10255
- - Direct command invocation (no shell; quote-aware argument parsing)
10256
- - Stdout is captured and delivered to the card as-is
10257
- - Timeout from source.timeout (default 120s)
10258
-
10259
- The source.cli command must:
10260
- - Execute successfully (exit 0)
10261
- - Write output to stdout
10262
- - Complete within the timeout
10263
-
10264
- The output format is the concern of the card's compute function to interpret.
10265
-
10266
- External task-executors can interpret source definitions however they want.
10267
-
10268
- EXAMPLES
10269
- board-live-cards-cli init ./my-board
10270
- board-live-cards-cli init ./my-board --task-executor ./executors/my-runner.py
10271
- board-live-cards-cli upsert-card --rg ./my-board --card cards/prices.json
10272
- board-live-cards-cli status --rg ./my-board
10273
- board-live-cards-cli retrigger --rg ./my-board --task price-fetch
10274
- board-live-cards-cli probe-source --card cards/card-market-prices.json --source-idx 0 --rg ./my-board --mock-projections '{"holdings":[{"ticker":"AAPL","quantity":10}]}'
10275
- `.trimStart());
10276
- }
10277
- var isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
10581
+ var isMain = process.argv[1] && path7.resolve(process.argv[1]) === path7.resolve(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
10278
10582
  if (isMain) {
10279
10583
  cli(process.argv.slice(2)).catch((err) => {
10280
10584
  const msg = err instanceof Error ? err.message : String(err);
@@ -10283,6 +10587,6 @@ if (isMain) {
10283
10587
  });
10284
10588
  }
10285
10589
 
10286
- export { BoardJournal, appendCardInventory, appendEventToJournal, buildCardInventoryIndex, cli, createBoardReactiveGraph, decideSourceAction, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, isSourceInFlight, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, nextEntryAfterFetchDelivery, nextEntryAfterFetchFailure, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
10590
+ export { BoardJournal, appendCardInventory, appendEventToJournal, buildCardInventoryIndex, cli, decideSourceAction, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, isSourceInFlight, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, nextEntryAfterFetchDelivery, nextEntryAfterFetchFailure, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
10287
10591
  //# sourceMappingURL=board-live-cards-cli.js.map
10288
10592
  //# sourceMappingURL=board-live-cards-cli.js.map