yaml-flow 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/asset-integrity.json +8 -4
- package/browser/board-livecards-client.js +1 -1
- package/browser/board-livecards-localstorage.js +5 -5
- package/browser/live-cards.js +19 -3307
- package/cli/board-live-cards-lib-tjYsPt5U.d.ts +321 -0
- package/{dist/cli → cli}/browser-api/board-live-cards-browser-adapter.d.ts +3 -5
- package/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
- package/{dist/cli → cli}/browser-api/card-store-browser-api.d.ts +1 -2
- package/{dist/cli → cli}/browser-api/card-store-browser-api.js +1 -1
- package/cli/execution-interface-C_A6WCiK.d.ts +284 -0
- package/cli/node/artifacts-store-cli.js +11 -0
- package/cli/node/batch-runner-cli.js +3 -0
- package/cli/node/board-live-cards-cli.js +15 -0
- package/cli/node/card-store-cli.js +8 -0
- package/{dist/cli → cli}/node/execution-adapter.d.ts +3 -3
- package/cli/node/execution-adapter.js +3 -0
- package/{dist/cli → cli}/node/fs-board-adapter.d.ts +24 -11
- package/cli/node/fs-board-adapter.js +14 -0
- package/{dist/cli → cli}/node/source-cli-task-executor.js +2 -2
- package/cli/node/step-machine-cli.d.ts +7 -0
- package/cli/node/step-machine-cli.js +5 -0
- package/{dist/board-live-cards-public-CW5074xr.d.cts → cli/types-CziUxkiv.d.ts} +69 -7
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.config/card-store-ref.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.config/chat-handler.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.config/outputs-store-ref.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.config/task-executor.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.state-snapshot/board/graph.json +29 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime/.state-snapshot/board/lastJournalProcessedId.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/gandalf-runtime-out/.outputs/status.json +25 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/cards/card-market-prices/computed_values.json +67 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/cards/card-portfolio/computed_values.json +1 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/cards/card-portfolio-value/computed_values.json +52 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/data-objects/holdings.json +22 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/data-objects/positions.json +46 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/data-objects/quotes.json +35 -0
- package/examples/board/.demo-setup/run-1778643703151-3360-dopnpv/board-default/runtime-out/.outputs/status.json +113 -0
- package/examples/{example-board/cards/card-market-prices.json → board/cards/cardT-market-prices.json} +2 -2
- package/examples/{example-board/cards/card-portfolio.json → board/cards/cardT-portfolio.json} +3 -13
- package/examples/{example-board → board}/demo-server-config.json +0 -1
- package/examples/{example-board → board}/demo-server.js +70 -72
- package/examples/{example-board → board}/demo-shell-with-server.html +3 -3
- package/examples/{example-board → board}/demo-task-executor.js +75 -32
- package/examples/board/gandalf-cards/card-source-kinds.json +36 -0
- package/examples/board/gandalf-cards/cards/_index.json +7 -0
- package/examples/board/gandalf-cards/cards/card-source-kinds.json +64 -0
- package/examples/board/source-def-flows/copilot.flow.json +33 -0
- package/examples/board/source-def-flows/mock.flow.json +35 -0
- package/examples/board/source-def-flows/url-list.flow.json +33 -0
- package/examples/board/source-def-flows/url.flow.json +33 -0
- package/examples/board/source-def-flows/workiq.flow.json +34 -0
- package/examples/board/source-def-handlers/copilot-source-handler.js +141 -0
- package/examples/board/source-def-handlers/http-source-handler.js +145 -0
- package/examples/board/source_def_flows.json +249 -0
- package/examples/board/test/demo-http-test.js +317 -0
- package/examples/{example-board → board-local}/demo-shell-localstorage.html +4 -4
- package/examples/{browser/boards/portfolio-tracker → portfolio-tracker/handlers}/portfolio-tracker-fetch-prices.js +1 -1
- package/examples/{browser/boards/portfolio-tracker/portfolio-tracker-public.js → portfolio-tracker/portfolio-tracker.js} +11 -14
- package/examples/{browser/boards/portfolio-tracker → portfolio-tracker/test}/portfolio-t4.js +32 -50
- package/lib/artifacts-store-lib-public-BABrgFkV.d.ts +119 -0
- package/lib/artifacts-store-lib-public-DGa8BpJT.d.cts +119 -0
- package/lib/artifacts-store-public.cjs +2 -0
- package/lib/artifacts-store-public.d.cts +5 -0
- package/lib/artifacts-store-public.d.ts +5 -0
- package/lib/artifacts-store-public.js +2 -0
- package/lib/board-live-cards-node.cjs +14 -0
- package/{dist/cli/node/execution-adapter.d.cts → lib/board-live-cards-node.d.cts} +45 -85
- package/lib/board-live-cards-node.d.ts +134 -0
- package/lib/board-live-cards-node.js +14 -0
- package/lib/board-live-cards-public-BnmRAbQV.d.cts +383 -0
- package/{dist/board-live-cards-public-hnZo0mAf.d.ts → lib/board-live-cards-public-CsmYrvpd.d.ts} +142 -77
- package/lib/board-live-cards-public.cjs +3 -0
- package/lib/board-live-cards-public.d.cts +4 -0
- package/lib/board-live-cards-public.d.ts +4 -0
- package/lib/board-live-cards-public.js +3 -0
- package/lib/board-live-cards-server-runtime.cjs +9 -0
- package/lib/board-live-cards-server-runtime.d.cts +6 -0
- package/lib/board-live-cards-server-runtime.d.ts +6 -0
- package/lib/board-live-cards-server-runtime.js +9 -0
- package/lib/board-livegraph-runtime/index.cjs +3 -0
- package/{dist → lib}/board-livegraph-runtime/index.d.cts +1 -2
- package/{dist → lib}/board-livegraph-runtime/index.d.ts +1 -2
- package/lib/board-livegraph-runtime/index.js +3 -0
- package/lib/board-worker-adapter.cjs +10 -0
- package/{dist/storage-refs.d.cts → lib/board-worker-adapter.d.cts} +5 -5
- package/{dist/storage-refs.d.ts → lib/board-worker-adapter.d.ts} +5 -5
- package/lib/board-worker-adapter.js +10 -0
- package/{dist → lib}/card-compute/index.cjs +1 -1
- package/{dist → lib}/card-compute/index.js +1 -1
- package/lib/card-store-public.cjs +2 -0
- package/lib/card-store-public.d.cts +61 -0
- package/lib/card-store-public.d.ts +61 -0
- package/lib/card-store-public.js +2 -0
- package/lib/card-validation.cjs +10 -0
- package/lib/card-validation.d.cts +35 -0
- package/lib/card-validation.d.ts +35 -0
- package/lib/card-validation.js +10 -0
- package/{dist/constants-oCEbNpul.d.ts → lib/constants-BPVLb3Es.d.ts} +1 -1
- package/{dist/constants-BzZUyYlp.d.cts → lib/constants-DXxsRN9y.d.cts} +1 -1
- package/{dist → lib}/continuous-event-graph/index.cjs +2 -2
- package/{dist → lib}/continuous-event-graph/index.d.cts +3 -5
- package/{dist → lib}/continuous-event-graph/index.d.ts +3 -5
- package/{dist → lib}/continuous-event-graph/index.js +2 -2
- package/{dist → lib}/event-graph/index.d.cts +2 -2
- package/{dist → lib}/event-graph/index.d.ts +2 -2
- package/lib/execution-refs.cjs +3 -0
- package/{dist → lib}/execution-refs.d.cts +38 -14
- package/{dist → lib}/execution-refs.d.ts +38 -14
- package/lib/execution-refs.js +3 -0
- package/lib/index.cjs +25 -0
- package/{dist → lib}/index.d.cts +7 -8
- package/{dist → lib}/index.d.ts +7 -8
- package/lib/index.js +25 -0
- package/{dist/live-cards-bridge-BXbVTsna.d.cts → lib/live-cards-bridge-DC_ZU0eS.d.ts} +134 -3
- package/{dist/live-cards-bridge-Ds28XR15.d.ts → lib/live-cards-bridge-b25aAVvE.d.cts} +134 -3
- package/lib/loader-CuuLjxVA.d.cts +42 -0
- package/lib/loader-Zborm2pq.d.ts +42 -0
- package/lib/server-runtime/index.cjs +9 -0
- package/{dist → lib}/server-runtime/index.d.cts +4 -4
- package/{dist → lib}/server-runtime/index.d.ts +4 -4
- package/lib/server-runtime/index.js +9 -0
- package/lib/step-machine/index.d.cts +64 -0
- package/lib/step-machine/index.d.ts +64 -0
- package/lib/step-machine-public/index.cjs +5 -0
- package/{dist → lib}/step-machine-public/index.d.cts +21 -1
- package/{dist → lib}/step-machine-public/index.d.ts +21 -1
- package/lib/step-machine-public/index.js +5 -0
- package/lib/storage-interface-BhAON-gW.d.cts +84 -0
- package/lib/storage-interface-BhAON-gW.d.ts +84 -0
- package/lib/stores/index.cjs +3 -0
- package/lib/stores/index.d.cts +4 -0
- package/lib/stores/index.d.ts +4 -0
- package/lib/stores/index.js +3 -0
- package/lib/stores/kv.cjs +3 -0
- package/lib/stores/kv.d.cts +32 -0
- package/lib/stores/kv.d.ts +32 -0
- package/lib/stores/kv.js +3 -0
- package/{dist → lib}/stores/memory.d.cts +1 -1
- package/{dist → lib}/stores/memory.d.ts +1 -1
- package/{dist/types-B1ZRa4aI.d.ts → lib/types-CBxkYuLY.d.ts} +2 -1
- package/{dist/types-ycun84cq.d.cts → lib/types-DQ1bKuB1.d.cts} +11 -0
- package/{dist/types-ycun84cq.d.ts → lib/types-DQ1bKuB1.d.ts} +11 -0
- package/{dist/types-BxEFcVK9.d.cts → lib/types-DkFvgxwq.d.cts} +2 -1
- package/package.json +79 -119
- package/board-live-cards-cli.js +0 -37
- package/browser/board-livecards-client.js.map +0 -1
- package/browser/board-livecards-localstorage.js.map +0 -1
- package/browser/board-livegraph-engine.js +0 -3
- package/browser/board-livegraph-engine.js.map +0 -1
- package/browser/card-compute.js +0 -266
- package/browser/compute-jsonata.js.map +0 -1
- package/card-store.js +0 -37
- package/dist/batch/index.cjs.map +0 -1
- package/dist/batch/index.js.map +0 -1
- package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +0 -136
- package/dist/board-live-cards-lib-jM2uYG1v.d.ts +0 -136
- package/dist/board-livegraph-runtime/index.cjs +0 -3
- package/dist/board-livegraph-runtime/index.cjs.map +0 -1
- package/dist/board-livegraph-runtime/index.js +0 -3
- package/dist/board-livegraph-runtime/index.js.map +0 -1
- package/dist/card-compute/index.cjs.map +0 -1
- package/dist/card-compute/index.js.map +0 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +0 -3
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +0 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +0 -37
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +0 -3
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +0 -1
- package/dist/cli/browser-api/card-store-browser-api.cjs +0 -2
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +0 -1
- package/dist/cli/browser-api/card-store-browser-api.d.cts +0 -26
- package/dist/cli/browser-api/card-store-browser-api.js.map +0 -1
- package/dist/cli/node/artifacts-store-cli.cjs +0 -11
- package/dist/cli/node/artifacts-store-cli.cjs.map +0 -1
- package/dist/cli/node/artifacts-store-cli.d.cts +0 -8
- package/dist/cli/node/artifacts-store-cli.js +0 -11
- package/dist/cli/node/artifacts-store-cli.js.map +0 -1
- package/dist/cli/node/board-live-cards-cli.cjs +0 -15
- package/dist/cli/node/board-live-cards-cli.cjs.map +0 -1
- package/dist/cli/node/board-live-cards-cli.d.cts +0 -20
- package/dist/cli/node/board-live-cards-cli.js +0 -15
- package/dist/cli/node/board-live-cards-cli.js.map +0 -1
- package/dist/cli/node/card-store-cli.cjs +0 -8
- package/dist/cli/node/card-store-cli.cjs.map +0 -1
- package/dist/cli/node/card-store-cli.d.cts +0 -15
- package/dist/cli/node/card-store-cli.js +0 -8
- package/dist/cli/node/card-store-cli.js.map +0 -1
- package/dist/cli/node/execution-adapter.cjs +0 -3
- package/dist/cli/node/execution-adapter.cjs.map +0 -1
- package/dist/cli/node/execution-adapter.js +0 -3
- package/dist/cli/node/execution-adapter.js.map +0 -1
- package/dist/cli/node/fs-board-adapter.cjs +0 -14
- package/dist/cli/node/fs-board-adapter.cjs.map +0 -1
- package/dist/cli/node/fs-board-adapter.d.cts +0 -204
- package/dist/cli/node/fs-board-adapter.js +0 -14
- package/dist/cli/node/fs-board-adapter.js.map +0 -1
- package/dist/cli/node/source-cli-task-executor.cjs +0 -11
- package/dist/cli/node/source-cli-task-executor.cjs.map +0 -1
- package/dist/cli/node/source-cli-task-executor.js.map +0 -1
- package/dist/config/index.cjs.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/continuous-event-graph/index.cjs.map +0 -1
- package/dist/continuous-event-graph/index.js.map +0 -1
- package/dist/event-graph/index.cjs.map +0 -1
- package/dist/event-graph/index.js.map +0 -1
- package/dist/execution-refs.cjs +0 -3
- package/dist/execution-refs.cjs.map +0 -1
- package/dist/execution-refs.js +0 -3
- package/dist/execution-refs.js.map +0 -1
- package/dist/index.cjs +0 -30
- package/dist/index.cjs.map +0 -1
- package/dist/index.js +0 -30
- package/dist/index.js.map +0 -1
- package/dist/inference/index.cjs +0 -7
- package/dist/inference/index.cjs.map +0 -1
- package/dist/inference/index.d.cts +0 -229
- package/dist/inference/index.d.ts +0 -229
- package/dist/inference/index.js +0 -7
- package/dist/inference/index.js.map +0 -1
- package/dist/server-runtime/index.cjs +0 -9
- package/dist/server-runtime/index.cjs.map +0 -1
- package/dist/server-runtime/index.js +0 -9
- package/dist/server-runtime/index.js.map +0 -1
- package/dist/step-machine/index.cjs.map +0 -1
- package/dist/step-machine/index.d.cts +0 -102
- package/dist/step-machine/index.d.ts +0 -102
- package/dist/step-machine/index.js.map +0 -1
- package/dist/step-machine-public/index.cjs +0 -2
- package/dist/step-machine-public/index.cjs.map +0 -1
- package/dist/step-machine-public/index.js +0 -2
- package/dist/step-machine-public/index.js.map +0 -1
- package/dist/storage-refs.cjs +0 -10
- package/dist/storage-refs.cjs.map +0 -1
- package/dist/storage-refs.js +0 -10
- package/dist/storage-refs.js.map +0 -1
- package/dist/stores/file.cjs +0 -2
- package/dist/stores/file.cjs.map +0 -1
- package/dist/stores/file.d.cts +0 -36
- package/dist/stores/file.d.ts +0 -36
- package/dist/stores/file.js +0 -2
- package/dist/stores/file.js.map +0 -1
- package/dist/stores/index.cjs +0 -2
- package/dist/stores/index.cjs.map +0 -1
- package/dist/stores/index.d.cts +0 -4
- package/dist/stores/index.d.ts +0 -4
- package/dist/stores/index.js +0 -2
- package/dist/stores/index.js.map +0 -1
- package/dist/stores/localStorage.cjs +0 -2
- package/dist/stores/localStorage.cjs.map +0 -1
- package/dist/stores/localStorage.d.cts +0 -34
- package/dist/stores/localStorage.d.ts +0 -34
- package/dist/stores/localStorage.js +0 -2
- package/dist/stores/localStorage.js.map +0 -1
- package/dist/stores/memory.cjs.map +0 -1
- package/dist/stores/memory.js.map +0 -1
- package/dist/types-CHSdoAAA.d.cts +0 -135
- package/dist/types-CoW0gQl3.d.ts +0 -135
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +0 -201
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +0 -357
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +0 -196
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +0 -300
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +0 -617
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +0 -366
- package/examples/browser/livecards-browser/index.html +0 -41
- package/examples/browser/step-machine-browser/index.html +0 -367
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +0 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +0 -32
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +0 -43
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/cli/step-machine-cli/portfolio-tracker/fetch-prices.js +0 -48
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -125
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +0 -32
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +0 -26
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +0 -49
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +0 -25
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +0 -23
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +0 -21
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +0 -38
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +0 -48
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +0 -31
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +0 -107
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +0 -51
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +0 -45
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +0 -71
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +0 -36
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +0 -26
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +0 -39
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +0 -80
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +0 -76
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +0 -44
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +0 -43
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +0 -77
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +0 -28
- package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +0 -31
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +0 -54
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +0 -21
- package/examples/cli/step-machine-demo/step-cli-echo-y.js +0 -15
- package/examples/cli/step-machine-demo/step2-double-cli.js +0 -33
- package/examples/cli/step-machine-demo/two-step-math.flow.yaml +0 -93
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +0 -43
- package/examples/example-board/agent-instructions-cardlayout.md +0 -56
- package/examples/example-board/agent-instructions.md +0 -834
- package/examples/example-board/cards/_index.json +0 -47
- package/examples/example-board/demo-shell.html +0 -63
- package/examples/index.html +0 -785
- package/examples/npm-libs/batch/batch-step-machine.ts +0 -121
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +0 -215
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +0 -555
- package/examples/npm-libs/continuous-event-graph/portfolio-tracker.ts +0 -287
- package/examples/npm-libs/continuous-event-graph/reactive-monitoring.ts +0 -265
- package/examples/npm-libs/continuous-event-graph/reactive-pipeline.ts +0 -168
- package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +0 -287
- package/examples/npm-libs/continuous-event-graph/stock-dashboard.ts +0 -229
- package/examples/npm-libs/event-graph/ci-cd-pipeline.ts +0 -243
- package/examples/npm-libs/event-graph/executor-diamond.ts +0 -165
- package/examples/npm-libs/event-graph/executor-pipeline.ts +0 -161
- package/examples/npm-libs/event-graph/research-pipeline.ts +0 -137
- package/examples/npm-libs/flows/ai-conversation.yaml +0 -116
- package/examples/npm-libs/flows/order-processing.yaml +0 -143
- package/examples/npm-libs/flows/simple-greeting.yaml +0 -54
- package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +0 -307
- package/examples/npm-libs/graph-of-graphs/url-processing-pipeline.ts +0 -254
- package/examples/npm-libs/inference/azure-deployment.ts +0 -149
- package/examples/npm-libs/inference/copilot-cli.ts +0 -138
- package/examples/npm-libs/inference/data-pipeline.ts +0 -145
- package/examples/npm-libs/inference/pluggable-adapters.ts +0 -254
- package/examples/npm-libs/node/ai-conversation.ts +0 -195
- package/examples/npm-libs/node/simple-greeting.ts +0 -101
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +0 -43
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/step-machine-cli/portfolio-tracker/fetch-prices.js +0 -48
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -57
- package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +0 -27
- package/examples/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +0 -25
- package/examples/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +0 -29
- package/examples/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +0 -27
- package/examples/step-machine-cli/portfolio-tracker/handlers/status-cli.js +0 -25
- package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +0 -37
- package/examples/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +0 -53
- package/examples/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +0 -35
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +0 -227
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +0 -38
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +0 -28
- package/step-machine-cli.js +0 -407
- /package/{dist/board-livegraph-runtime → cli/browser-api}/jsonata-sync.cjs +0 -0
- /package/{dist/cli → cli}/node/artifacts-store-cli.d.ts +0 -0
- /package/{dist/cli/node/source-cli-task-executor.d.cts → cli/node/batch-runner-cli.d.ts} +0 -0
- /package/{dist/cli → cli}/node/board-live-cards-cli.d.ts +0 -0
- /package/{dist/cli → cli}/node/card-store-cli.d.ts +0 -0
- /package/{dist/card-compute → cli/node}/jsonata-sync.cjs +0 -0
- /package/{dist/cli → cli}/node/source-cli-task-executor.d.ts +0 -0
- /package/examples/{example-board → board}/cards/card-concentration.json +0 -0
- /package/examples/{example-board → board}/cards/card-my-identity.json +0 -0
- /package/examples/{example-board → board}/cards/card-portfolio-action.json +0 -0
- /package/examples/{example-board → board}/cards/card-portfolio-intelligence.json +0 -0
- /package/examples/{example-board → board}/cards/card-portfolio-risks.json +0 -0
- /package/examples/{example-board → board}/cards/card-rebalance-impact.json +0 -0
- /package/examples/{example-board → board}/cards/card-rebalance-sim.json +0 -0
- /package/examples/{example-board/cards/card-portfolio-value.json → board/cards/cardT-portfolio-value.json} +0 -0
- /package/examples/{example-board → board}/demo-chat-handler.js +0 -0
- /package/examples/{example-board → board}/scripts/copilot_wrapper.bat +0 -0
- /package/examples/{example-board → board}/scripts/copilot_wrapper_helper.ps1 +0 -0
- /package/examples/{example-board → board}/scripts/workiq_wrapper.mjs +0 -0
- /package/examples/{browser/boards/portfolio-tracker → board/test}/portfolio-tracker-sse-worker.js +0 -0
- /package/{dist → lib}/batch/index.cjs +0 -0
- /package/{dist → lib}/batch/index.d.cts +0 -0
- /package/{dist → lib}/batch/index.d.ts +0 -0
- /package/{dist → lib}/batch/index.js +0 -0
- /package/{dist/cli/browser-api → lib/board-livegraph-runtime}/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/card-compute/index.d.cts +0 -0
- /package/{dist → lib}/card-compute/index.d.ts +0 -0
- /package/{dist/cli/node → lib/card-compute}/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/config/index.cjs +0 -0
- /package/{dist → lib}/config/index.d.cts +0 -0
- /package/{dist → lib}/config/index.d.ts +0 -0
- /package/{dist → lib}/config/index.js +0 -0
- /package/{dist → lib}/continuous-event-graph/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/event-graph/index.cjs +0 -0
- /package/{dist → lib}/event-graph/index.js +0 -0
- /package/{dist → lib}/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/server-runtime/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/step-machine/index.cjs +0 -0
- /package/{dist → lib}/step-machine/index.js +0 -0
- /package/{dist → lib}/step-machine-public/jsonata-sync.cjs +0 -0
- /package/{dist → lib}/stores/memory.cjs +0 -0
- /package/{dist → lib}/stores/memory.js +0 -0
- /package/{dist → lib}/types-BBhqYGhE.d.cts +0 -0
- /package/{dist → lib}/types-BBhqYGhE.d.ts +0 -0
- /package/{dist → lib}/validate-BAVzUJWa.d.ts +0 -0
- /package/{dist → lib}/validate-Dbu7ygys.d.cts +0 -0
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { L as LiveCard } from '../../board-live-cards-lib-Bg6EvCo5.cjs';
|
|
2
|
-
import '../../types-BBhqYGhE.cjs';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* card-store-browser-api.ts
|
|
6
|
-
*
|
|
7
|
-
* Simple browser-facing card store API.
|
|
8
|
-
* Wraps createCardStore() + createLocalStorageCardStorageAdapter()
|
|
9
|
-
* into a minimal read/write interface suitable for browser consumption.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
interface BrowserCardStoreApi {
|
|
13
|
-
getCard(id: string): LiveCard | null;
|
|
14
|
-
getAllCards(): LiveCard[];
|
|
15
|
-
upsertCard(card: LiveCard): void;
|
|
16
|
-
removeCard(id: string): void;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Create a browser card store backed by localStorage.
|
|
20
|
-
*
|
|
21
|
-
* @param namespace - localStorage key prefix (e.g. 'my-board:cards').
|
|
22
|
-
* Multiple stores can coexist by using distinct namespaces.
|
|
23
|
-
*/
|
|
24
|
-
declare function createBrowserCardStoreApi(namespace: string): BrowserCardStoreApi;
|
|
25
|
-
|
|
26
|
-
export { type BrowserCardStoreApi, LiveCard, createBrowserCardStoreApi };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/card-compute/schema-validator.ts","../../../src/card-compute/index.ts","../../../src/cli/common/board-live-cards-lib.ts","../../../src/cli/browser-api/storage-localstorage-adapters.ts","../../../src/cli/browser-api/card-store-browser-api.ts"],"names":["_require","createRequire","createCardStore","adapter","onWarn","loadIndex","applyJsonPath","obj","jsonPath","value","segments","out","target","i","key","cur","next","id","entry","cards","card","result","snapshotChecksumIndex","localIndex","changed","cardKey","index","existingById","existingByKey","e","resolvedKey","checksum","current","stableJson","k","fnv32a","str","seed","h","computeStableJsonHashBrowser","a","b","c","d","n","createLocalStorageKvStorage","prefix","raw","prefix2","fullPrefix","lsKey","deepMergeObjects","patch","v","head","tail","nested","createLocalStorageJsonStorage","kv","segment","existing","createLocalStorageCardStorageAdapter","json","cardId","createBrowserCardStoreApi","namespace","store"],"mappings":"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","file":"card-store-browser-api.js","sourcesContent":["/**\n * schema-validator — Full JSON Schema validation for LiveCards nodes.\n *\n * Uses AJV to validate against the published live-cards.schema.json.\n * For a lightweight sync check without AJV, use `CardCompute.validate()` instead.\n *\n * @example\n * ```typescript\n * import { validateLiveCardSchema } from 'yaml-flow/card-compute';\n *\n * const result = validateLiveCardSchema(node);\n * if (!result.ok) console.error(result.errors);\n * ```\n */\n\nimport type { ValidationResult } from './index.js';\nimport liveCardsSchema from '../../schema/live-cards.schema.json';\nimport Ajv from 'ajv';\nimport addFormats from 'ajv-formats';\nimport { createRequire } from 'module';\nconst _require = createRequire(import.meta.url);\nconst jsonata: (expr: string) => { evaluate: (data: unknown) => unknown } = _require('./jsonata-sync.cjs');\n\ntype AjvValidateFunction = {\n (data: unknown): boolean;\n errors?: Array<{ instancePath?: string; message?: string }> | null;\n};\n\nlet _compiled: AjvValidateFunction | null = null;\n\nconst KNOWN_NAMESPACES = [\n 'card_data',\n 'requires',\n 'fetched_sources',\n 'computed_values',\n 'source_defs',\n] as const;\n\ntype KnownNamespace = typeof KNOWN_NAMESPACES[number];\n\nconst NAMESPACE_REFERENCE_RE = /\\b(card_data|requires|fetched_sources|computed_values|source_defs)\\b/g;\nconst ROOT_PATH_NAMESPACE_RE = /^\\s*(card_data|requires|fetched_sources|computed_values|source_defs)(\\.|$)/;\n\nfunction referencedNamespaces(expression: string): Set<KnownNamespace> {\n const namespaces = new Set<KnownNamespace>();\n let match: RegExpExecArray | null;\n NAMESPACE_REFERENCE_RE.lastIndex = 0;\n while ((match = NAMESPACE_REFERENCE_RE.exec(expression)) !== null) {\n namespaces.add(match[1] as KnownNamespace);\n }\n return namespaces;\n}\n\nfunction parseRootPathNamespace(pathValue: string): KnownNamespace | null {\n const match = ROOT_PATH_NAMESPACE_RE.exec(pathValue);\n return match ? (match[1] as KnownNamespace) : null;\n}\n\nfunction validateJsonataExprWithNamespaces(\n expr: string,\n path: string,\n allowedNamespaces: Set<KnownNamespace>,\n errors: string[],\n): void {\n try {\n jsonata(expr);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push(`${path}: invalid JSONata expression (${message})`);\n return;\n }\n\n const usedNamespaces = referencedNamespaces(expr);\n for (const namespace of usedNamespaces) {\n if (!allowedNamespaces.has(namespace)) {\n errors.push(`${path}: disallowed namespace \"${namespace}\" in expression`);\n }\n }\n}\n\nfunction walkViewPathReferences(\n value: unknown,\n path: string,\n errors: string[],\n): void {\n if (Array.isArray(value)) {\n value.forEach((entry, index) => {\n walkViewPathReferences(entry, `${path}/${index}`, errors);\n });\n return;\n }\n\n if (typeof value === 'string') {\n const rootNamespace = parseRootPathNamespace(value);\n if (!rootNamespace) return;\n if (!new Set<KnownNamespace>(['card_data', 'requires', 'computed_values']).has(rootNamespace)) {\n errors.push(`${path}: disallowed namespace \"${rootNamespace}\" in view reference`);\n }\n return;\n }\n\n if (!value || typeof value !== 'object') return;\n\n const record = value as Record<string, unknown>;\n for (const [key, next] of Object.entries(record)) {\n walkViewPathReferences(next, `${path}/${key}`, errors);\n }\n}\n\nfunction getValidator(): AjvValidateFunction {\n if (_compiled) return _compiled;\n const ajv = new Ajv({ allErrors: true });\n addFormats(ajv);\n _compiled = ajv.compile(liveCardsSchema);\n return _compiled;\n}\n\n/**\n * Validate a node against the full LiveCards JSON Schema (draft-07).\n *\n * Requires `ajv` and `ajv-formats` to be installed.\n * Returns the same `ValidationResult` shape as `CardCompute.validate()`.\n */\nexport function validateLiveCardSchema(\n node: unknown,\n): ValidationResult {\n const validate = getValidator();\n const valid = validate(node);\n\n const errors = (validate.errors ?? []).map(e => {\n const path = e.instancePath || '/';\n return `${path}: ${e.message ?? 'unknown error'}`;\n });\n\n // JSON Schema draft-07 cannot enforce per-property uniqueness across array items.\n // Check bindTo and outputFile uniqueness here after the AJV structural pass.\n if (node && typeof node === 'object' && !Array.isArray(node)) {\n const source_defs = (node as Record<string, unknown>).source_defs;\n if (Array.isArray(source_defs)) {\n const bindTos = new Set<string>();\n const outputFiles = new Set<string>();\n source_defs.forEach((src, i) => {\n if (!src || typeof src !== 'object' || Array.isArray(src)) return;\n const s = src as Record<string, unknown>;\n if (typeof s.bindTo === 'string' && s.bindTo) {\n if (bindTos.has(s.bindTo)) {\n errors.push(`/source_defs/${i}/bindTo: bindTo \"${s.bindTo}\" must be unique across all source_defs`);\n }\n bindTos.add(s.bindTo);\n }\n if (typeof s.outputFile === 'string' && s.outputFile) {\n if (outputFiles.has(s.outputFile)) {\n errors.push(`/source_defs/${i}/outputFile: outputFile \"${s.outputFile}\" must be unique across all source_defs`);\n }\n outputFiles.add(s.outputFile);\n }\n });\n }\n }\n\n if (!valid || errors.length > 0) return { ok: false, errors };\n return { ok: true, errors: [] };\n}\n\n/**\n * Validate JSONata expressions in compute[] by compiling with the same parser used at runtime.\n */\nexport function validateLiveCardRuntimeExpressions(\n node: unknown,\n): ValidationResult {\n const errors: string[] = [];\n\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n return { ok: true, errors: [] };\n }\n\n const asRecord = node as Record<string, unknown>;\n\n const compute = asRecord.compute;\n if (Array.isArray(compute)) {\n compute.forEach((step, i) => {\n if (!step || typeof step !== 'object' || Array.isArray(step)) return;\n const expr = (step as Record<string, unknown>).expr;\n if (typeof expr !== 'string' || expr.trim().length === 0) return;\n validateJsonataExprWithNamespaces(\n expr,\n `/compute/${i}/expr`,\n new Set<KnownNamespace>(['card_data', 'requires', 'fetched_sources', 'computed_values']),\n errors,\n );\n });\n }\n\n // Validate provides[].ref paths use a valid root namespace.\n const VALID_PROVIDES_SRC_NAMESPACES = new Set<KnownNamespace>([\n 'card_data', 'requires', 'fetched_sources', 'computed_values',\n ]);\n const provides = asRecord.provides;\n if (Array.isArray(provides)) {\n provides.forEach((entry, i) => {\n if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return;\n const ref = (entry as Record<string, unknown>).ref;\n if (typeof ref !== 'string' || ref.trim().length === 0) return;\n const rootNamespace = parseRootPathNamespace(ref);\n if (rootNamespace === null) {\n errors.push(`/provides/${i}/ref: path \"${ref}\" must start with a valid namespace (${[...VALID_PROVIDES_SRC_NAMESPACES].join(', ')})`);\n } else if (!VALID_PROVIDES_SRC_NAMESPACES.has(rootNamespace)) {\n errors.push(`/provides/${i}/ref: disallowed namespace \"${rootNamespace}\" in path \"${ref}\" (valid: ${[...VALID_PROVIDES_SRC_NAMESPACES].join(', ')})`);\n }\n });\n }\n\n const view = asRecord.view;\n if (view && typeof view === 'object' && !Array.isArray(view)) {\n walkViewPathReferences(view, '/view', errors);\n }\n\n // Validate source_defs[i].projections values: each must be a JSONata expression rooted at\n // card_data or requires only. fetched_sources/computed_values/source_defs are not\n // valid here because sources run before those namespaces exist.\n const VALID_PROJECTION_NAMESPACES = new Set<KnownNamespace>(['card_data', 'requires']);\n const source_defs = asRecord.source_defs;\n if (Array.isArray(source_defs)) {\n source_defs.forEach((srcDef, i) => {\n if (!srcDef || typeof srcDef !== 'object' || Array.isArray(srcDef)) return;\n const projections = (srcDef as Record<string, unknown>).projections;\n if (!projections || typeof projections !== 'object' || Array.isArray(projections)) return;\n for (const [key, exprVal] of Object.entries(projections as Record<string, unknown>)) {\n if (typeof exprVal !== 'string' || exprVal.trim().length === 0) continue;\n validateJsonataExprWithNamespaces(\n exprVal,\n `/source_defs/${i}/projections/${key}`,\n VALID_PROJECTION_NAMESPACES,\n errors,\n );\n }\n });\n }\n\n return { ok: errors.length === 0, errors };\n}\n\nexport function validateLiveCard(\n node: unknown,\n): ValidationResult {\n return validateLiveCardDefinition(node);\n}\n\n/**\n * Full validation for live card definitions:\n * 1) JSON Schema structure/contract checks\n * 2) Runtime JSONata parser compatibility checks for compute expressions\n */\nexport function validateLiveCardDefinition(\n node: unknown,\n): ValidationResult {\n const schema = validateLiveCardSchema(node);\n if (!schema.ok) return schema;\n\n const runtime = validateLiveCardRuntimeExpressions(node);\n if (!runtime.ok) return { ok: false, errors: runtime.errors };\n\n return { ok: true, errors: [] };\n}\n","/**\n * card-compute — JSONata-powered compute engine for LiveCards nodes.\n *\n * Isomorphic: works in browser, Node.js, and bundlers.\n * No DOM dependency. Compute expressions are JSONata strings.\n *\n * @example\n * ```typescript\n * import { CardCompute } from 'yaml-flow/card-compute';\n *\n * const node = {\n * id: 'sales',\n * card_data: { data: [{ revenue: 100 }, { revenue: 200 }] },\n * compute: [\n * { bindTo: 'total', expr: '$sum(card_data.data.revenue)' },\n * { bindTo: 'avg', expr: '$average(card_data.data.revenue)' },\n * ],\n * };\n * await CardCompute.run(node);\n * // node.computed_values.total === 300\n * // node.computed_values.avg === 150\n * ```\n *\n * Expressions are evaluated against { card_data, requires, fetched_sources, computed_values }.\n * computed_values is ephemeral — never persisted to disk.\n */\n\nimport { createRequire } from 'module';\nconst _require = createRequire(import.meta.url);\nconst jsonata: (expr: string) => { evaluate: (data: unknown) => unknown } = _require('./jsonata-sync.cjs');\nconst jsonataSync = jsonata;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A source definition: cli writes to outputFile; bindTo names the fetched_sources.* key in compute context. Both bindTo and outputFile must be unique across source_defs in a card. */\nexport interface ComputeSource {\n bindTo: string;\n outputFile: string;\n cli?: string;\n // Deprecated alias retained for compatibility with older cards.\n script?: string;\n optionalForCompletionGating?: boolean;\n /** Named data projections: each key maps to a JSONata expression rooted at card_data or requires.\n * The engine evaluates these before spawning the executor and passes results as _projections. */\n projections?: Record<string, string>;\n [key: string]: unknown;\n}\n\n/** Options for CardCompute.run() */\nexport interface RunOptions {\n /** Pre-loaded source results map (keyed by bindTo). Use in browser or when caller loads files. */\n sourcesData?: Record<string, unknown>;\n}\n\n/** A single compute step: bindTo names the computed_values key; expr is a JSONata expression. */\nexport interface ComputeStep {\n bindTo: string;\n expr: string;\n}\n\n/** Minimal node shape expected by CardCompute. */\nexport interface ComputeNode {\n id?: string;\n card_data?: Record<string, unknown>;\n requires?: Record<string, unknown>;\n source_defs?: ComputeSource[];\n compute?: ComputeStep[];\n computed_values?: Record<string, unknown>;\n /** Ephemeral: populated by run() from sourcesData option. Never persisted. */\n _sourcesData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Deep path utilities\n// ---------------------------------------------------------------------------\n\nfunction deepGet(obj: unknown, path: string): unknown {\n if (!path || !obj) return undefined;\n const parts = path.split('.');\n let cur: unknown = obj;\n for (let i = 0; i < parts.length; i++) {\n if (cur == null) return undefined;\n cur = (cur as Record<string, unknown>)[parts[i]];\n }\n return cur;\n}\n\nfunction deepSet(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (cur[parts[i]] == null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};\n cur = cur[parts[i]] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]] = value;\n}\n\n// ---------------------------------------------------------------------------\n// Engine — JSONata-based async evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Run all compute steps on a node.\n * Each step's expr is evaluated against { card_data, requires, fetched_sources, computed_values }.\n * Results are written to node.computed_values[bindTo].\n * computed_values and _sourcesData are reset on each call — ephemeral, never persisted.\n *\n * @param options.sourcesData Pre-loaded map of { [bindTo]: data } for fetched_sources namespace.\n * In Node/CLI: loaded from outputFiles by the caller (card-handler).\n * In browser: passed in by the caller (e.g. from fetch results).\n */\nasync function run(node: ComputeNode, options?: RunOptions): Promise<ComputeNode> {\n if (!node?.compute?.length) return node;\n if (!node.card_data) node.card_data = {};\n node.computed_values = {};\n node._sourcesData = options?.sourcesData ?? {};\n\n // Context passed to JSONata. Structural keys take precedence over any user data.\n const _requires = node.requires ?? {};\n const ctx: Record<string, unknown> = {\n card_data: node.card_data,\n requires: _requires,\n expects_data: _requires, // alias: same reference as requires\n fetched_sources: node._sourcesData,\n data: node.computed_values, // alias: same reference as computed_values\n computed_values: node.computed_values,\n };\n\n for (const step of node.compute) {\n try {\n const val = await jsonata(step.expr).evaluate(ctx);\n deepSet(node.computed_values, step.bindTo, val);\n ctx.computed_values = node.computed_values; // subsequent steps see earlier results\n // ctx.data is the same reference as node.computed_values — already in sync\n } catch (err) {\n console.error(`CardCompute.run error on \"${node.id ?? '?'}.${step.bindTo}\":`, err);\n }\n }\n\n return node;\n}\n\n/**\n * Synchronous version of run() — uses a vendored sync JSONata build\n * (async/await stripped from jsonata.js since all built-in functions\n * are CPU-only).\n *\n * Same semantics as `run()`: evaluates all compute steps, populates\n * `node.computed_values`, returns the mutated node.\n *\n * @returns `{ ok: true, node }` when all steps evaluated successfully.\n * `{ ok: false, node }` is currently never returned but reserved\n * for future use if an expression requires true async evaluation.\n */\nfunction runSync(\n node: ComputeNode,\n options?: RunOptions,\n): { ok: boolean; node: ComputeNode; errors?: Array<{ bindTo: string; error: string }> } {\n if (!node?.compute?.length) return { ok: true, node };\n if (!node.card_data) node.card_data = {};\n node.computed_values = {};\n node._sourcesData = options?.sourcesData ?? {};\n\n const _requires2 = node.requires ?? {};\n const ctx: Record<string, unknown> = {\n card_data: node.card_data,\n requires: _requires2,\n expects_data: _requires2, // alias: same reference as requires\n fetched_sources: node._sourcesData,\n data: node.computed_values, // alias: same reference as computed_values\n computed_values: node.computed_values,\n };\n\n const errors: Array<{ bindTo: string; error: string }> = [];\n for (const step of node.compute) {\n try {\n const val = jsonataSync(step.expr).evaluate(ctx);\n deepSet(node.computed_values, step.bindTo, val);\n ctx.computed_values = node.computed_values;\n // ctx.data is the same reference as node.computed_values — already in sync\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push({ bindTo: step.bindTo, error: msg });\n console.error(`CardCompute.runSync error on \"${node.id ?? '?'}.${step.bindTo}\":`, err);\n }\n }\n\n return errors.length > 0 ? { ok: true, node, errors } : { ok: true, node };\n}\n\n/**\n * Evaluate a single JSONata expression against a node's context.\n * Context is { card_data, requires, fetched_sources, computed_values }.\n */\nasync function evalExpr(\n expr: string,\n node: ComputeNode,\n vars?: Record<string, unknown>,\n): Promise<unknown> {\n const ctx: Record<string, unknown> = {\n ...(vars ?? {}),\n card_data: node.card_data ?? {},\n requires: node.requires ?? {},\n fetched_sources: node._sourcesData ?? {},\n computed_values: node.computed_values ?? {},\n };\n return jsonata(expr).evaluate(ctx);\n}\n\n// ---------------------------------------------------------------------------\n// resolve — synchronous deep-get from node\n// ---------------------------------------------------------------------------\n\nfunction resolve(node: ComputeNode, path: string): unknown {\n if (path.startsWith('fetched_sources.')) {\n return deepGet(node._sourcesData ?? {}, path.slice('fetched_sources.'.length));\n }\n\n return deepGet(node, path);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/** Result of validateNode — ok: true means valid, ok: false has errors[]. */\nexport interface ValidationResult {\n ok: boolean;\n errors: string[];\n}\n\nconst VALID_ELEMENT_KINDS = new Set([\n 'metric', 'table', 'editable-table', 'chart', 'form', 'filter', 'list',\n 'notes', 'todo', 'alert', 'narrative', 'badge', 'text',\n 'markdown', 'ref', 'custom', 'actions',\n]);\n\nconst ALLOWED_KEYS = new Set(['id', 'meta', 'requires', 'provides', 'view', 'card_data', 'compute', 'source_defs']);\n\nfunction validateNode(node: unknown): ValidationResult {\n const errors: string[] = [];\n\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n return { ok: false, errors: ['Node must be a non-null object'] };\n }\n\n const n = node as Record<string, unknown>;\n\n if (typeof n.id !== 'string' || !n.id) errors.push('id: required, must be a non-empty string');\n\n for (const key of Object.keys(n)) {\n if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: \"${key}\"`);\n }\n\n if (n.card_data == null || typeof n.card_data !== 'object' || Array.isArray(n.card_data)) {\n errors.push('card_data: required, must be an object');\n }\n\n if (n.meta != null) {\n if (typeof n.meta !== 'object' || Array.isArray(n.meta)) {\n errors.push('meta: must be an object');\n } else {\n const meta = n.meta as Record<string, unknown>;\n if (meta.title != null && typeof meta.title !== 'string') errors.push('meta.title: must be a string');\n if (meta.tags != null && !Array.isArray(meta.tags)) errors.push('meta.tags: must be an array');\n }\n }\n\n if (n.requires != null && !Array.isArray(n.requires)) errors.push('requires: must be an array of strings');\n\n if (n.provides != null) {\n if (!Array.isArray(n.provides)) {\n errors.push('provides: must be an array of { bindTo, ref } bindings');\n } else {\n (n.provides as unknown[]).forEach((p, i) => {\n if (!p || typeof p !== 'object' || Array.isArray(p)) {\n errors.push(`provides[${i}]: must be an object with bindTo and ref`);\n } else {\n const b = p as Record<string, unknown>;\n if (typeof b.bindTo !== 'string' || !b.bindTo) errors.push(`provides[${i}]: missing required \"bindTo\" string`);\n if (typeof b.ref !== 'string' || !b.ref) errors.push(`provides[${i}]: missing required \"ref\" string`);\n }\n });\n }\n }\n\n // compute — ordered array of { bindTo, expr } steps\n if (n.compute != null) {\n if (!Array.isArray(n.compute)) {\n errors.push('compute: must be an array of compute steps');\n } else {\n (n.compute as unknown[]).forEach((step, i) => {\n if (!step || typeof step !== 'object' || Array.isArray(step)) {\n errors.push(`compute[${i}]: must be a compute step object`);\n } else {\n const s = step as Record<string, unknown>;\n if (typeof s.bindTo !== 'string' || !s.bindTo) errors.push(`compute[${i}]: missing required \"bindTo\" property`);\n if (typeof s.expr !== 'string' || !s.expr) errors.push(`compute[${i}]: missing required \"expr\" string (JSONata expression)`);\n }\n });\n }\n }\n\n if (n.source_defs != null) {\n if (!Array.isArray(n.source_defs)) {\n errors.push('source_defs: must be an array');\n } else {\n const bindTos = new Set<string>();\n const outputFiles = new Set<string>();\n (n.source_defs as unknown[]).forEach((src, i) => {\n if (!src || typeof src !== 'object' || Array.isArray(src)) {\n errors.push(`source_defs[${i}]: must be an object`);\n } else {\n const s = src as Record<string, unknown>;\n if (typeof s.bindTo !== 'string' || !s.bindTo) {\n errors.push(`source_defs[${i}]: missing required \"bindTo\" property`);\n } else {\n if (bindTos.has(s.bindTo)) {\n errors.push(`source_defs[${i}]: bindTo \"${s.bindTo}\" is not unique across source_defs`);\n }\n bindTos.add(s.bindTo);\n }\n if (typeof s.outputFile !== 'string' || !s.outputFile) {\n errors.push(`source_defs[${i}]: missing required \"outputFile\" property`);\n } else {\n if (outputFiles.has(s.outputFile)) {\n errors.push(`source_defs[${i}]: outputFile \"${s.outputFile}\" is not unique across source_defs`);\n }\n outputFiles.add(s.outputFile);\n }\n if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== 'boolean') {\n errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);\n }\n }\n });\n }\n }\n\n if (n.view != null) {\n if (typeof n.view !== 'object' || Array.isArray(n.view)) {\n errors.push('view: must be an object');\n } else {\n const view = n.view as Record<string, unknown>;\n if (!Array.isArray(view.elements) || view.elements.length === 0) {\n errors.push('view.elements: required, must be a non-empty array');\n } else {\n (view.elements as Record<string, unknown>[]).forEach((elem, i) => {\n if (!elem || typeof elem !== 'object') { errors.push(`view.elements[${i}]: must be an object`); return; }\n if (!elem.kind || typeof elem.kind !== 'string') {\n errors.push(`view.elements[${i}].kind: required, must be a string`);\n } else if (!VALID_ELEMENT_KINDS.has(elem.kind as string)) {\n errors.push(`view.elements[${i}].kind: unknown kind \"${elem.kind}\". Valid: ${[...VALID_ELEMENT_KINDS].join(', ')}`);\n }\n if (elem.data != null && (typeof elem.data !== 'object' || Array.isArray(elem.data))) {\n errors.push(`view.elements[${i}].data: must be an object`);\n }\n });\n }\n if (view.layout != null && (typeof view.layout !== 'object' || Array.isArray(view.layout))) errors.push('view.layout: must be an object');\n if (view.features != null && (typeof view.features !== 'object' || Array.isArray(view.features))) errors.push('view.features: must be an object');\n }\n }\n\n return { ok: errors.length === 0, errors };\n}\n\n/**\n * Enrich source_defs with execution context for template interpolation and prompt rendering.\n * Pure function: no side effects, returns new enriched source_defs array.\n * \n * @param source_defs - Array of source definitions\n * @param context - Execution context containing requires, sourcesData, computed_values\n * @returns Promise resolving to a new array of source_defs with _projections attached.\n * Each _projections entry is the evaluated result of the corresponding projections expression.\n */\nasync function enrichSources(\n source_defs: any[] | undefined,\n context: {\n card_data?: Record<string, any>;\n requires?: Record<string, any>;\n sourcesData?: Record<string, any>; // unused post-projections, kept for call-site compat\n computed_values?: Record<string, any>; // unused post-projections, kept for call-site compat\n }\n): Promise<any[]> {\n if (!source_defs || source_defs.length === 0) return [];\n\n const evalCtx = {\n card_data: context.card_data ?? {},\n requires: context.requires ?? {},\n };\n\n return Promise.all(\n source_defs.map(async (src: any) => {\n const _projections: Record<string, unknown> = {};\n if (src.projections && typeof src.projections === 'object' && !Array.isArray(src.projections)) {\n for (const [key, expr] of Object.entries(src.projections as Record<string, string>)) {\n if (typeof expr === 'string' && expr.trim().length > 0) {\n try {\n _projections[key] = await jsonata(expr).evaluate(evalCtx);\n } catch {\n _projections[key] = undefined;\n }\n }\n }\n }\n return { ...src, _projections };\n })\n );\n}\n\nfunction enrichSourcesSync(\n source_defs: any[] | undefined,\n context: {\n card_data?: Record<string, any>;\n requires?: Record<string, any>;\n }\n): any[] {\n if (!source_defs || source_defs.length === 0) return [];\n\n const evalCtx = {\n card_data: context.card_data ?? {},\n requires: context.requires ?? {},\n };\n\n return source_defs.map((src: any) => {\n const _projections: Record<string, unknown> = {};\n if (src.projections && typeof src.projections === 'object' && !Array.isArray(src.projections)) {\n for (const [key, expr] of Object.entries(src.projections as Record<string, string>)) {\n if (typeof expr === 'string' && expr.trim().length > 0) {\n try {\n _projections[key] = jsonataSync(expr).evaluate(evalCtx);\n } catch {\n _projections[key] = undefined;\n }\n }\n }\n }\n return { ...src, _projections };\n });\n}\n\nexport const CardCompute = {\n run,\n runSync,\n eval: evalExpr,\n resolve,\n validate: validateNode,\n enrichSources,\n enrichSourcesSync,\n};\n\nexport {\n validateLiveCard,\n validateLiveCardSchema,\n validateLiveCardRuntimeExpressions,\n validateLiveCardDefinition,\n} from './schema-validator.js';\n\nexport default CardCompute;\n\n","/**\n * board-live-cards-lib — Pure logic library for the board-live-cards CLI.\n *\n * Merged from:\n * board-live-cards-all-stores.ts\n * board-live-cards-lib-types.ts\n * board-live-cards-lib-board-status.ts\n * board-live-cards-lib-card-handler.ts\n * board-live-cards-cli-board-commands.ts\n * board-live-cards-cli-card-commands.ts\n * board-live-cards-cli-callbacks.ts\n *\n * Zero platform imports. All storage is injected via adapter interfaces.\n * Safe for Node, browser, and neutral (V8/PyMiniRacer) bundles.\n */\n\nimport type { KVStorage, BlobStorage, KindValueRef } from './storage-interface.js';\nimport { serializeRef } from './storage-interface.js';\nimport { parseExecutionRef, serializeExecutionRef } from './execution-interface.js';\nimport type { ExecutionRef } from './execution-interface.js';\nimport type { GraphEvent, TaskConfig, GraphConfig } from '../../event-graph/types.js';\nimport type { LiveGraph, LiveGraphSnapshot } from '../../continuous-event-graph/types.js';\nimport { schedule } from '../../continuous-event-graph/schedule.js';\nimport type { TaskHandlerFn } from '../../continuous-event-graph/reactive.js';\nimport { CardCompute } from '../../card-compute/index.js';\nimport type { ComputeNode, ComputeStep, ComputeSource } from '../../card-compute/index.js';\nexport type { DispatchResult, InvocationAdapter } from './process-interface.js';\n\n// ============================================================================\n// ---- from board-live-cards-all-stores.ts ----\n// ============================================================================\n\n// ============================================================================\n// Card store — types\n// ============================================================================\n\nexport interface LiveCard {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface CardIndexEntry {\n /** Storage-specific address (file path, Cosmos doc id, localStorage key). */\n key: string;\n /** Checksum of card content — computed by the adapter at write time. */\n checksum: string;\n updatedAt: string;\n}\n\nexport type CardIndex = Record<string, CardIndexEntry>;\nexport type CardChecksumIndex = Record<string, string>;\n\n/**\n * Per-card entry stored in the card-upsert KV cache (one key per cardId).\n * Lives alongside the board journal — NOT inside the board snapshot.\n * Purpose: dedup gate to avoid redundant task-upsert journal entries.\n *\n * Write order: journal.append() THEN kv.write() — so a crash between the two\n * leaves the journal entry intact (board is correct) and the KV stale (next\n * upsert will see \"changed\" and re-append; addNode is idempotent in the board).\n */\nexport interface CardUpsertIndexEntry {\n /** Logical reference to the card blob — absolute path for fs, blob name for cloud. */\n blobRef: string;\n /** SHA-256 of stable-JSON-serialised taskConfig. Dedup key. */\n taskConfigHash: string;\n updatedAt: string;\n}\n\n// ============================================================================\n// CardStorageAdapter — injected by the caller\n// ============================================================================\n\nexport interface CardStorageAdapter {\n readIndex(): CardIndex | null;\n writeIndex(index: CardIndex): void;\n readCard(key: string): LiveCard | null;\n /** Write card content; returns checksum of what was written. */\n writeCard(key: string, card: LiveCard): string;\n cardExists(key: string): boolean;\n defaultCardKey(cardId: string): string;\n}\n\n// ============================================================================\n// CardStore — board one-cycle (read-only)\n// ============================================================================\n\nexport interface CardStore {\n readCard(id: string): LiveCard | null;\n readCardKey(id: string): string | null;\n readAllCards(): LiveCard[];\n readChecksumIndex(): CardChecksumIndex;\n changedSince(snapshotChecksumIndex: CardChecksumIndex): string[];\n}\n\n// ============================================================================\n// CardAdminStore — CLI write interface\n// ============================================================================\n\nexport interface CardUpsertValidation {\n ok: boolean;\n error?: string;\n}\n\nexport interface CardAdminStore extends CardStore {\n validateUpsert(id: string, cardKey: string): CardUpsertValidation;\n writeCard(id: string, card: LiveCard, cardKey?: string): void;\n patchCard(id: string, jsonPath: string, value: unknown): void;\n removeCard(id: string): void;\n readIndex(): CardIndex;\n}\n\n// ============================================================================\n// createCardStore — pure logic factory\n// ============================================================================\n\nexport function createCardStore(adapter: CardStorageAdapter, onWarn?: (msg: string) => void): CardAdminStore {\n function loadIndex(): CardIndex {\n return adapter.readIndex() ?? {};\n }\n\n function applyJsonPath(obj: Record<string, unknown>, jsonPath: string, value: unknown): Record<string, unknown> {\n const segments = String(jsonPath || '').split('.').filter(Boolean);\n if (segments.length === 0) {\n return (value && typeof value === 'object' && !Array.isArray(value))\n ? value as Record<string, unknown>\n : { value };\n }\n\n const out: Record<string, unknown> = { ...obj };\n let target: Record<string, unknown> = out;\n for (let i = 0; i < segments.length - 1; i++) {\n const key = segments[i];\n const cur = target[key];\n const next = (cur && typeof cur === 'object' && !Array.isArray(cur))\n ? { ...(cur as Record<string, unknown>) }\n : {};\n target[key] = next;\n target = next;\n }\n target[segments[segments.length - 1]] = value;\n return out;\n }\n\n return {\n readCard(id: string): LiveCard | null {\n const entry = loadIndex()[id];\n if (!entry || !adapter.cardExists(entry.key)) return null;\n return adapter.readCard(entry.key);\n },\n\n readCardKey(id: string): string | null {\n return loadIndex()[id]?.key ?? null;\n },\n\n readAllCards(): LiveCard[] {\n const cards: LiveCard[] = [];\n for (const [id, entry] of Object.entries(loadIndex())) {\n if (!adapter.cardExists(entry.key)) continue;\n const card = adapter.readCard(entry.key);\n if (card) cards.push(card);\n else onWarn?.(`[card-store] could not read card \"${id}\" at key \"${entry.key}\"`);\n }\n return cards;\n },\n\n readChecksumIndex(): CardChecksumIndex {\n const result: CardChecksumIndex = {};\n for (const [id, entry] of Object.entries(loadIndex())) result[id] = entry.checksum;\n return result;\n },\n\n changedSince(snapshotChecksumIndex: CardChecksumIndex): string[] {\n const localIndex = loadIndex();\n const changed: string[] = [];\n for (const [id, entry] of Object.entries(localIndex)) {\n if (snapshotChecksumIndex[id] !== entry.checksum) changed.push(id);\n }\n for (const id of Object.keys(snapshotChecksumIndex)) {\n if (!localIndex[id]) changed.push(id);\n }\n return changed;\n },\n\n validateUpsert(id: string, cardKey: string): CardUpsertValidation {\n const index = loadIndex();\n const existingById = index[id];\n const existingByKey = Object.entries(index).find(([, e]) => e.key === cardKey);\n if (existingById && existingById.key !== cardKey)\n return { ok: false, error: `Card id \"${id}\" is already mapped to key \"${existingById.key}\", cannot remap to \"${cardKey}\"` };\n if (existingByKey && existingByKey[0] !== id)\n return { ok: false, error: `Key \"${cardKey}\" is already mapped to card id \"${existingByKey[0]}\", cannot remap to \"${id}\"` };\n return { ok: true };\n },\n\n writeCard(id: string, card: LiveCard, cardKey?: string): void {\n const index = loadIndex();\n const resolvedKey = cardKey ?? index[id]?.key ?? adapter.defaultCardKey(id);\n const checksum = adapter.writeCard(resolvedKey, card);\n index[id] = { key: resolvedKey, checksum, updatedAt: new Date().toISOString() };\n adapter.writeIndex(index);\n },\n\n patchCard(id: string, jsonPath: string, value: unknown): void {\n const index = loadIndex();\n const entry = index[id];\n if (!entry || !adapter.cardExists(entry.key)) {\n throw new Error(`card \"${id}\" not found`);\n }\n const current = adapter.readCard(entry.key);\n if (!current || typeof current !== 'object' || Array.isArray(current)) {\n throw new Error(`card \"${id}\" is not patchable`);\n }\n const next = applyJsonPath(current as Record<string, unknown>, jsonPath, value) as LiveCard;\n const checksum = adapter.writeCard(entry.key, next);\n index[id] = { key: entry.key, checksum, updatedAt: new Date().toISOString() };\n adapter.writeIndex(index);\n },\n\n removeCard(id: string): void {\n const index = loadIndex();\n if (!index[id]) return;\n delete index[id];\n adapter.writeIndex(index);\n },\n\n readIndex(): CardIndex {\n return loadIndex();\n },\n };\n}\n\n// ============================================================================\n// FetchedSourcesStore\n// ============================================================================\n\nexport interface FetchedSourcesStore {\n /** Read committed source content. Returns parsed JSON or raw string; null if not yet committed. */\n readSourceData(cardId: string, outputFile: string): unknown;\n /** Stage incoming source data under deliveryToken. resolveRef converts the ref to content bytes. */\n ingestSourceDataStaged(cardId: string, outputFile: string, ref: KindValueRef, deliveryToken: string): void;\n /** Move staged data to live position. Returns false if staged entry is absent (stale delivery). */\n commitSourceData(cardId: string, outputFile: string, deliveryToken: string): boolean;\n /** True if live (committed) source data exists for this outputFile. */\n hasSource(cardId: string, outputFile: string): boolean;\n}\n\nexport function createFetchedSourcesStore(\n blob: BlobStorage,\n resolveRef: (ref: KindValueRef) => string,\n): FetchedSourcesStore {\n return {\n readSourceData(cardId, outputFile): unknown {\n const raw = blob.read(`${cardId}/${outputFile}`);\n if (raw == null) return null;\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try { return JSON.parse(trimmed); } catch { return trimmed; }\n },\n ingestSourceDataStaged(cardId, outputFile, ref, deliveryToken): void {\n const content = resolveRef(ref);\n blob.write(`${cardId}/.staged/${deliveryToken}/${outputFile}`, content);\n },\n commitSourceData(cardId, outputFile, deliveryToken): boolean {\n const stagedKey = `${cardId}/.staged/${deliveryToken}/${outputFile}`;\n const content = blob.read(stagedKey);\n if (content == null) return false;\n blob.write(`${cardId}/${outputFile}`, content);\n blob.remove(stagedKey);\n return true;\n },\n hasSource(cardId, outputFile): boolean {\n return blob.exists(`${cardId}/${outputFile}`);\n },\n };\n}\n\n// ============================================================================\n// Journal store — types\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n event: GraphEvent;\n}\n\nexport interface JournalStorageAdapter {\n readAllEntries(): JournalEntry[];\n appendEntry(entry: JournalEntry): void;\n generateId(): string;\n}\n\nexport interface JournalStore {\n readEntriesAfterCursor(cursor: string): { events: GraphEvent[]; newCursor: string };\n pendingCount(cursor: string): number;\n}\n\nexport interface JournalAdminStore extends JournalStore {\n appendEvent(event: GraphEvent): void;\n}\n\nexport function createJournalStore(adapter: JournalStorageAdapter): JournalAdminStore {\n function entriesAfterCursor(cursor: string): JournalEntry[] {\n const all = adapter.readAllEntries();\n if (!cursor) return all;\n const idx = all.findIndex(e => e.id === cursor);\n return idx === -1 ? all : all.slice(idx + 1);\n }\n\n return {\n readEntriesAfterCursor(cursor: string): { events: GraphEvent[]; newCursor: string } {\n const entries = entriesAfterCursor(cursor);\n if (entries.length === 0) return { events: [], newCursor: cursor };\n return { events: entries.map(e => e.event), newCursor: entries[entries.length - 1].id };\n },\n\n pendingCount(cursor: string): number {\n return entriesAfterCursor(cursor).length;\n },\n\n appendEvent(event: GraphEvent): void {\n adapter.appendEntry({ id: adapter.generateId(), event });\n },\n };\n}\n\n// ============================================================================\n// ExecutionRequest store\n// ============================================================================\n\nexport interface ExecutionRequestEntry {\n taskKind: string;\n payload: unknown;\n}\n\nexport interface ExecutionRequestStore {\n appendEntries(journalId: string, entries: ExecutionRequestEntry[]): void;\n dispatchEntriesForJournalId(journalId: string, processorFn: (entry: ExecutionRequestEntry) => void): void;\n}\n\nexport function createExecutionRequestStore(\n kv: KVStorage,\n onDispatchFailed: (entry: ExecutionRequestEntry, error: string) => void,\n): ExecutionRequestStore {\n return {\n appendEntries(journalId: string, entries: ExecutionRequestEntry[]): void {\n if (!journalId || entries.length === 0) return;\n const existing = (kv.read(journalId) as ExecutionRequestEntry[] | null) ?? [];\n kv.write(journalId, [...existing, ...entries]);\n },\n\n dispatchEntriesForJournalId(journalId: string, processorFn: (entry: ExecutionRequestEntry) => void): void {\n if (!journalId) return;\n const entries = kv.read(journalId) as ExecutionRequestEntry[] | null;\n if (!entries || entries.length === 0) return;\n for (const entry of entries) {\n try { processorFn(entry); } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n try { onDispatchFailed(entry, msg); } catch { /* guard against failure in error handler */ }\n }\n }\n kv.delete(journalId);\n },\n };\n}\n\n// ============================================================================\n// StateSnapshot store\n// ============================================================================\n\nexport const SNAPSHOT_SCHEMA_VERSION_V1 = 'v1';\n\nexport const BOARD_GRAPH_KEY = 'board/graph';\nexport const BOARD_LAST_JOURNAL_PROCESSED_ID_KEY = 'board/lastJournalProcessedId';\n\nexport function cardRuntimeKey(cardId: string): string {\n return `cards/${cardId}/runtime`;\n}\n\nexport function cardFetchedSourcesManifestKey(cardId: string): string {\n return `cards/${cardId}/fetched-sources-manifest`;\n}\n\nexport interface CardRuntimeSnapshot {\n _sources: Record<string, { lastRequestedAt?: string; lastFetchedAt?: string; queueRequestedAt?: string }>;\n _lastExecutionCount?: number;\n}\n\nexport interface CardRuntimeStore {\n readRuntime(cardId: string): CardRuntimeSnapshot;\n writeRuntime(cardId: string, state: CardRuntimeSnapshot): void;\n}\n\nexport function createCardRuntimeStore(kv: KVStorage): CardRuntimeStore {\n return {\n readRuntime(cardId) {\n return (kv.read(cardRuntimeKey(cardId)) as CardRuntimeSnapshot | null) ?? { _sources: {} };\n },\n writeRuntime(cardId, state) {\n kv.write(cardRuntimeKey(cardId), state);\n },\n };\n}\n\nexport interface FetchedSourceManifestEntry {\n outputFile: string;\n blobRef: string;\n fetchedAt: string;\n sourceChecksum?: string;\n contentType?: string;\n sizeBytes?: number;\n}\n\nexport interface StateSnapshotReadView {\n version: string | null;\n values: Record<string, unknown>;\n}\n\nexport interface StateSnapshotCommitEnvelope {\n schemaVersion: typeof SNAPSHOT_SCHEMA_VERSION_V1;\n expectedVersion: string | null;\n commitId: string;\n committedAt: string;\n deleteKeys: string[];\n shallowMerge: Record<string, unknown>;\n}\n\nexport interface StateSnapshotCommitSuccess {\n ok: true;\n newVersion: string;\n}\n\nexport interface StateSnapshotCommitVersionMismatch {\n ok: false;\n reason: 'version-mismatch';\n currentVersion: string | null;\n}\n\nexport type StateSnapshotCommitResult =\n | StateSnapshotCommitSuccess\n | StateSnapshotCommitVersionMismatch;\n\nexport interface StateSnapshotStorageAdapter {\n readValues(scopeId: string): StateSnapshotReadView;\n writeValues(scopeId: string, nextValues: Record<string, unknown>, deletedKeys: string[]): string;\n}\n\nexport interface StateSnapshotStore {\n readSnapshot(scopeId: string): StateSnapshotReadView;\n commitSnapshot(scopeId: string, envelope: StateSnapshotCommitEnvelope): StateSnapshotCommitResult;\n}\n\nexport function applyStateSnapshotCommitEnvelope(\n current: Record<string, unknown>,\n envelope: Pick<StateSnapshotCommitEnvelope, 'deleteKeys' | 'shallowMerge'>,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...current };\n for (const key of envelope.deleteKeys) {\n delete next[key];\n }\n return { ...next, ...envelope.shallowMerge };\n}\n\nexport function createStateSnapshotStore(adapter: StateSnapshotStorageAdapter): StateSnapshotStore {\n return {\n readSnapshot(scopeId: string): StateSnapshotReadView {\n return adapter.readValues(scopeId);\n },\n\n commitSnapshot(scopeId: string, envelope: StateSnapshotCommitEnvelope): StateSnapshotCommitResult {\n if (envelope.schemaVersion !== SNAPSHOT_SCHEMA_VERSION_V1) {\n throw new Error(`Unsupported snapshot schema version: ${envelope.schemaVersion}`);\n }\n const current = adapter.readValues(scopeId);\n if (current.version !== envelope.expectedVersion) {\n return { ok: false, reason: 'version-mismatch', currentVersion: current.version };\n }\n const nextValues = applyStateSnapshotCommitEnvelope(current.values, envelope);\n const newVersion = adapter.writeValues(scopeId, nextValues, envelope.deleteKeys);\n return { ok: true, newVersion };\n },\n };\n}\n\n// ============================================================================\n// BoardConfigStore\n// ============================================================================\n\nexport interface BoardConfigStore {\n readTaskExecutorRef(): ExecutionRef | undefined;\n writeTaskExecutorRef(ref: ExecutionRef): void;\n readChatHandlerRef(): ExecutionRef | undefined;\n writeChatHandlerRef(ref: ExecutionRef): void;\n readCardStoreRef(): string | null;\n writeCardStoreRef(ref: string): void;\n readOutputsStoreRef(): string | null;\n writeOutputsStoreRef(ref: string): void;\n /** @deprecated use readChatHandlerRef */\n readChatHandler(): string | undefined;\n /** @deprecated use writeChatHandlerRef */\n writeChatHandler(value: string): void;\n}\n\nexport function createBoardConfigStore(kv: KVStorage): BoardConfigStore {\n function readKey(key: string): string | null {\n const v = kv.read(key);\n if (v == null) return null;\n return typeof v === 'string' ? v : JSON.stringify(v);\n }\n\n return {\n readTaskExecutorRef(): ExecutionRef | undefined {\n const raw = readKey('task-executor');\n if (!raw?.trim()) return undefined;\n return parseExecutionRef(raw.trim());\n },\n\n writeTaskExecutorRef(ref: ExecutionRef): void {\n kv.write('task-executor', serializeExecutionRef(ref));\n },\n\n readChatHandlerRef(): ExecutionRef | undefined {\n const raw = readKey('chat-handler');\n if (!raw?.trim()) return undefined;\n return parseExecutionRef(raw.trim());\n },\n\n writeChatHandlerRef(ref: ExecutionRef): void {\n kv.write('chat-handler', serializeExecutionRef(ref));\n },\n\n readCardStoreRef(): string | null {\n return readKey('card-store-ref');\n },\n\n writeCardStoreRef(ref: string): void {\n kv.write('card-store-ref', ref);\n },\n\n readOutputsStoreRef(): string | null {\n return readKey('outputs-store-ref');\n },\n\n writeOutputsStoreRef(ref: string): void {\n kv.write('outputs-store-ref', ref);\n },\n\n readChatHandler(): string | undefined {\n return readKey('chat-handler')?.trim() || undefined;\n },\n\n writeChatHandler(value: string): void {\n kv.write('chat-handler', value);\n },\n };\n}\n\n// ============================================================================\n// PublishedOutputsStore\n// ============================================================================\n\nexport type OutputStoreEvent =\n | { kind: 'computed_values'; cardId: string; values: Record<string, unknown> }\n | { kind: 'data_object'; key: string; payload: unknown }\n | { kind: 'status'; status: unknown };\n\nexport interface PublishedOutputsStore {\n writeComputedValues(cardId: string, values: Record<string, unknown>): void;\n readComputedValues(cardId: string): unknown | null;\n readAllComputedValues(): Record<string, unknown>;\n writeDataObjects(data: Record<string, unknown>): void;\n readDataObject(key: string): unknown | null;\n readAllDataObjects(): Record<string, unknown>;\n writeStatusSnapshot(status: unknown): void;\n readStatusSnapshot(): unknown | null;\n}\n\nexport function createPublishedOutputsStore(kv: KVStorage): PublishedOutputsStore {\n return {\n writeComputedValues(cardId, values) {\n kv.write(`cards/${cardId}/computed_values`, values);\n },\n readComputedValues(cardId) { return kv.read(`cards/${cardId}/computed_values`); },\n readAllComputedValues() {\n const out: Record<string, unknown> = {};\n for (const key of kv.listKeys('cards/')) {\n const m = key.match(/^cards\\/([^/]+)\\/computed_values$/);\n if (m) out[m[1]] = kv.read(key);\n }\n return out;\n },\n writeDataObjects(data) {\n for (const [token, payload] of Object.entries(data)) {\n if (!token) continue;\n kv.write(`data-objects/${token}`, payload);\n }\n },\n readDataObject(key) { return kv.read(`data-objects/${key}`); },\n readAllDataObjects() {\n const out: Record<string, unknown> = {};\n for (const key of kv.listKeys('data-objects/')) {\n out[key.slice('data-objects/'.length)] = kv.read(key);\n }\n return out;\n },\n writeStatusSnapshot(status) {\n kv.write('status', status);\n },\n readStatusSnapshot() { return kv.read('status'); },\n };\n}\n\n// ============================================================================\n// Future-facing blob and read-model cache interfaces\n// ============================================================================\n\nexport interface FetchedSourcesBlobStore {\n readBlob(blobRef: string): Promise<unknown | null>;\n}\n\nexport interface PublishedBoardStatusCache {\n writeStatusBestEffort(scopeId: string, statusPayload: unknown): Promise<void>;\n readStatus(scopeId: string): Promise<unknown | null>;\n}\n\n// ============================================================================\n// ---- from board-live-cards-lib-types.ts ----\n// ============================================================================\n\nexport interface SourceRuntimeEntry {\n lastRequestedAt?: string;\n lastFetchedAt?: string;\n lastError?: string;\n queueRequestedAt?: string;\n}\n\nexport type FetchRuntimeEntry = SourceRuntimeEntry;\n\nexport interface SourceTokenPayload {\n cbk: string;\n rg: string;\n br: string;\n cid: string;\n b: string;\n d: string;\n cs?: string;\n rqt: string;\n}\n\nexport function isSourceInFlight(entry: FetchRuntimeEntry | undefined): boolean {\n if (!entry?.lastRequestedAt) return false;\n return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;\n}\n\nexport function decideSourceAction(\n entry: FetchRuntimeEntry | undefined,\n queueRequestedAt: string,\n): 'dispatch' | 'in-flight' | 'idle' {\n if (!entry?.lastRequestedAt) return 'dispatch';\n const inFlight = isSourceInFlight(entry);\n if (inFlight) return 'in-flight';\n if (!entry.lastFetchedAt) return 'dispatch';\n if (entry.lastFetchedAt < queueRequestedAt) return 'dispatch';\n return 'idle';\n}\n\nexport function nextEntryAfterFetchDelivery<T extends FetchRuntimeEntry>(\n entry: T,\n fetchedAt: string,\n): T {\n const next = { ...entry, lastFetchedAt: fetchedAt };\n delete (next as FetchRuntimeEntry).lastError;\n return next as T;\n}\n\nexport function nextEntryAfterFetchFailure<T extends FetchRuntimeEntry>(\n entry: T,\n reason: string,\n): T {\n const next = { ...entry, lastError: reason };\n delete (next as FetchRuntimeEntry).lastFetchedAt;\n return next as T;\n}\n\nexport interface CardHandlerAdapters {\n cardStore: CardStore;\n cardRuntimeStore: CardRuntimeStore;\n fetchedSourcesStore: FetchedSourcesStore;\n outputStore: PublishedOutputsStore;\n executionRequestStore: ExecutionRequestStore;\n}\n\nexport interface CommandResponse<T extends Record<string, unknown> = Record<string, unknown>> {\n status: 'success' | 'error';\n data: T;\n error?: string;\n}\n\nexport const Resp = {\n success<T extends Record<string, unknown>>(data: T): CommandResponse<T> {\n return { status: 'success', data };\n },\n\n error(error: string, data: Record<string, unknown> = {}): CommandResponse {\n return { status: 'error', data, error };\n },\n\n getStatus(r: CommandResponse): 'success' | 'error' {\n return r.status;\n },\n\n getData<T extends Record<string, unknown>>(r: CommandResponse<T>): T {\n return r.data;\n },\n\n isSuccess(r: CommandResponse): boolean {\n return r.status === 'success';\n },\n} as const;\n\n// ============================================================================\n// ---- from board-live-cards-lib-board-status.ts ----\n// ============================================================================\n\nexport interface BoardStatusCard {\n name: string;\n status: string;\n error?: {\n message: string;\n code?: string;\n at?: string;\n source?: 'task-runtime' | 'source-fetch' | 'timeout' | 'unknown';\n };\n requires: string[];\n requires_satisfied: string[];\n requires_missing: string[];\n provides_declared: string[];\n provides_runtime: string[];\n blocked_by: string[];\n unblocks: string[];\n runtime: {\n attempt_count: number;\n restart_count: number;\n in_progress_since: string | null;\n last_transition_at: string | null;\n last_completed_at: string | null;\n last_restarted_at: string | null;\n status_age_ms: number | null;\n };\n}\n\nexport interface BoardStatusObject {\n schema_version: 'v1';\n meta: {\n board: {\n path: string;\n };\n };\n summary: {\n card_count: number;\n completed: number;\n eligible: number;\n pending: number;\n blocked: number;\n unresolved: number;\n failed?: number;\n in_progress?: number;\n orphan_cards?: number;\n topology?: {\n edge_count: number;\n max_fan_out_card: string | null;\n max_fan_out: number;\n };\n };\n cards: BoardStatusCard[];\n}\n\nexport function buildBoardStatusObject(boardPath: string, live: LiveGraph): BoardStatusObject {\n const taskState = live.state.tasks;\n const taskConfig = live.config.tasks;\n const cardNames = Object.keys(taskState);\n const sched = schedule(live);\n\n const statusCounts = {\n completed: 0,\n failed: 0,\n in_progress: 0,\n pending: 0,\n blocked: 0,\n unresolved: 0,\n };\n\n const waitingByCard = new Map<string, string[]>();\n for (const p of sched.pending) waitingByCard.set(p.taskName, p.waitingOn);\n for (const u of sched.unresolved) waitingByCard.set(u.taskName, u.missingTokens);\n for (const b of sched.blocked) waitingByCard.set(b.taskName, b.failedTokens);\n\n const dependentsByToken = new Map<string, string[]>();\n for (const [name, cfg] of Object.entries(taskConfig)) {\n for (const token of cfg.requires ?? []) {\n const dependents = dependentsByToken.get(token) ?? [];\n dependents.push(name);\n dependentsByToken.set(token, dependents);\n }\n }\n\n const cards: BoardStatusCard[] = cardNames.sort().map((name) => {\n const state = taskState[name] as {\n status: string;\n data?: Record<string, unknown>;\n error?: string;\n startedAt?: string;\n completedAt?: string;\n failedAt?: string;\n lastUpdated?: string;\n executionCount?: number;\n retryCount?: number;\n };\n const cfg = taskConfig[name] ?? { requires: [], provides: [] };\n\n if (state.status === 'completed') statusCounts.completed += 1;\n else if (state.status === 'failed') statusCounts.failed += 1;\n else if (state.status === 'in-progress') statusCounts.in_progress += 1;\n\n const requires = cfg.requires ?? [];\n const provides = cfg.provides ?? [];\n const runtimeKeys = Object.keys(state.data ?? {}).sort();\n const requiresSatisfied = requires.filter(token => live.state.availableOutputs.includes(token));\n const requiresMissing = requires.filter(token => !live.state.availableOutputs.includes(token));\n const blockedBy = waitingByCard.get(name) ?? requiresMissing;\n\n const unblocks = new Set<string>();\n for (const token of provides) {\n for (const dependent of dependentsByToken.get(token) ?? []) {\n if (dependent !== name) unblocks.add(dependent);\n }\n }\n\n const lastFailureAt = state.failedAt;\n const error = state.error\n ? {\n message: state.error,\n code: 'TASK_FAILED',\n at: lastFailureAt,\n source: 'task-runtime' as const,\n }\n : undefined;\n\n return {\n name,\n status: state.status,\n error,\n requires,\n requires_satisfied: requiresSatisfied,\n requires_missing: requiresMissing,\n provides_declared: provides,\n provides_runtime: runtimeKeys,\n blocked_by: blockedBy,\n unblocks: Array.from(unblocks).sort(),\n runtime: {\n attempt_count: state.executionCount ?? 0,\n restart_count: state.retryCount ?? 0,\n in_progress_since: state.status === 'in-progress' ? (state.startedAt ?? null) : null,\n last_transition_at: state.lastUpdated ?? null,\n last_completed_at: state.completedAt ?? null,\n last_restarted_at: state.startedAt ?? null,\n // Keep status snapshots immutable across reads: this field must not depend on wall-clock pull time.\n status_age_ms: state.lastUpdated ? 0 : null,\n },\n };\n });\n\n statusCounts.pending = sched.pending.length;\n statusCounts.blocked = sched.blocked.length;\n statusCounts.unresolved = sched.unresolved.length;\n\n const fanOut = cards\n .map(c => ({ name: c.name, fanOut: c.unblocks.length }))\n .sort((a, b) => b.fanOut - a.fanOut || a.name.localeCompare(b.name));\n const maxFanOut = fanOut.length > 0 ? fanOut[0] : { name: null, fanOut: 0 };\n\n const allRequires = new Set<string>();\n for (const cfg of Object.values(taskConfig)) {\n for (const r of cfg.requires ?? []) allRequires.add(r);\n }\n let orphanCards = 0;\n for (const [name, cfg] of Object.entries(taskConfig)) {\n const requiresNone = (cfg.requires ?? []).length === 0;\n const providesList = cfg.provides ?? [];\n const feedsAny = providesList.some(p => (dependentsByToken.get(p) ?? []).some(d => d !== name));\n if (requiresNone && !feedsAny) orphanCards += 1;\n }\n\n return {\n schema_version: 'v1',\n meta: { board: { path: boardPath } },\n summary: {\n card_count: cardNames.length,\n completed: statusCounts.completed,\n eligible: sched.eligible.length,\n pending: statusCounts.pending,\n blocked: statusCounts.blocked,\n unresolved: statusCounts.unresolved,\n failed: statusCounts.failed,\n in_progress: statusCounts.in_progress,\n orphan_cards: orphanCards,\n topology: {\n edge_count: Array.from(allRequires).length,\n max_fan_out_card: maxFanOut.name,\n max_fan_out: maxFanOut.fanOut,\n },\n },\n cards,\n };\n}\n\n// ============================================================================\n// ---- from board-live-cards-lib-card-handler.ts ----\n// ============================================================================\n\nfunction nowHighRes(): string {\n return new Date().toISOString();\n}\n\nexport function createCardHandlerFn(\n baseRef: KindValueRef,\n journalId: string,\n adapters: CardHandlerAdapters,\n taskCompletedFn: (taskName: string, data: Record<string, unknown>) => void,\n _taskFailedFn: (taskName: string, error: string) => void,\n writeComputedValuesFn?: (cardId: string, values: Record<string, unknown>) => void,\n writeDataObjectsFn?: (data: Record<string, unknown>) => void,\n): TaskHandlerFn {\n return async (input) => {\n const pendingRequests: ExecutionRequestEntry[] = [];\n const card = adapters.cardStore.readCard(input.nodeId);\n if (!card) return 'task-initiate-failure';\n\n const cardId = card.id as string;\n const cardState = (card.card_data ?? {}) as Record<string, unknown>;\n const allSources: ComputeSource[] = (card.source_defs ?? []) as ComputeSource[];\n const requiredSources = allSources.filter(s => s.optionalForCompletionGating !== true);\n\n let state: CardRuntimeSnapshot = adapters.cardRuntimeStore.readRuntime(cardId);\n let dirty = false;\n\n const flush = (): void => {\n if (!dirty) return;\n adapters.cardRuntimeStore.writeRuntime(cardId, state);\n dirty = false;\n };\n\n const getSourceEntry = (outputFile: string): SourceRuntimeEntry =>\n ({ ...(state._sources[outputFile] ?? {}) });\n const setSourceEntry = (outputFile: string, entry: SourceRuntimeEntry): void => {\n state._sources[outputFile] = entry; dirty = true;\n };\n\n const currentExecutionCount = input.taskState?.executionCount ?? 0;\n const lastExecCount = state._lastExecutionCount;\n if (typeof lastExecCount === 'number' && lastExecCount !== currentExecutionCount) {\n state._sources = {}; dirty = true;\n }\n if (lastExecCount !== currentExecutionCount) {\n state._lastExecutionCount = currentExecutionCount; dirty = true;\n }\n\n\n if (input.update) {\n const u = input.update;\n const outputFile = u.outputFile as string;\n if (outputFile) {\n const entry = getSourceEntry(outputFile);\n if (u.failure) {\n setSourceEntry(outputFile, nextEntryAfterFetchFailure(entry, (u.reason as string | undefined) ?? 'unknown'));\n } else {\n const incomingRqt = u.rqt as string;\n if (!entry.lastFetchedAt || incomingRqt > entry.lastFetchedAt) {\n const deliveryToken = typeof u.deliveryToken === 'string' ? u.deliveryToken : undefined;\n if (deliveryToken) {\n adapters.fetchedSourcesStore.commitSourceData(cardId, outputFile, deliveryToken);\n }\n setSourceEntry(outputFile, nextEntryAfterFetchDelivery(entry, incomingRqt));\n }\n }\n flush();\n }\n }\n\n const sourcesData: Record<string, unknown> = {};\n for (const src of allSources) {\n if (src.outputFile) {\n const content = adapters.fetchedSourcesStore.readSourceData(cardId, src.outputFile as string);\n if (content !== null) {\n sourcesData[src.bindTo] = content;\n }\n }\n }\n\n const requires: Record<string, unknown> = {};\n for (const [token, taskData] of Object.entries(input.state ?? {})) {\n if (taskData !== null && typeof taskData === 'object' && !Array.isArray(taskData)) {\n const unwrapped = (taskData as Record<string, unknown>)[token];\n requires[token] = unwrapped !== undefined ? unwrapped : taskData;\n } else {\n requires[token] = taskData;\n }\n }\n\n const computeNode: ComputeNode = {\n id: cardId,\n card_data: { ...cardState },\n requires,\n source_defs: allSources,\n compute: card.compute as ComputeStep[] | undefined,\n };\n computeNode._sourcesData = sourcesData;\n if (card.compute) {\n CardCompute.runSync(computeNode, { sourcesData });\n }\n\n (writeComputedValuesFn ?? adapters.outputStore.writeComputedValues.bind(adapters.outputStore))(cardId, computeNode.computed_values ?? {});\n\n const enrichedCard = { ...card };\n const enrichedSources = CardCompute.enrichSourcesSync(\n Array.isArray(card.source_defs) ? card.source_defs : undefined,\n {\n card_data: card.card_data as Record<string, unknown>,\n requires,\n },\n );\n\n const dir = baseRef.value;\n enrichedCard.source_defs = Array.isArray(enrichedSources)\n ? enrichedSources.map(src => ({\n ...src,\n boardDir: typeof src.boardDir === 'string' && src.boardDir ? src.boardDir : dir,\n }))\n : enrichedSources;\n\n const now = nowHighRes();\n const runQueuedAt = input.update ? undefined : now;\n\n const undeliveredRequired = requiredSources.filter(s => {\n const outputFile = s.outputFile;\n if (typeof outputFile !== 'string' || !outputFile) return true;\n let entry = getSourceEntry(outputFile);\n if (runQueuedAt) {\n entry = { ...entry, queueRequestedAt: runQueuedAt };\n setSourceEntry(outputFile, entry);\n }\n const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;\n const action = decideSourceAction(entry, qrt);\n if (action === 'in-flight') return false;\n return action === 'dispatch';\n });\n\n flush();\n\n if (undeliveredRequired.length > 0) {\n let stampedAny = false;\n let dispatchRqt = now;\n for (const src of undeliveredRequired) {\n const outputFile = src.outputFile;\n if (typeof outputFile !== 'string' || !outputFile) continue;\n const entry = getSourceEntry(outputFile);\n const queuedAt = entry.queueRequestedAt ?? now;\n setSourceEntry(outputFile, { ...entry, lastRequestedAt: queuedAt });\n dispatchRqt = queuedAt;\n stampedAny = true;\n }\n if (stampedAny) flush();\n if (!stampedAny) return 'task-initiated';\n\n pendingRequests.push({ taskKind: 'source-fetch', payload: { boardRef: serializeRef(baseRef), enrichedCard: enrichedCard as Record<string, unknown>, callbackToken: input.callbackToken, rqt: dispatchRqt } });\n adapters.executionRequestStore.appendEntries(journalId, pendingRequests);\n return 'task-initiated';\n }\n\n const providesBindings = (card.provides ?? []) as { bindTo: string; ref: string }[];\n const data: Record<string, unknown> = {};\n for (const { bindTo, ref } of providesBindings) {\n data[bindTo] = CardCompute.resolve(computeNode, ref);\n }\n\n (writeDataObjectsFn ?? adapters.outputStore.writeDataObjects.bind(adapters.outputStore))(data);\n\n const undeliveredOptional = allSources.filter(s => {\n if (s.optionalForCompletionGating !== true) return false;\n const entry = getSourceEntry(s.outputFile as string);\n if (!entry.lastRequestedAt) return true;\n if (!entry.lastFetchedAt) return true;\n return entry.lastFetchedAt <= entry.lastRequestedAt;\n });\n if (undeliveredOptional.length > 0) {\n pendingRequests.push({ taskKind: 'source-fetch', payload: { boardRef: serializeRef(baseRef), enrichedCard: enrichedCard as Record<string, unknown>, callbackToken: input.callbackToken, rqt: now } });\n }\n\n taskCompletedFn(input.nodeId, data);\n if (pendingRequests.length > 0) adapters.executionRequestStore.appendEntries(journalId, pendingRequests);\n return 'task-initiated';\n };\n}\n\n// ============================================================================\n// ---- pure constants / codecs lifted from board-live-cards-cli.ts ----\n// ============================================================================\n\nexport const EMPTY_CONFIG: GraphConfig = { settings: { completion: 'manual', refreshStrategy: 'data-changed' }, tasks: {} } as GraphConfig;\n\n/** Envelope stored in the snapshot store — wraps the LiveGraph snapshot with journal pointer. */\nexport interface BoardEnvelope {\n lastDrainedJournalId: string;\n graph: LiveGraphSnapshot;\n}\n\nexport function boardEnvelopeToSnapshotEntries(envelope: BoardEnvelope): Record<string, unknown> {\n return {\n [BOARD_GRAPH_KEY]: envelope.graph,\n [BOARD_LAST_JOURNAL_PROCESSED_ID_KEY]: envelope.lastDrainedJournalId,\n };\n}\n\nexport function snapshotEntriesToBoardEnvelope(entries: Record<string, unknown>): BoardEnvelope {\n const graph = entries[BOARD_GRAPH_KEY] as LiveGraphSnapshot | undefined;\n const lastDrainedJournalId = entries[BOARD_LAST_JOURNAL_PROCESSED_ID_KEY] as string | undefined;\n if (!graph || typeof graph !== 'object') {\n throw new Error(`State snapshot is missing required key: ${BOARD_GRAPH_KEY}`);\n }\n return {\n graph,\n lastDrainedJournalId: typeof lastDrainedJournalId === 'string' ? lastDrainedJournalId : '',\n };\n}\n\nexport interface CardInventoryEntry {\n cardId: string;\n cardFilePath: string;\n addedAt: string;\n}\n\nexport interface CardInventoryIndex {\n byCardId: Map<string, CardInventoryEntry>;\n byCardPath: Map<string, CardInventoryEntry>;\n}\n\n/**\n * Transform a LiveCard into a TaskConfig for the reactive graph.\n * Every card gets handler: 'card-handler'.\n */\nexport function liveCardToTaskConfig(card: LiveCard): TaskConfig {\n const requires = card.requires as string[] | undefined;\n const provides = (card.provides as Array<{ bindTo: string }> | undefined)?.map(p => p.bindTo) ?? [];\n\n return {\n requires: requires && requires.length > 0 ? requires : undefined,\n provides,\n taskHandlers: ['card-handler'],\n description: (card.meta as { title?: string } | undefined)?.title ?? card.id,\n };\n}\n","/**\n * storage-localstorage-adapters.ts\n *\n * Browser localStorage implementations of the board-live-cards storage primitives:\n * BlobStorage — localStorage keys prefixed with `${prefix}:blob:`\n * KVStorage — localStorage keys prefixed with `${prefix}:kv:`, values JSON-encoded\n * JournalStorageAdapter — single localStorage key holding a JSON array of entries\n * CardStorageAdapter — KV-backed, compatible with createCardStore()\n *\n * No Node imports. Requires globalThis.localStorage (browser / jsdom environment).\n */\n\nimport type { BlobStorage, KVStorage, JSONStorage } from '../common/storage-interface.js';\nimport type { JournalStorageAdapter, CardStorageAdapter, JournalEntry, LiveCard, CardIndex } from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// Stable JSON + sync hash\n// Used for card dedup and snapshot versioning. Not security-sensitive.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nfunction fnv32a(str: string, seed: number): number {\n let h = seed >>> 0;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h;\n}\n\n/**\n * Synchronous stable content hash for browser environments.\n * Uses four FNV-1a 32-bit passes to produce 32 hex chars.\n * Deterministic and cross-session stable; NOT cryptographically secure.\n */\nexport function computeStableJsonHashBrowser(value: unknown): string {\n const str = stableJson(value);\n const a = fnv32a(str, 0x811c9dc5);\n const b = fnv32a(str, 0xdeadbeef);\n const c = fnv32a(str, 0x01234567);\n const d = fnv32a(str, 0xfeedface);\n return [a, b, c, d].map(n => n.toString(16).padStart(8, '0')).join('');\n}\n\n// ============================================================================\n// createLocalStorageBlobStorage\n// ============================================================================\n\nexport function createLocalStorageBlobStorage(prefix: string): BlobStorage {\n function key(k: string): string { return `${prefix}:blob:${k}`; }\n const textEncoder = new TextEncoder();\n\n function encodeBytes(bytes: Uint8Array): string {\n if (typeof btoa === 'function') {\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n return btoa(bin);\n }\n return '';\n }\n\n function decodeBytes(encoded: string): Uint8Array {\n if (typeof atob === 'function') {\n const bin = atob(encoded);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array();\n }\n\n return {\n read(k: string): string | null {\n return globalThis.localStorage.getItem(key(k));\n },\n write(k: string, content: string): void {\n globalThis.localStorage.setItem(key(k), content);\n },\n exists(k: string): boolean {\n return globalThis.localStorage.getItem(key(k)) !== null;\n },\n remove(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n\n readBytes(k: string): Uint8Array | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n return decodeBytes(parsed.data);\n }\n } catch {\n // fall through to plain text path\n }\n return textEncoder.encode(raw);\n },\n\n writeBytes(k: string, content: Uint8Array): void {\n // Store binary payloads as base64 envelope to avoid lossy UTF-8 coercion.\n const envelope = JSON.stringify({ __kind: 'bytes-b64', data: encodeBytes(content) });\n globalThis.localStorage.setItem(key(k), envelope);\n },\n\n listKeys(prefix2?: string): string[] {\n const marker = key(prefix2 ?? '');\n const out: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const k = globalThis.localStorage.key(i);\n if (k && k.startsWith(marker)) out.push(k.slice(key('').length));\n }\n return out.sort();\n },\n\n stat(k: string) {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n let size = textEncoder.encode(raw).byteLength;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n size = decodeBytes(parsed.data).byteLength;\n }\n } catch {\n // plain text path\n }\n return { key: k, size };\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageKvStorage\n// ============================================================================\n\nexport function createLocalStorageKvStorage(prefix: string): KVStorage {\n function key(k: string): string { return `${prefix}:kv:${k}`; }\n\n return {\n read(k: string): unknown | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try { return JSON.parse(raw); } catch { return null; }\n },\n write(k: string, value: unknown): void {\n globalThis.localStorage.setItem(key(k), JSON.stringify(value));\n },\n delete(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n listKeys(prefix2?: string): string[] {\n const fullPrefix = key(prefix2 ?? '');\n const result: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const lsKey = globalThis.localStorage.key(i);\n if (lsKey !== null && lsKey.startsWith(fullPrefix)) {\n // Strip the outer prefix + ':kv:' to return the logical key\n result.push(lsKey.slice(key('').length));\n }\n }\n return result;\n },\n };\n}\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createLocalStorageJsonStorage(prefix: string): JSONStorage {\n const kv = createLocalStorageKvStorage(prefix);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix2?) => kv.listKeys(prefix2),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageJournalStorageAdapter\n// All entries stored as a JSON array under a single localStorage key.\n// ============================================================================\n\nexport function createLocalStorageJournalStorageAdapter(storageKey: string): JournalStorageAdapter {\n function load(): JournalEntry[] {\n const raw = globalThis.localStorage.getItem(storageKey);\n if (!raw) return [];\n try { return JSON.parse(raw) as JournalEntry[]; } catch { return []; }\n }\n\n function save(entries: JournalEntry[]): void {\n globalThis.localStorage.setItem(storageKey, JSON.stringify(entries));\n }\n\n return {\n readAllEntries(): JournalEntry[] {\n return load();\n },\n appendEntry(entry: JournalEntry): void {\n const entries = load();\n entries.push(entry);\n save(entries);\n },\n generateId(): string {\n return globalThis.crypto.randomUUID();\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageCardStorageAdapter\n// Mirrors createFsCardStorageAdapter — KV-backed, cards keyed by cardId.\n// ============================================================================\n\nexport function createLocalStorageCardStorageAdapter(prefix: string): CardStorageAdapter {\n const json = createLocalStorageJsonStorage(prefix);\n\n return {\n readIndex(): CardIndex | null {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index: CardIndex): void {\n json.write('_index', index);\n },\n readCard(id: string): LiveCard | null {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id: string, card: LiveCard): string {\n json.write(id, card);\n return computeStableJsonHashBrowser(card);\n },\n cardExists(id: string): boolean {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId: string): string {\n return cardId;\n },\n };\n}\n","/**\n * card-store-browser-api.ts\n *\n * Simple browser-facing card store API.\n * Wraps createCardStore() + createLocalStorageCardStorageAdapter()\n * into a minimal read/write interface suitable for browser consumption.\n */\n\nimport { createCardStore } from '../common/board-live-cards-lib.js';\nimport type { LiveCard } from '../common/board-live-cards-lib.js';\nimport { createLocalStorageCardStorageAdapter } from './storage-localstorage-adapters.js';\n\nexport type { LiveCard };\n\nexport interface BrowserCardStoreApi {\n getCard(id: string): LiveCard | null;\n getAllCards(): LiveCard[];\n upsertCard(card: LiveCard): void;\n removeCard(id: string): void;\n}\n\n/**\n * Create a browser card store backed by localStorage.\n *\n * @param namespace - localStorage key prefix (e.g. 'my-board:cards').\n * Multiple stores can coexist by using distinct namespaces.\n */\nexport function createBrowserCardStoreApi(namespace: string): BrowserCardStoreApi {\n const adapter = createLocalStorageCardStorageAdapter(namespace);\n const store = createCardStore(adapter);\n\n return {\n getCard(id: string): LiveCard | null {\n return store.readCard(id);\n },\n getAllCards(): LiveCard[] {\n return store.readAllCards();\n },\n upsertCard(card: LiveCard): void {\n const key = adapter.defaultCardKey(card.id);\n store.writeCard(card.id, card, key);\n },\n removeCard(id: string): void {\n store.removeCard(id);\n },\n };\n}\n"]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
'use strict';var c=require('fs'),l=require('path'),crypto=require('crypto');require('proper-lockfile');var B=require('os');require('net'),require('url'),require('child_process');var _documentCurrentScript=typeof document!=='undefined'?document.currentScript:null;function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var c__namespace=/*#__PURE__*/_interopNamespace(c);var l__namespace=/*#__PURE__*/_interopNamespace(l);var B__namespace=/*#__PURE__*/_interopNamespace(B);var k="b64:";function J(e){let s=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4),n=globalThis.Buffer;if(n)return n.from(s,"base64").toString("utf8");if(typeof atob=="function"){let o=atob(s),r=new Uint8Array(o.length);for(let t=0;t<o.length;t+=1)r[t]=o.charCodeAt(t);return new TextDecoder().decode(r)}throw new Error("No base64 decoder available in this runtime")}function b(e){if(e.startsWith("::fs-path::"))return {kind:"fs-path",value:e.slice(11)};if(!e.startsWith(k))throw new Error(`Invalid ref format (expected ${k}<base64url(json)>): ${e}`);let s;try{s=JSON.parse(J(e.slice(k.length)));}catch{throw new Error(`Invalid ref format (malformed base64url/json): ${e}`)}if(!s||typeof s!="object")throw new Error(`Invalid ref format (expected object payload): ${e}`);let n=s;if(typeof n.kind!="string"||typeof n.value!="string")throw new Error(`Invalid ref format (payload must contain string kind/value): ${e}`);return {kind:n.kind,value:n.value}}function R(e,s){if(process.platform!=="win32"){c__namespace.renameSync(e,s);return}let n=[10,20,40,80,160];for(let o=0;o<=n.length;o++)try{c__namespace.renameSync(e,s);return}catch(r){let t=r.code;if((t==="EPERM"||t==="EBUSY")&&o<n.length){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,n[o]);continue}throw r}}function I(e){function s(r){return l__namespace.join(e,...r.split("/"))}function n(r){return l__namespace.relative(e,r).replace(/\\/g,"/")}function o(r,t){if(c__namespace.existsSync(r))for(let i of c__namespace.readdirSync(r,{withFileTypes:true})){let a=l__namespace.join(r,i.name);if(i.isDirectory()){o(a,t);continue}i.isFile()&&t.push(n(a));}}return {read(r){let t=s(r);if(!c__namespace.existsSync(t))return null;try{return c__namespace.readFileSync(t,"utf-8")}catch{return null}},write(r,t){let i=s(r),a=`${i}.${process.pid}.${crypto.randomUUID()}.tmp`;c__namespace.mkdirSync(l__namespace.dirname(i),{recursive:true}),c__namespace.writeFileSync(a,t,"utf-8"),R(a,i);},exists(r){return c__namespace.existsSync(s(r))},remove(r){let t=s(r);try{c__namespace.existsSync(t)&&c__namespace.unlinkSync(t);}catch{}},readBytes(r){let t=s(r);if(!c__namespace.existsSync(t))return null;try{return new Uint8Array(c__namespace.readFileSync(t))}catch{return null}},writeBytes(r,t){let i=s(r),a=`${i}.${process.pid}.${crypto.randomUUID()}.tmp`;c__namespace.mkdirSync(l__namespace.dirname(i),{recursive:true}),c__namespace.writeFileSync(a,Buffer.from(t)),R(a,i);},listKeys(r){let t=[];o(e,t);let i=t.sort();return r?i.filter(a=>a.startsWith(r)):i},stat(r){let t=s(r);if(!c__namespace.existsSync(t))return null;try{let i=c__namespace.statSync(t);return {key:r,size:Number(i.size||0),updatedAt:new Date(i.mtimeMs).toISOString()}}catch{return null}}}}var S=".artifacts-index.json";function _(){return new Date().toISOString()}function F(e){return new TextEncoder().encode(e).byteLength}function y(e){let s=e.read(S);if(!s)return {entries:{}};try{let n=JSON.parse(s);if(n&&n.entries&&typeof n.entries=="object")return n}catch{}return {entries:{}}}function A(e,s){e.write(S,JSON.stringify(s,null,2));}function O(e){return e?{key:e.key,size:e.size,updatedAt:e.updatedAt,contentType:e.contentType}:null}function E(e,s,n){e.entries[s]={key:s,size:n.size,updatedAt:n.updatedAt,contentType:n.contentType};}function $(e){function s(n){let o=e.stat?O(e.stat(n)):null;if(o)return o;let t=y(e).entries[n];if(t)return {...t};if(!e.exists(n))return null;let i=e.read(n);return i===null?{key:n}:{key:n,size:F(i)}}return {exists(n){return e.exists(n)},putText(n,o,r="text/plain; charset=utf-8"){e.write(n,o);let t=s(n)??{key:n};t.contentType=r,t.updatedAt=t.updatedAt??_(),t.size=t.size??F(o);let i=y(e);return E(i,n,t),A(e,i),t},putBytes(n,o,r="application/octet-stream"){if(e.writeBytes)e.writeBytes(n,o);else {let a=JSON.stringify({__kind:"bytes-array",data:[...o]});e.write(n,a);}let t=s(n)??{key:n};t.contentType=r,t.updatedAt=t.updatedAt??_(),t.size=t.size??o.byteLength;let i=y(e);return E(i,n,t),A(e,i),t},getText(n){let o=e.read(n);if(o===null){if(!e.readBytes)return null;let r=e.readBytes(n);return r===null?null:Buffer.from(r).toString("utf-8")}try{let r=JSON.parse(o);if(r&&r.__kind==="bytes-array"&&Array.isArray(r.data))return new TextDecoder("utf-8").decode(new Uint8Array(r.data))}catch{}return o},getBytes(n){if(e.readBytes){let r=e.readBytes(n);if(r!==null)return r}let o=e.read(n);if(o===null)return null;try{let r=JSON.parse(o);if(r&&r.__kind==="bytes-array"&&Array.isArray(r.data))return new Uint8Array(r.data)}catch{}return new TextEncoder().encode(o)},head:s,list(n=""){let o=new Map;if(e.listKeys)for(let t of e.listKeys(n)){if(t===S)continue;let i=s(t)??{key:t};o.set(t,i);}let r=y(e);for(let[t,i]of Object.entries(r.entries))t===S||n&&!t.startsWith(n)||o.has(t)||o.set(t,{...i});return [...o.values()].sort((t,i)=>t.key.localeCompare(i.key))},remove(n){e.remove(n);let o=y(e);delete o.entries[n],A(e,o);}}}function j(e){function s(r){return {status:"success",data:r}}function n(r){return {status:"fail",error:r}}function o(r){return {status:"error",error:r instanceof Error?r.message:String(r)}}return {list(r){try{let t=r.params?.prefix??"";return s({artifacts:e.list(t)})}catch(t){return o(t)}},head(r){try{let t=r.params?.key;return t?s({artifact:e.head(t)}):n("head requires params.key")}catch(t){return o(t)}},put(r){try{let t=r.params?.key,i=r.params?.contentType;if(!t)return n("put requires params.key");let a=r.body;if(typeof a=="string")return s({artifact:e.putText(t,a,i)});if(a&&typeof a=="object"&&typeof a.text=="string")return s({artifact:e.putText(t,a.text,i)});if(a&&typeof a=="object"&&Array.isArray(a.bytes)){let d=a.bytes,u=new Uint8Array(d.map(f=>Math.max(0,Math.min(255,Number(f)||0))));return s({artifact:e.putBytes(t,u,i)})}return n("put requires body as string, {text}, or {bytes:number[]}")}catch(t){return o(t)}},get(r){try{let t=r.params?.key,i=r.params?.as??"base64";if(!t)return n("get requires params.key");let a=e.head(t);if(!a)return n(`artifact "${t}" not found`);if(i==="text"){let u=e.getText(t);return u===null?n(`artifact "${t}" not found`):s({key:t,contentType:a.contentType,size:a.size,text:u})}let d=e.getBytes(t);return d===null?n(`artifact "${t}" not found`):s({key:t,contentType:a.contentType,size:a.size,bytes:[...d]})}catch(t){return o(t)}},del(r){try{let t=r.params?.key;return t?(e.remove(t),s({ok:!0})):n("del requires params.key")}catch(t){return o(t)}}}}function v(...e){return l__namespace.resolve(...e)}l__namespace.join(B__namespace.tmpdir(),".board-live-cards-git-bash-cache.json");function h(e,s,n){let o=e.indexOf(s),r=o!==-1?e[o+1]:void 0;if(!r)throw new Error(`Missing ${s}
|
|
2
|
-
Usage: ${n}`);return r}function m(e,s){let n=e.indexOf(s);return n!==-1?e[n+1]:void 0}async function P(){let e=[];for await(let s of process.stdin)e.push(Buffer.isBuffer(s)?s:Buffer.from(s));return new Uint8Array(Buffer.concat(e))}var N=["artifacts-store \u2014 generic artifact CRUD on a blob-backed store",""," artifacts-store put --store-ref <ref> --key <key> [--file <path> | --text <text>] [--content-type <mime>]"," artifacts-store get --store-ref <ref> --key <key> [--out <path>] [--as text|bytes]"," artifacts-store head --store-ref <ref> --key <key>"," artifacts-store list --store-ref <ref> [--prefix <prefix>]"," artifacts-store del --store-ref <ref> --key <key>"].join(`
|
|
3
|
-
`);async function M(e){let s=e[0],n=e.slice(1);if(!s||s==="help"||s==="--help"||s==="-h"){console.error(N);return}let o=h(n,"--store-ref",`artifacts-store ${s} --store-ref <b64-ref>`),r=b(o).value,t=j($(I(r)));if(s==="put"){let i=h(n,"--key","artifacts-store put --store-ref <ref> --key <key>"),a=m(n,"--content-type"),d=m(n,"--file"),u=m(n,"--text"),f;if(d)f={bytes:[...new Uint8Array(c__namespace.readFileSync(d))]};else if(typeof u=="string")f={text:u};else if(!process.stdin.isTTY)f={bytes:[...await P()]};else throw new Error("put requires --file, --text, or stdin bytes");let g=t.put({params:{key:i,...a?{contentType:a}:{}},body:f});if(g.status!=="success")throw new Error(g.error||"put failed");process.stdout.write(JSON.stringify(g.data,null,2)+`
|
|
4
|
-
`);return}if(s==="get"){let i=h(n,"--key","artifacts-store get --store-ref <ref> --key <key>"),a=(m(n,"--as")||"bytes").toLowerCase(),d=m(n,"--out"),u=t.get({params:{key:i,as:a}});if(u.status!=="success")throw new Error(u.error||"get failed");if(a==="text"){let g=u.data.text??"";d?c__namespace.writeFileSync(d,g,"utf-8"):process.stdout.write(g);return}let f=new Uint8Array(u.data.bytes??[]);d?c__namespace.writeFileSync(d,Buffer.from(f)):process.stdout.write(JSON.stringify({...u.data,bytes:void 0,byteLength:f.byteLength},null,2)+`
|
|
5
|
-
`);return}if(s==="head"){let i=h(n,"--key","artifacts-store head --store-ref <ref> --key <key>"),a=t.head({params:{key:i}});if(a.status!=="success")throw new Error(a.error||"head failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
6
|
-
`);return}if(s==="list"){let i=m(n,"--prefix")||"",a=t.list({params:i?{prefix:i}:{}});if(a.status!=="success")throw new Error(a.error||"list failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
7
|
-
`);return}if(s==="del"||s==="delete"||s==="rm"){let i=h(n,"--key","artifacts-store del --store-ref <ref> --key <key>"),a=t.del({params:{key:i}});if(a.status!=="success")throw new Error(a.error||"del failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
8
|
-
`);return}throw new Error(`Unknown command "${s}"
|
|
9
|
-
|
|
10
|
-
${N}`)}var z=process.argv[1]&&v(process.argv[1])===v(new URL((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('artifacts-store-cli.cjs', document.baseURI).href))).pathname.replace(/^\/([A-Z]:)/,"$1"));z&&M(process.argv.slice(2)).catch(e=>{let s=e instanceof Error?e.message:String(e);console.error(s),process.exit(1);});exports.cli=M;//# sourceMappingURL=artifacts-store-cli.cjs.map
|
|
11
|
-
//# sourceMappingURL=artifacts-store-cli.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/cli/common/storage-interface.ts","../../../src/cli/node/storage-fs-adapters.ts","../../../src/cli/common/artifacts-store-lib.ts","../../../src/cli/common/artifacts-store-lib-public.ts","../../../src/cli/node/process-runner.ts","../../../src/cli/node/artifacts-store-cli.ts"],"names":["REF_PREFIX","fromBase64Url","input","base64","buf","binary","bytes","i","parseRef","s","parsed","candidate","renameSync","src","dest","c","delays","err","code","createFsBlobStorage","rootDir","resolve","key","l","toKey","fullPath","walk","dir","out","entry","p","content","tmp","randomUUID","prefix","all","sorted","k","st","INDEX_KEY","nowIso","utf8ByteLength","text","loadIndex","blob","raw","saveIndex","index","statToInfo","stat","updateIndex","info","createArtifactsStore","head","fromStat","contentType","envelope","infoByKey","a","b","createArtifactsStorePublic","store","ok","data","fail","error","oops","e","body","byteValues","n","as","resolvePath","segments","B","requireFlag","args","flag","usage","idx","val","optFlag","readStdinBytes","parts","chunk","HELP","cli","argv","cmd","rest","ref","root","filePath","x","result","outPath","isMain","msg"],"mappings":"mtBAqFA,IAAMA,CAAAA,CAAa,OAkBnB,SAASC,CAAAA,CAAcC,EAAuB,CAC5C,IAAMC,EAASD,CAAAA,CAAM,OAAA,CAAQ,KAAM,GAAG,CAAA,CAAE,QAAQ,IAAA,CAAM,GAAG,CAAA,CACrD,GAAA,CAAI,MAAA,CAAA,CAAQ,CAAA,CAAKA,EAAM,MAAA,CAAS,CAAA,EAAM,CAAC,CAAA,CACrCE,CAAAA,CAAO,WAAmG,MAAA,CAChH,GAAIA,CAAAA,CAAK,OAAOA,CAAAA,CAAI,IAAA,CAAKD,EAAQ,QAAQ,CAAA,CAAE,SAAS,MAAM,CAAA,CAC1D,GAAI,OAAO,IAAA,EAAS,UAAA,CAAY,CAC9B,IAAME,CAAAA,CAAS,KAAKF,CAAM,CAAA,CACpBG,EAAQ,IAAI,UAAA,CAAWD,EAAO,MAAM,CAAA,CAC1C,IAAA,IAASE,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAIF,EAAO,MAAA,CAAQE,CAAAA,EAAK,EAAGD,CAAAA,CAAMC,CAAC,EAAIF,CAAAA,CAAO,UAAA,CAAWE,CAAC,CAAA,CACzE,OAAO,IAAI,aAAY,CAAE,MAAA,CAAOD,CAAK,CACvC,CACA,MAAM,IAAI,KAAA,CAAM,6CAA6C,CAC/D,CASO,SAASE,EAASC,CAAAA,CAAyB,CAEhD,GAAIA,CAAAA,CAAE,UAAA,CAAW,aAAa,CAAA,CAC5B,OAAO,CAAE,IAAA,CAAM,SAAA,CAAW,KAAA,CAAOA,EAAE,KAAA,CAAM,EAAoB,CAAE,CAAA,CAEjE,GAAI,CAACA,CAAAA,CAAE,UAAA,CAAWT,CAAU,CAAA,CAAG,MAAM,IAAI,MAAM,CAAA,6BAAA,EAAgCA,CAAU,uBAAuBS,CAAC,CAAA,CAAE,EACnH,IAAIC,CAAAA,CACJ,GAAI,CACFA,CAAAA,CAAS,IAAA,CAAK,MAAMT,CAAAA,CAAcQ,CAAAA,CAAE,MAAMT,CAAAA,CAAW,MAAM,CAAC,CAAC,EAC/D,CAAA,KAAQ,CACN,MAAM,IAAI,MAAM,CAAA,+CAAA,EAAkDS,CAAC,EAAE,CACvE,CACA,GAAI,CAACC,CAAAA,EAAU,OAAOA,CAAAA,EAAW,QAAA,CAC/B,MAAM,IAAI,KAAA,CAAM,CAAA,8CAAA,EAAiDD,CAAC,CAAA,CAAE,CAAA,CAEtE,IAAME,CAAAA,CAAYD,CAAAA,CAClB,GAAI,OAAOC,CAAAA,CAAU,IAAA,EAAS,UAAY,OAAOA,CAAAA,CAAU,OAAU,QAAA,CACnE,MAAM,IAAI,KAAA,CAAM,CAAA,6DAAA,EAAgEF,CAAC,CAAA,CAAE,CAAA,CAErF,OAAO,CAAE,IAAA,CAAME,CAAAA,CAAU,KAAM,KAAA,CAAOA,CAAAA,CAAU,KAAM,CACxD,CCxHA,SAASC,EAAWC,CAAAA,CAAaC,CAAAA,CAAoB,CACnD,GAAI,OAAA,CAAQ,WAAa,OAAA,CAAS,CAAKC,YAAA,CAAA,UAAA,CAAWF,CAAAA,CAAKC,CAAI,CAAA,CAAG,MAAQ,CACtE,IAAME,EAAS,CAAC,EAAA,CAAI,GAAI,EAAA,CAAI,EAAA,CAAI,GAAG,CAAA,CACnC,IAAA,IAAST,CAAAA,CAAI,EAAGA,CAAAA,EAAKS,CAAAA,CAAO,OAAQT,CAAAA,EAAAA,CAClC,GAAI,CAAKQ,YAAA,CAAA,UAAA,CAAWF,CAAAA,CAAKC,CAAI,CAAA,CAAG,MAAQ,CAAA,MAASG,EAAc,CAC7D,IAAMC,EAAQD,CAAAA,CAA8B,IAAA,CAC5C,IAAKC,CAAAA,GAAS,OAAA,EAAWA,CAAAA,GAAS,OAAA,GAAYX,CAAAA,CAAIS,CAAAA,CAAO,OAAQ,CAC/D,OAAA,CAAQ,KAAK,IAAI,UAAA,CAAW,IAAI,iBAAA,CAAkB,CAAC,CAAC,CAAA,CAAG,CAAA,CAAG,CAAA,CAAGA,EAAOT,CAAC,CAAC,EACtE,QACF,CACA,MAAMU,CACR,CAEJ,CA2BO,SAASE,CAAAA,CAAoBC,CAAAA,CAA8B,CAChE,SAASC,CAAAA,CAAQC,EAAqB,CACpC,OAAYC,kBAAKH,CAAAA,CAAS,GAAGE,CAAAA,CAAI,KAAA,CAAM,GAAG,CAAC,CAC7C,CAEA,SAASE,EAAMC,CAAAA,CAA0B,CAEvC,OADiBF,YAAA,CAAA,QAAA,CAASH,CAAAA,CAASK,CAAQ,CAAA,CAAE,OAAA,CAAQ,KAAA,CAAO,GAAG,CAEjE,CAEA,SAASC,CAAAA,CAAKC,CAAAA,CAAaC,EAAqB,CAC9C,GAAQb,YAAA,CAAA,UAAA,CAAWY,CAAG,CAAA,CACtB,IAAA,IAAWE,KAAYd,YAAA,CAAA,WAAA,CAAYY,CAAAA,CAAK,CAAE,aAAA,CAAe,IAAK,CAAC,CAAA,CAAG,CAChE,IAAMG,CAAAA,CAASP,YAAA,CAAA,IAAA,CAAKI,CAAAA,CAAKE,EAAM,IAAI,CAAA,CACnC,GAAIA,CAAAA,CAAM,WAAA,GAAe,CACvBH,CAAAA,CAAKI,CAAAA,CAAGF,CAAG,CAAA,CACX,QACF,CACKC,CAAAA,CAAM,MAAA,IACXD,CAAAA,CAAI,IAAA,CAAKJ,EAAMM,CAAC,CAAC,EACnB,CACF,CAEA,OAAO,CACL,IAAA,CAAKR,CAAAA,CAA4B,CAC/B,IAAMQ,CAAAA,CAAIT,EAAQC,CAAG,CAAA,CACrB,GAAI,CAAIP,YAAA,CAAA,UAAA,CAAWe,CAAC,EAAG,OAAO,IAAA,CAC9B,GAAI,CAAE,OAAUf,0BAAae,CAAAA,CAAG,OAAO,CAAG,CAAA,KAAQ,CAAE,OAAO,IAAM,CACnE,CAAA,CAEA,MAAMR,CAAAA,CAAaS,CAAAA,CAAuB,CACxC,IAAMD,CAAAA,CAAIT,CAAAA,CAAQC,CAAG,CAAA,CACfU,CAAAA,CAAM,GAAGF,CAAC,CAAA,CAAA,EAAI,QAAQ,GAAG,CAAA,CAAA,EAAIG,mBAAY,CAAA,IAAA,CAAA,CAC5ClB,YAAA,CAAA,SAAA,CAAeQ,YAAA,CAAA,OAAA,CAAQO,CAAC,CAAA,CAAG,CAAE,SAAA,CAAW,IAAK,CAAC,CAAA,CAC9Cf,YAAA,CAAA,aAAA,CAAciB,EAAKD,CAAAA,CAAS,OAAO,CAAA,CACtCnB,CAAAA,CAAWoB,CAAAA,CAAKF,CAAC,EACnB,CAAA,CAEA,MAAA,CAAOR,EAAsB,CAC3B,OAAUP,wBAAWM,CAAAA,CAAQC,CAAG,CAAC,CACnC,CAAA,CAEA,MAAA,CAAOA,EAAmB,CACxB,IAAMQ,EAAIT,CAAAA,CAAQC,CAAG,EACrB,GAAI,CAASP,YAAA,CAAA,UAAA,CAAWe,CAAC,CAAA,EAAMf,YAAA,CAAA,UAAA,CAAWe,CAAC,EAAG,CAAA,KAAQ,CAAoB,CAC5E,CAAA,CAEA,UAAUR,CAAAA,CAAgC,CACxC,IAAMQ,CAAAA,CAAIT,CAAAA,CAAQC,CAAG,EACrB,GAAI,CAAIP,wBAAWe,CAAC,CAAA,CAAG,OAAO,IAAA,CAC9B,GAAI,CAAE,OAAO,IAAI,UAAA,CAAcf,0BAAae,CAAC,CAAC,CAAG,CAAA,KAAQ,CAAE,OAAO,IAAM,CAC1E,CAAA,CAEA,UAAA,CAAWR,CAAAA,CAAaS,CAAAA,CAA2B,CACjD,IAAMD,CAAAA,CAAIT,EAAQC,CAAG,CAAA,CACfU,EAAM,CAAA,EAAGF,CAAC,CAAA,CAAA,EAAI,OAAA,CAAQ,GAAG,CAAA,CAAA,EAAIG,mBAAY,CAAA,IAAA,CAAA,CAC5ClB,uBAAeQ,YAAA,CAAA,OAAA,CAAQO,CAAC,EAAG,CAAE,SAAA,CAAW,IAAK,CAAC,CAAA,CAC9Cf,YAAA,CAAA,aAAA,CAAciB,EAAK,MAAA,CAAO,IAAA,CAAKD,CAAO,CAAC,CAAA,CAC1CnB,EAAWoB,CAAAA,CAAKF,CAAC,EACnB,CAAA,CAEA,QAAA,CAASI,CAAAA,CAA2B,CAClC,IAAMC,CAAAA,CAAgB,EAAC,CACvBT,CAAAA,CAAKN,EAASe,CAAG,CAAA,CACjB,IAAMC,CAAAA,CAASD,CAAAA,CAAI,IAAA,GACnB,OAAKD,CAAAA,CACEE,EAAO,MAAA,CAAQC,CAAAA,EAAMA,EAAE,UAAA,CAAWH,CAAM,CAAC,CAAA,CAD5BE,CAEtB,CAAA,CAEA,KAAKd,CAAAA,CAAa,CAChB,IAAMQ,CAAAA,CAAIT,CAAAA,CAAQC,CAAG,CAAA,CACrB,GAAI,CAAIP,YAAA,CAAA,UAAA,CAAWe,CAAC,CAAA,CAAG,OAAO,IAAA,CAC9B,GAAI,CACF,IAAMQ,CAAAA,CAAQvB,sBAASe,CAAC,CAAA,CACxB,OAAO,CACL,GAAA,CAAAR,CAAAA,CACA,KAAM,MAAA,CAAOgB,CAAAA,CAAG,MAAQ,CAAC,CAAA,CACzB,UAAW,IAAI,IAAA,CAAKA,CAAAA,CAAG,OAAO,CAAA,CAAE,WAAA,EAClC,CACF,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CACF,CACF,CClEA,IAAMC,CAAAA,CAAY,uBAAA,CAalB,SAASC,CAAAA,EAAiB,CACxB,OAAO,IAAI,IAAA,GAAO,WAAA,EACpB,CAEA,SAASC,CAAAA,CAAeC,CAAAA,CAAsB,CAC5C,OAAO,IAAI,aAAY,CAAE,MAAA,CAAOA,CAAI,CAAA,CAAE,UACxC,CAEA,SAASC,CAAAA,CAAUC,CAAAA,CAAkC,CACnD,IAAMC,CAAAA,CAAMD,EAAK,IAAA,CAAKL,CAAS,EAC/B,GAAI,CAACM,CAAAA,CAAK,OAAO,CAAE,OAAA,CAAS,EAAG,CAAA,CAC/B,GAAI,CACF,IAAMnC,EAAS,IAAA,CAAK,KAAA,CAAMmC,CAAG,CAAA,CAC7B,GAAInC,CAAAA,EAAUA,EAAO,OAAA,EAAW,OAAOA,EAAO,OAAA,EAAY,QAAA,CAAU,OAAOA,CAC7E,CAAA,KAAQ,CAER,CACA,OAAO,CAAE,QAAS,EAAG,CACvB,CAEA,SAASoC,EAAUF,CAAAA,CAAmBG,CAAAA,CAA4B,CAChEH,CAAAA,CAAK,KAAA,CAAML,CAAAA,CAAW,KAAK,SAAA,CAAUQ,CAAAA,CAAO,KAAM,CAAC,CAAC,EACtD,CAEA,SAASC,CAAAA,CAAWC,CAAAA,CAA4C,CAC9D,OAAKA,EACE,CACL,GAAA,CAAKA,EAAK,GAAA,CACV,IAAA,CAAMA,EAAK,IAAA,CACX,SAAA,CAAWA,CAAAA,CAAK,SAAA,CAChB,WAAA,CAAaA,CAAAA,CAAK,WACpB,CAAA,CANkB,IAOpB,CAEA,SAASC,CAAAA,CAAYH,EAAsBzB,CAAAA,CAAa6B,CAAAA,CAA0B,CAChFJ,CAAAA,CAAM,OAAA,CAAQzB,CAAG,EAAI,CACnB,GAAA,CAAAA,EACA,IAAA,CAAM6B,CAAAA,CAAK,KACX,SAAA,CAAWA,CAAAA,CAAK,SAAA,CAChB,WAAA,CAAaA,CAAAA,CAAK,WACpB,EACF,CA2CO,SAASC,EAAqBR,CAAAA,CAAmC,CACtE,SAASS,CAAAA,CAAK/B,CAAAA,CAAkC,CAC9C,IAAMgC,CAAAA,CAAWV,CAAAA,CAAK,KAAOI,CAAAA,CAAWJ,CAAAA,CAAK,KAAKtB,CAAG,CAAC,EAAI,IAAA,CAC1D,GAAIgC,CAAAA,CAAU,OAAOA,CAAAA,CAGrB,IAAMzB,EADQc,CAAAA,CAAUC,CAAI,EACR,OAAA,CAAQtB,CAAG,EAC/B,GAAIO,CAAAA,CAAO,OAAO,CAAE,GAAGA,CAAM,EAE7B,GAAI,CAACe,EAAK,MAAA,CAAOtB,CAAG,EAAG,OAAO,IAAA,CAC9B,IAAMS,CAAAA,CAAUa,CAAAA,CAAK,IAAA,CAAKtB,CAAG,CAAA,CAC7B,OAAIS,IAAY,IAAA,CAAa,CAAE,IAAAT,CAAI,CAAA,CAC5B,CACL,GAAA,CAAAA,CAAAA,CACA,IAAA,CAAMmB,EAAeV,CAAO,CAC9B,CACF,CAEA,OAAO,CACL,MAAA,CAAOT,CAAAA,CAAsB,CAC3B,OAAOsB,CAAAA,CAAK,MAAA,CAAOtB,CAAG,CACxB,CAAA,CAEA,QAAQA,CAAAA,CAAaS,CAAAA,CAAiBwB,EAAc,2BAAA,CAA2C,CAC7FX,CAAAA,CAAK,KAAA,CAAMtB,CAAAA,CAAKS,CAAO,EACvB,IAAMoB,CAAAA,CAAOE,EAAK/B,CAAG,CAAA,EAAK,CAAE,GAAA,CAAAA,CAAI,EAChC6B,CAAAA,CAAK,WAAA,CAAcI,EACnBJ,CAAAA,CAAK,SAAA,CAAYA,EAAK,SAAA,EAAaX,CAAAA,GACnCW,CAAAA,CAAK,IAAA,CAAOA,CAAAA,CAAK,IAAA,EAAQV,CAAAA,CAAeV,CAAO,EAC/C,IAAMgB,CAAAA,CAAQJ,EAAUC,CAAI,CAAA,CAC5B,OAAAM,CAAAA,CAAYH,CAAAA,CAAOzB,CAAAA,CAAK6B,CAAI,CAAA,CAC5BL,CAAAA,CAAUF,EAAMG,CAAK,CAAA,CACdI,CACT,CAAA,CAEA,QAAA,CAAS7B,EAAaS,CAAAA,CAAqBwB,CAAAA,CAAc,0BAAA,CAA0C,CACjG,GAAIX,CAAAA,CAAK,WACPA,CAAAA,CAAK,UAAA,CAAWtB,EAAKS,CAAO,CAAA,CAAA,KACvB,CAEL,IAAMyB,CAAAA,CAAW,IAAA,CAAK,SAAA,CAAU,CAAE,MAAA,CAAQ,cAAe,IAAA,CAAM,CAAC,GAAGzB,CAAO,CAAE,CAAC,CAAA,CAC7Ea,CAAAA,CAAK,KAAA,CAAMtB,CAAAA,CAAKkC,CAAQ,EAC1B,CACA,IAAML,CAAAA,CAAOE,EAAK/B,CAAG,CAAA,EAAK,CAAE,GAAA,CAAAA,CAAI,CAAA,CAChC6B,CAAAA,CAAK,WAAA,CAAcI,CAAAA,CACnBJ,EAAK,SAAA,CAAYA,CAAAA,CAAK,WAAaX,CAAAA,EAAO,CAC1CW,EAAK,IAAA,CAAOA,CAAAA,CAAK,IAAA,EAAQpB,CAAAA,CAAQ,UAAA,CACjC,IAAMgB,EAAQJ,CAAAA,CAAUC,CAAI,EAC5B,OAAAM,CAAAA,CAAYH,EAAOzB,CAAAA,CAAK6B,CAAI,CAAA,CAC5BL,CAAAA,CAAUF,CAAAA,CAAMG,CAAK,EACdI,CACT,CAAA,CAEA,QAAQ7B,CAAAA,CAA4B,CAClC,IAAMuB,CAAAA,CAAMD,CAAAA,CAAK,IAAA,CAAKtB,CAAG,CAAA,CACzB,GAAIuB,IAAQ,IAAA,CAAM,CAChB,GAAI,CAACD,CAAAA,CAAK,UAAW,OAAO,IAAA,CAC5B,IAAMtC,CAAAA,CAAQsC,CAAAA,CAAK,SAAA,CAAUtB,CAAG,CAAA,CAChC,OAAIhB,IAAU,IAAA,CAAa,IAAA,CACpB,OAAO,IAAA,CAAKA,CAAK,CAAA,CAAE,QAAA,CAAS,OAAO,CAC5C,CACA,GAAI,CACF,IAAMI,CAAAA,CAAS,IAAA,CAAK,MAAMmC,CAAG,CAAA,CAC7B,GAAInC,CAAAA,EAAUA,CAAAA,CAAO,MAAA,GAAW,eAAiB,KAAA,CAAM,OAAA,CAAQA,EAAO,IAAI,CAAA,CACxE,OAAO,IAAI,WAAA,CAAY,OAAO,CAAA,CAAE,MAAA,CAAO,IAAI,WAAWA,CAAAA,CAAO,IAAI,CAAC,CAEtE,CAAA,KAAQ,CAER,CACA,OAAOmC,CACT,CAAA,CAEA,QAAA,CAASvB,CAAAA,CAAgC,CACvC,GAAIsB,CAAAA,CAAK,UAAW,CAClB,IAAMtC,EAAQsC,CAAAA,CAAK,SAAA,CAAUtB,CAAG,CAAA,CAChC,GAAIhB,CAAAA,GAAU,KAAM,OAAOA,CAC7B,CACA,IAAMuC,CAAAA,CAAMD,EAAK,IAAA,CAAKtB,CAAG,CAAA,CACzB,GAAIuB,CAAAA,GAAQ,IAAA,CAAM,OAAO,IAAA,CACzB,GAAI,CACF,IAAMnC,CAAAA,CAAS,KAAK,KAAA,CAAMmC,CAAG,CAAA,CAC7B,GAAInC,CAAAA,EAAUA,CAAAA,CAAO,SAAW,aAAA,EAAiB,KAAA,CAAM,QAAQA,CAAAA,CAAO,IAAI,EACxE,OAAO,IAAI,UAAA,CAAWA,CAAAA,CAAO,IAAI,CAErC,MAAQ,CAER,CACA,OAAO,IAAI,WAAA,GAAc,MAAA,CAAOmC,CAAG,CACrC,CAAA,CAEA,IAAA,CAAAQ,CAAAA,CAEA,KAAKnB,CAAAA,CAAS,EAAA,CAAoB,CAChC,IAAMuB,CAAAA,CAAY,IAAI,GAAA,CAEtB,GAAIb,CAAAA,CAAK,QAAA,CACP,IAAA,IAAWtB,CAAAA,IAAOsB,EAAK,QAAA,CAASV,CAAM,EAAG,CACvC,GAAIZ,IAAQiB,CAAAA,CAAW,SACvB,IAAMY,CAAAA,CAAOE,CAAAA,CAAK/B,CAAG,GAAK,CAAE,GAAA,CAAAA,CAAI,CAAA,CAChCmC,CAAAA,CAAU,IAAInC,CAAAA,CAAK6B,CAAI,EACzB,CAGF,IAAMJ,CAAAA,CAAQJ,EAAUC,CAAI,CAAA,CAC5B,OAAW,CAACtB,CAAAA,CAAKO,CAAK,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQkB,CAAAA,CAAM,OAAO,CAAA,CACjDzB,IAAQiB,CAAAA,EAAcL,CAAAA,EAAU,CAACZ,CAAAA,CAAI,UAAA,CAAWY,CAAM,CAAA,EACrDuB,CAAAA,CAAU,GAAA,CAAInC,CAAG,CAAA,EAAGmC,CAAAA,CAAU,IAAInC,CAAAA,CAAK,CAAE,GAAGO,CAAM,CAAC,EAG1D,OAAO,CAAC,GAAG4B,CAAAA,CAAU,MAAA,EAAQ,EAAE,IAAA,CAAK,CAACC,EAAGC,CAAAA,GAAMD,CAAAA,CAAE,IAAI,aAAA,CAAcC,CAAAA,CAAE,GAAG,CAAC,CAC1E,CAAA,CAEA,OAAOrC,CAAAA,CAAmB,CACxBsB,EAAK,MAAA,CAAOtB,CAAG,EACf,IAAMyB,CAAAA,CAAQJ,CAAAA,CAAUC,CAAI,CAAA,CAC5B,OAAOG,EAAM,OAAA,CAAQzB,CAAG,EACxBwB,CAAAA,CAAUF,CAAAA,CAAMG,CAAK,EACvB,CACF,CACF,CCzRO,SAASa,CAAAA,CAA2BC,EAA6C,CACtF,SAASC,EAAMC,CAAAA,CAA2B,CACxC,OAAO,CAAE,MAAA,CAAQ,SAAA,CAAW,IAAA,CAAAA,CAAK,CACnC,CACA,SAASC,CAAAA,CAAQC,EAAiC,CAChD,OAAO,CAAE,MAAA,CAAQ,MAAA,CAAQ,KAAA,CAAAA,CAAM,CACjC,CACA,SAASC,CAAAA,CAAQC,CAAAA,CAA8B,CAC7C,OAAO,CAAE,OAAQ,OAAA,CAAS,KAAA,CAAOA,CAAAA,YAAa,KAAA,CAAQA,CAAAA,CAAE,OAAA,CAAU,OAAOA,CAAC,CAAE,CAC9E,CAEA,OAAO,CACL,IAAA,CAAKjE,CAAAA,CAAmE,CACtE,GAAI,CACF,IAAMgC,EAAUhC,CAAAA,CAAM,MAAA,EAAS,QAAoC,EAAA,CACnE,OAAO4D,EAAG,CAAE,SAAA,CAAWD,CAAAA,CAAM,IAAA,CAAK3B,CAAM,CAAE,CAAC,CAC7C,CAAA,MAASiC,EAAG,CAAE,OAAOD,EAAKC,CAAC,CAAG,CAChC,CAAA,CAEA,IAAA,CAAKjE,CAAAA,CAAuE,CAC1E,GAAI,CACF,IAAMoB,CAAAA,CAAMpB,CAAAA,CAAM,QAAS,GAAA,CAC3B,OAAKoB,CAAAA,CACEwC,CAAAA,CAAG,CAAE,QAAA,CAAUD,EAAM,IAAA,CAAKvC,CAAG,CAAE,CAAC,CAAA,CADtB0C,EAAK,0BAA0B,CAElD,CAAA,MAASG,CAAAA,CAAG,CAAE,OAAOD,EAAKC,CAAC,CAAG,CAChC,CAAA,CAEA,GAAA,CAAIjE,EAAgE,CAClE,GAAI,CACF,IAAMoB,CAAAA,CAAMpB,CAAAA,CAAM,QAAS,GAAA,CACrBqD,CAAAA,CAAcrD,EAAM,MAAA,EAAS,WAAA,CACnC,GAAI,CAACoB,CAAAA,CAAK,OAAO0C,CAAAA,CAAK,yBAAyB,CAAA,CAE/C,IAAMI,CAAAA,CAAOlE,CAAAA,CAAM,KACnB,GAAI,OAAOkE,GAAS,QAAA,CAClB,OAAON,CAAAA,CAAG,CAAE,QAAA,CAAUD,CAAAA,CAAM,QAAQvC,CAAAA,CAAK8C,CAAAA,CAAMb,CAAW,CAAE,CAAC,EAG/D,GAAIa,CAAAA,EAAQ,OAAOA,CAAAA,EAAS,QAAA,EAAY,OAAQA,EAA4B,IAAA,EAAS,QAAA,CACnF,OAAON,CAAAA,CAAG,CAAE,SAAUD,CAAAA,CAAM,OAAA,CAAQvC,CAAAA,CAAM8C,CAAAA,CAA0B,IAAA,CAAMb,CAAW,CAAE,CAAC,CAAA,CAG1F,GAAIa,CAAAA,EAAQ,OAAOA,GAAS,QAAA,EAAY,KAAA,CAAM,OAAA,CAASA,CAAAA,CAA6B,KAAK,CAAA,CAAG,CAC1F,IAAMC,CAAAA,CAAcD,EAA6B,KAAA,CAC3C9D,CAAAA,CAAQ,IAAI,UAAA,CAAW+D,CAAAA,CAAW,GAAA,CAAKC,CAAAA,EAAM,IAAA,CAAK,GAAA,CAAI,EAAG,IAAA,CAAK,GAAA,CAAI,IAAK,MAAA,CAAOA,CAAC,GAAK,CAAC,CAAC,CAAC,CAAC,CAAA,CAC9F,OAAOR,EAAG,CAAE,QAAA,CAAUD,EAAM,QAAA,CAASvC,CAAAA,CAAKhB,EAAOiD,CAAW,CAAE,CAAC,CACjE,CAEA,OAAOS,EAAK,0DAA0D,CACxE,OAASG,CAAAA,CAAG,CAAE,OAAOD,CAAAA,CAAKC,CAAC,CAAG,CAChC,CAAA,CAEA,GAAA,CAAIjE,EAA2H,CAC7H,GAAI,CACF,IAAMoB,CAAAA,CAAMpB,EAAM,MAAA,EAAS,GAAA,CACrBqE,CAAAA,CAAMrE,CAAAA,CAAM,MAAA,EAAS,EAAA,EAAgC,SAC3D,GAAI,CAACoB,EAAK,OAAO0C,CAAAA,CAAK,yBAAyB,CAAA,CAE/C,IAAMX,CAAAA,CAAOQ,CAAAA,CAAM,IAAA,CAAKvC,CAAG,EAC3B,GAAI,CAAC+B,EAAM,OAAOW,CAAAA,CAAK,aAAa1C,CAAG,CAAA,WAAA,CAAa,CAAA,CAEpD,GAAIiD,CAAAA,GAAO,MAAA,CAAQ,CACjB,IAAM7B,CAAAA,CAAOmB,EAAM,OAAA,CAAQvC,CAAG,EAC9B,OAAIoB,CAAAA,GAAS,IAAA,CAAasB,CAAAA,CAAK,CAAA,UAAA,EAAa1C,CAAG,aAAa,CAAA,CACrDwC,CAAAA,CAAG,CAAE,GAAA,CAAAxC,CAAAA,CAAK,YAAa+B,CAAAA,CAAK,WAAA,CAAa,IAAA,CAAMA,CAAAA,CAAK,IAAA,CAAM,IAAA,CAAAX,CAAK,CAAC,CACzE,CAEA,IAAMpC,CAAAA,CAAQuD,EAAM,QAAA,CAASvC,CAAG,CAAA,CAChC,OAAIhB,CAAAA,GAAU,IAAA,CAAa0D,EAAK,CAAA,UAAA,EAAa1C,CAAG,aAAa,CAAA,CACtDwC,CAAAA,CAAG,CAAE,GAAA,CAAAxC,CAAAA,CAAK,WAAA,CAAa+B,CAAAA,CAAK,WAAA,CAAa,IAAA,CAAMA,EAAK,IAAA,CAAM,KAAA,CAAO,CAAC,GAAG/C,CAAK,CAAE,CAAC,CACtF,CAAA,MAAS6D,CAAAA,CAAG,CAAE,OAAOD,EAAKC,CAAC,CAAG,CAChC,CAAA,CAEA,GAAA,CAAIjE,EAAkD,CACpD,GAAI,CACF,IAAMoB,CAAAA,CAAMpB,CAAAA,CAAM,QAAS,GAAA,CAC3B,OAAKoB,GACLuC,CAAAA,CAAM,MAAA,CAAOvC,CAAG,CAAA,CACTwC,CAAAA,CAAG,CAAE,EAAA,CAAI,CAAA,CAAK,CAAC,GAFLE,CAAAA,CAAK,yBAAyB,CAGjD,CAAA,MAASG,CAAAA,CAAG,CAAE,OAAOD,CAAAA,CAAKC,CAAC,CAAG,CAChC,CACF,CACF,CCxCO,SAASK,CAAAA,CAAAA,GAAeC,CAAAA,CAA4B,CAAE,OAAY3C,YAAA,CAAA,OAAA,CAAQ,GAAG2C,CAAQ,CAAG,CAiJlE3C,YAAA,CAAA,IAAA,CAAQ4C,qBAAO,CAAG,uCAAuC,EC/LtF,SAASC,CAAAA,CAAYC,CAAAA,CAAgBC,EAAcC,CAAAA,CAAuB,CACxE,IAAMC,CAAAA,CAAMH,CAAAA,CAAK,QAAQC,CAAI,CAAA,CACvBG,CAAAA,CAAMD,CAAAA,GAAQ,EAAA,CAAKH,CAAAA,CAAKG,EAAM,CAAC,CAAA,CAAI,OACzC,GAAI,CAACC,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAWH,CAAI;AAAA,OAAA,EAAYC,CAAK,CAAA,CAAE,CAAA,CAC5D,OAAOE,CACT,CAEA,SAASC,CAAAA,CAAQL,CAAAA,CAAgBC,CAAAA,CAAkC,CACjE,IAAME,CAAAA,CAAMH,EAAK,OAAA,CAAQC,CAAI,CAAA,CAC7B,OAAOE,CAAAA,GAAQ,EAAA,CAAKH,CAAAA,CAAKG,CAAAA,CAAM,CAAC,CAAA,CAAI,MACtC,CAEA,eAAeG,CAAAA,EAAsC,CACnD,IAAMC,CAAAA,CAAkB,EAAC,CACzB,UAAA,IAAiBC,CAAAA,IAAS,OAAA,CAAQ,KAAA,CAChCD,CAAAA,CAAM,IAAA,CAAK,MAAA,CAAO,SAASC,CAAK,CAAA,CAAIA,CAAAA,CAAQ,MAAA,CAAO,IAAA,CAAKA,CAAmB,CAAC,CAAA,CAE9E,OAAO,IAAI,UAAA,CAAW,MAAA,CAAO,MAAA,CAAOD,CAAK,CAAC,CAC5C,CAEA,IAAME,CAAAA,CAAO,CACX,qEAAA,CACA,EAAA,CACA,8GACA,sFAAA,CACA,sDAAA,CACA,8DAAA,CACA,qDACF,EAAE,IAAA,CAAK;AAAA,CAAI,EAEX,eAAsBC,CAAAA,CAAIC,CAAAA,CAA+B,CACvD,IAAMC,CAAAA,CAAMD,CAAAA,CAAK,CAAC,CAAA,CACZE,EAAOF,CAAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAEzB,GAAI,CAACC,CAAAA,EAAOA,CAAAA,GAAQ,MAAA,EAAUA,CAAAA,GAAQ,UAAYA,CAAAA,GAAQ,IAAA,CAAM,CAC9D,OAAA,CAAQ,MAAMH,CAAI,CAAA,CAClB,MACF,CAEA,IAAMK,CAAAA,CAAMf,CAAAA,CAAYc,EAAM,aAAA,CAAe,CAAA,gBAAA,EAAmBD,CAAG,CAAA,sBAAA,CAAwB,CAAA,CACrFG,CAAAA,CAAOnF,CAAAA,CAASkF,CAAG,CAAA,CAAE,KAAA,CACrB7B,CAAAA,CAAQD,CAAAA,CAA2BR,EAAqBjC,CAAAA,CAAoBwE,CAAI,CAAC,CAAC,EAExF,GAAIH,CAAAA,GAAQ,MAAO,CACjB,IAAMlE,EAAMqD,CAAAA,CAAYc,CAAAA,CAAM,OAAA,CAAS,mDAAmD,EACpFlC,CAAAA,CAAc0B,CAAAA,CAAQQ,CAAAA,CAAM,gBAAgB,EAC5CG,CAAAA,CAAWX,CAAAA,CAAQQ,CAAAA,CAAM,QAAQ,EACjC/C,CAAAA,CAAOuC,CAAAA,CAAQQ,EAAM,QAAQ,CAAA,CAE/BrB,EACJ,GAAIwB,CAAAA,CAEFxB,CAAAA,CAAO,CAAE,MAAO,CAAC,GADH,IAAI,UAAA,CAAcyB,0BAAaD,CAAQ,CAAC,CAC7B,CAAE,UAClB,OAAOlD,CAAAA,EAAS,SACzB0B,CAAAA,CAAO,CAAE,KAAA1B,CAAK,CAAA,CAAA,KAAA,GACL,CAAC,OAAA,CAAQ,MAAM,KAAA,CAExB0B,CAAAA,CAAO,CAAE,KAAA,CAAO,CAAC,GADH,MAAMc,CAAAA,EACK,CAAE,CAAA,CAAA,KAE3B,MAAM,IAAI,KAAA,CAAM,6CAA6C,EAG/D,IAAMY,CAAAA,CAASjC,CAAAA,CAAM,GAAA,CAAI,CAAE,MAAA,CAAQ,CAAE,GAAA,CAAAvC,CAAAA,CAAK,GAAIiC,CAAAA,CAAc,CAAE,WAAA,CAAAA,CAAY,EAAI,EAAI,EAAG,IAAA,CAAAa,CAAK,CAAC,CAAA,CAC3F,GAAI0B,CAAAA,CAAO,MAAA,GAAW,UAAW,MAAM,IAAI,KAAA,CAAMA,CAAAA,CAAO,OAAS,YAAY,CAAA,CAC7E,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAA,CAAK,SAAA,CAAUA,EAAO,IAAA,CAAM,IAAA,CAAM,CAAC,CAAA,CAAI;AAAA,CAAI,CAAA,CAChE,MACF,CAEA,GAAIN,CAAAA,GAAQ,MAAO,CACjB,IAAMlE,CAAAA,CAAMqD,CAAAA,CAAYc,CAAAA,CAAM,OAAA,CAAS,mDAAmD,CAAA,CACpFlB,CAAAA,CAAAA,CAAMU,CAAAA,CAAQQ,CAAAA,CAAM,MAAM,CAAA,EAAK,SAAS,WAAA,EAAY,CACpDM,CAAAA,CAAUd,CAAAA,CAAQQ,CAAAA,CAAM,OAAO,EAC/BK,CAAAA,CAASjC,CAAAA,CAAM,GAAA,CAAI,CAAE,MAAA,CAAQ,CAAE,IAAAvC,CAAAA,CAAK,EAAA,CAAAiD,CAAG,CAAE,CAAC,CAAA,CAChD,GAAIuB,CAAAA,CAAO,MAAA,GAAW,SAAA,CAAW,MAAM,IAAI,KAAA,CAAMA,EAAO,KAAA,EAAS,YAAY,CAAA,CAE7E,GAAIvB,CAAAA,GAAO,MAAA,CAAQ,CACjB,IAAM7B,CAAAA,CAAOoD,CAAAA,CAAO,IAAA,CAAK,IAAA,EAAQ,EAAA,CAC7BC,EAAYF,YAAA,CAAA,aAAA,CAAcE,CAAAA,CAASrD,CAAAA,CAAM,OAAO,CAAA,CAC/C,OAAA,CAAQ,OAAO,KAAA,CAAMA,CAAI,CAAA,CAC9B,MACF,CAEA,IAAMpC,EAAQ,IAAI,UAAA,CAAWwF,CAAAA,CAAO,IAAA,CAAK,KAAA,EAAS,EAAE,CAAA,CAChDC,CAAAA,CAAYF,YAAA,CAAA,aAAA,CAAcE,CAAAA,CAAS,MAAA,CAAO,IAAA,CAAKzF,CAAK,CAAC,CAAA,CACpD,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,UAAU,CAAE,GAAGwF,CAAAA,CAAO,IAAA,CAAM,KAAA,CAAO,MAAA,CAAW,UAAA,CAAYxF,CAAAA,CAAM,UAAW,CAAA,CAAG,IAAA,CAAM,CAAC,CAAA,CAAI;AAAA,CAAI,CAAA,CAC5H,MACF,CAEA,GAAIkF,IAAQ,MAAA,CAAQ,CAClB,IAAMlE,CAAAA,CAAMqD,CAAAA,CAAYc,EAAM,OAAA,CAAS,oDAAoD,EACrFK,CAAAA,CAASjC,CAAAA,CAAM,KAAK,CAAE,MAAA,CAAQ,CAAE,GAAA,CAAAvC,CAAI,CAAE,CAAC,CAAA,CAC7C,GAAIwE,CAAAA,CAAO,MAAA,GAAW,UAAW,MAAM,IAAI,MAAMA,CAAAA,CAAO,KAAA,EAAS,aAAa,CAAA,CAC9E,OAAA,CAAQ,OAAO,KAAA,CAAM,IAAA,CAAK,UAAUA,CAAAA,CAAO,IAAA,CAAM,IAAA,CAAM,CAAC,CAAA,CAAI;AAAA,CAAI,CAAA,CAChE,MACF,CAEA,GAAIN,IAAQ,MAAA,CAAQ,CAClB,IAAMtD,CAAAA,CAAS+C,CAAAA,CAAQQ,CAAAA,CAAM,UAAU,CAAA,EAAK,EAAA,CACtCK,EAASjC,CAAAA,CAAM,IAAA,CAAK,CAAE,MAAA,CAAQ3B,CAAAA,CAAS,CAAE,MAAA,CAAAA,CAAO,CAAA,CAAI,EAAG,CAAC,EAC9D,GAAI4D,CAAAA,CAAO,SAAW,SAAA,CAAW,MAAM,IAAI,KAAA,CAAMA,CAAAA,CAAO,KAAA,EAAS,aAAa,CAAA,CAC9E,OAAA,CAAQ,OAAO,KAAA,CAAM,IAAA,CAAK,UAAUA,CAAAA,CAAO,IAAA,CAAM,IAAA,CAAM,CAAC,CAAA,CAAI;AAAA,CAAI,CAAA,CAChE,MACF,CAEA,GAAIN,IAAQ,KAAA,EAASA,CAAAA,GAAQ,QAAA,EAAYA,CAAAA,GAAQ,IAAA,CAAM,CACrD,IAAMlE,CAAAA,CAAMqD,CAAAA,CAAYc,EAAM,OAAA,CAAS,mDAAmD,EACpFK,CAAAA,CAASjC,CAAAA,CAAM,GAAA,CAAI,CAAE,MAAA,CAAQ,CAAE,IAAAvC,CAAI,CAAE,CAAC,CAAA,CAC5C,GAAIwE,EAAO,MAAA,GAAW,SAAA,CAAW,MAAM,IAAI,KAAA,CAAMA,CAAAA,CAAO,OAAS,YAAY,CAAA,CAC7E,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,UAAUA,CAAAA,CAAO,IAAA,CAAM,IAAA,CAAM,CAAC,CAAA,CAAI;AAAA,CAAI,EAChE,MACF,CAEA,MAAM,IAAI,KAAA,CAAM,oBAAoBN,CAAG,CAAA;;AAAA,EAAQH,CAAI,CAAA,CAAE,CACvD,CAEA,IAAMW,CAAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,EAAKxB,CAAAA,CAAY,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAC,CAAA,GAAMA,CAAAA,CAAY,IAAI,GAAA,CAAI,yQAAe,CAAA,CAAE,QAAA,CAAS,OAAA,CAAQ,aAAA,CAAe,IAAI,CAAC,CAAA,CACzIwB,GACFV,CAAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA,CAAE,KAAA,CAAOrE,CAAAA,EAAQ,CACxC,IAAMgF,CAAAA,CAAMhF,CAAAA,YAAe,KAAA,CAAQA,EAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAA,CAC3D,OAAA,CAAQ,KAAA,CAAMgF,CAAG,CAAA,CACjB,OAAA,CAAQ,IAAA,CAAK,CAAC,EAChB,CAAC,CAAA","file":"artifacts-store-cli.cjs","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * storage-fs-adapters.ts\n *\n * Node fs implementations of the three StorageProvider primitives:\n * FsBlobStorage — files under a root directory, key segments → subdirectories\n * FsKvStorage — each key stored as a JSON file under a kv directory\n * FsJournalStorage — append-only JSONL file\n *\n * All three are pure Node — no board-specific logic. They can be composed into\n * a StorageProvider and passed to any adapter factory.\n *\n * blobRef keys and KV keys must be logical (e.g. \"cards/abc123.json\"),\n * not physical fs paths. The adapters resolve them to fs paths internally.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { randomUUID, createHash } from 'crypto';\nimport { lockSync } from 'proper-lockfile';\n\n/**\n * On Windows, renameSync can fail with EPERM/EBUSY when the destination file\n * is held open by another process. Retry with exponential back-off (~280ms max).\n */\nfunction renameSync(src: string, dest: string): void {\n if (process.platform !== 'win32') { fs.renameSync(src, dest); return; }\n const delays = [10, 20, 40, 80, 160];\n for (let i = 0; i <= delays.length; i++) {\n try { fs.renameSync(src, dest); return; } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if ((code === 'EPERM' || code === 'EBUSY') && i < delays.length) {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delays[i]);\n continue;\n }\n throw err;\n }\n }\n}\n\nimport type { GraphEvent } from '../../event-graph/types.js';\nimport type {\n AtomicRelayLock,\n BlobStorage,\n JournalEntry,\n JournalReadResult,\n JournalStorage,\n JSONStorage,\n KVStorage,\n StorageProvider,\n} from '../common/storage-interface.js';\nimport type {\n CardIndex,\n LiveCard,\n StateSnapshotStorageAdapter,\n StateSnapshotReadView,\n} from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// FsBlobStorage\n//\n// key \"cards/abc123.json\" → <rootDir>/cards/abc123.json\n// write is atomic: write to tmp file then rename.\n// ============================================================================\n\nexport function createFsBlobStorage(rootDir: string): BlobStorage {\n function resolve(key: string): string {\n return path.join(rootDir, ...key.split('/'));\n }\n\n function toKey(fullPath: string): string {\n const rel = path.relative(rootDir, fullPath).replace(/\\\\/g, '/');\n return rel;\n }\n\n function walk(dir: string, out: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const p = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(p, out);\n continue;\n }\n if (!entry.isFile()) continue;\n out.push(toKey(p));\n }\n }\n\n return {\n read(key: string): string | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return fs.readFileSync(p, 'utf-8'); } catch { return null; }\n },\n\n write(key: string, content: string): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, p);\n },\n\n exists(key: string): boolean {\n return fs.existsSync(resolve(key));\n },\n\n remove(key: string): void {\n const p = resolve(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try { return new Uint8Array(fs.readFileSync(p)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const p = resolve(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, p);\n },\n\n listKeys(prefix?: string): string[] {\n const all: string[] = [];\n walk(rootDir, all);\n const sorted = all.sort();\n if (!prefix) return sorted;\n return sorted.filter((k) => k.startsWith(prefix));\n },\n\n stat(key: string) {\n const p = resolve(key);\n if (!fs.existsSync(p)) return null;\n try {\n const st = fs.statSync(p);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n/**\n * Create a BlobStorage where the key IS the absolute file path.\n * Implements the full BlobStorage interface (read, write, exists, remove).\n * Use this for operations on known absolute paths (e.g., temp file cleanup).\n */\nexport function createFsAbsolutePathBlobStorage(): BlobStorage {\n return {\n read(key: string): string | null {\n if (!fs.existsSync(key)) return null;\n try { return fs.readFileSync(key, 'utf-8'); } catch { return null; }\n },\n write(key: string, content: string): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, content, 'utf-8');\n renameSync(tmp, key);\n },\n exists(key: string): boolean {\n return fs.existsSync(key);\n },\n remove(key: string): void {\n try { if (fs.existsSync(key)) fs.unlinkSync(key); } catch { /* best-effort */ }\n },\n\n readBytes(key: string): Uint8Array | null {\n if (!fs.existsSync(key)) return null;\n try { return new Uint8Array(fs.readFileSync(key)); } catch { return null; }\n },\n\n writeBytes(key: string, content: Uint8Array): void {\n const tmp = `${key}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(key), { recursive: true });\n fs.writeFileSync(tmp, Buffer.from(content));\n renameSync(tmp, key);\n },\n\n stat(key: string) {\n if (!fs.existsSync(key)) return null;\n try {\n const st = fs.statSync(key);\n return {\n key,\n size: Number(st.size || 0),\n updatedAt: new Date(st.mtimeMs).toISOString(),\n };\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// FsKvStorage\n//\n// key \"cards/abc123/runtime\" → <kvDir>/cards/abc123/runtime.json\n// Values are JSON-serialised on write and parsed on read.\n// listKeys(prefix) does a recursive walk and filters by prefix.\n// ============================================================================\n\nexport function createFsKvStorage(kvDir: string): KVStorage {\n function keyToPath(key: string): string {\n return path.join(kvDir, ...key.split('/')) + '.json';\n }\n\n function walkKeys(dir: string, relPrefix: string, prefix: string | undefined, results: string[]): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n walkKeys(path.join(dir, entry.name), rel, prefix, results);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.json')) continue;\n const key = rel.replace(/\\.json$/, '');\n if (!prefix || key.startsWith(prefix)) results.push(key);\n }\n }\n\n return {\n read(key: string): unknown | null {\n const p = keyToPath(key);\n if (!fs.existsSync(p)) return null;\n try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }\n },\n\n write(key: string, value: unknown): void {\n const p = keyToPath(key);\n const tmp = `${p}.${process.pid}.${randomUUID()}.tmp`;\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(tmp, JSON.stringify(value, null, 2), 'utf-8');\n renameSync(tmp, p);\n },\n\n delete(key: string): void {\n const p = keyToPath(key);\n try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* best-effort */ }\n },\n\n listKeys(prefix?: string): string[] {\n const results: string[] = [];\n walkKeys(kvDir, '', prefix, results);\n return results.sort();\n },\n };\n}\n\n// ============================================================================\n// FsJournalStorage\n//\n// Each entry is a JSON line: { \"id\": \"<uuid>\", \"payload\": <any> }\n// readAfter(cursor) returns all entries after the entry with id === cursor.\n// A null/empty cursor returns all entries from the beginning.\n// ============================================================================\n\nexport function createFsJournalStorage(journalPath: string): JournalStorage {\n function readLines(): JournalEntry[] {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map(l => JSON.parse(l) as JournalEntry);\n }\n\n return {\n append(payload: unknown): JournalEntry {\n const entry: JournalEntry = { id: randomUUID(), payload };\n fs.mkdirSync(path.dirname(journalPath), { recursive: true });\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n return entry;\n },\n\n readAll(): JournalEntry[] {\n return readLines();\n },\n\n readAfter(cursor: string | null): JournalReadResult {\n const all = readLines();\n if (!cursor) {\n return { entries: all, newCursor: all.length > 0 ? all[all.length - 1].id : null };\n }\n const idx = all.findIndex(e => e.id === cursor);\n const entries = idx === -1 ? all : all.slice(idx + 1);\n return {\n entries,\n newCursor: entries.length > 0 ? entries[entries.length - 1].id : cursor,\n };\n },\n };\n}\n\n// ============================================================================\n// createFsStorageProvider\n//\n// Convenience factory that wires up all three fs adapters under a board directory:\n// blob → boardDir (card/source blobs resolved relative to boardDir)\n// kv → boardDir/.kv/\n// journal → boardDir/<journalFile>\n// ============================================================================\n\n// ============================================================================\n// computeStableJsonHash — canonical content hash for any value\n//\n// Used by card-commands to dedup upserts without needing node:crypto at the\n// pure-logic layer.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nexport function computeStableJsonHash(value: unknown): string {\n return createHash('sha256').update(stableJson(value)).digest('hex');\n}\n\n// ============================================================================\n// createFsJsonStorage — KVStorage with JSON-aware merge and patch operations\n// ============================================================================\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createFsJsonStorage(kvDir: string): JSONStorage {\n const kv = createFsKvStorage(kvDir);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix?) => kv.listKeys(prefix),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createFsJournalStorageAdapter — JournalStorageAdapter backed by a JSONL file\n// ============================================================================\n\nexport function createFsJournalStorageAdapter(boardDir: string): {\n readAllEntries(): { id: string; event: GraphEvent }[];\n appendEntry(entry: { id: string; event: GraphEvent }): void;\n generateId(): string;\n} {\n const journalPath = path.join(boardDir, 'board-journal.jsonl');\n return {\n readAllEntries() {\n if (!fs.existsSync(journalPath)) return [];\n const content = fs.readFileSync(journalPath, 'utf-8').trim();\n if (!content) return [];\n return content.split('\\n').filter(Boolean).map((l) => JSON.parse(l) as { id: string; event: GraphEvent });\n },\n appendEntry(entry) {\n fs.appendFileSync(journalPath, JSON.stringify(entry) + '\\n', 'utf-8');\n },\n generateId() { return randomUUID(); },\n };\n}\n\nexport function createFsStorageProvider(boardDir: string, journalFile: string): StorageProvider {\n return {\n blob: createFsBlobStorage(boardDir),\n kv: createFsKvStorage(path.join(boardDir, '.kv')),\n journal: createFsJournalStorage(path.join(boardDir, journalFile)),\n };\n}\n\n/**\n * FS implementation of AtomicRelayLock.\n * Uses proper-lockfile on the given file path as the lock target.\n * tryAcquire() is non-blocking (retries: 0) — returns null immediately if busy.\n */\nexport function createFsAtomicRelayLock(lockTargetPath: string): AtomicRelayLock {\n return {\n tryAcquire() {\n try {\n // proper-lockfile requires the target file to exist before locking.\n if (!fs.existsSync(lockTargetPath)) {\n fs.mkdirSync(path.dirname(lockTargetPath), { recursive: true });\n try { fs.writeFileSync(lockTargetPath, '{}', { flag: 'wx' }); } catch { /* race: another init won */ }\n }\n return lockSync(lockTargetPath, { retries: 0 });\n } catch {\n return null;\n }\n },\n };\n}\n\n// ============================================================================\n// createFsCardStorageAdapter — KV-backed card storage\n// kvDir is the KV storage directory (used directly, no hidden subdirectory added).\n// ============================================================================\n\nexport function createFsCardStorageAdapter(kvDir: string): {\n readIndex(): CardIndex | null;\n writeIndex(index: CardIndex): void;\n readCard(key: string): LiveCard | null;\n writeCard(key: string, card: LiveCard): string;\n cardExists(key: string): boolean;\n defaultCardKey(cardId: string): string;\n} {\n const json = createFsJsonStorage(kvDir);\n return {\n readIndex() {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index) {\n json.write('_index', index);\n },\n readCard(id) {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id, card) {\n json.write(id, card);\n return computeStableJsonHash(card);\n },\n cardExists(id) {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId) {\n return cardId;\n },\n };\n}\n\n// ============================================================================\n// createFsStateSnapshotStorageAdapter — KV-backed state snapshot storage\n// Each key stored under <scopeDir>/.state-snapshot/\n// ============================================================================\n\nexport function createFsStateSnapshotStorageAdapter(): StateSnapshotStorageAdapter {\n return {\n readValues(scopeDir: string): StateSnapshotReadView {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n const keys = kv.listKeys().sort();\n if (keys.length === 0) return { version: null, values: {} };\n const values: Record<string, unknown> = {};\n for (const key of keys) values[key] = kv.read(key);\n return { version: computeStableJsonHash(values), values };\n },\n writeValues(scopeDir: string, nextValues: Record<string, unknown>, deletedKeys: string[]): string {\n const kv = createFsKvStorage(path.join(scopeDir, '.state-snapshot'));\n for (const key of deletedKeys) kv.delete(key);\n for (const [key, value] of Object.entries(nextValues)) kv.write(key, value);\n return computeStableJsonHash(nextValues);\n },\n };\n}\n","/**\n * artifacts-store-lib.ts\n *\n * Backend-neutral artifact store built on BlobStorage.\n * Supports text + binary content, CRUD, listing, and lightweight metadata.\n */\n\nimport type { BlobStat, BlobStorage } from './storage-interface.js';\n\nexport interface ArtifactInfo {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface ArtifactsStore {\n exists(key: string): boolean;\n putText(key: string, content: string, contentType?: string): ArtifactInfo;\n putBytes(key: string, content: Uint8Array, contentType?: string): ArtifactInfo;\n getText(key: string): string | null;\n getBytes(key: string): Uint8Array | null;\n head(key: string): ArtifactInfo | null;\n list(prefix?: string): ArtifactInfo[];\n remove(key: string): void;\n}\n\nexport interface ChatIndexRecord {\n serial: number;\n role: string;\n stored_name: string;\n path: string;\n updated_at?: string | null;\n}\n\nexport interface ChatRecord extends ChatIndexRecord {\n text: string;\n}\n\nexport interface ChatSignal {\n count: number;\n latest_mtime_ms: number;\n processing: boolean;\n}\n\nexport interface ChatArtifactsStore {\n indexKey(cardPrefix: string): string;\n loadIndex(cardPrefix: string): ChatIndexRecord[];\n saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void;\n nextSerial(cardPrefix: string): number;\n appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void;\n readRecords(cardPrefix: string): ChatRecord[];\n clear(cardPrefix: string): void;\n readSignal(cardPrefix: string): ChatSignal;\n}\n\nexport interface FileArtifactsStore {\n nextSerial(cardPrefix: string, seedNames?: string[]): number;\n buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string;\n allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string;\n}\n\nexport interface CardFileMetadata {\n name: string;\n stored_name: string;\n size: number | null;\n mime_type: string | null;\n path: string | null;\n uploaded_at: string | null;\n}\n\nexport type CardFileLookupResult =\n | { ok: true; file: CardFileMetadata }\n | { ok: false; reason: 'index_out_of_range' | 'missing_stored_name' | 'stale_reference' };\n\nexport interface CardFileMetadataStore {\n read(cardData: unknown): CardFileMetadata[];\n normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[];\n merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[];\n resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult;\n}\n\nconst INDEX_KEY = '.artifacts-index.json';\n\ninterface ArtifactIndexEntry {\n key: string;\n size?: number;\n updatedAt?: string;\n contentType?: string;\n}\n\ninterface ArtifactIndex {\n entries: Record<string, ArtifactIndexEntry>;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction utf8ByteLength(text: string): number {\n return new TextEncoder().encode(text).byteLength;\n}\n\nfunction loadIndex(blob: BlobStorage): ArtifactIndex {\n const raw = blob.read(INDEX_KEY);\n if (!raw) return { entries: {} };\n try {\n const parsed = JSON.parse(raw) as ArtifactIndex;\n if (parsed && parsed.entries && typeof parsed.entries === 'object') return parsed;\n } catch {\n // fall through\n }\n return { entries: {} };\n}\n\nfunction saveIndex(blob: BlobStorage, index: ArtifactIndex): void {\n blob.write(INDEX_KEY, JSON.stringify(index, null, 2));\n}\n\nfunction statToInfo(stat: BlobStat | null): ArtifactInfo | null {\n if (!stat) return null;\n return {\n key: stat.key,\n size: stat.size,\n updatedAt: stat.updatedAt,\n contentType: stat.contentType,\n };\n}\n\nfunction updateIndex(index: ArtifactIndex, key: string, info: ArtifactInfo): void {\n index.entries[key] = {\n key,\n size: info.size,\n updatedAt: info.updatedAt,\n contentType: info.contentType,\n };\n}\n\nfunction parseLeadingSerial(fileName: string): number {\n const m = String(fileName || '').match(/^(\\d+)[-_]/);\n return m ? parseInt(m[1], 10) : 0;\n}\n\nfunction normalizeDisplayFileName(name: string): string {\n const input = String(name || '').trim();\n if (!input) return 'upload.bin';\n const slash = Math.max(input.lastIndexOf('/'), input.lastIndexOf('\\\\'));\n const base = slash >= 0 ? input.slice(slash + 1) : input;\n return base || 'upload.bin';\n}\n\nfunction normalizeStem(rawStem: string): string {\n const normalized = String(rawStem || '')\n .toLowerCase()\n .replace(/\\s+/g, '_')\n .replace(/[^a-z0-9_-]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '');\n return normalized || 'file';\n}\n\nfunction normalizeExt(rawExt: string): string {\n if (!rawExt || rawExt === '.') return '';\n const extBody = String(rawExt).replace(/^\\./, '').toLowerCase().replace(/[^a-z0-9]/g, '');\n return extBody ? `.${extBody}` : '';\n}\n\nfunction splitBaseExt(name: string): { stem: string; ext: string } {\n const base = normalizeDisplayFileName(name);\n const dot = base.lastIndexOf('.');\n if (dot <= 0 || dot === base.length - 1) return { stem: base, ext: '' };\n return { stem: base.slice(0, dot), ext: base.slice(dot) };\n}\n\nfunction basenameFromKey(key: string): string {\n const slash = key.lastIndexOf('/');\n return slash >= 0 ? key.slice(slash + 1) : key;\n}\n\nexport function createArtifactsStore(blob: BlobStorage): ArtifactsStore {\n function head(key: string): ArtifactInfo | null {\n const fromStat = blob.stat ? statToInfo(blob.stat(key)) : null;\n if (fromStat) return fromStat;\n\n const index = loadIndex(blob);\n const entry = index.entries[key];\n if (entry) return { ...entry };\n\n if (!blob.exists(key)) return null;\n const content = blob.read(key);\n if (content === null) return { key };\n return {\n key,\n size: utf8ByteLength(content),\n };\n }\n\n return {\n exists(key: string): boolean {\n return blob.exists(key);\n },\n\n putText(key: string, content: string, contentType = 'text/plain; charset=utf-8'): ArtifactInfo {\n blob.write(key, content);\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? utf8ByteLength(content);\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n putBytes(key: string, content: Uint8Array, contentType = 'application/octet-stream'): ArtifactInfo {\n if (blob.writeBytes) {\n blob.writeBytes(key, content);\n } else {\n // Fallback for text-only backends.\n const envelope = JSON.stringify({ __kind: 'bytes-array', data: [...content] });\n blob.write(key, envelope);\n }\n const info = head(key) ?? { key };\n info.contentType = contentType;\n info.updatedAt = info.updatedAt ?? nowIso();\n info.size = info.size ?? content.byteLength;\n const index = loadIndex(blob);\n updateIndex(index, key, info);\n saveIndex(blob, index);\n return info;\n },\n\n getText(key: string): string | null {\n const raw = blob.read(key);\n if (raw === null) {\n if (!blob.readBytes) return null;\n const bytes = blob.readBytes(key);\n if (bytes === null) return null;\n return Buffer.from(bytes).toString('utf-8');\n }\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new TextDecoder('utf-8').decode(new Uint8Array(parsed.data));\n }\n } catch {\n // plain text path\n }\n return raw;\n },\n\n getBytes(key: string): Uint8Array | null {\n if (blob.readBytes) {\n const bytes = blob.readBytes(key);\n if (bytes !== null) return bytes;\n }\n const raw = blob.read(key);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: number[] };\n if (parsed && parsed.__kind === 'bytes-array' && Array.isArray(parsed.data)) {\n return new Uint8Array(parsed.data);\n }\n } catch {\n // plain text path\n }\n return new TextEncoder().encode(raw);\n },\n\n head,\n\n list(prefix = ''): ArtifactInfo[] {\n const infoByKey = new Map<string, ArtifactInfo>();\n\n if (blob.listKeys) {\n for (const key of blob.listKeys(prefix)) {\n if (key === INDEX_KEY) continue;\n const info = head(key) ?? { key };\n infoByKey.set(key, info);\n }\n }\n\n const index = loadIndex(blob);\n for (const [key, entry] of Object.entries(index.entries)) {\n if (key === INDEX_KEY || (prefix && !key.startsWith(prefix))) continue;\n if (!infoByKey.has(key)) infoByKey.set(key, { ...entry });\n }\n\n return [...infoByKey.values()].sort((a, b) => a.key.localeCompare(b.key));\n },\n\n remove(key: string): void {\n blob.remove(key);\n const index = loadIndex(blob);\n delete index.entries[key];\n saveIndex(blob, index);\n },\n };\n}\n\nexport function createChatArtifactsStore(\n store: ArtifactsStore,\n opts?: { indexFileName?: string },\n): ChatArtifactsStore {\n const indexFileName = opts?.indexFileName || '.index.json';\n\n function indexKey(cardPrefix: string): string {\n return `${cardPrefix}/${indexFileName}`;\n }\n\n function loadIndex(cardPrefix: string): ChatIndexRecord[] {\n const raw = store.getText(indexKey(cardPrefix));\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed\n .filter((row) => row && typeof row.stored_name === 'string')\n .map((row) => ({\n serial: Number(row.serial || parseLeadingSerial(String(row.stored_name)) || 0),\n role: String(row.role || 'system').toLowerCase(),\n stored_name: String(row.stored_name),\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${String(row.stored_name)}`,\n updated_at: typeof row.updated_at === 'string' ? row.updated_at : null,\n }));\n } catch {\n return [];\n }\n }\n\n function saveIndex(cardPrefix: string, records: ChatIndexRecord[]): void {\n store.putText(indexKey(cardPrefix), JSON.stringify(records, null, 2), 'application/json; charset=utf-8');\n }\n\n function nextSerial(cardPrefix: string): number {\n const index = loadIndex(cardPrefix);\n let maxSeen = 0;\n for (const row of index) {\n const serial = Number(row.serial || 0);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function appendIndexRecord(cardPrefix: string, record: ChatIndexRecord): void {\n const index = loadIndex(cardPrefix);\n index.push(record);\n saveIndex(cardPrefix, index);\n }\n\n function readRecords(cardPrefix: string): ChatRecord[] {\n const index = loadIndex(cardPrefix);\n const out: ChatRecord[] = [];\n for (const row of index) {\n const key = `${cardPrefix}/${row.stored_name}`;\n const text = store.getText(key);\n if (text === null) continue;\n out.push({\n serial: Number(row.serial || parseLeadingSerial(row.stored_name) || 0),\n role: String(row.role || 'system').toLowerCase(),\n text,\n path: typeof row.path === 'string' ? row.path : `${cardPrefix}/chats/${row.stored_name}`,\n stored_name: row.stored_name,\n updated_at: row.updated_at || null,\n });\n }\n out.sort((a, b) => a.serial - b.serial || a.stored_name.localeCompare(b.stored_name));\n return out;\n }\n\n function clear(cardPrefix: string): void {\n const prefix = `${cardPrefix}/`;\n for (const entry of store.list(prefix)) store.remove(entry.key);\n }\n\n function readSignal(cardPrefix: string): ChatSignal {\n const prefix = `${cardPrefix}/`;\n const entries = store.list(prefix);\n let count = 0;\n let latestMtimeMs = 0;\n let processing = false;\n for (const entry of entries) {\n const name = entry.key.slice(prefix.length);\n if (name === '.processing') {\n processing = true;\n continue;\n }\n if (!/^(\\d+)[-_]([a-z0-9_-]+)\\.txt$/i.test(name)) continue;\n count += 1;\n const mtimeMs = entry.updatedAt ? Number(new Date(entry.updatedAt).getTime() || 0) : 0;\n if (mtimeMs > latestMtimeMs) latestMtimeMs = mtimeMs;\n }\n return { count, latest_mtime_ms: latestMtimeMs, processing };\n }\n\n return {\n indexKey,\n loadIndex,\n saveIndex,\n nextSerial,\n appendIndexRecord,\n readRecords,\n clear,\n readSignal,\n };\n}\n\nexport function createFileArtifactsStore(store: ArtifactsStore): FileArtifactsStore {\n function nextSerial(cardPrefix: string, seedNames?: string[]): number {\n let maxSeen = 0;\n const names: string[] = [];\n if (Array.isArray(seedNames)) names.push(...seedNames);\n for (const entry of store.list(`${cardPrefix}/`)) {\n names.push(basenameFromKey(entry.key));\n }\n for (const name of names) {\n const serial = parseLeadingSerial(name);\n if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;\n }\n return maxSeen + 1;\n }\n\n function buildStoredName(displayName: string, serial: number, opts?: { maxLen?: number }): string {\n const maxLen = Number(opts?.maxLen || 32);\n const { stem, ext } = splitBaseExt(displayName);\n const safeExt = normalizeExt(ext);\n const safeStem = normalizeStem(stem);\n const prefix = `${String(serial).padStart(3, '0')}-`;\n\n let keepExt = safeExt;\n let stemBudget = maxLen - prefix.length - keepExt.length;\n if (stemBudget < 1) {\n keepExt = '';\n stemBudget = maxLen - prefix.length;\n }\n\n const outStem = safeStem.slice(0, Math.max(1, stemBudget));\n let out = `${prefix}${outStem}${keepExt}`;\n if (out.length > maxLen) out = out.slice(0, maxLen).replace(/\\.$/, '');\n return out;\n }\n\n function allocateStoredName(cardPrefix: string, displayName: string, opts?: { seedNames?: string[]; maxLen?: number }): string {\n let serial = nextSerial(cardPrefix, opts?.seedNames);\n let out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n while (store.exists(`${cardPrefix}/${out}`)) {\n serial += 1;\n out = buildStoredName(displayName, serial, { maxLen: opts?.maxLen });\n }\n return out;\n }\n\n return {\n nextSerial,\n buildStoredName,\n allocateStoredName,\n };\n}\n\nexport function createCardFileMetadataStore(): CardFileMetadataStore {\n function normalizeIncoming(payloadFiles: unknown, defaultUploadedAt?: string): CardFileMetadata[] {\n if (!Array.isArray(payloadFiles)) return [];\n const out: CardFileMetadata[] = [];\n for (const raw of payloadFiles) {\n if (!raw || typeof raw !== 'object') continue;\n const row = raw as Record<string, unknown>;\n if (typeof row.stored_name !== 'string') continue;\n out.push({\n name: typeof row.name === 'string' ? row.name : row.stored_name,\n stored_name: row.stored_name,\n size: typeof row.size === 'number' && Number.isFinite(row.size) ? row.size : null,\n mime_type: typeof row.mime_type === 'string' ? row.mime_type : null,\n path: typeof row.path === 'string' ? row.path : null,\n uploaded_at: typeof row.uploaded_at === 'string' ? row.uploaded_at : (defaultUploadedAt || null),\n });\n }\n return out;\n }\n\n function read(cardData: unknown): CardFileMetadata[] {\n if (!cardData || typeof cardData !== 'object') return [];\n const row = cardData as Record<string, unknown>;\n return normalizeIncoming(row.files, undefined);\n }\n\n function merge(cardData: Record<string, unknown>, incoming: CardFileMetadata[]): CardFileMetadata[] {\n const existing = read(cardData);\n if (incoming.length === 0) {\n cardData.files = existing;\n return existing;\n }\n const known = new Set(existing.map((f) => f.stored_name));\n for (const file of incoming) {\n if (known.has(file.stored_name)) continue;\n existing.push(file);\n known.add(file.stored_name);\n }\n cardData.files = existing;\n return existing;\n }\n\n function resolve(cardData: unknown, index: number, expectedStoredName?: string | null): CardFileLookupResult {\n const files = read(cardData);\n if (!Number.isInteger(index) || index < 0 || index >= files.length) {\n return { ok: false, reason: 'index_out_of_range' };\n }\n const file = files[index];\n if (!file || !file.stored_name) return { ok: false, reason: 'missing_stored_name' };\n if (expectedStoredName && expectedStoredName !== file.stored_name) {\n return { ok: false, reason: 'stale_reference' };\n }\n return { ok: true, file };\n }\n\n return {\n read,\n normalizeIncoming,\n merge,\n resolve,\n };\n}\n","/**\n * artifacts-store-lib-public.ts\n *\n * Public API wrapper for ArtifactsStore, following CommandInput/CommandResult.\n */\n\nimport type { CommandInput, CommandResult } from './board-live-cards-public.js';\nimport type { ArtifactInfo, ArtifactsStore } from './artifacts-store-lib.js';\n\nexport interface ArtifactsStorePublic {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }>;\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }>;\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }>;\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }>;\n del(input: CommandInput): CommandResult<{ ok: true }>;\n}\n\nexport function createArtifactsStorePublic(store: ArtifactsStore): ArtifactsStorePublic {\n function ok<T>(data: T): CommandResult<T> {\n return { status: 'success', data } as CommandResult<T>;\n }\n function fail<T>(error: string): CommandResult<T> {\n return { status: 'fail', error } as CommandResult<T>;\n }\n function oops<T>(e: unknown): CommandResult<T> {\n return { status: 'error', error: e instanceof Error ? e.message : String(e) } as CommandResult<T>;\n }\n\n return {\n list(input: CommandInput): CommandResult<{ artifacts: ArtifactInfo[] }> {\n try {\n const prefix = (input.params?.['prefix'] as string | undefined) ?? '';\n return ok({ artifacts: store.list(prefix) });\n } catch (e) { return oops(e); }\n },\n\n head(input: CommandInput): CommandResult<{ artifact: ArtifactInfo | null }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('head requires params.key');\n return ok({ artifact: store.head(key) });\n } catch (e) { return oops(e); }\n },\n\n put(input: CommandInput): CommandResult<{ artifact: ArtifactInfo }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const contentType = input.params?.['contentType'] as string | undefined;\n if (!key) return fail('put requires params.key');\n\n const body = input.body;\n if (typeof body === 'string') {\n return ok({ artifact: store.putText(key, body, contentType) });\n }\n\n if (body && typeof body === 'object' && typeof (body as { text?: unknown }).text === 'string') {\n return ok({ artifact: store.putText(key, (body as { text: string }).text, contentType) });\n }\n\n if (body && typeof body === 'object' && Array.isArray((body as { bytes?: unknown }).bytes)) {\n const byteValues = (body as { bytes: number[] }).bytes;\n const bytes = new Uint8Array(byteValues.map((n) => Math.max(0, Math.min(255, Number(n) || 0))));\n return ok({ artifact: store.putBytes(key, bytes, contentType) });\n }\n\n return fail('put requires body as string, {text}, or {bytes:number[]}');\n } catch (e) { return oops(e); }\n },\n\n get(input: CommandInput): CommandResult<{ key: string; contentType?: string; size?: number; text?: string; bytes?: number[] }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n const as = (input.params?.['as'] as string | undefined) ?? 'base64';\n if (!key) return fail('get requires params.key');\n\n const head = store.head(key);\n if (!head) return fail(`artifact \"${key}\" not found`);\n\n if (as === 'text') {\n const text = store.getText(key);\n if (text === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, text });\n }\n\n const bytes = store.getBytes(key);\n if (bytes === null) return fail(`artifact \"${key}\" not found`);\n return ok({ key, contentType: head.contentType, size: head.size, bytes: [...bytes] });\n } catch (e) { return oops(e); }\n },\n\n del(input: CommandInput): CommandResult<{ ok: true }> {\n try {\n const key = input.params?.['key'] as string | undefined;\n if (!key) return fail('del requires params.key');\n store.remove(key);\n return ok({ ok: true });\n } catch (e) { return oops(e); }\n },\n };\n}\n","/**\n * process-runner.ts — Single source of truth for child process execution.\n *\n * All CLI execution paths (task-executor, source.cli, inference-adapter,\n * detached background workers) route through these helpers.\n *\n * DESIGN:\n * - CommandSpec is the structured command form: { command, args, cwd, env, timeoutMs }\n * - runSync / runAsync use execFileSync / execFile (no ambient shell)\n * - runDetached handles OS differences in one place\n * - parseCommandSpec reads both legacy string form and new { command, args } form\n *\n * WHY NO SHELL BY DEFAULT:\n * - Shell interpretation is platform-dependent (cmd.exe vs /bin/sh vs bash)\n * - Shell parsing of argument strings is fragile and platform-fragile\n * - execFile / execFileSync avoids all quoting and escaping issues\n *\n * BACKWARD COMPAT:\n * - parseCommandSpec(\"node my-tool.js --flag\") → { command: process.execPath, args: ['my-tool.js', '--flag'] }\n * - Legacy .task-executor / .inference-adapter / source.cli string values still load correctly\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as net from 'node:net';\nimport { fileURLToPath } from 'node:url';\nimport { execFileSync, execFile, spawn } from 'node:child_process';\nimport { randomUUID, createHash } from 'node:crypto';\n\nimport type { CommandSpec } from '../../continuous-event-graph/handlers.js';\nimport type { KindValueRef } from '../common/storage-interface.js';\nimport { serializeRef } from '../common/storage-interface.js';\nexport type { CommandSpec };\n\n// ============================================================================\n// makeBoardTempFilePath — board-scoped temp file path for external process handoff\n// ============================================================================\n\n/**\n * Return a unique file path under `<boardDir>/.tmp/` suitable for passing\n * to an external binary (task-executor, inference-adapter) as `--in`, `--out`,\n * or `--err` arguments.\n *\n * - Files are co-located with the board they belong to (not global os.tmpdir()).\n * - The `.tmp/` directory is created on demand.\n * - The file itself is NOT created here — the caller writes it before use.\n * - `ext` defaults to `.json`; use `.txt` for plain-text error files.\n */\nexport function makeBoardTempFilePath(boardDir: string, label: string, ext = '.json'): string {\n const tmpDir = path.join(boardDir, '.tmp');\n fs.mkdirSync(tmpDir, { recursive: true });\n return path.join(tmpDir, `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`);\n}\n\n/** Join path segments — thin wrapper so callers don't need to import node:path. */\nexport function joinPath(...segments: string[]): string { return path.join(...segments); }\n\n/** Resolve a path to absolute — thin wrapper so callers don't need to import node:path. */\nexport function resolvePath(...segments: string[]): string { return path.resolve(...segments); }\n\n/** Return the directory name of a path. */\nexport function dirnamePath(p: string): string { return path.dirname(p); }\n\n/** Return true if the path is absolute. */\nexport function isAbsolutePath(p: string): boolean { return path.isAbsolute(p); }\n\n/** Generate a new random UUID. */\nexport function genUUID(): string { return randomUUID(); }\n\n/** SHA-256 hex hash of a string. */\nexport function getHash(x: string): string { return createHash('sha256').update(x).digest('hex'); }\n\n/** Resolve the directory of an ESM module from its import.meta.url. */\nexport function resolveModuleDir(importMetaUrl: string): string { return path.dirname(fileURLToPath(importMetaUrl)); }\n\n// ============================================================================\n// parseCommandSpec — legacy string or structured CommandSpec → normalized form\n// ============================================================================\n\n/**\n * Parse a legacy string command or pass through a structured CommandSpec.\n *\n * - Legacy string: \"node script.js --flag value\"\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * - Structured: { command: 'node', args: ['script.js', '--flag', 'value'] }\n * → { command: process.execPath, args: ['script.js', '--flag', 'value'] }\n *\n * After parsing, 'node'/'node.exe' is resolved to process.execPath, and bare\n * '.js'/'.mjs' paths are wrapped in a node invocation.\n */\nexport function parseCommandSpec(raw: string | CommandSpec): CommandSpec {\n if (typeof raw === 'object' && raw !== null) {\n const { command, args = [], ...rest } = raw;\n const resolved = _resolveNode(command, args);\n return { ...rest, command: resolved.command, args: resolved.args };\n }\n const parts = splitCommandLine(raw);\n if (parts.length === 0) throw new Error(`Empty command spec: ${JSON.stringify(raw)}`);\n return _resolveNode(parts[0], parts.slice(1));\n}\n\nfunction _resolveNode(cmd: string, args: string[]): { command: string; args: string[] } {\n if (/^(node|node\\.exe)$/i.test(cmd)) return { command: process.execPath, args };\n if (/\\.m?js$/i.test(cmd)) return { command: process.execPath, args: [cmd, ...args] };\n return { command: cmd, args };\n}\n\n// ============================================================================\n// splitCommandLine — shell-style string splitting (legacy compat only)\n// ============================================================================\n\n/**\n * Split a shell-style command string into tokens, respecting single/double quotes.\n * Used only for backward-compat parsing of legacy string-format config values.\n */\nexport function splitCommandLine(command: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let quote: '\"' | '\\'' | null = null;\n\n for (const ch of command.trim()) {\n if (quote) {\n if (ch === quote) { quote = null; } else { current += ch; }\n continue;\n }\n if (ch === '\"' || ch === '\\'') { quote = ch; continue; }\n if (/\\s/.test(ch)) {\n if (current) { tokens.push(current); current = ''; }\n continue;\n }\n current += ch;\n }\n\n if (quote) throw new Error(`Unterminated quote in command: ${command}`);\n if (current) tokens.push(current);\n return tokens;\n}\n\n// ============================================================================\n// .cmd/.bat on Windows needs shell: true\n// ============================================================================\n\nfunction _needsWindowsShell(cmd: string): boolean {\n return process.platform === 'win32' && /\\.(cmd|bat)$/i.test(cmd);\n}\n\n// ============================================================================\n// runSync — synchronous process execution\n// ============================================================================\n\n/**\n * Run a command synchronously and return stdout as a string.\n * Uses execFileSync — no ambient shell. Safe on all platforms.\n */\nexport function runSync(spec: CommandSpec, options?: { encoding?: BufferEncoding; input?: string }): string {\n const { command, args = [], cwd, env, timeoutMs } = spec;\n const output = execFileSync(command, args, {\n shell: _needsWindowsShell(command),\n timeout: timeoutMs,\n encoding: options?.encoding ?? 'utf-8',\n cwd,\n windowsHide: true,\n env: env ? { ...process.env, ...env } : undefined,\n input: options?.input,\n });\n return output as string;\n}\n\n// ============================================================================\n// runAsync — async process execution with callback\n// ============================================================================\n\n/**\n * Run a command asynchronously, calling back with (err, stdout, stderr).\n * Uses execFile — no ambient shell. Safe on all platforms.\n */\nexport function runAsync(\n spec: CommandSpec,\n callback: (err: Error | null, stdout: string, stderr: string) => void,\n): void {\n const { command, args = [], cwd, env, timeoutMs = 30_000 } = spec;\n execFile(\n command,\n args,\n {\n shell: _needsWindowsShell(command),\n encoding: 'utf8',\n windowsHide: true,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n cwd,\n env: env ? { ...process.env, ...env } : undefined,\n },\n (err, stdout, stderr) => callback(err ?? null, stdout, stderr),\n );\n}\n\n// ============================================================================\n// Git Bash detection (Windows only — needed for runDetached)\n// ============================================================================\n\nlet _gitBashPath: string | false | undefined;\nconst _GIT_BASH_CACHE = path.join(os.tmpdir(), '.board-live-cards-git-bash-cache.json');\n\nexport function findGitBash(): string | false {\n if (_gitBashPath !== undefined) return _gitBashPath;\n if (process.platform !== 'win32') return (_gitBashPath = false);\n\n try {\n const cached = JSON.parse(fs.readFileSync(_GIT_BASH_CACHE, 'utf8')) as { path: string | false };\n if (cached.path === false || (typeof cached.path === 'string' && fs.existsSync(cached.path))) {\n return (_gitBashPath = cached.path);\n }\n } catch { /* cache miss */ }\n\n const candidates: Array<string | undefined> = [\n process.env.SHELL,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'usr', 'bin', 'bash.exe')\n : undefined,\n process.env.PROGRAMFILES\n ? path.join(process.env.PROGRAMFILES, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env['PROGRAMFILES(X86)']\n ? path.join(process.env['PROGRAMFILES(X86)']!, 'Git', 'bin', 'bash.exe')\n : undefined,\n process.env.LOCALAPPDATA\n ? path.join(process.env.LOCALAPPDATA, 'Programs', 'Git', 'bin', 'bash.exe')\n : undefined,\n ];\n\n for (const c of candidates) {\n if (c && /bash(\\.exe)?$/i.test(c) && fs.existsSync(c)) {\n _gitBashPath = c;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: c })); } catch { /* best-effort */ }\n return _gitBashPath;\n }\n }\n\n _gitBashPath = false;\n try { fs.writeFileSync(_GIT_BASH_CACHE, JSON.stringify({ path: false })); } catch { /* best-effort */ }\n return _gitBashPath;\n}\n\nfunction _shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n// ============================================================================\n// runDetached — fire-and-forget background spawn\n// ============================================================================\n\n/**\n * Spawn a detached background process that survives parent exit.\n * Handles Windows (Git Bash / cmd /c start /b) and Linux/macOS transparently.\n */\nexport function runDetached(spec: CommandSpec): void {\n const { command, args = [] } = spec;\n\n if (process.platform === 'win32') {\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n windowsHide: true,\n shell: _needsWindowsShell(command),\n });\n child.unref();\n return;\n }\n\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.unref();\n}\n\n// ============================================================================\n// buildBoardCliInvocation — resolve how to invoke board-live-cards-cli\n//\n// cliDir is the directory containing board-live-cards-cli.ts / .js.\n// Probe order: compiled .js → tsx dev → npx tsx fallback.\n// ============================================================================\n\n/**\n * Return { cmd, args } that invokes `board-live-cards-cli <command> [...args]`\n * in whatever environment is available (compiled dist, dev tsx, npx fallback).\n *\n * Pass `__dirname` (from the calling file's own directory) as `cliDir`.\n */\nexport function buildBoardCliInvocation(\n cliDir: string,\n command: string,\n args: string[],\n): { cmd: string; args: string[] } {\n const jsPath = path.join(cliDir, 'board-live-cards-cli.js');\n if (fs.existsSync(jsPath)) {\n return { cmd: process.execPath, args: [jsPath, command, ...args] };\n }\n\n const tsPath = path.join(cliDir, 'board-live-cards-cli.ts');\n const tsxMjs = path.join(cliDir, '..', '..', 'node_modules', 'tsx', 'dist', 'cli.mjs');\n const tsxBin = path.join(cliDir, '..', '..', 'node_modules', '.bin', 'tsx');\n const tsx = fs.existsSync(tsxMjs) ? tsxMjs : tsxBin;\n if (fs.existsSync(tsPath) && fs.existsSync(tsx)) {\n return { cmd: process.execPath, args: [tsx, tsPath, command, ...args] };\n }\n\n const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';\n return { cmd: npxCmd, args: ['tsx', tsPath, command, ...args] };\n}\n\n// ============================================================================\n// requestProcessAccumulatedDetached — fire-and-forget dispatch of next drain pass\n// ============================================================================\n\n/**\n * Spawn a detached board-live-cards process-accumulated-events pass for the given board.\n */\nexport function requestProcessAccumulatedDetached(cliDir: string, baseRef: KindValueRef, notifyChannel?: string): void {\n const cliArgs = ['--base-ref', serializeRef(baseRef)];\n if (notifyChannel) cliArgs.push('--notify-channel', notifyChannel);\n const { cmd, args } = buildBoardCliInvocation(cliDir, 'process-accumulated-events', cliArgs);\n runDetached({ command: cmd, args });\n}\n\n// ============================================================================\n// Named-pipe event transport (cross-process board notifications)\n// ============================================================================\n\n/** Return canonical named-pipe/socket path for the given channel name. */\nexport function getNamedPipePath(pipeName: string): string {\n if (process.platform === 'win32') return `\\\\\\\\.\\\\pipe\\\\${pipeName}`;\n return path.join(os.tmpdir(), `${pipeName}.sock`);\n}\n\n/**\n * Publish a batch of JSON notifications as newline-delimited records to a named pipe.\n * Best-effort: if the pipe is unavailable, logs via onWarn and drops the batch.\n *\n * All payloads are concatenated into a single `socket.write()` call so the consumer\n * receives them atomically (no interleaving from other drain cycles).\n * Uses a per-pipeName persistent connection so ordering is preserved across calls.\n */\nconst _pipeClients = new Map<string, { socket: net.Socket; ready: boolean; queue: string[] }>();\n\nexport function publishJsonEventsToNamedPipe(\n pipeName: string,\n payloads: unknown[],\n onWarn?: (msg: string) => void,\n): void {\n if (payloads.length === 0) return;\n const chunk = payloads.map(p => JSON.stringify(p)).join('\\n') + '\\n';\n let entry = _pipeClients.get(pipeName);\n\n if (entry && !entry.socket.destroyed) {\n if (entry.ready) {\n entry.socket.write(chunk);\n } else {\n entry.queue.push(chunk);\n }\n return;\n }\n\n // Create a new persistent connection\n const pipePath = getNamedPipePath(pipeName);\n const socket = net.createConnection(pipePath);\n entry = { socket, ready: false, queue: [chunk] };\n _pipeClients.set(pipeName, entry);\n\n socket.on('connect', () => {\n entry!.ready = true;\n for (const queued of entry!.queue) socket.write(queued);\n entry!.queue.length = 0;\n });\n\n socket.on('error', (e) => {\n onWarn?.(`[named-pipe publish] ${pipePath}: ${e instanceof Error ? e.message : String(e)}`);\n _pipeClients.delete(pipeName);\n });\n\n socket.on('close', () => {\n _pipeClients.delete(pipeName);\n });\n}\n\n// ============================================================================\n// createNodeCommandExecutor — Node implementation of CommandExecutor\n//\n// Wraps runSync / runAsync / runDetached / parseCommandSpec / splitCommandLine\n// into a single injectable object. Pass to command handlers instead of the\n// individual execCommandSync / execCommandAsync / resolveCommandInvocation /\n// splitCommandLine / spawnDetachedCommand dep functions.\n// ============================================================================\n\nimport type { CommandExecutor, ExecOptions } from '../common/process-interface.js';\n\nexport function createNodeCommandExecutor(): CommandExecutor {\n return {\n executeSync(cmd: string, args: string[], options?: ExecOptions): string {\n return runSync(\n { command: cmd, args, cwd: options?.cwd, timeoutMs: options?.timeout, env: options?.env as Record<string, string> | undefined },\n { encoding: options?.encoding as BufferEncoding | undefined, input: options?.input },\n );\n },\n executeAsync(cmd: string, args: string[], callback: (err: Error | null, stdout: string, stderr: string) => void): void {\n runAsync({ command: cmd, args }, callback);\n },\n resolveInvocation(rawCmd: string, rawArgs: string[]): { cmd: string; args: string[] } {\n const spec = parseCommandSpec({ command: rawCmd, args: rawArgs });\n return { cmd: spec.command, args: spec.args ?? [] };\n },\n splitCommand: splitCommandLine,\n spawnDetached(cmd: string, args: string[]): void {\n runDetached({ command: cmd, args });\n },\n };\n}\n","/**\n * artifacts-store-cli.ts\n *\n * Thin arg parser for ArtifactsStore public API.\n */\n\nimport * as fs from 'node:fs';\nimport { parseRef } from '../common/storage-interface.js';\nimport { createFsBlobStorage } from './storage-fs-adapters.js';\nimport { createArtifactsStore } from '../common/artifacts-store-lib.js';\nimport { createArtifactsStorePublic } from '../common/artifacts-store-lib-public.js';\nimport { resolvePath } from './process-runner.js';\n\nfunction requireFlag(args: string[], flag: string, usage: string): string {\n const idx = args.indexOf(flag);\n const val = idx !== -1 ? args[idx + 1] : undefined;\n if (!val) throw new Error(`Missing ${flag}\\nUsage: ${usage}`);\n return val;\n}\n\nfunction optFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag);\n return idx !== -1 ? args[idx + 1] : undefined;\n}\n\nasync function readStdinBytes(): Promise<Uint8Array> {\n const parts: Buffer[] = [];\n for await (const chunk of process.stdin) {\n parts.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array));\n }\n return new Uint8Array(Buffer.concat(parts));\n}\n\nconst HELP = [\n 'artifacts-store — generic artifact CRUD on a blob-backed store',\n '',\n ' artifacts-store put --store-ref <ref> --key <key> [--file <path> | --text <text>] [--content-type <mime>]',\n ' artifacts-store get --store-ref <ref> --key <key> [--out <path>] [--as text|bytes]',\n ' artifacts-store head --store-ref <ref> --key <key>',\n ' artifacts-store list --store-ref <ref> [--prefix <prefix>]',\n ' artifacts-store del --store-ref <ref> --key <key>',\n].join('\\n');\n\nexport async function cli(argv: string[]): Promise<void> {\n const cmd = argv[0];\n const rest = argv.slice(1);\n\n if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {\n console.error(HELP);\n return;\n }\n\n const ref = requireFlag(rest, '--store-ref', `artifacts-store ${cmd} --store-ref <b64-ref>`);\n const root = parseRef(ref).value;\n const store = createArtifactsStorePublic(createArtifactsStore(createFsBlobStorage(root)));\n\n if (cmd === 'put') {\n const key = requireFlag(rest, '--key', 'artifacts-store put --store-ref <ref> --key <key>');\n const contentType = optFlag(rest, '--content-type');\n const filePath = optFlag(rest, '--file');\n const text = optFlag(rest, '--text');\n\n let body: unknown;\n if (filePath) {\n const bytes = new Uint8Array(fs.readFileSync(filePath));\n body = { bytes: [...bytes] };\n } else if (typeof text === 'string') {\n body = { text };\n } else if (!process.stdin.isTTY) {\n const bytes = await readStdinBytes();\n body = { bytes: [...bytes] };\n } else {\n throw new Error('put requires --file, --text, or stdin bytes');\n }\n\n const result = store.put({ params: { key, ...(contentType ? { contentType } : {}) }, body });\n if (result.status !== 'success') throw new Error(result.error || 'put failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'get') {\n const key = requireFlag(rest, '--key', 'artifacts-store get --store-ref <ref> --key <key>');\n const as = (optFlag(rest, '--as') || 'bytes').toLowerCase();\n const outPath = optFlag(rest, '--out');\n const result = store.get({ params: { key, as } });\n if (result.status !== 'success') throw new Error(result.error || 'get failed');\n\n if (as === 'text') {\n const text = result.data.text ?? '';\n if (outPath) fs.writeFileSync(outPath, text, 'utf-8');\n else process.stdout.write(text);\n return;\n }\n\n const bytes = new Uint8Array(result.data.bytes ?? []);\n if (outPath) fs.writeFileSync(outPath, Buffer.from(bytes));\n else process.stdout.write(JSON.stringify({ ...result.data, bytes: undefined, byteLength: bytes.byteLength }, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'head') {\n const key = requireFlag(rest, '--key', 'artifacts-store head --store-ref <ref> --key <key>');\n const result = store.head({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'head failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'list') {\n const prefix = optFlag(rest, '--prefix') || '';\n const result = store.list({ params: prefix ? { prefix } : {} });\n if (result.status !== 'success') throw new Error(result.error || 'list failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n if (cmd === 'del' || cmd === 'delete' || cmd === 'rm') {\n const key = requireFlag(rest, '--key', 'artifacts-store del --store-ref <ref> --key <key>');\n const result = store.del({ params: { key } });\n if (result.status !== 'success') throw new Error(result.error || 'del failed');\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n return;\n }\n\n throw new Error(`Unknown command \"${cmd}\"\\n\\n${HELP}`);\n}\n\nconst isMain = process.argv[1] && resolvePath(process.argv[1]) === resolvePath(new URL(import.meta.url).pathname.replace(/^\\/([A-Z]:)/, '$1'));\nif (isMain) {\n cli(process.argv.slice(2)).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(msg);\n process.exit(1);\n });\n}\n"]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import*as c from'fs';import*as l from'path';import {randomUUID}from'crypto';import'proper-lockfile';import*as B from'os';import'net';import'url';import'child_process';var k="b64:";function J(e){let s=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4),n=globalThis.Buffer;if(n)return n.from(s,"base64").toString("utf8");if(typeof atob=="function"){let o=atob(s),r=new Uint8Array(o.length);for(let t=0;t<o.length;t+=1)r[t]=o.charCodeAt(t);return new TextDecoder().decode(r)}throw new Error("No base64 decoder available in this runtime")}function b(e){if(e.startsWith("::fs-path::"))return {kind:"fs-path",value:e.slice(11)};if(!e.startsWith(k))throw new Error(`Invalid ref format (expected ${k}<base64url(json)>): ${e}`);let s;try{s=JSON.parse(J(e.slice(k.length)));}catch{throw new Error(`Invalid ref format (malformed base64url/json): ${e}`)}if(!s||typeof s!="object")throw new Error(`Invalid ref format (expected object payload): ${e}`);let n=s;if(typeof n.kind!="string"||typeof n.value!="string")throw new Error(`Invalid ref format (payload must contain string kind/value): ${e}`);return {kind:n.kind,value:n.value}}function R(e,s){if(process.platform!=="win32"){c.renameSync(e,s);return}let n=[10,20,40,80,160];for(let o=0;o<=n.length;o++)try{c.renameSync(e,s);return}catch(r){let t=r.code;if((t==="EPERM"||t==="EBUSY")&&o<n.length){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,n[o]);continue}throw r}}function I(e){function s(r){return l.join(e,...r.split("/"))}function n(r){return l.relative(e,r).replace(/\\/g,"/")}function o(r,t){if(c.existsSync(r))for(let i of c.readdirSync(r,{withFileTypes:true})){let a=l.join(r,i.name);if(i.isDirectory()){o(a,t);continue}i.isFile()&&t.push(n(a));}}return {read(r){let t=s(r);if(!c.existsSync(t))return null;try{return c.readFileSync(t,"utf-8")}catch{return null}},write(r,t){let i=s(r),a=`${i}.${process.pid}.${randomUUID()}.tmp`;c.mkdirSync(l.dirname(i),{recursive:true}),c.writeFileSync(a,t,"utf-8"),R(a,i);},exists(r){return c.existsSync(s(r))},remove(r){let t=s(r);try{c.existsSync(t)&&c.unlinkSync(t);}catch{}},readBytes(r){let t=s(r);if(!c.existsSync(t))return null;try{return new Uint8Array(c.readFileSync(t))}catch{return null}},writeBytes(r,t){let i=s(r),a=`${i}.${process.pid}.${randomUUID()}.tmp`;c.mkdirSync(l.dirname(i),{recursive:true}),c.writeFileSync(a,Buffer.from(t)),R(a,i);},listKeys(r){let t=[];o(e,t);let i=t.sort();return r?i.filter(a=>a.startsWith(r)):i},stat(r){let t=s(r);if(!c.existsSync(t))return null;try{let i=c.statSync(t);return {key:r,size:Number(i.size||0),updatedAt:new Date(i.mtimeMs).toISOString()}}catch{return null}}}}var S=".artifacts-index.json";function _(){return new Date().toISOString()}function F(e){return new TextEncoder().encode(e).byteLength}function y(e){let s=e.read(S);if(!s)return {entries:{}};try{let n=JSON.parse(s);if(n&&n.entries&&typeof n.entries=="object")return n}catch{}return {entries:{}}}function A(e,s){e.write(S,JSON.stringify(s,null,2));}function O(e){return e?{key:e.key,size:e.size,updatedAt:e.updatedAt,contentType:e.contentType}:null}function E(e,s,n){e.entries[s]={key:s,size:n.size,updatedAt:n.updatedAt,contentType:n.contentType};}function $(e){function s(n){let o=e.stat?O(e.stat(n)):null;if(o)return o;let t=y(e).entries[n];if(t)return {...t};if(!e.exists(n))return null;let i=e.read(n);return i===null?{key:n}:{key:n,size:F(i)}}return {exists(n){return e.exists(n)},putText(n,o,r="text/plain; charset=utf-8"){e.write(n,o);let t=s(n)??{key:n};t.contentType=r,t.updatedAt=t.updatedAt??_(),t.size=t.size??F(o);let i=y(e);return E(i,n,t),A(e,i),t},putBytes(n,o,r="application/octet-stream"){if(e.writeBytes)e.writeBytes(n,o);else {let a=JSON.stringify({__kind:"bytes-array",data:[...o]});e.write(n,a);}let t=s(n)??{key:n};t.contentType=r,t.updatedAt=t.updatedAt??_(),t.size=t.size??o.byteLength;let i=y(e);return E(i,n,t),A(e,i),t},getText(n){let o=e.read(n);if(o===null){if(!e.readBytes)return null;let r=e.readBytes(n);return r===null?null:Buffer.from(r).toString("utf-8")}try{let r=JSON.parse(o);if(r&&r.__kind==="bytes-array"&&Array.isArray(r.data))return new TextDecoder("utf-8").decode(new Uint8Array(r.data))}catch{}return o},getBytes(n){if(e.readBytes){let r=e.readBytes(n);if(r!==null)return r}let o=e.read(n);if(o===null)return null;try{let r=JSON.parse(o);if(r&&r.__kind==="bytes-array"&&Array.isArray(r.data))return new Uint8Array(r.data)}catch{}return new TextEncoder().encode(o)},head:s,list(n=""){let o=new Map;if(e.listKeys)for(let t of e.listKeys(n)){if(t===S)continue;let i=s(t)??{key:t};o.set(t,i);}let r=y(e);for(let[t,i]of Object.entries(r.entries))t===S||n&&!t.startsWith(n)||o.has(t)||o.set(t,{...i});return [...o.values()].sort((t,i)=>t.key.localeCompare(i.key))},remove(n){e.remove(n);let o=y(e);delete o.entries[n],A(e,o);}}}function j(e){function s(r){return {status:"success",data:r}}function n(r){return {status:"fail",error:r}}function o(r){return {status:"error",error:r instanceof Error?r.message:String(r)}}return {list(r){try{let t=r.params?.prefix??"";return s({artifacts:e.list(t)})}catch(t){return o(t)}},head(r){try{let t=r.params?.key;return t?s({artifact:e.head(t)}):n("head requires params.key")}catch(t){return o(t)}},put(r){try{let t=r.params?.key,i=r.params?.contentType;if(!t)return n("put requires params.key");let a=r.body;if(typeof a=="string")return s({artifact:e.putText(t,a,i)});if(a&&typeof a=="object"&&typeof a.text=="string")return s({artifact:e.putText(t,a.text,i)});if(a&&typeof a=="object"&&Array.isArray(a.bytes)){let d=a.bytes,u=new Uint8Array(d.map(f=>Math.max(0,Math.min(255,Number(f)||0))));return s({artifact:e.putBytes(t,u,i)})}return n("put requires body as string, {text}, or {bytes:number[]}")}catch(t){return o(t)}},get(r){try{let t=r.params?.key,i=r.params?.as??"base64";if(!t)return n("get requires params.key");let a=e.head(t);if(!a)return n(`artifact "${t}" not found`);if(i==="text"){let u=e.getText(t);return u===null?n(`artifact "${t}" not found`):s({key:t,contentType:a.contentType,size:a.size,text:u})}let d=e.getBytes(t);return d===null?n(`artifact "${t}" not found`):s({key:t,contentType:a.contentType,size:a.size,bytes:[...d]})}catch(t){return o(t)}},del(r){try{let t=r.params?.key;return t?(e.remove(t),s({ok:!0})):n("del requires params.key")}catch(t){return o(t)}}}}function v(...e){return l.resolve(...e)}l.join(B.tmpdir(),".board-live-cards-git-bash-cache.json");function h(e,s,n){let o=e.indexOf(s),r=o!==-1?e[o+1]:void 0;if(!r)throw new Error(`Missing ${s}
|
|
2
|
-
Usage: ${n}`);return r}function m(e,s){let n=e.indexOf(s);return n!==-1?e[n+1]:void 0}async function P(){let e=[];for await(let s of process.stdin)e.push(Buffer.isBuffer(s)?s:Buffer.from(s));return new Uint8Array(Buffer.concat(e))}var N=["artifacts-store \u2014 generic artifact CRUD on a blob-backed store",""," artifacts-store put --store-ref <ref> --key <key> [--file <path> | --text <text>] [--content-type <mime>]"," artifacts-store get --store-ref <ref> --key <key> [--out <path>] [--as text|bytes]"," artifacts-store head --store-ref <ref> --key <key>"," artifacts-store list --store-ref <ref> [--prefix <prefix>]"," artifacts-store del --store-ref <ref> --key <key>"].join(`
|
|
3
|
-
`);async function M(e){let s=e[0],n=e.slice(1);if(!s||s==="help"||s==="--help"||s==="-h"){console.error(N);return}let o=h(n,"--store-ref",`artifacts-store ${s} --store-ref <b64-ref>`),r=b(o).value,t=j($(I(r)));if(s==="put"){let i=h(n,"--key","artifacts-store put --store-ref <ref> --key <key>"),a=m(n,"--content-type"),d=m(n,"--file"),u=m(n,"--text"),f;if(d)f={bytes:[...new Uint8Array(c.readFileSync(d))]};else if(typeof u=="string")f={text:u};else if(!process.stdin.isTTY)f={bytes:[...await P()]};else throw new Error("put requires --file, --text, or stdin bytes");let g=t.put({params:{key:i,...a?{contentType:a}:{}},body:f});if(g.status!=="success")throw new Error(g.error||"put failed");process.stdout.write(JSON.stringify(g.data,null,2)+`
|
|
4
|
-
`);return}if(s==="get"){let i=h(n,"--key","artifacts-store get --store-ref <ref> --key <key>"),a=(m(n,"--as")||"bytes").toLowerCase(),d=m(n,"--out"),u=t.get({params:{key:i,as:a}});if(u.status!=="success")throw new Error(u.error||"get failed");if(a==="text"){let g=u.data.text??"";d?c.writeFileSync(d,g,"utf-8"):process.stdout.write(g);return}let f=new Uint8Array(u.data.bytes??[]);d?c.writeFileSync(d,Buffer.from(f)):process.stdout.write(JSON.stringify({...u.data,bytes:void 0,byteLength:f.byteLength},null,2)+`
|
|
5
|
-
`);return}if(s==="head"){let i=h(n,"--key","artifacts-store head --store-ref <ref> --key <key>"),a=t.head({params:{key:i}});if(a.status!=="success")throw new Error(a.error||"head failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
6
|
-
`);return}if(s==="list"){let i=m(n,"--prefix")||"",a=t.list({params:i?{prefix:i}:{}});if(a.status!=="success")throw new Error(a.error||"list failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
7
|
-
`);return}if(s==="del"||s==="delete"||s==="rm"){let i=h(n,"--key","artifacts-store del --store-ref <ref> --key <key>"),a=t.del({params:{key:i}});if(a.status!=="success")throw new Error(a.error||"del failed");process.stdout.write(JSON.stringify(a.data,null,2)+`
|
|
8
|
-
`);return}throw new Error(`Unknown command "${s}"
|
|
9
|
-
|
|
10
|
-
${N}`)}var z=process.argv[1]&&v(process.argv[1])===v(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/,"$1"));z&&M(process.argv.slice(2)).catch(e=>{let s=e instanceof Error?e.message:String(e);console.error(s),process.exit(1);});export{M as cli};//# sourceMappingURL=artifacts-store-cli.js.map
|
|
11
|
-
//# sourceMappingURL=artifacts-store-cli.js.map
|