yaml-flow 3.1.0 → 4.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/README.md +81 -20
- package/board-live-cards-cli.js +37 -0
- package/browser/card-compute.js +132 -431
- package/browser/live-cards.js +41 -27
- package/browser/live-cards.schema.json +59 -77
- package/dist/card-compute/index.cjs +135 -415
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +52 -49
- package/dist/card-compute/index.d.ts +52 -49
- package/dist/card-compute/index.js +134 -415
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +2379 -0
- package/dist/cli/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/board-live-cards-cli.d.cts +213 -0
- package/dist/cli/board-live-cards-cli.d.ts +213 -0
- package/dist/cli/board-live-cards-cli.js +2332 -0
- package/dist/cli/board-live-cards-cli.js.map +1 -0
- package/dist/{constants-B2zqu10b.d.ts → constants-DuzE5n03.d.ts} +2 -2
- package/dist/{constants-DJZU1pwJ.d.cts → constants-ozjf1Ejw.d.cts} +2 -2
- package/dist/continuous-event-graph/index.cjs +201 -448
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +16 -340
- package/dist/continuous-event-graph/index.d.ts +16 -340
- package/dist/continuous-event-graph/index.js +198 -448
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +4 -4
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +4 -4
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +278 -533
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +278 -533
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +138 -19
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +138 -19
- package/dist/inference/index.js.map +1 -1
- package/dist/journal-BJDjWb5Q.d.cts +343 -0
- package/dist/journal-B_2JnBMF.d.ts +343 -0
- package/dist/step-machine/index.cjs +18 -1
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.d.cts +2 -2
- package/dist/step-machine/index.d.ts +2 -2
- package/dist/step-machine/index.js +18 -1
- package/dist/step-machine/index.js.map +1 -1
- package/dist/stores/file.d.cts +1 -1
- package/dist/stores/file.d.ts +1 -1
- package/dist/stores/index.d.cts +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/localStorage.d.cts +1 -1
- package/dist/stores/localStorage.d.ts +1 -1
- package/dist/stores/memory.d.cts +1 -1
- package/dist/stores/memory.d.ts +1 -1
- package/dist/{types-BwvgvlOO.d.cts → types-BzLD8bjb.d.cts} +1 -1
- package/dist/{types-ClRA8hzC.d.ts → types-C2eJ7DAV.d.ts} +1 -1
- package/dist/{types-DEj7OakX.d.cts → types-CMFSIjpc.d.cts} +39 -4
- package/dist/{types-DEj7OakX.d.ts → types-CMFSIjpc.d.ts} +39 -4
- package/dist/{types-FZ_eyErS.d.cts → types-ycun84cq.d.cts} +1 -0
- package/dist/{types-FZ_eyErS.d.ts → types-ycun84cq.d.ts} +1 -0
- package/dist/{validate-DEZ2Ymdb.d.ts → validate-DJQTQ6bP.d.ts} +1 -1
- package/dist/{validate-DqKTZg_o.d.cts → validate-ke92Cleg.d.cts} +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +16 -0
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +43 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +7 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +189 -0
- package/examples/browser/livecards-browser/index.html +688 -0
- package/examples/browser/step-machine-browser/index.html +367 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
- package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +36 -0
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +30 -0
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +19 -0
- package/examples/cli/step-machine-demo/step-cli-echo-y.js +15 -0
- package/examples/cli/step-machine-demo/step2-double-cli.js +39 -0
- package/examples/cli/step-machine-demo/two-step-math-handlers.js +32 -0
- package/examples/cli/step-machine-demo/two-step-math.flow.yaml +31 -0
- package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +24 -0
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +35 -0
- package/examples/index.html +792 -0
- package/examples/ingest.js +733 -0
- package/examples/npm-libs/batch/batch-step-machine.ts +121 -0
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +215 -0
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +555 -0
- package/examples/npm-libs/continuous-event-graph/portfolio-tracker.ts +287 -0
- package/examples/npm-libs/continuous-event-graph/reactive-monitoring.ts +265 -0
- package/examples/npm-libs/continuous-event-graph/reactive-pipeline.ts +168 -0
- package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +287 -0
- package/examples/npm-libs/continuous-event-graph/stock-dashboard.ts +229 -0
- package/examples/npm-libs/event-graph/ci-cd-pipeline.ts +243 -0
- package/examples/npm-libs/event-graph/executor-diamond.ts +165 -0
- package/examples/npm-libs/event-graph/executor-pipeline.ts +161 -0
- package/examples/npm-libs/event-graph/research-pipeline.ts +137 -0
- package/examples/npm-libs/flows/ai-conversation.yaml +116 -0
- package/examples/npm-libs/flows/order-processing.yaml +143 -0
- package/examples/npm-libs/flows/simple-greeting.yaml +54 -0
- package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +307 -0
- package/examples/npm-libs/graph-of-graphs/url-processing-pipeline.ts +254 -0
- package/examples/npm-libs/inference/azure-deployment.ts +149 -0
- package/examples/npm-libs/inference/copilot-cli.ts +138 -0
- package/examples/npm-libs/inference/data-pipeline.ts +145 -0
- package/examples/npm-libs/inference/pluggable-adapters.ts +254 -0
- package/examples/npm-libs/node/ai-conversation.ts +195 -0
- package/examples/npm-libs/node/simple-greeting.ts +101 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
- package/package.json +14 -2
- package/schema/board-status.schema.json +118 -0
- package/schema/flow.schema.json +5 -0
- package/schema/live-cards.schema.json +59 -77
- package/step-machine-cli.js +674 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Step Machine Browser Example — yaml-flow</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
max-width: 800px;
|
|
12
|
+
margin: 40px auto;
|
|
13
|
+
padding: 20px;
|
|
14
|
+
background: #f5f5f5;
|
|
15
|
+
}
|
|
16
|
+
h1 { color: #333; }
|
|
17
|
+
.card {
|
|
18
|
+
background: white;
|
|
19
|
+
border-radius: 8px;
|
|
20
|
+
padding: 20px;
|
|
21
|
+
margin: 20px 0;
|
|
22
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
23
|
+
}
|
|
24
|
+
.step-list {
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 10px;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
margin: 10px 0;
|
|
29
|
+
}
|
|
30
|
+
.step {
|
|
31
|
+
padding: 8px 16px;
|
|
32
|
+
border-radius: 20px;
|
|
33
|
+
background: #e0e0e0;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
}
|
|
36
|
+
.step.active {
|
|
37
|
+
background: #2196F3;
|
|
38
|
+
color: white;
|
|
39
|
+
}
|
|
40
|
+
.step.completed {
|
|
41
|
+
background: #4CAF50;
|
|
42
|
+
color: white;
|
|
43
|
+
}
|
|
44
|
+
button {
|
|
45
|
+
background: #2196F3;
|
|
46
|
+
color: white;
|
|
47
|
+
border: none;
|
|
48
|
+
padding: 12px 24px;
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
font-size: 16px;
|
|
52
|
+
margin: 5px;
|
|
53
|
+
}
|
|
54
|
+
button:hover { background: #1976D2; }
|
|
55
|
+
button:disabled { background: #ccc; cursor: not-allowed; }
|
|
56
|
+
.log {
|
|
57
|
+
background: #263238;
|
|
58
|
+
color: #aed581;
|
|
59
|
+
padding: 15px;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
font-family: 'Consolas', monospace;
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
max-height: 300px;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
}
|
|
66
|
+
.log-entry { margin: 5px 0; }
|
|
67
|
+
.log-entry.error { color: #ef5350; }
|
|
68
|
+
.log-entry.transition { color: #64B5F6; }
|
|
69
|
+
.result {
|
|
70
|
+
padding: 15px;
|
|
71
|
+
border-radius: 6px;
|
|
72
|
+
margin-top: 15px;
|
|
73
|
+
}
|
|
74
|
+
.result.success { background: #C8E6C9; }
|
|
75
|
+
.result.error { background: #FFCDD2; }
|
|
76
|
+
input {
|
|
77
|
+
padding: 10px;
|
|
78
|
+
border: 1px solid #ddd;
|
|
79
|
+
border-radius: 4px;
|
|
80
|
+
font-size: 16px;
|
|
81
|
+
width: 200px;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<h1>yaml-flow Step Machine Browser Example</h1>
|
|
87
|
+
|
|
88
|
+
<div class="card">
|
|
89
|
+
<h2>Simple Greeting Flow</h2>
|
|
90
|
+
<p>Enter your name and run the flow to see it in action.</p>
|
|
91
|
+
|
|
92
|
+
<div style="margin: 15px 0;">
|
|
93
|
+
<input type="text" id="nameInput" placeholder="Enter your name" value="Developer">
|
|
94
|
+
<button onclick="runFlow()">Run Flow</button>
|
|
95
|
+
<button onclick="clearLog()">Clear Log</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<h3>Steps</h3>
|
|
99
|
+
<div class="step-list" id="stepList">
|
|
100
|
+
<div class="step" data-step="greet">greet</div>
|
|
101
|
+
<div class="step" data-step="validate">validate</div>
|
|
102
|
+
<div class="step" data-step="personalize">personalize</div>
|
|
103
|
+
<div class="step" data-step="success_state">✓ success</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="card">
|
|
108
|
+
<h3>Execution Log</h3>
|
|
109
|
+
<div class="log" id="log"></div>
|
|
110
|
+
|
|
111
|
+
<div id="result"></div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<script type="module">
|
|
115
|
+
// Import from CDN (in production) or local build
|
|
116
|
+
// For this demo, we'll inline a simplified version
|
|
117
|
+
|
|
118
|
+
// ============= Inline yaml-flow (simplified for demo) =============
|
|
119
|
+
|
|
120
|
+
class MemoryStore {
|
|
121
|
+
constructor() {
|
|
122
|
+
this.runs = new Map();
|
|
123
|
+
this.data = new Map();
|
|
124
|
+
}
|
|
125
|
+
async saveRunState(runId, state) { this.runs.set(runId, {...state}); }
|
|
126
|
+
async loadRunState(runId) { return this.runs.get(runId) || null; }
|
|
127
|
+
async deleteRunState(runId) { this.runs.delete(runId); this.data.delete(runId); }
|
|
128
|
+
async setData(runId, key, value) {
|
|
129
|
+
if (!this.data.has(runId)) this.data.set(runId, {});
|
|
130
|
+
this.data.get(runId)[key] = value;
|
|
131
|
+
}
|
|
132
|
+
async getData(runId, key) { return this.data.get(runId)?.[key]; }
|
|
133
|
+
async getAllData(runId) { return {...(this.data.get(runId) || {})}; }
|
|
134
|
+
async clearData(runId) { this.data.delete(runId); }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class FlowEngine {
|
|
138
|
+
constructor(flow, handlers, options = {}) {
|
|
139
|
+
this.flow = flow;
|
|
140
|
+
this.handlers = handlers;
|
|
141
|
+
this.store = options.store || new MemoryStore();
|
|
142
|
+
this.components = options.components || {};
|
|
143
|
+
this.options = options;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async run(initialData = {}) {
|
|
147
|
+
const runId = crypto.randomUUID();
|
|
148
|
+
const startedAt = Date.now();
|
|
149
|
+
|
|
150
|
+
const runState = {
|
|
151
|
+
runId,
|
|
152
|
+
currentStep: this.flow.settings.start_step,
|
|
153
|
+
status: 'running',
|
|
154
|
+
stepHistory: [],
|
|
155
|
+
iterationCounts: {},
|
|
156
|
+
retryCounts: {},
|
|
157
|
+
startedAt,
|
|
158
|
+
updatedAt: startedAt,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
await this.store.saveRunState(runId, runState);
|
|
162
|
+
for (const [key, value] of Object.entries(initialData)) {
|
|
163
|
+
await this.store.setData(runId, key, value);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let iterations = 0;
|
|
167
|
+
const maxSteps = this.flow.settings.max_total_steps || 100;
|
|
168
|
+
|
|
169
|
+
while (iterations < maxSteps) {
|
|
170
|
+
const currentStep = runState.currentStep;
|
|
171
|
+
|
|
172
|
+
// Check terminal state
|
|
173
|
+
const terminalState = this.flow.terminal_states[currentStep];
|
|
174
|
+
if (terminalState) {
|
|
175
|
+
const allData = await this.store.getAllData(runId);
|
|
176
|
+
return {
|
|
177
|
+
runId,
|
|
178
|
+
status: 'completed',
|
|
179
|
+
intent: terminalState.return_intent,
|
|
180
|
+
data: this.extractReturnData(terminalState.return_artifacts, allData),
|
|
181
|
+
finalStep: currentStep,
|
|
182
|
+
stepHistory: runState.stepHistory,
|
|
183
|
+
durationMs: Date.now() - startedAt,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stepConfig = this.flow.steps[currentStep];
|
|
188
|
+
const handler = this.handlers[currentStep];
|
|
189
|
+
|
|
190
|
+
// Build input
|
|
191
|
+
const allData = await this.store.getAllData(runId);
|
|
192
|
+
const input = stepConfig.expects_data
|
|
193
|
+
? Object.fromEntries(stepConfig.expects_data.map(k => [k, allData[k]]))
|
|
194
|
+
: allData;
|
|
195
|
+
|
|
196
|
+
// Execute
|
|
197
|
+
this.options.onStep?.(currentStep, 'start');
|
|
198
|
+
const result = await handler(input, { runId, stepName: currentStep, components: this.components, store: this.store });
|
|
199
|
+
this.options.onStep?.(currentStep, result.result);
|
|
200
|
+
|
|
201
|
+
// Store output
|
|
202
|
+
if (result.data) {
|
|
203
|
+
for (const [key, value] of Object.entries(result.data)) {
|
|
204
|
+
await this.store.setData(runId, key, value);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Transition
|
|
209
|
+
const nextStep = stepConfig.transitions[result.result];
|
|
210
|
+
this.options.onTransition?.(currentStep, nextStep);
|
|
211
|
+
|
|
212
|
+
runState.stepHistory.push(currentStep);
|
|
213
|
+
runState.currentStep = nextStep;
|
|
214
|
+
iterations++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { runId, status: 'max_iterations', data: {}, stepHistory: runState.stepHistory };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
extractReturnData(artifacts, allData) {
|
|
221
|
+
if (!artifacts || artifacts === false) return {};
|
|
222
|
+
if (typeof artifacts === 'string') return { [artifacts]: allData[artifacts] };
|
|
223
|
+
if (Array.isArray(artifacts)) return Object.fromEntries(artifacts.map(k => [k, allData[k]]));
|
|
224
|
+
return allData;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============= Flow Definition =============
|
|
229
|
+
|
|
230
|
+
const flow = {
|
|
231
|
+
settings: {
|
|
232
|
+
start_step: 'greet',
|
|
233
|
+
max_total_steps: 10
|
|
234
|
+
},
|
|
235
|
+
steps: {
|
|
236
|
+
greet: {
|
|
237
|
+
produces_data: ['greeting', 'user_name'],
|
|
238
|
+
transitions: { success: 'validate', failure: 'error_state' }
|
|
239
|
+
},
|
|
240
|
+
validate: {
|
|
241
|
+
expects_data: ['greeting', 'user_name'],
|
|
242
|
+
produces_data: ['is_valid'],
|
|
243
|
+
transitions: { success: 'personalize', invalid: 'error_state', failure: 'error_state' }
|
|
244
|
+
},
|
|
245
|
+
personalize: {
|
|
246
|
+
expects_data: ['greeting', 'user_name'],
|
|
247
|
+
produces_data: ['final_message'],
|
|
248
|
+
transitions: { success: 'success_state', failure: 'error_state' }
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
terminal_states: {
|
|
252
|
+
success_state: { return_intent: 'success', return_artifacts: ['final_message', 'user_name'] },
|
|
253
|
+
error_state: { return_intent: 'error', return_artifacts: false }
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// ============= Step Handlers =============
|
|
258
|
+
|
|
259
|
+
const handlers = {
|
|
260
|
+
async greet(input) {
|
|
261
|
+
const userName = input.initial_name || 'World';
|
|
262
|
+
await sleep(300); // Simulate async work
|
|
263
|
+
return {
|
|
264
|
+
result: 'success',
|
|
265
|
+
data: { greeting: 'Hello', user_name: userName }
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async validate(input) {
|
|
270
|
+
const { greeting, user_name } = input;
|
|
271
|
+
await sleep(200);
|
|
272
|
+
const isValid = greeting && user_name;
|
|
273
|
+
return {
|
|
274
|
+
result: isValid ? 'success' : 'invalid',
|
|
275
|
+
data: { is_valid: isValid }
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async personalize(input) {
|
|
280
|
+
const { greeting, user_name } = input;
|
|
281
|
+
await sleep(200);
|
|
282
|
+
return {
|
|
283
|
+
result: 'success',
|
|
284
|
+
data: { final_message: `${greeting}, ${user_name}! Welcome to yaml-flow in the browser! 🎉` }
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
function sleep(ms) {
|
|
290
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============= UI Functions =============
|
|
294
|
+
|
|
295
|
+
const logEl = document.getElementById('log');
|
|
296
|
+
const resultEl = document.getElementById('result');
|
|
297
|
+
const stepEls = document.querySelectorAll('.step');
|
|
298
|
+
|
|
299
|
+
function log(message, type = '') {
|
|
300
|
+
const entry = document.createElement('div');
|
|
301
|
+
entry.className = `log-entry ${type}`;
|
|
302
|
+
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
303
|
+
logEl.appendChild(entry);
|
|
304
|
+
logEl.scrollTop = logEl.scrollHeight;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function updateStep(stepName, status) {
|
|
308
|
+
stepEls.forEach(el => {
|
|
309
|
+
if (el.dataset.step === stepName) {
|
|
310
|
+
el.classList.remove('active', 'completed');
|
|
311
|
+
el.classList.add(status);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resetSteps() {
|
|
317
|
+
stepEls.forEach(el => el.classList.remove('active', 'completed'));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
window.clearLog = function() {
|
|
321
|
+
logEl.innerHTML = '';
|
|
322
|
+
resultEl.innerHTML = '';
|
|
323
|
+
resetSteps();
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
window.runFlow = async function() {
|
|
327
|
+
resetSteps();
|
|
328
|
+
resultEl.innerHTML = '';
|
|
329
|
+
|
|
330
|
+
const name = document.getElementById('nameInput').value || 'World';
|
|
331
|
+
log(`Starting flow with name: "${name}"`);
|
|
332
|
+
|
|
333
|
+
const engine = new FlowEngine(flow, handlers, {
|
|
334
|
+
store: new MemoryStore(),
|
|
335
|
+
onStep: (step, status) => {
|
|
336
|
+
log(`Step [${step}]: ${status}`);
|
|
337
|
+
updateStep(step, status === 'start' ? 'active' : 'completed');
|
|
338
|
+
},
|
|
339
|
+
onTransition: (from, to) => {
|
|
340
|
+
log(`Transition: ${from} → ${to}`, 'transition');
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const result = await engine.run({ initial_name: name });
|
|
346
|
+
|
|
347
|
+
log(`Flow completed: ${result.intent}`);
|
|
348
|
+
updateStep(result.finalStep, 'completed');
|
|
349
|
+
|
|
350
|
+
resultEl.innerHTML = `
|
|
351
|
+
<div class="result ${result.intent === 'success' ? 'success' : 'error'}">
|
|
352
|
+
<strong>Result:</strong> ${result.intent}<br>
|
|
353
|
+
<strong>Message:</strong> ${result.data.final_message || 'N/A'}<br>
|
|
354
|
+
<strong>Duration:</strong> ${result.durationMs}ms<br>
|
|
355
|
+
<strong>Steps:</strong> ${result.stepHistory.join(' → ')} → ${result.finalStep}
|
|
356
|
+
</div>
|
|
357
|
+
`;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
log(`Error: ${error.message}`, 'error');
|
|
360
|
+
resultEl.innerHTML = `<div class="result error">Error: ${error.message}</div>`;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
log('yaml-flow browser demo loaded. Click "Run Flow" to start.');
|
|
365
|
+
</script>
|
|
366
|
+
</body>
|
|
367
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "holdings-table",
|
|
3
|
+
"meta": { "title": "Holdings Table" },
|
|
4
|
+
"requires": ["holdings", "prices"],
|
|
5
|
+
"provides": [{ "bindTo": "table", "src": "computed_values.table" }],
|
|
6
|
+
"state": {},
|
|
7
|
+
"compute": [
|
|
8
|
+
{
|
|
9
|
+
"bindTo": "table",
|
|
10
|
+
"expr": "{ \"rows\": $map(requires.holdings, function($h) { { \"symbol\": $h.symbol, \"qty\": $h.qty, \"price\": $lookup(requires.prices, $h.symbol), \"value\": $h.qty * $lookup(requires.prices, $h.symbol) } }) }"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"view": {
|
|
14
|
+
"elements": [
|
|
15
|
+
{
|
|
16
|
+
"kind": "table",
|
|
17
|
+
"label": "Portfolio Positions",
|
|
18
|
+
"data": { "bind": "computed_values.table.rows", "columns": ["symbol", "qty", "price", "value"] }
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "portfolio-form",
|
|
3
|
+
"meta": {
|
|
4
|
+
"title": "Portfolio Holdings Form"
|
|
5
|
+
},
|
|
6
|
+
"provides": [
|
|
7
|
+
{
|
|
8
|
+
"bindTo": "holdings",
|
|
9
|
+
"src": "state.holdings"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"state": {
|
|
13
|
+
"holdings": [
|
|
14
|
+
{
|
|
15
|
+
"symbol": "AAPL",
|
|
16
|
+
"qty": 50
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"symbol": "MSFT",
|
|
20
|
+
"qty": 30
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"symbol": "GOOG",
|
|
24
|
+
"qty": 100
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"view": {
|
|
29
|
+
"elements": [
|
|
30
|
+
{
|
|
31
|
+
"kind": "table",
|
|
32
|
+
"label": "Holdings",
|
|
33
|
+
"data": {
|
|
34
|
+
"bind": "state.holdings",
|
|
35
|
+
"columns": [
|
|
36
|
+
"symbol",
|
|
37
|
+
"qty"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "portfolio-value",
|
|
3
|
+
"meta": { "title": "Portfolio Total Value" },
|
|
4
|
+
"requires": ["table"],
|
|
5
|
+
"provides": [{ "bindTo": "totalValue", "src": "computed_values.totalValue" }],
|
|
6
|
+
"state": {},
|
|
7
|
+
"compute": [
|
|
8
|
+
{ "bindTo": "totalValue", "expr": "$sum(requires.table.rows.value)" }
|
|
9
|
+
],
|
|
10
|
+
"view": {
|
|
11
|
+
"elements": [
|
|
12
|
+
{ "kind": "metric", "label": "Total Portfolio Value", "data": { "bind": "computed_values.totalValue" } }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "price-fetch",
|
|
3
|
+
"meta": { "title": "Fetch Market Prices" },
|
|
4
|
+
"requires": ["holdings"],
|
|
5
|
+
"provides": [{ "bindTo": "prices", "src": "sources.prices" }],
|
|
6
|
+
"state": {},
|
|
7
|
+
"sources": [
|
|
8
|
+
{ "cli": "node ../fetch-prices.js --tmp-file-name tmp_file1", "bindTo": "prices", "outputFile": "prices.json" }
|
|
9
|
+
],
|
|
10
|
+
"view": {
|
|
11
|
+
"elements": [
|
|
12
|
+
{ "kind": "table", "label": "Market Prices", "data": { "bind": "sources.prices" } }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch-prices.js
|
|
3
|
+
* Polls for tmp_file1 payload and outputs JSON to stdout once available.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
|
|
8
|
+
function getArgValue(flagName) {
|
|
9
|
+
const idx = process.argv.indexOf(flagName);
|
|
10
|
+
if (idx < 0) return undefined;
|
|
11
|
+
return process.argv[idx + 1];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const envBoardDir = (process.env.BOARD_DIR ?? '').trim();
|
|
15
|
+
const tmpFileName = String(getArgValue('--tmp-file-name') ?? process.env.TMP_FILE_NAME ?? 'tmp_file1').trim();
|
|
16
|
+
const tmpFilePath = envBoardDir ? path.join(envBoardDir, tmpFileName) : '';
|
|
17
|
+
|
|
18
|
+
if (!tmpFilePath) {
|
|
19
|
+
console.error('BOARD_DIR environment variable is required for fetch-prices.js');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getReadableTmpFile() {
|
|
24
|
+
if (!fs.existsSync(tmpFilePath)) return undefined;
|
|
25
|
+
const content = fs.readFileSync(tmpFilePath, 'utf-8').trim();
|
|
26
|
+
if (!content) return undefined;
|
|
27
|
+
return { tmpFile: tmpFilePath, content };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function waitForFile(timeoutMs = 120000) {
|
|
31
|
+
const started = Date.now();
|
|
32
|
+
const interval = setInterval(() => {
|
|
33
|
+
if (Date.now() - started > timeoutMs) {
|
|
34
|
+
clearInterval(interval);
|
|
35
|
+
console.error('Timed out waiting for tmp_file1 market prices input.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ready = getReadableTmpFile();
|
|
40
|
+
if (!ready) return;
|
|
41
|
+
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
fs.writeFileSync(ready.tmpFile, '', 'utf-8');
|
|
44
|
+
process.stdout.write(`${ready.content}\n`);
|
|
45
|
+
}, 250);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
waitForFile();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..');
|
|
8
|
+
const boardCliPath = path.join(repoRoot, 'board-live-cards-cli.js');
|
|
9
|
+
|
|
10
|
+
export function runBoardCli(args, options = {}) {
|
|
11
|
+
const { capture = false, cwd = process.cwd() } = options;
|
|
12
|
+
const result = spawnSync(process.execPath, [boardCliPath, ...args], {
|
|
13
|
+
cwd,
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
windowsHide: true,
|
|
16
|
+
stdio: capture ? 'pipe' : 'pipe',
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
BOARD_LIVE_CARDS_NO_SPAWN: process.env.BOARD_LIVE_CARDS_NO_SPAWN ?? '1',
|
|
20
|
+
BOARD_DIR: process.env.BOARD_DIR ?? '',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (result.error) {
|
|
25
|
+
throw new Error(`Failed to launch board-live-cards-cli: ${result.error.message}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ((result.status ?? 1) !== 0) {
|
|
29
|
+
const stderr = (result.stderr ?? '').trim();
|
|
30
|
+
const stdout = (result.stdout ?? '').trim();
|
|
31
|
+
throw new Error(`board-live-cards-cli failed (${result.status}): ${stderr || stdout || 'no output'}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return capture ? (result.stdout ?? '') : '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function readStdinJson() {
|
|
38
|
+
let raw = '';
|
|
39
|
+
process.stdin.setEncoding('utf-8');
|
|
40
|
+
|
|
41
|
+
for await (const chunk of process.stdin) {
|
|
42
|
+
raw += chunk;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!raw.trim()) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeResult(payload) {
|
|
53
|
+
process.stdout.write(JSON.stringify(payload));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function writeFailure(message) {
|
|
57
|
+
writeResult({ result: 'failure', error: message });
|
|
58
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const input = await readStdinJson();
|
|
7
|
+
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
8
|
+
const cardsGlob = String(input.CARDS_GLOB ?? '').trim();
|
|
9
|
+
|
|
10
|
+
if (!boardDir || !cardsGlob) {
|
|
11
|
+
writeFailure('BOARD_DIR and CARDS_GLOB are required');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
runBoardCli(['add-cards', '--rg', boardDir, '--card-glob', cardsGlob]);
|
|
16
|
+
|
|
17
|
+
writeResult({
|
|
18
|
+
result: 'success',
|
|
19
|
+
data: {
|
|
20
|
+
board_dir: boardDir,
|
|
21
|
+
cards_glob: cardsGlob,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
writeFailure(message);
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const input = await readStdinJson();
|
|
7
|
+
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
8
|
+
|
|
9
|
+
if (!boardDir) {
|
|
10
|
+
writeFailure('BOARD_DIR is required');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
runBoardCli(['init', boardDir]);
|
|
15
|
+
writeResult({
|
|
16
|
+
result: 'success',
|
|
17
|
+
data: {
|
|
18
|
+
board_dir: boardDir,
|
|
19
|
+
message: `initialized ${boardDir}`,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
writeFailure(message);
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { readStdinJson, writeFailure, writeResult } from './_board-cli.js';
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const input = await readStdinJson();
|
|
9
|
+
const boardDirInput = String(input.BOARD_DIR ?? '').trim();
|
|
10
|
+
|
|
11
|
+
if (!boardDirInput) {
|
|
12
|
+
writeFailure('BOARD_DIR is required');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const boardDir = path.resolve(boardDirInput);
|
|
17
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
18
|
+
|
|
19
|
+
writeResult({
|
|
20
|
+
result: 'success',
|
|
21
|
+
data: {
|
|
22
|
+
board_dir: boardDir,
|
|
23
|
+
reset: true,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
writeFailure(message);
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const input = await readStdinJson();
|
|
7
|
+
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
8
|
+
const task = String(input.TASK ?? '').trim();
|
|
9
|
+
|
|
10
|
+
if (!boardDir || !task) {
|
|
11
|
+
writeFailure('BOARD_DIR and TASK are required');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
runBoardCli(['retrigger', '--rg', boardDir, '--task', task]);
|
|
16
|
+
|
|
17
|
+
writeResult({
|
|
18
|
+
result: 'success',
|
|
19
|
+
data: {
|
|
20
|
+
task,
|
|
21
|
+
retriggered: true,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
writeFailure(message);
|
|
27
|
+
}
|