@synergenius/flow-weaver-pack-weaver 0.9.185 → 0.9.187
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/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/capability-registry.d.ts.map +1 -1
- package/dist/bot/capability-registry.js +15 -0
- package/dist/bot/capability-registry.js.map +1 -1
- package/dist/bot/error-guide.d.ts +5 -0
- package/dist/bot/error-guide.d.ts.map +1 -0
- package/dist/bot/error-guide.js +5 -0
- package/dist/bot/error-guide.js.map +1 -0
- package/dist/bot/retry-utils.d.ts +5 -0
- package/dist/bot/retry-utils.d.ts.map +1 -0
- package/dist/bot/retry-utils.js +5 -0
- package/dist/bot/retry-utils.js.map +1 -0
- package/dist/bot/session-state.d.ts +25 -0
- package/dist/bot/session-state.d.ts.map +1 -0
- package/dist/bot/session-state.js +110 -0
- package/dist/bot/session-state.js.map +1 -0
- package/dist/bot/task-queue.d.ts +46 -0
- package/dist/bot/task-queue.d.ts.map +1 -0
- package/dist/bot/task-queue.js +237 -0
- package/dist/bot/task-queue.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/weaver-config.md +14 -28
- package/dist/node-types/receive-task.js.bak +38 -0
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/capability-editor.js +15 -0
- package/dist/ui/profile-editor.js +15 -0
- package/dist/ui/swarm-dashboard.js +15 -0
- package/dist/ui/task-create-form.js +98 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +1 -1
- package/package.json +1 -1
- package/src/bot/capability-registry.ts +15 -0
- package/dist/docs/weaver-bot-usage.md +0 -51
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -46
package/dist/cli.js
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { runWorkflow } from './bot/runner.js';
|
|
4
|
+
import { RunStore } from './bot/run-store.js';
|
|
5
|
+
import { CostStore } from './bot/cost-store.js';
|
|
6
|
+
import { defaultRegistry, discoverProviders } from './bot/provider-registry.js';
|
|
7
|
+
import { WatchDaemon } from './bot/watch-daemon.js';
|
|
8
|
+
import { PipelineRunner } from './bot/pipeline-runner.js';
|
|
9
|
+
import { DashboardServer } from './bot/dashboard.js';
|
|
10
|
+
import { openBrowser } from './bot/utils.js';
|
|
11
|
+
const HELP = `
|
|
12
|
+
weaver - Autonomous workflow runner for Flow Weaver
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
weaver <file> Run a workflow file
|
|
16
|
+
weaver run <file> Same as above
|
|
17
|
+
weaver history List recent runs
|
|
18
|
+
weaver history <id> Show details of a specific run
|
|
19
|
+
weaver costs Show cost summary
|
|
20
|
+
weaver watch <file> Watch file, re-run on change
|
|
21
|
+
weaver cron "<schedule>" <file> Run on cron schedule
|
|
22
|
+
weaver pipeline <config.json> Run multi-stage pipeline
|
|
23
|
+
weaver dashboard [file] Start live dashboard (optionally run file)
|
|
24
|
+
weaver providers List available providers
|
|
25
|
+
weaver --help Show this help
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
-v, --verbose Show detailed execution info
|
|
29
|
+
-n, --dry-run Preview without executing
|
|
30
|
+
-p, --params <json> Input parameters as JSON
|
|
31
|
+
-c, --config <path> Path to .weaver.json config
|
|
32
|
+
--quiet Suppress progress output
|
|
33
|
+
--version Show version
|
|
34
|
+
--dashboard Enable live dashboard for run
|
|
35
|
+
--port <number> Dashboard port (default 4242)
|
|
36
|
+
--open Auto-open browser
|
|
37
|
+
--approval <mode> Override approval mode
|
|
38
|
+
|
|
39
|
+
Watch/Cron options:
|
|
40
|
+
--cron "<schedule>" Add cron trigger to watch mode
|
|
41
|
+
--debounce <ms> File watch debounce (default 500)
|
|
42
|
+
--log <path> Write output to log file
|
|
43
|
+
|
|
44
|
+
Pipeline options:
|
|
45
|
+
--stage <id> Run single stage + its dependencies
|
|
46
|
+
|
|
47
|
+
History options:
|
|
48
|
+
--limit <n> Number of entries (default: 20)
|
|
49
|
+
--outcome <type> Filter: completed|failed|error|skipped
|
|
50
|
+
--workflow <path> Filter by workflow file
|
|
51
|
+
--since <date> Show runs after date (ISO-8601)
|
|
52
|
+
--json JSON output
|
|
53
|
+
--prune Prune old entries (keep 500, 90 days)
|
|
54
|
+
--clear Delete all history
|
|
55
|
+
|
|
56
|
+
Cost options:
|
|
57
|
+
--since <duration|date> Filter: 7d, 30d, or ISO-8601 date
|
|
58
|
+
--model <name> Filter by model
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
weaver my-workflow.ts
|
|
62
|
+
weaver run pipeline.ts --verbose --params '{"env":"prod"}'
|
|
63
|
+
weaver watch my-workflow.ts --cron "*/5 * * * *"
|
|
64
|
+
weaver pipeline deploy.json --stage test
|
|
65
|
+
weaver history --outcome failed --limit 10
|
|
66
|
+
weaver costs --since 7d
|
|
67
|
+
`.trim();
|
|
68
|
+
function parseArgs(argv) {
|
|
69
|
+
const result = {
|
|
70
|
+
command: 'run',
|
|
71
|
+
file: undefined,
|
|
72
|
+
verbose: false,
|
|
73
|
+
dryRun: false,
|
|
74
|
+
quiet: false,
|
|
75
|
+
params: undefined,
|
|
76
|
+
configPath: undefined,
|
|
77
|
+
showHelp: false,
|
|
78
|
+
showVersion: false,
|
|
79
|
+
historyLimit: 20,
|
|
80
|
+
historyJson: false,
|
|
81
|
+
historyPrune: false,
|
|
82
|
+
historyClear: false,
|
|
83
|
+
debounceMs: 500,
|
|
84
|
+
dashboard: false,
|
|
85
|
+
dashboardPort: 4242,
|
|
86
|
+
dashboardOpen: false,
|
|
87
|
+
};
|
|
88
|
+
const args = argv.slice(2);
|
|
89
|
+
let i = 0;
|
|
90
|
+
while (i < args.length) {
|
|
91
|
+
const arg = args[i];
|
|
92
|
+
if (arg === '--help' || arg === '-h') {
|
|
93
|
+
result.showHelp = true;
|
|
94
|
+
}
|
|
95
|
+
else if (arg === '--version') {
|
|
96
|
+
result.showVersion = true;
|
|
97
|
+
}
|
|
98
|
+
else if (arg === '--verbose' || arg === '-v') {
|
|
99
|
+
result.verbose = true;
|
|
100
|
+
}
|
|
101
|
+
else if (arg === '--dry-run' || arg === '-n') {
|
|
102
|
+
result.dryRun = true;
|
|
103
|
+
}
|
|
104
|
+
else if (arg === '--quiet') {
|
|
105
|
+
result.quiet = true;
|
|
106
|
+
}
|
|
107
|
+
else if ((arg === '--params' || arg === '-p') && i + 1 < args.length) {
|
|
108
|
+
i++;
|
|
109
|
+
try {
|
|
110
|
+
result.params = JSON.parse(args[i]);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
console.error(`[weaver] Invalid JSON for --params: ${args[i]}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if ((arg === '--config' || arg === '-c') && i + 1 < args.length) {
|
|
118
|
+
i++;
|
|
119
|
+
result.configPath = args[i];
|
|
120
|
+
}
|
|
121
|
+
else if (arg === '--limit' && i + 1 < args.length) {
|
|
122
|
+
i++;
|
|
123
|
+
result.historyLimit = parseInt(args[i], 10) || 20;
|
|
124
|
+
}
|
|
125
|
+
else if (arg === '--outcome' && i + 1 < args.length) {
|
|
126
|
+
i++;
|
|
127
|
+
result.historyOutcome = args[i];
|
|
128
|
+
}
|
|
129
|
+
else if (arg === '--workflow' && i + 1 < args.length) {
|
|
130
|
+
i++;
|
|
131
|
+
result.historyWorkflow = args[i];
|
|
132
|
+
}
|
|
133
|
+
else if (arg === '--since' && i + 1 < args.length) {
|
|
134
|
+
i++;
|
|
135
|
+
if (result.command === 'costs') {
|
|
136
|
+
result.costsSince = args[i];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
result.historySince = args[i];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (arg === '--model' && i + 1 < args.length) {
|
|
143
|
+
i++;
|
|
144
|
+
result.costsModel = args[i];
|
|
145
|
+
}
|
|
146
|
+
else if (arg === '--json') {
|
|
147
|
+
result.historyJson = true;
|
|
148
|
+
}
|
|
149
|
+
else if (arg === '--prune') {
|
|
150
|
+
result.historyPrune = true;
|
|
151
|
+
}
|
|
152
|
+
else if (arg === '--clear') {
|
|
153
|
+
result.historyClear = true;
|
|
154
|
+
}
|
|
155
|
+
else if (arg === 'history') {
|
|
156
|
+
result.command = 'history';
|
|
157
|
+
}
|
|
158
|
+
else if (arg === 'costs') {
|
|
159
|
+
result.command = 'costs';
|
|
160
|
+
}
|
|
161
|
+
else if (arg === 'providers') {
|
|
162
|
+
result.command = 'providers';
|
|
163
|
+
}
|
|
164
|
+
else if (arg === 'watch') {
|
|
165
|
+
result.command = 'watch';
|
|
166
|
+
}
|
|
167
|
+
else if (arg === 'cron') {
|
|
168
|
+
result.command = 'cron';
|
|
169
|
+
// Next arg is the schedule
|
|
170
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
171
|
+
i++;
|
|
172
|
+
result.cronSchedule = args[i];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (arg === 'pipeline') {
|
|
176
|
+
result.command = 'pipeline';
|
|
177
|
+
}
|
|
178
|
+
else if (arg === '--cron' && i + 1 < args.length) {
|
|
179
|
+
i++;
|
|
180
|
+
result.cronSchedule = args[i];
|
|
181
|
+
}
|
|
182
|
+
else if (arg === '--debounce' && i + 1 < args.length) {
|
|
183
|
+
i++;
|
|
184
|
+
result.debounceMs = parseInt(args[i], 10) || 500;
|
|
185
|
+
}
|
|
186
|
+
else if (arg === '--log' && i + 1 < args.length) {
|
|
187
|
+
i++;
|
|
188
|
+
result.logFile = args[i];
|
|
189
|
+
}
|
|
190
|
+
else if (arg === '--stage' && i + 1 < args.length) {
|
|
191
|
+
i++;
|
|
192
|
+
result.pipelineStage = args[i];
|
|
193
|
+
}
|
|
194
|
+
else if (arg === '--dashboard') {
|
|
195
|
+
result.dashboard = true;
|
|
196
|
+
}
|
|
197
|
+
else if (arg === '--port' && i + 1 < args.length) {
|
|
198
|
+
i++;
|
|
199
|
+
result.dashboardPort = parseInt(args[i], 10) || 4242;
|
|
200
|
+
}
|
|
201
|
+
else if (arg === '--open') {
|
|
202
|
+
result.dashboardOpen = true;
|
|
203
|
+
}
|
|
204
|
+
else if (arg === '--approval' && i + 1 < args.length) {
|
|
205
|
+
i++;
|
|
206
|
+
result.approvalMode = args[i];
|
|
207
|
+
}
|
|
208
|
+
else if (arg === 'dashboard') {
|
|
209
|
+
result.command = 'dashboard';
|
|
210
|
+
result.dashboard = true;
|
|
211
|
+
}
|
|
212
|
+
else if (arg === 'run') {
|
|
213
|
+
// skip, next arg is the file
|
|
214
|
+
}
|
|
215
|
+
else if (!arg.startsWith('-')) {
|
|
216
|
+
if (result.command === 'history' && !result.file) {
|
|
217
|
+
result.historyId = arg;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
result.file = arg;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.error(`[weaver] Unknown option: ${arg}`);
|
|
225
|
+
console.error('Run "weaver --help" for usage');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
function formatDuration(ms) {
|
|
233
|
+
if (ms < 1000)
|
|
234
|
+
return `${ms}ms`;
|
|
235
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
236
|
+
}
|
|
237
|
+
const STATUS_ICONS = {
|
|
238
|
+
'node-start': '\x1b[36m>\x1b[0m',
|
|
239
|
+
'node-complete': '\x1b[32m+\x1b[0m',
|
|
240
|
+
'node-error': '\x1b[31mx\x1b[0m',
|
|
241
|
+
};
|
|
242
|
+
const OUTCOME_COLORS = {
|
|
243
|
+
completed: '\x1b[32m',
|
|
244
|
+
failed: '\x1b[31m',
|
|
245
|
+
error: '\x1b[31m',
|
|
246
|
+
skipped: '\x1b[33m',
|
|
247
|
+
};
|
|
248
|
+
const RESET = '\x1b[0m';
|
|
249
|
+
// --- History ---
|
|
250
|
+
function printRunTable(records) {
|
|
251
|
+
console.log('ID'.padEnd(10) +
|
|
252
|
+
'OUTCOME'.padEnd(12) +
|
|
253
|
+
'DURATION'.padEnd(10) +
|
|
254
|
+
'WORKFLOW'.padEnd(24) +
|
|
255
|
+
'STARTED');
|
|
256
|
+
for (const r of records) {
|
|
257
|
+
const id = r.id.slice(0, 8);
|
|
258
|
+
const color = OUTCOME_COLORS[r.outcome] ?? '';
|
|
259
|
+
const outcome = `${color}${r.outcome}${RESET}`;
|
|
260
|
+
const duration = formatDuration(r.durationMs);
|
|
261
|
+
const workflow = path.basename(r.workflowFile);
|
|
262
|
+
const started = r.startedAt.replace('T', ' ').slice(0, 16);
|
|
263
|
+
console.log(id.padEnd(10) +
|
|
264
|
+
outcome.padEnd(12 + color.length + RESET.length) +
|
|
265
|
+
duration.padEnd(10) +
|
|
266
|
+
(workflow.length > 22 ? workflow.slice(0, 21) + '~' : workflow).padEnd(24) +
|
|
267
|
+
started);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function printRunDetail(r) {
|
|
271
|
+
const outcomeColor = r.success ? '\x1b[32m' : '\x1b[31m';
|
|
272
|
+
console.log(`\nRun ${r.id}\n`);
|
|
273
|
+
console.log(` Workflow: ${r.workflowFile}`);
|
|
274
|
+
if (r.functionName)
|
|
275
|
+
console.log(` Function: ${r.functionName}`);
|
|
276
|
+
console.log(` Outcome: ${outcomeColor}${r.outcome}${RESET} (${r.success ? 'success' : 'failure'})`);
|
|
277
|
+
console.log(` Started: ${r.startedAt}`);
|
|
278
|
+
console.log(` Finished: ${r.finishedAt}`);
|
|
279
|
+
console.log(` Duration: ${formatDuration(r.durationMs)}`);
|
|
280
|
+
if (r.executionTime !== undefined) {
|
|
281
|
+
console.log(` Execution time: ${formatDuration(r.executionTime)}`);
|
|
282
|
+
}
|
|
283
|
+
if (r.provider)
|
|
284
|
+
console.log(` Provider: ${r.provider}`);
|
|
285
|
+
console.log(` Dry run: ${r.dryRun ? 'yes' : 'no'}`);
|
|
286
|
+
console.log(` Summary: ${r.summary}`);
|
|
287
|
+
if (r.params) {
|
|
288
|
+
console.log(` Params: ${JSON.stringify(r.params)}`);
|
|
289
|
+
}
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
async function handleHistory(opts) {
|
|
293
|
+
const store = new RunStore();
|
|
294
|
+
if (opts.historyClear) {
|
|
295
|
+
const deleted = store.clear();
|
|
296
|
+
console.log(deleted ? 'History cleared.' : 'No history to clear.');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (opts.historyPrune) {
|
|
300
|
+
const pruned = store.prune({ maxRecords: 500, maxAgeDays: 90 });
|
|
301
|
+
console.log(`Pruned ${pruned} record(s).`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (opts.historyId) {
|
|
305
|
+
const record = store.get(opts.historyId);
|
|
306
|
+
if (!record) {
|
|
307
|
+
console.error(`[weaver] No run found matching "${opts.historyId}"`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
if (opts.historyJson) {
|
|
311
|
+
console.log(JSON.stringify(record, null, 2));
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
printRunDetail(record);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const records = store.list({
|
|
319
|
+
outcome: opts.historyOutcome,
|
|
320
|
+
workflowFile: opts.historyWorkflow ? path.resolve(opts.historyWorkflow) : undefined,
|
|
321
|
+
since: opts.historySince,
|
|
322
|
+
limit: opts.historyLimit,
|
|
323
|
+
});
|
|
324
|
+
if (records.length === 0) {
|
|
325
|
+
console.log('No runs recorded yet.');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (opts.historyJson) {
|
|
329
|
+
console.log(JSON.stringify(records, null, 2));
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
printRunTable(records);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// --- Costs ---
|
|
336
|
+
function parseSince(spec) {
|
|
337
|
+
if (!spec)
|
|
338
|
+
return undefined;
|
|
339
|
+
const match = spec.match(/^(\d+)([dhm])$/);
|
|
340
|
+
if (match) {
|
|
341
|
+
const n = parseInt(match[1], 10);
|
|
342
|
+
const unit = match[2];
|
|
343
|
+
const ms = unit === 'd' ? n * 86_400_000 : unit === 'h' ? n * 3_600_000 : n * 60_000;
|
|
344
|
+
return Date.now() - ms;
|
|
345
|
+
}
|
|
346
|
+
const ts = new Date(spec).getTime();
|
|
347
|
+
return isNaN(ts) ? undefined : ts;
|
|
348
|
+
}
|
|
349
|
+
function formatCostTable(summary) {
|
|
350
|
+
const lines = [];
|
|
351
|
+
lines.push(`Weaver Cost Summary (${summary.totalRuns} runs)`);
|
|
352
|
+
lines.push(`Total: ~$${summary.totalCost.toFixed(4)}`);
|
|
353
|
+
lines.push(`Tokens: ${summary.totalInputTokens.toLocaleString()} in / ${summary.totalOutputTokens.toLocaleString()} out`);
|
|
354
|
+
const models = Object.entries(summary.byModel);
|
|
355
|
+
if (models.length > 0) {
|
|
356
|
+
lines.push('');
|
|
357
|
+
lines.push('By model:');
|
|
358
|
+
for (const [model, data] of models) {
|
|
359
|
+
lines.push(` ${model}: ${data.runs} runs, ~$${data.cost.toFixed(4)}, ${data.inputTokens.toLocaleString()} in / ${data.outputTokens.toLocaleString()} out`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return lines.join('\n');
|
|
363
|
+
}
|
|
364
|
+
function formatRunCost(cost) {
|
|
365
|
+
const inp = cost.totalInputTokens.toLocaleString();
|
|
366
|
+
const out = cost.totalOutputTokens.toLocaleString();
|
|
367
|
+
const usd = cost.totalCost < 0.01
|
|
368
|
+
? `$${cost.totalCost.toFixed(4)}`
|
|
369
|
+
: `$${cost.totalCost.toFixed(2)}`;
|
|
370
|
+
return `tokens: ${inp} in / ${out} out | cost: ~${usd} (${cost.model})`;
|
|
371
|
+
}
|
|
372
|
+
async function handleCosts(opts) {
|
|
373
|
+
const store = new CostStore();
|
|
374
|
+
const sinceTs = parseSince(opts.costsSince);
|
|
375
|
+
const summary = store.summarize({ since: sinceTs, model: opts.costsModel });
|
|
376
|
+
if (summary.totalRuns === 0) {
|
|
377
|
+
console.log('No cost data found.');
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
console.log(formatCostTable(summary));
|
|
381
|
+
}
|
|
382
|
+
async function loadConfig(configPath) {
|
|
383
|
+
if (!configPath)
|
|
384
|
+
return undefined;
|
|
385
|
+
try {
|
|
386
|
+
const { readFileSync } = await import('node:fs');
|
|
387
|
+
return JSON.parse(readFileSync(path.resolve(configPath), 'utf-8'));
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
391
|
+
console.error(`[weaver] Failed to read config: ${msg}`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// --- Watch/Cron ---
|
|
396
|
+
async function handleWatch(opts) {
|
|
397
|
+
if (!opts.file) {
|
|
398
|
+
console.error('[weaver] No workflow file specified for watch');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
const config = await loadConfig(opts.configPath);
|
|
402
|
+
const daemon = new WatchDaemon({
|
|
403
|
+
filePath: path.resolve(opts.file),
|
|
404
|
+
watchFile: true,
|
|
405
|
+
cron: opts.cronSchedule,
|
|
406
|
+
debounceMs: opts.debounceMs,
|
|
407
|
+
logFile: opts.logFile,
|
|
408
|
+
verbose: opts.verbose,
|
|
409
|
+
params: opts.params,
|
|
410
|
+
config,
|
|
411
|
+
quiet: opts.quiet,
|
|
412
|
+
});
|
|
413
|
+
await daemon.start();
|
|
414
|
+
}
|
|
415
|
+
async function handleCron(opts) {
|
|
416
|
+
if (!opts.cronSchedule) {
|
|
417
|
+
console.error('[weaver] No cron schedule specified');
|
|
418
|
+
console.error('Usage: weaver cron "*/5 * * * *" <file>');
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
if (!opts.file) {
|
|
422
|
+
console.error('[weaver] No workflow file specified for cron');
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
const config = await loadConfig(opts.configPath);
|
|
426
|
+
const daemon = new WatchDaemon({
|
|
427
|
+
filePath: path.resolve(opts.file),
|
|
428
|
+
watchFile: false,
|
|
429
|
+
cron: opts.cronSchedule,
|
|
430
|
+
debounceMs: opts.debounceMs,
|
|
431
|
+
logFile: opts.logFile,
|
|
432
|
+
verbose: opts.verbose,
|
|
433
|
+
params: opts.params,
|
|
434
|
+
config,
|
|
435
|
+
quiet: opts.quiet,
|
|
436
|
+
});
|
|
437
|
+
await daemon.start();
|
|
438
|
+
}
|
|
439
|
+
// --- Pipeline ---
|
|
440
|
+
const STAGE_ICONS = {
|
|
441
|
+
running: '\x1b[36m>\x1b[0m',
|
|
442
|
+
completed: '\x1b[32m+\x1b[0m',
|
|
443
|
+
failed: '\x1b[31mx\x1b[0m',
|
|
444
|
+
skipped: '\x1b[33m-\x1b[0m',
|
|
445
|
+
cancelled: '\x1b[33m~\x1b[0m',
|
|
446
|
+
};
|
|
447
|
+
async function handlePipeline(opts) {
|
|
448
|
+
if (!opts.file) {
|
|
449
|
+
console.error('[weaver] No pipeline config specified');
|
|
450
|
+
console.error('Usage: weaver pipeline <config.json>');
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
let config;
|
|
454
|
+
try {
|
|
455
|
+
config = PipelineRunner.load(opts.file);
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
459
|
+
console.error(`\x1b[31m[weaver] Pipeline config error: ${msg}\x1b[0m`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
const runner = new PipelineRunner();
|
|
463
|
+
if (!opts.quiet) {
|
|
464
|
+
console.log(`Pipeline: ${config.name}`);
|
|
465
|
+
console.log(` ${config.stages.length} stages\n`);
|
|
466
|
+
}
|
|
467
|
+
const stageTimings = new Map();
|
|
468
|
+
const onStageEvent = opts.quiet
|
|
469
|
+
? undefined
|
|
470
|
+
: (stageId, status, result) => {
|
|
471
|
+
const icon = STAGE_ICONS[status] ?? ' ';
|
|
472
|
+
const stage = config.stages.find((s) => s.id === stageId);
|
|
473
|
+
const label = stage?.label ?? stageId;
|
|
474
|
+
if (status === 'running') {
|
|
475
|
+
stageTimings.set(stageId, Date.now());
|
|
476
|
+
if (opts.verbose) {
|
|
477
|
+
console.log(` ${icon} ${label}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
const start = stageTimings.get(stageId);
|
|
482
|
+
const dur = start ? ` (${formatDuration(Date.now() - start)})` : '';
|
|
483
|
+
console.log(` ${icon} ${label}${dur}`);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
const weaverConfig = await loadConfig(opts.configPath);
|
|
487
|
+
try {
|
|
488
|
+
const pipelineResult = await runner.run(config, {
|
|
489
|
+
verbose: opts.verbose,
|
|
490
|
+
dryRun: opts.dryRun,
|
|
491
|
+
config: weaverConfig,
|
|
492
|
+
stage: opts.pipelineStage,
|
|
493
|
+
onStageEvent,
|
|
494
|
+
onNotificationError: (channel, _event, error) => {
|
|
495
|
+
console.error(`[weaver] Notification error (${channel}): ${error}`);
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
if (!opts.quiet) {
|
|
499
|
+
const elapsed = formatDuration(pipelineResult.durationMs);
|
|
500
|
+
const color = pipelineResult.success ? '\x1b[32m' : '\x1b[31m';
|
|
501
|
+
console.log(`\n${color}Pipeline: ${pipelineResult.outcome}\x1b[0m (${elapsed})`);
|
|
502
|
+
}
|
|
503
|
+
process.exit(pipelineResult.success ? 0 : 1);
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
507
|
+
console.error(`\x1b[31m[weaver] Pipeline error: ${msg}\x1b[0m`);
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// --- Dashboard ---
|
|
512
|
+
async function handleDashboard(opts) {
|
|
513
|
+
const dashboard = new DashboardServer({ port: opts.dashboardPort });
|
|
514
|
+
const port = await dashboard.start();
|
|
515
|
+
const url = dashboard.getUrl();
|
|
516
|
+
console.log(`[weaver] Dashboard: ${url}`);
|
|
517
|
+
if (opts.dashboardOpen)
|
|
518
|
+
openBrowser(url);
|
|
519
|
+
if (!opts.file) {
|
|
520
|
+
// Dashboard-only mode: view history, wait for SIGINT
|
|
521
|
+
console.log('[weaver] Dashboard running (Ctrl+C to stop)');
|
|
522
|
+
await new Promise((resolve) => {
|
|
523
|
+
process.on('SIGINT', () => { dashboard.stop().then(resolve); });
|
|
524
|
+
process.on('SIGTERM', () => { dashboard.stop().then(resolve); });
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
// Run workflow with live dashboard
|
|
529
|
+
const config = await loadConfig(opts.configPath);
|
|
530
|
+
if (opts.approvalMode) {
|
|
531
|
+
const cfg = config ?? { provider: 'auto' };
|
|
532
|
+
cfg.approval = { mode: opts.approvalMode, webOpen: opts.dashboardOpen };
|
|
533
|
+
}
|
|
534
|
+
dashboard.broadcastWorkflowStart(path.resolve(opts.file));
|
|
535
|
+
const nodeTimings = new Map();
|
|
536
|
+
const onEvent = (event) => {
|
|
537
|
+
dashboard.broadcastExecution(event);
|
|
538
|
+
if (opts.quiet)
|
|
539
|
+
return;
|
|
540
|
+
const icon = STATUS_ICONS[event.type] ?? ' ';
|
|
541
|
+
const label = event.nodeType ? `${event.nodeId} (${event.nodeType})` : event.nodeId;
|
|
542
|
+
if (event.type === 'node-start') {
|
|
543
|
+
nodeTimings.set(event.nodeId, event.timestamp);
|
|
544
|
+
if (opts.verbose)
|
|
545
|
+
console.log(` ${icon} ${label}`);
|
|
546
|
+
}
|
|
547
|
+
else if (event.type === 'node-complete') {
|
|
548
|
+
const start = nodeTimings.get(event.nodeId);
|
|
549
|
+
const dur = start ? ` ${formatDuration(event.timestamp - start)}` : '';
|
|
550
|
+
console.log(` ${icon} ${label}${dur}`);
|
|
551
|
+
}
|
|
552
|
+
else if (event.type === 'node-error') {
|
|
553
|
+
console.log(` ${icon} ${label}: ${event.error ?? 'unknown error'}`);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const startTime = Date.now();
|
|
557
|
+
try {
|
|
558
|
+
const result = await runWorkflow(path.resolve(opts.file), {
|
|
559
|
+
params: opts.params,
|
|
560
|
+
verbose: opts.verbose,
|
|
561
|
+
dryRun: opts.dryRun,
|
|
562
|
+
config,
|
|
563
|
+
onEvent,
|
|
564
|
+
dashboardServer: dashboard,
|
|
565
|
+
onNotificationError: (channel, _event, error) => {
|
|
566
|
+
console.error(`[weaver] Notification error (${channel}): ${error}`);
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
dashboard.broadcastWorkflowComplete(result.summary, result.success);
|
|
570
|
+
const elapsed = formatDuration(Date.now() - startTime);
|
|
571
|
+
if (!opts.quiet) {
|
|
572
|
+
console.log('');
|
|
573
|
+
const color = result.success ? '\x1b[32m' : '\x1b[31m';
|
|
574
|
+
console.log(`${color}Weaver: ${result.outcome}\x1b[0m (${elapsed})`);
|
|
575
|
+
console.log(` ${result.summary}`);
|
|
576
|
+
if (result.cost && result.cost.totalInputTokens > 0) {
|
|
577
|
+
console.log(` ${formatRunCost(result.cost)}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// Keep alive for 5 minutes after completion
|
|
581
|
+
console.log(`[weaver] Dashboard at ${url} (5min keep-alive, Ctrl+C to stop)`);
|
|
582
|
+
await new Promise((resolve) => {
|
|
583
|
+
const timer = setTimeout(() => resolve(), 300_000);
|
|
584
|
+
const handler = () => { clearTimeout(timer); resolve(); };
|
|
585
|
+
process.on('SIGINT', handler);
|
|
586
|
+
process.on('SIGTERM', handler);
|
|
587
|
+
});
|
|
588
|
+
await dashboard.stop();
|
|
589
|
+
process.exit(result.success ? 0 : 1);
|
|
590
|
+
}
|
|
591
|
+
catch (err) {
|
|
592
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
593
|
+
dashboard.broadcastWorkflowError(msg);
|
|
594
|
+
console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
|
|
595
|
+
await dashboard.stop();
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// --- Providers ---
|
|
600
|
+
async function handleProviders() {
|
|
601
|
+
await discoverProviders(defaultRegistry);
|
|
602
|
+
const providers = defaultRegistry.list();
|
|
603
|
+
if (providers.length === 0) {
|
|
604
|
+
console.log('No providers found.');
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
console.log('Available providers:\n');
|
|
608
|
+
for (const { name, metadata } of providers) {
|
|
609
|
+
const tag = `[${metadata.source}]`;
|
|
610
|
+
console.log(` ${name} ${tag}`);
|
|
611
|
+
if (metadata.description) {
|
|
612
|
+
console.log(` ${metadata.description}`);
|
|
613
|
+
}
|
|
614
|
+
if (metadata.requiredEnvVars && metadata.requiredEnvVars.length > 0) {
|
|
615
|
+
const present = metadata.requiredEnvVars.every((v) => process.env[v]);
|
|
616
|
+
const status = present ? '\x1b[32mset\x1b[0m' : '\x1b[33mnot set\x1b[0m';
|
|
617
|
+
console.log(` env: ${metadata.requiredEnvVars.join(', ')} (${status})`);
|
|
618
|
+
}
|
|
619
|
+
if (metadata.detectCliCommand) {
|
|
620
|
+
console.log(` cli: ${metadata.detectCliCommand}`);
|
|
621
|
+
}
|
|
622
|
+
console.log('');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
// --- Main ---
|
|
626
|
+
async function main() {
|
|
627
|
+
const opts = parseArgs(process.argv);
|
|
628
|
+
// Recover orphaned runs from previous crashes
|
|
629
|
+
try {
|
|
630
|
+
const store = new RunStore();
|
|
631
|
+
const orphans = store.checkOrphans();
|
|
632
|
+
for (const orphan of orphans) {
|
|
633
|
+
console.error(`[weaver] Recovered orphaned run ${orphan.id.slice(0, 8)} (${orphan.workflowFile}) killed at PID ${orphan.pid}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch { /* non-fatal */ }
|
|
637
|
+
if (opts.showHelp) {
|
|
638
|
+
console.log(HELP);
|
|
639
|
+
process.exit(0);
|
|
640
|
+
}
|
|
641
|
+
if (opts.showVersion) {
|
|
642
|
+
try {
|
|
643
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
644
|
+
const { default: pkg } = await import(pkgPath.href, { with: { type: 'json' } });
|
|
645
|
+
console.log(`weaver v${pkg.version}`);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
console.log('weaver (version unknown)');
|
|
649
|
+
}
|
|
650
|
+
process.exit(0);
|
|
651
|
+
}
|
|
652
|
+
if (opts.command === 'history') {
|
|
653
|
+
await handleHistory(opts);
|
|
654
|
+
process.exit(0);
|
|
655
|
+
}
|
|
656
|
+
if (opts.command === 'costs') {
|
|
657
|
+
await handleCosts(opts);
|
|
658
|
+
process.exit(0);
|
|
659
|
+
}
|
|
660
|
+
if (opts.command === 'providers') {
|
|
661
|
+
await handleProviders();
|
|
662
|
+
process.exit(0);
|
|
663
|
+
}
|
|
664
|
+
if (opts.command === 'watch') {
|
|
665
|
+
await handleWatch(opts);
|
|
666
|
+
process.exit(0);
|
|
667
|
+
}
|
|
668
|
+
if (opts.command === 'cron') {
|
|
669
|
+
await handleCron(opts);
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
672
|
+
if (opts.command === 'pipeline') {
|
|
673
|
+
await handlePipeline(opts);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (opts.command === 'dashboard' || opts.dashboard) {
|
|
677
|
+
await handleDashboard(opts);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (!opts.file) {
|
|
681
|
+
console.error('[weaver] No workflow file specified');
|
|
682
|
+
console.error('Run "weaver --help" for usage');
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
const filePath = path.resolve(opts.file);
|
|
686
|
+
const config = await loadConfig(opts.configPath);
|
|
687
|
+
// Apply --approval override
|
|
688
|
+
if (opts.approvalMode && config) {
|
|
689
|
+
config.approval = { mode: opts.approvalMode };
|
|
690
|
+
}
|
|
691
|
+
const nodeTimings = new Map();
|
|
692
|
+
const onEvent = opts.quiet
|
|
693
|
+
? undefined
|
|
694
|
+
: (event) => {
|
|
695
|
+
const icon = STATUS_ICONS[event.type] ?? ' ';
|
|
696
|
+
const label = event.nodeType
|
|
697
|
+
? `${event.nodeId} (${event.nodeType})`
|
|
698
|
+
: event.nodeId;
|
|
699
|
+
if (event.type === 'node-start') {
|
|
700
|
+
nodeTimings.set(event.nodeId, event.timestamp);
|
|
701
|
+
if (opts.verbose) {
|
|
702
|
+
console.log(` ${icon} ${label}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else if (event.type === 'node-complete') {
|
|
706
|
+
const start = nodeTimings.get(event.nodeId);
|
|
707
|
+
const dur = start ? ` ${formatDuration(event.timestamp - start)}` : '';
|
|
708
|
+
console.log(` ${icon} ${label}${dur}`);
|
|
709
|
+
}
|
|
710
|
+
else if (event.type === 'node-error') {
|
|
711
|
+
console.log(` ${icon} ${label}: ${event.error ?? 'unknown error'}`);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
const startTime = Date.now();
|
|
715
|
+
try {
|
|
716
|
+
const result = await runWorkflow(filePath, {
|
|
717
|
+
params: opts.params,
|
|
718
|
+
verbose: opts.verbose,
|
|
719
|
+
dryRun: opts.dryRun,
|
|
720
|
+
config,
|
|
721
|
+
onEvent,
|
|
722
|
+
onNotificationError: (channel, _event, error) => {
|
|
723
|
+
console.error(`[weaver] Notification error (${channel}): ${error}`);
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
const elapsed = formatDuration(Date.now() - startTime);
|
|
727
|
+
if (!opts.quiet) {
|
|
728
|
+
console.log('');
|
|
729
|
+
if (result.success) {
|
|
730
|
+
console.log(`\x1b[32mWeaver: ${result.outcome}\x1b[0m (${elapsed})`);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
console.log(`\x1b[31mWeaver: ${result.outcome}\x1b[0m (${elapsed})`);
|
|
734
|
+
}
|
|
735
|
+
console.log(` ${result.summary}`);
|
|
736
|
+
if (result.cost && result.cost.totalInputTokens > 0) {
|
|
737
|
+
console.log(` ${formatRunCost(result.cost)}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
process.exit(result.success ? 0 : 1);
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
744
|
+
console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
main();
|
|
749
|
+
//# sourceMappingURL=cli.js.map
|