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