yaml-flow 5.4.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/board-live-cards-cli.js +6 -6
- package/browser/asset-integrity.json +10 -0
- package/browser/board-livecards-client.js +2 -0
- package/browser/board-livecards-client.js.map +1 -0
- package/browser/board-livecards-localstorage.js +10 -0
- package/browser/board-livecards-localstorage.js.map +1 -0
- package/browser/board-livegraph-engine.js +2 -1676
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/card-compute.js +28 -28
- package/browser/compute-jsonata.js +5 -0
- package/browser/compute-jsonata.js.map +1 -0
- package/browser/live-cards.js +561 -129
- package/browser/live-cards.schema.json +418 -132
- package/card-store.js +37 -0
- package/dist/batch/index.cjs +1 -108
- package/dist/batch/index.cjs.map +1 -1
- package/dist/batch/index.js +1 -106
- package/dist/batch/index.js.map +1 -1
- package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
- package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
- package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
- package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
- package/dist/board-livegraph-runtime/index.cjs +2 -1671
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +12 -11
- package/dist/board-livegraph-runtime/index.d.ts +12 -11
- package/dist/board-livegraph-runtime/index.js +2 -1662
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
- package/dist/card-compute/index.cjs +9 -7159
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +27 -1
- package/dist/card-compute/index.d.ts +27 -1
- package/dist/card-compute/index.js +9 -7145
- package/dist/card-compute/index.js.map +1 -1
- package/dist/card-compute/jsonata-sync.cjs +7623 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.js +2 -0
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
- package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
- package/dist/cli/node/artifacts-store-cli.cjs +11 -0
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
- package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
- package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
- package/dist/cli/node/artifacts-store-cli.js +11 -0
- package/dist/cli/node/artifacts-store-cli.js.map +1 -0
- package/dist/cli/node/board-live-cards-cli.cjs +15 -0
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
- package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
- package/dist/cli/node/board-live-cards-cli.js +15 -0
- package/dist/cli/node/board-live-cards-cli.js.map +1 -0
- package/dist/cli/node/card-store-cli.cjs +8 -0
- package/dist/cli/node/card-store-cli.cjs.map +1 -0
- package/dist/cli/node/card-store-cli.d.cts +15 -0
- package/dist/cli/node/card-store-cli.d.ts +15 -0
- package/dist/cli/node/card-store-cli.js +8 -0
- package/dist/cli/node/card-store-cli.js.map +1 -0
- package/dist/cli/node/execution-adapter.cjs +3 -0
- package/dist/cli/node/execution-adapter.cjs.map +1 -0
- package/dist/cli/node/execution-adapter.d.cts +174 -0
- package/dist/cli/node/execution-adapter.d.ts +174 -0
- package/dist/cli/node/execution-adapter.js +3 -0
- package/dist/cli/node/execution-adapter.js.map +1 -0
- package/dist/cli/node/fs-board-adapter.cjs +14 -0
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
- package/dist/cli/node/fs-board-adapter.d.cts +204 -0
- package/dist/cli/node/fs-board-adapter.d.ts +204 -0
- package/dist/cli/node/fs-board-adapter.js +14 -0
- package/dist/cli/node/fs-board-adapter.js.map +1 -0
- package/dist/cli/node/jsonata-sync.cjs +7623 -0
- package/dist/cli/node/source-cli-task-executor.cjs +11 -0
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
- package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
- package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
- package/dist/cli/node/source-cli-task-executor.js +11 -0
- package/dist/cli/node/source-cli-task-executor.js.map +1 -0
- package/dist/config/index.cjs +1 -79
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.js +1 -76
- package/dist/config/index.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +2 -2129
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +81 -5
- package/dist/continuous-event-graph/index.d.ts +81 -5
- package/dist/continuous-event-graph/index.js +2 -2088
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
- package/dist/event-graph/index.cjs +22 -8292
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.js +22 -8237
- package/dist/event-graph/index.js.map +1 -1
- package/dist/execution-refs.cjs +3 -0
- package/dist/execution-refs.cjs.map +1 -0
- package/dist/execution-refs.d.cts +260 -0
- package/dist/execution-refs.d.ts +260 -0
- package/dist/execution-refs.js +3 -0
- package/dist/execution-refs.js.map +1 -0
- package/dist/index.cjs +29 -13221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +29 -13112
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +5 -617
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.js +5 -610
- package/dist/inference/index.js.map +1 -1
- package/dist/jsonata-sync.cjs +7623 -0
- package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
- package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
- package/dist/server-runtime/index.cjs +9 -0
- package/dist/server-runtime/index.cjs.map +1 -0
- package/dist/server-runtime/index.d.cts +31 -0
- package/dist/server-runtime/index.d.ts +31 -0
- package/dist/server-runtime/index.js +9 -0
- package/dist/server-runtime/index.js.map +1 -0
- package/dist/server-runtime/jsonata-sync.cjs +7623 -0
- package/dist/step-machine/index.cjs +11 -7129
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.js +11 -7113
- package/dist/step-machine/index.js.map +1 -1
- package/dist/step-machine-public/index.cjs +2 -0
- package/dist/step-machine-public/index.cjs.map +1 -0
- package/dist/step-machine-public/index.d.cts +159 -0
- package/dist/step-machine-public/index.d.ts +159 -0
- package/dist/step-machine-public/index.js +2 -0
- package/dist/step-machine-public/index.js.map +1 -0
- package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
- package/dist/storage-refs.cjs +10 -0
- package/dist/storage-refs.cjs.map +1 -0
- package/dist/storage-refs.d.cts +93 -0
- package/dist/storage-refs.d.ts +93 -0
- package/dist/storage-refs.js +10 -0
- package/dist/storage-refs.js.map +1 -0
- package/dist/stores/file.cjs +1 -114
- package/dist/stores/file.cjs.map +1 -1
- package/dist/stores/file.js +1 -112
- package/dist/stores/file.js.map +1 -1
- package/dist/stores/index.cjs +1 -231
- package/dist/stores/index.cjs.map +1 -1
- package/dist/stores/index.js +1 -227
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/localStorage.cjs +1 -76
- package/dist/stores/localStorage.cjs.map +1 -1
- package/dist/stores/localStorage.js +1 -74
- package/dist/stores/localStorage.js.map +1 -1
- package/dist/stores/memory.cjs +1 -47
- package/dist/stores/memory.cjs.map +1 -1
- package/dist/stores/memory.js +1 -45
- package/dist/stores/memory.js.map +1 -1
- package/dist/types-B1ZRa4aI.d.ts +147 -0
- package/dist/types-BxEFcVK9.d.cts +147 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +291 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +366 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +9 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +107 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
- package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
- package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
- package/examples/example-board/agent-instructions.md +11 -5
- package/examples/example-board/cards/_index.json +47 -0
- package/examples/example-board/cards/card-market-prices.json +33 -9
- package/examples/example-board/cards/card-my-identity.json +30 -6
- package/examples/example-board/cards/card-portfolio-action.json +24 -6
- package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
- package/examples/example-board/cards/card-portfolio-risks.json +24 -6
- package/examples/example-board/cards/card-portfolio-value.json +38 -10
- package/examples/example-board/cards/card-portfolio.json +57 -13
- package/examples/example-board/cards/card-rebalance-impact.json +22 -6
- package/examples/example-board/cards/card-rebalance-sim.json +66 -15
- package/examples/example-board/demo-chat-handler.js +14 -4
- package/examples/example-board/demo-server-config.json +1 -0
- package/examples/example-board/demo-server.js +366 -68
- package/examples/example-board/demo-shell-localstorage.html +774 -0
- package/examples/example-board/demo-shell-with-server.html +20 -37
- package/examples/example-board/demo-shell.html +5 -4
- package/examples/example-board/demo-task-executor.js +273 -275
- package/examples/index.html +0 -14
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/package.json +46 -8
- package/schema/live-cards.schema.json +418 -132
- package/step-machine-cli.js +43 -310
- package/board-livecards-server-runtime.js +0 -1574
- package/browser/board-livecards-runtime-client.js +0 -263
- package/dist/cli/board-live-cards-cli.cjs +0 -10650
- package/dist/cli/board-live-cards-cli.cjs.map +0 -1
- package/dist/cli/board-live-cards-cli.d.cts +0 -179
- package/dist/cli/board-live-cards-cli.d.ts +0 -179
- package/dist/cli/board-live-cards-cli.js +0 -10598
- package/dist/cli/board-live-cards-cli.js.map +0 -1
- package/dist/journal-9HEgs7dU.d.ts +0 -28
- package/dist/journal-B-JCfQnh.d.cts +0 -28
- package/dist/schedule-Cszq9LYY.d.ts +0 -21
- package/dist/schedule-qWNL0RQh.d.cts +0 -21
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
- package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
- package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
- package/examples/example-board/demo-shell-browser.html +0 -674
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""portfolio-tracker.py — E2E orchestrator for the portfolio board demo.
|
|
3
|
+
|
|
4
|
+
Black-box CLI client. All board and card-store operations are performed by
|
|
5
|
+
shelling out to board-live-cards-cli and card-store-cli. No env vars. No
|
|
6
|
+
fallbacks or defaults.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import time
|
|
18
|
+
import argparse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def serialize_ref(ref: dict[str, str]) -> str:
|
|
22
|
+
payload = json.dumps({"kind": ref.get("kind", ""), "value": ref.get("value", "")}, separators=(",", ":")).encode("utf-8")
|
|
23
|
+
return "b64:" + base64.urlsafe_b64encode(payload).decode("ascii").rstrip("=")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_CLI_PARSER = argparse.ArgumentParser()
|
|
27
|
+
_MODE_GROUP = _CLI_PARSER.add_mutually_exclusive_group()
|
|
28
|
+
_MODE_GROUP.add_argument('--run-pycli', action='store_true', help='Use pycli for board/card operations (default)')
|
|
29
|
+
_MODE_GROUP.add_argument('--run-nodecli', action='store_true', help='Use node cli for board/card operations')
|
|
30
|
+
_CLI_ARGS = _CLI_PARSER.parse_args()
|
|
31
|
+
RUN_PYCLI = not _CLI_ARGS.run_nodecli
|
|
32
|
+
|
|
33
|
+
# ── Path resolution ────────────────────────────────────────────────────────────
|
|
34
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
_REPO_ROOT = os.path.normpath(os.path.join(_HERE, '..', '..', '..', '..'))
|
|
36
|
+
|
|
37
|
+
NODE = shutil.which('node')
|
|
38
|
+
if not NODE:
|
|
39
|
+
print('[ERROR] node not found on PATH', file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
BOARD_CLI = os.path.join(_REPO_ROOT, 'board-live-cards-cli.js')
|
|
43
|
+
CARD_STORE_CLI = os.path.join(_REPO_ROOT, 'card-store.js')
|
|
44
|
+
FETCH_PRICES_PY = os.path.join(_HERE, 'portfolio-tracker-fetch-prices.py')
|
|
45
|
+
|
|
46
|
+
PYTHON = shutil.which('python')
|
|
47
|
+
if not PYTHON:
|
|
48
|
+
print('[ERROR] python not found on PATH (required for portfolio-tracker-fetch-prices.py)', file=sys.stderr)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
PYTHON_RUNNER = sys.executable or PYTHON
|
|
52
|
+
|
|
53
|
+
BOARD_PYCLI = os.path.join(_REPO_ROOT, 'pycli', 'main', 'board_live_cards_pycli.py')
|
|
54
|
+
CARD_STORE_PYCLI = os.path.join(_REPO_ROOT, 'pycli', 'main', 'card_store_pycli.py')
|
|
55
|
+
|
|
56
|
+
if RUN_PYCLI:
|
|
57
|
+
if not os.path.exists(BOARD_PYCLI):
|
|
58
|
+
print(f'[ERROR] pycli entry not found: {BOARD_PYCLI}', file=sys.stderr)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
if not os.path.exists(CARD_STORE_PYCLI):
|
|
61
|
+
print(f'[ERROR] pycli entry not found: {CARD_STORE_PYCLI}', file=sys.stderr)
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
if RUN_PYCLI:
|
|
65
|
+
ACTIVE_BOARD_BIN = [PYTHON_RUNNER, BOARD_PYCLI]
|
|
66
|
+
ACTIVE_CARD_STORE_BIN = [PYTHON_RUNNER, CARD_STORE_PYCLI]
|
|
67
|
+
ACTIVE_BOARD_SUFFIX = []
|
|
68
|
+
else:
|
|
69
|
+
ACTIVE_BOARD_BIN = [NODE, BOARD_CLI]
|
|
70
|
+
ACTIVE_CARD_STORE_BIN = [NODE, CARD_STORE_CLI]
|
|
71
|
+
ACTIVE_BOARD_SUFFIX = []
|
|
72
|
+
|
|
73
|
+
# ── Runtime directories (under os.tmpdir()/experiment/) ───────────────────────
|
|
74
|
+
_TMP_BASE = tempfile.mkdtemp(prefix='experiment-')
|
|
75
|
+
CARDSTORE_DIR = os.path.join(_TMP_BASE, 'cardstore')
|
|
76
|
+
BOARDRUNTIME_DIR = os.path.join(_TMP_BASE, 'boardruntime')
|
|
77
|
+
OUTPUTS_DIR = os.path.join(_TMP_BASE, 'outputs')
|
|
78
|
+
|
|
79
|
+
CARDSTORE_REF = serialize_ref({'kind': 'fs-path', 'value': CARDSTORE_DIR})
|
|
80
|
+
BOARDRUNTIME_REF = serialize_ref({'kind': 'fs-path', 'value': BOARDRUNTIME_DIR})
|
|
81
|
+
OUTPUTS_REF = serialize_ref({'kind': 'fs-path', 'value': OUTPUTS_DIR})
|
|
82
|
+
|
|
83
|
+
# ── Inline card definitions ────────────────────────────────────────────────────
|
|
84
|
+
CARD_PORTFOLIO_FORM = {
|
|
85
|
+
"id": "portfolio-form",
|
|
86
|
+
"meta": {"title": "Portfolio Holdings Form"},
|
|
87
|
+
"provides": [{"bindTo": "holdings", "ref": "card_data.holdings"}],
|
|
88
|
+
"card_data": {"holdings": []},
|
|
89
|
+
"view": {
|
|
90
|
+
"elements": [
|
|
91
|
+
{"kind": "table", "label": "Holdings",
|
|
92
|
+
"data": {"bind": "card_data.holdings", "columns": ["symbol", "qty"]}}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
CARD_PRICE_FETCH = {
|
|
98
|
+
"id": "price-fetch",
|
|
99
|
+
"meta": {"title": "Fetch Market Prices"},
|
|
100
|
+
"requires": ["holdings"],
|
|
101
|
+
"provides": [{"bindTo": "prices", "ref": "fetched_sources.prices"}],
|
|
102
|
+
"card_data": {},
|
|
103
|
+
"source_defs": [{
|
|
104
|
+
"kind": "mock-quotes",
|
|
105
|
+
"bindTo": "prices",
|
|
106
|
+
"outputFile": "prices.json",
|
|
107
|
+
"projections": {"tickers": "requires.holdings.symbol"}
|
|
108
|
+
}],
|
|
109
|
+
"view": {
|
|
110
|
+
"elements": [
|
|
111
|
+
{"kind": "table", "label": "Market Prices",
|
|
112
|
+
"data": {"bind": "fetched_sources.prices"}}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
CARD_HOLDINGS_TABLE = {
|
|
118
|
+
"id": "holdings-table",
|
|
119
|
+
"meta": {"title": "Holdings Table"},
|
|
120
|
+
"requires": ["holdings", "prices"],
|
|
121
|
+
"provides": [{"bindTo": "table", "ref": "computed_values.table"}],
|
|
122
|
+
"card_data": {},
|
|
123
|
+
"compute": [{
|
|
124
|
+
"bindTo": "table",
|
|
125
|
+
"expr": (
|
|
126
|
+
'{ "rows": $map(requires.holdings, function($h) { '
|
|
127
|
+
'{ "symbol": $h.symbol, "qty": $h.qty, '
|
|
128
|
+
'"price": $lookup(requires.prices, $h.symbol), '
|
|
129
|
+
'"value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }'
|
|
130
|
+
)
|
|
131
|
+
}],
|
|
132
|
+
"view": {
|
|
133
|
+
"elements": [
|
|
134
|
+
{"kind": "table", "label": "Portfolio Positions",
|
|
135
|
+
"data": {"bind": "computed_values.table.rows",
|
|
136
|
+
"columns": ["symbol", "qty", "price", "value"]}}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
CARD_PORTFOLIO_VALUE = {
|
|
142
|
+
"id": "portfolio-value",
|
|
143
|
+
"meta": {"title": "Portfolio Total Value"},
|
|
144
|
+
"requires": ["table"],
|
|
145
|
+
"provides": [{"bindTo": "totalValue", "ref": "computed_values.totalValue"}],
|
|
146
|
+
"card_data": {},
|
|
147
|
+
"compute": [
|
|
148
|
+
{"bindTo": "totalValue", "expr": "$sum(requires.table.rows.value)"}
|
|
149
|
+
],
|
|
150
|
+
"view": {
|
|
151
|
+
"elements": [
|
|
152
|
+
{"kind": "metric", "label": "Total Portfolio Value",
|
|
153
|
+
"data": {"bind": "computed_values.totalValue"}}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
159
|
+
def set_holdings(card_json: dict, holdings: dict) -> dict:
|
|
160
|
+
card = copy.deepcopy(card_json)
|
|
161
|
+
card["card_data"]["holdings"] = [
|
|
162
|
+
{"symbol": symbol, "qty": qty}
|
|
163
|
+
for symbol, qty in holdings.items()
|
|
164
|
+
]
|
|
165
|
+
return card
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def run_board(*args):
|
|
169
|
+
subprocess.run([*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX], check=True, shell=False)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def run_board_with_input(*args, input_json: str):
|
|
173
|
+
subprocess.run(
|
|
174
|
+
[*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX],
|
|
175
|
+
input=input_json, check=True, shell=False, text=True,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def run_board_capture(*args) -> str:
|
|
180
|
+
result = subprocess.run(
|
|
181
|
+
[*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX],
|
|
182
|
+
check=True, shell=False, capture_output=True, text=True,
|
|
183
|
+
)
|
|
184
|
+
return result.stdout
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def run_card_store_set(card: dict):
|
|
188
|
+
subprocess.run(
|
|
189
|
+
[*ACTIVE_CARD_STORE_BIN, 'set', '--store-ref', CARDSTORE_REF],
|
|
190
|
+
input=json.dumps(card),
|
|
191
|
+
check=True,
|
|
192
|
+
shell=False,
|
|
193
|
+
text=True,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def read_json(path: str):
|
|
198
|
+
with open(path, encoding='utf-8') as f:
|
|
199
|
+
return json.load(f)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def wait_for_completed(label: str, timeout_s: float = 10.0, poll_s: float = 0.5):
|
|
203
|
+
required_names = {'portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value'}
|
|
204
|
+
deadline = time.monotonic() + timeout_s
|
|
205
|
+
while time.monotonic() < deadline:
|
|
206
|
+
raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
207
|
+
data = json.loads(raw).get('data', {})
|
|
208
|
+
cards = data.get('cards', [])
|
|
209
|
+
completed = {c['name'] for c in cards if c.get('status') == 'completed'}
|
|
210
|
+
if required_names.issubset(completed):
|
|
211
|
+
print(f'[{label}] all cards completed.')
|
|
212
|
+
return
|
|
213
|
+
time.sleep(poll_s)
|
|
214
|
+
# timed out — print status and exit
|
|
215
|
+
raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
216
|
+
print(f'[ERROR] {label}: timed out waiting for all cards to complete.', file=sys.stderr)
|
|
217
|
+
print(raw, file=sys.stderr)
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ── T0a — Create runtime directories ──────────────────────────────────────────
|
|
222
|
+
print('\n=== T0a: Create runtime directories ===')
|
|
223
|
+
for d in (CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR):
|
|
224
|
+
os.makedirs(d)
|
|
225
|
+
print(f' created: {d}')
|
|
226
|
+
|
|
227
|
+
# ── T0b — Init board ───────────────────────────────────────────────────────────
|
|
228
|
+
print('\n=== T0b: Init board ===')
|
|
229
|
+
_task_executor_body = json.dumps({
|
|
230
|
+
'task-executor-ref': {
|
|
231
|
+
'meta': 'task-executor',
|
|
232
|
+
'howToRun': 'local-python',
|
|
233
|
+
'whatToRun': serialize_ref({'kind': 'fs-path', 'value': FETCH_PRICES_PY}),
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
run_board_with_input(
|
|
237
|
+
'init',
|
|
238
|
+
'--base-ref', BOARDRUNTIME_REF,
|
|
239
|
+
'--card-store-ref', CARDSTORE_REF,
|
|
240
|
+
'--outputs-store-ref', OUTPUTS_REF,
|
|
241
|
+
input_json=_task_executor_body,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# ── T0c — Set all cards into card store ────────────────────────────────────────
|
|
245
|
+
print('\n=== T0c: Set all cards into card store ===')
|
|
246
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, {"AAPL": 50, "MSFT": 30}))
|
|
247
|
+
run_card_store_set(CARD_PRICE_FETCH)
|
|
248
|
+
run_card_store_set(CARD_HOLDINGS_TABLE)
|
|
249
|
+
run_card_store_set(CARD_PORTFOLIO_VALUE)
|
|
250
|
+
|
|
251
|
+
# ── T0d — Upsert cards to board ────────────────────────────────────────────────
|
|
252
|
+
print('\n=== T0d: Upsert cards to board ===')
|
|
253
|
+
for _card_id in ('portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value'):
|
|
254
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF, '--card-id', _card_id)
|
|
255
|
+
|
|
256
|
+
# ── T1 — Wait for all cards completed ──────────────────────────────────────────
|
|
257
|
+
print('\n=== T1: Wait for all cards completed ===')
|
|
258
|
+
wait_for_completed('T1')
|
|
259
|
+
|
|
260
|
+
_prices_path = os.path.join(OUTPUTS_DIR, 'data-objects', 'prices.json')
|
|
261
|
+
_prices_t1 = read_json(_prices_path)
|
|
262
|
+
assert isinstance(_prices_t1, dict) and len(_prices_t1) > 0, \
|
|
263
|
+
'T1: prices.json is empty or not an object'
|
|
264
|
+
assert set(_prices_t1.keys()) == {'AAPL', 'MSFT'}, \
|
|
265
|
+
f'T1: expected keys {{AAPL, MSFT}}, got {set(_prices_t1.keys())}'
|
|
266
|
+
assert all(isinstance(v, (int, float)) for v in _prices_t1.values()), \
|
|
267
|
+
'T1: all price values must be numbers'
|
|
268
|
+
print('[T1] assertion passed: prices.json has AAPL, MSFT with numeric values.')
|
|
269
|
+
|
|
270
|
+
# ── T2a — Update holdings (GOOG added) ────────────────────────────────────────
|
|
271
|
+
print('\n=== T2a: Update holdings (GOOG added) ===')
|
|
272
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, {"AAPL": 50, "MSFT": 30, "GOOG": 100}))
|
|
273
|
+
|
|
274
|
+
# ── T2b — Upsert portfolio-form with --restart ─────────────────────────────────
|
|
275
|
+
print('\n=== T2b: Upsert portfolio-form --restart ===')
|
|
276
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF,
|
|
277
|
+
'--card-id', 'portfolio-form', '--restart')
|
|
278
|
+
|
|
279
|
+
# ── T2c — Wait and assert ──────────────────────────────────────────────────────
|
|
280
|
+
print('\n=== T2c: Wait for all cards completed ===')
|
|
281
|
+
wait_for_completed('T2c')
|
|
282
|
+
|
|
283
|
+
_prices_t2c = read_json(_prices_path)
|
|
284
|
+
assert set(_prices_t2c.keys()) == {'AAPL', 'MSFT', 'GOOG'}, \
|
|
285
|
+
f'T2c: expected keys {{AAPL, MSFT, GOOG}}, got {set(_prices_t2c.keys())}'
|
|
286
|
+
|
|
287
|
+
_ht_cv_path = os.path.join(OUTPUTS_DIR, 'cards', 'holdings-table', 'computed_values.json')
|
|
288
|
+
_ht_cv_t2c = read_json(_ht_cv_path)
|
|
289
|
+
assert len(_ht_cv_t2c['table']['rows']) == 3, \
|
|
290
|
+
f'T2c: expected 3 rows in holdings-table, got {len(_ht_cv_t2c["table"]["rows"])}'
|
|
291
|
+
print('[T2c] assertions passed: 3 tickers in prices, 3 rows in holdings-table.')
|
|
292
|
+
|
|
293
|
+
# ── T3 — Retrigger price-fetch, wait ──────────────────────────────────────────
|
|
294
|
+
print('\n=== T3: Retrigger price-fetch ===')
|
|
295
|
+
run_board('retrigger', '--base-ref', BOARDRUNTIME_REF, '--id', 'price-fetch')
|
|
296
|
+
wait_for_completed('T3')
|
|
297
|
+
|
|
298
|
+
_prices_t3 = read_json(_prices_path)
|
|
299
|
+
assert set(_prices_t3.keys()) == {'AAPL', 'MSFT', 'GOOG'}, \
|
|
300
|
+
f'T3: expected 3 tickers, got {set(_prices_t3.keys())}'
|
|
301
|
+
assert _prices_t3 != _prices_t2c, \
|
|
302
|
+
'T3: prices must differ from T2c values after retrigger (random regeneration)'
|
|
303
|
+
print('[T3] assertions passed: 3 tickers, prices differ from T2c.')
|
|
304
|
+
|
|
305
|
+
# ── T4 — Rapid 5× portfolio-form updates (queue stress test) ──────────────────
|
|
306
|
+
print('\n=== T4: Rapid 5x portfolio-form updates ===')
|
|
307
|
+
for _holdings in [
|
|
308
|
+
{"AAPL": 50, "MSFT": 30, "GOOG": 100, "AMZN": 40}, # V3
|
|
309
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 40, "TSLA": 60}, # V4
|
|
310
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 100}, # V4a
|
|
311
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 140, "TSLA": 60}, # V4b
|
|
312
|
+
{"AAPL": 40, "MSFT": 35, "GOOG": 120, "TSLA": 70}, # V5
|
|
313
|
+
]:
|
|
314
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, _holdings))
|
|
315
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF,
|
|
316
|
+
'--card-id', 'portfolio-form', '--restart')
|
|
317
|
+
|
|
318
|
+
wait_for_completed('T4')
|
|
319
|
+
|
|
320
|
+
_prices_t4 = read_json(_prices_path)
|
|
321
|
+
assert set(_prices_t4.keys()) == {'AAPL', 'MSFT', 'GOOG', 'TSLA'}, \
|
|
322
|
+
f'T4: expected keys {{AAPL, MSFT, GOOG, TSLA}}, got {set(_prices_t4.keys())}'
|
|
323
|
+
assert 'AMZN' not in _prices_t4, \
|
|
324
|
+
'T4: AMZN must not be present (board must have settled on V5 holdings)'
|
|
325
|
+
print('[T4] assertions passed: V5 tickers only, AMZN absent.')
|
|
326
|
+
|
|
327
|
+
# ── T5 — Print final status and cross-check ────────────────────────────────────
|
|
328
|
+
print('\n=== T5: Print final status and cross-check ===')
|
|
329
|
+
|
|
330
|
+
# Step 1: Wait for all cards completed (stable state before any assertions)
|
|
331
|
+
wait_for_completed('T5')
|
|
332
|
+
|
|
333
|
+
# Step 2: Capture live CLI status
|
|
334
|
+
_cli_raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
335
|
+
_cli_status = json.loads(_cli_raw)['data']
|
|
336
|
+
|
|
337
|
+
# Step 3: Read status.json from outputs store
|
|
338
|
+
_file_status = read_json(os.path.join(OUTPUTS_DIR, 'status.json'))
|
|
339
|
+
|
|
340
|
+
# Step 4: Cross-check CLI vs file status
|
|
341
|
+
assert json.dumps(_cli_status, sort_keys=True) == json.dumps(_file_status, sort_keys=True), \
|
|
342
|
+
'T5: CLI status does not match status.json snapshot'
|
|
343
|
+
print('[T5] cross-check passed: CLI status matches status.json.')
|
|
344
|
+
|
|
345
|
+
# Step 5: Print holdings-table computed values
|
|
346
|
+
_ht_cv = read_json(_ht_cv_path)
|
|
347
|
+
print('\nFinal portfolio positions table:')
|
|
348
|
+
print(json.dumps(_ht_cv['table'], indent=2))
|
|
349
|
+
|
|
350
|
+
# Step 6: Totals cross-verify: holdings × prices == totalValue
|
|
351
|
+
V5_HOLDINGS = {"AAPL": 40, "MSFT": 35, "GOOG": 120, "TSLA": 70}
|
|
352
|
+
_prices_final = read_json(_prices_path)
|
|
353
|
+
_pv_cv = read_json(
|
|
354
|
+
os.path.join(OUTPUTS_DIR, 'cards', 'portfolio-value', 'computed_values.json')
|
|
355
|
+
)
|
|
356
|
+
_total_value = _pv_cv['totalValue']
|
|
357
|
+
_expected = sum(qty * _prices_final[sym] for sym, qty in V5_HOLDINGS.items())
|
|
358
|
+
assert round(_expected, 2) == round(_total_value, 2), \
|
|
359
|
+
f'T5: totals mismatch: expected={round(_expected, 2)}, got={round(_total_value, 2)}'
|
|
360
|
+
print(f'[T5] totals assertion passed: expected={round(_expected, 2)}, totalValue={round(_total_value, 2)}')
|
|
361
|
+
|
|
362
|
+
# Step 7: Print full CLI status
|
|
363
|
+
print('\nFinal board status:')
|
|
364
|
+
print(json.dumps(_cli_status, indent=2))
|
|
365
|
+
|
|
366
|
+
print('\n=== portfolio-tracker completed successfully ===')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--base-ref\runtime-out
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"graph": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"config": {
|
|
5
|
+
"settings": {
|
|
6
|
+
"completion": "manual",
|
|
7
|
+
"refreshStrategy": "data-changed"
|
|
8
|
+
},
|
|
9
|
+
"tasks": {}
|
|
10
|
+
},
|
|
11
|
+
"state": {
|
|
12
|
+
"status": "running",
|
|
13
|
+
"tasks": {},
|
|
14
|
+
"availableOutputs": [],
|
|
15
|
+
"stuckDetection": {
|
|
16
|
+
"is_stuck": false,
|
|
17
|
+
"stuck_description": null,
|
|
18
|
+
"outputs_unresolvable": [],
|
|
19
|
+
"tasks_blocked": []
|
|
20
|
+
},
|
|
21
|
+
"lastUpdated": "2026-05-02T15:18:33.305Z",
|
|
22
|
+
"executionId": "live-1777735113305",
|
|
23
|
+
"executionConfig": {
|
|
24
|
+
"executionMode": "eligibility-mode",
|
|
25
|
+
"conflictStrategy": "alphabetical",
|
|
26
|
+
"completionStrategy": "manual"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"snapshotAt": "2026-05-02T15:18:33.305Z"
|
|
30
|
+
},
|
|
31
|
+
"lastDrainedJournalId": ""
|
|
32
|
+
}
|
|
@@ -6,17 +6,31 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
const repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..');
|
|
8
8
|
const boardCliPath = path.join(repoRoot, 'board-live-cards-cli.js');
|
|
9
|
+
const cardStoreCliPath = path.join(repoRoot, 'card-store.js');
|
|
10
|
+
|
|
11
|
+
export function toFsRef(value) {
|
|
12
|
+
const payload = Buffer.from(JSON.stringify({ kind: 'fs-path', value }), 'utf-8').toString('base64url');
|
|
13
|
+
return `b64:${payload}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeRefArg(v) {
|
|
17
|
+
if (typeof v !== 'string') return v;
|
|
18
|
+
return v;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeArgs(args) {
|
|
22
|
+
return args.map((v) => normalizeRefArg(v));
|
|
23
|
+
}
|
|
9
24
|
|
|
10
25
|
export function runBoardCli(args, options = {}) {
|
|
11
26
|
const { capture = false, cwd = process.cwd() } = options;
|
|
12
|
-
const result = spawnSync(process.execPath, [boardCliPath, ...args], {
|
|
27
|
+
const result = spawnSync(process.execPath, [boardCliPath, ...normalizeArgs(args)], {
|
|
13
28
|
cwd,
|
|
14
29
|
encoding: 'utf-8',
|
|
15
30
|
windowsHide: true,
|
|
16
31
|
stdio: capture ? 'pipe' : 'pipe',
|
|
17
32
|
env: {
|
|
18
33
|
...process.env,
|
|
19
|
-
BOARD_LIVE_CARDS_NO_SPAWN: process.env.BOARD_LIVE_CARDS_NO_SPAWN ?? '1',
|
|
20
34
|
BOARD_DIR: process.env.BOARD_DIR ?? '',
|
|
21
35
|
},
|
|
22
36
|
});
|
|
@@ -34,6 +48,58 @@ export function runBoardCli(args, options = {}) {
|
|
|
34
48
|
return capture ? (result.stdout ?? '') : '';
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
/** Spawn CLI with JSON piped to stdin. */
|
|
52
|
+
export function runBoardCliWithInput(args, inputJson, options = {}) {
|
|
53
|
+
const { cwd = process.cwd() } = options;
|
|
54
|
+
const result = spawnSync(process.execPath, [boardCliPath, ...normalizeArgs(args)], {
|
|
55
|
+
input: inputJson,
|
|
56
|
+
cwd,
|
|
57
|
+
encoding: 'utf-8',
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
BOARD_DIR: process.env.BOARD_DIR ?? '',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (result.error) {
|
|
67
|
+
throw new Error(`Failed to launch board-live-cards-cli: ${result.error.message}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ((result.status ?? 1) !== 0) {
|
|
71
|
+
const stderr = (result.stderr ?? '').trim();
|
|
72
|
+
const stdout = (result.stdout ?? '').trim();
|
|
73
|
+
throw new Error(`board-live-cards-cli failed (${result.status}): ${stderr || stdout || 'no output'}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result.stdout ?? '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Spawn card-store-cli with JSON piped to stdin. */
|
|
80
|
+
export function runCardStoreCliWithInput(args, inputJson, options = {}) {
|
|
81
|
+
const { cwd = process.cwd() } = options;
|
|
82
|
+
const result = spawnSync(process.execPath, [cardStoreCliPath, ...normalizeArgs(args)], {
|
|
83
|
+
input: inputJson,
|
|
84
|
+
cwd,
|
|
85
|
+
encoding: 'utf-8',
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (result.error) {
|
|
91
|
+
throw new Error(`Failed to launch card-store-cli: ${result.error.message}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ((result.status ?? 1) !== 0) {
|
|
95
|
+
const stderr = (result.stderr ?? '').trim();
|
|
96
|
+
const stdout = (result.stdout ?? '').trim();
|
|
97
|
+
throw new Error(`card-store-cli failed (${result.status}): ${stderr || stdout || 'no output'}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result.stdout ?? '';
|
|
101
|
+
}
|
|
102
|
+
|
|
37
103
|
export async function readStdinJson() {
|
|
38
104
|
let raw = '';
|
|
39
105
|
process.stdin.setEncoding('utf-8');
|
|
@@ -54,5 +120,6 @@ export function writeResult(payload) {
|
|
|
54
120
|
}
|
|
55
121
|
|
|
56
122
|
export function writeFailure(message) {
|
|
57
|
-
|
|
123
|
+
process.stderr.write(message);
|
|
124
|
+
process.exit(1);
|
|
58
125
|
}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
3
|
+
import { readStdinJson, runBoardCli, runCardStoreCliWithInput, toFsRef, writeFailure, writeResult } from './_board-cli.js';
|
|
4
4
|
|
|
5
5
|
try {
|
|
6
6
|
const input = await readStdinJson();
|
|
7
7
|
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
8
|
-
const
|
|
8
|
+
const cards = Array.isArray(input.CARDS) ? input.CARDS : [];
|
|
9
9
|
|
|
10
|
-
if (!boardDir ||
|
|
11
|
-
writeFailure('BOARD_DIR and
|
|
12
|
-
process.exit(0);
|
|
10
|
+
if (!boardDir || cards.length === 0) {
|
|
11
|
+
writeFailure('BOARD_DIR and CARDS (array) are required');
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
const baseRef = toFsRef(boardDir);
|
|
15
|
+
|
|
16
|
+
// Write all cards to the card store in one call
|
|
17
|
+
runCardStoreCliWithInput(
|
|
18
|
+
['set', '--store-ref', baseRef],
|
|
19
|
+
JSON.stringify(cards),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Upsert all cards at once
|
|
23
|
+
runBoardCli(['upsert-card', '--base-ref', baseRef, '--all']);
|
|
16
24
|
|
|
17
25
|
writeResult({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
board_dir: boardDir,
|
|
21
|
-
cards_glob: cardsGlob,
|
|
22
|
-
},
|
|
26
|
+
board_dir: boardDir,
|
|
27
|
+
cards_added: cards.length,
|
|
23
28
|
});
|
|
24
29
|
} catch (error) {
|
|
25
30
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
3
|
+
import { readStdinJson, runBoardCli, toFsRef, writeFailure, writeResult } from './_board-cli.js';
|
|
4
4
|
|
|
5
5
|
try {
|
|
6
6
|
const input = await readStdinJson();
|
|
@@ -8,16 +8,17 @@ try {
|
|
|
8
8
|
|
|
9
9
|
if (!boardDir) {
|
|
10
10
|
writeFailure('BOARD_DIR is required');
|
|
11
|
-
process.exit(0);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
runBoardCli([
|
|
13
|
+
runBoardCli([
|
|
14
|
+
'init',
|
|
15
|
+
'--base-ref', toFsRef(boardDir),
|
|
16
|
+
'--card-store-ref', toFsRef(boardDir),
|
|
17
|
+
'--outputs-store-ref', toFsRef(boardDir),
|
|
18
|
+
]);
|
|
15
19
|
writeResult({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
board_dir: boardDir,
|
|
19
|
-
message: `initialized ${boardDir}`,
|
|
20
|
-
},
|
|
20
|
+
board_dir: boardDir,
|
|
21
|
+
message: `initialized ${boardDir}`,
|
|
21
22
|
});
|
|
22
23
|
} catch (error) {
|
|
23
24
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readStdinJson, runBoardCli, toFsRef, writeFailure, writeResult } from './_board-cli.js';
|
|
4
|
+
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const input = await readStdinJson();
|
|
11
|
+
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
12
|
+
const expectedCardCount = Number(input.EXPECTED_CARD_COUNT ?? 0);
|
|
13
|
+
const timeoutMs = Number(input.TIMEOUT_MS ?? 30000);
|
|
14
|
+
const pollMs = Number(input.POLL_MS ?? 500);
|
|
15
|
+
|
|
16
|
+
if (!boardDir || expectedCardCount <= 0) {
|
|
17
|
+
writeFailure('BOARD_DIR and EXPECTED_CARD_COUNT are required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const started = Date.now();
|
|
21
|
+
|
|
22
|
+
while (Date.now() - started < timeoutMs) {
|
|
23
|
+
const statusJson = runBoardCli(['status', '--base-ref', toFsRef(boardDir)], { capture: true });
|
|
24
|
+
let cards = [];
|
|
25
|
+
try {
|
|
26
|
+
cards = JSON.parse(statusJson)?.data?.cards ?? [];
|
|
27
|
+
} catch { /* ignore parse errors */ }
|
|
28
|
+
|
|
29
|
+
const completedCount = cards.filter(c => c.status === 'completed').length;
|
|
30
|
+
|
|
31
|
+
if (cards.length >= expectedCardCount && completedCount >= expectedCardCount) {
|
|
32
|
+
writeResult({
|
|
33
|
+
all_completed: true,
|
|
34
|
+
card_count: cards.length,
|
|
35
|
+
completed_count: completedCount,
|
|
36
|
+
});
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await sleep(pollMs);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Timeout — exit non-zero
|
|
44
|
+
process.stderr.write(`timed out waiting for ${expectedCardCount} cards to complete`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
writeFailure(message);
|
|
49
|
+
}
|
|
@@ -10,18 +10,14 @@ try {
|
|
|
10
10
|
|
|
11
11
|
if (!boardDirInput) {
|
|
12
12
|
writeFailure('BOARD_DIR is required');
|
|
13
|
-
process.exit(0);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
const boardDir = path.resolve(boardDirInput);
|
|
17
16
|
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
18
17
|
|
|
19
18
|
writeResult({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
board_dir: boardDir,
|
|
23
|
-
reset: true,
|
|
24
|
-
},
|
|
19
|
+
board_dir: boardDir,
|
|
20
|
+
reset: true,
|
|
25
21
|
});
|
|
26
22
|
} catch (error) {
|
|
27
23
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
3
|
+
import { readStdinJson, runBoardCli, toFsRef, writeFailure, writeResult } from './_board-cli.js';
|
|
4
4
|
|
|
5
5
|
try {
|
|
6
6
|
const input = await readStdinJson();
|
|
@@ -9,17 +9,13 @@ try {
|
|
|
9
9
|
|
|
10
10
|
if (!boardDir || !task) {
|
|
11
11
|
writeFailure('BOARD_DIR and TASK are required');
|
|
12
|
-
process.exit(0);
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
runBoardCli(['retrigger', '--
|
|
14
|
+
runBoardCli(['retrigger', '--base-ref', toFsRef(boardDir), '--id', task]);
|
|
16
15
|
|
|
17
16
|
writeResult({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
task,
|
|
21
|
-
retriggered: true,
|
|
22
|
-
},
|
|
17
|
+
task,
|
|
18
|
+
retriggered: true,
|
|
23
19
|
});
|
|
24
20
|
} catch (error) {
|
|
25
21
|
const message = error instanceof Error ? error.message : String(error);
|