@synergenius/flowweaver-pack-weaver 0.6.1 → 0.7.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.
Files changed (168) hide show
  1. package/dist/bot/ai-client.d.ts +1 -0
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +52 -1
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/audit-logger.d.ts +5 -0
  6. package/dist/bot/audit-logger.d.ts.map +1 -0
  7. package/dist/bot/audit-logger.js +42 -0
  8. package/dist/bot/audit-logger.js.map +1 -0
  9. package/dist/bot/audit-store.d.ts +13 -0
  10. package/dist/bot/audit-store.d.ts.map +1 -0
  11. package/dist/bot/audit-store.js +59 -0
  12. package/dist/bot/audit-store.js.map +1 -0
  13. package/dist/bot/cli-provider.d.ts +1 -0
  14. package/dist/bot/cli-provider.d.ts.map +1 -1
  15. package/dist/bot/cli-provider.js +86 -22
  16. package/dist/bot/cli-provider.js.map +1 -1
  17. package/dist/bot/cli-stream-parser.d.ts +11 -0
  18. package/dist/bot/cli-stream-parser.d.ts.map +1 -0
  19. package/dist/bot/cli-stream-parser.js +53 -0
  20. package/dist/bot/cli-stream-parser.js.map +1 -0
  21. package/dist/bot/file-validator.d.ts +1 -1
  22. package/dist/bot/file-validator.d.ts.map +1 -1
  23. package/dist/bot/file-validator.js +13 -27
  24. package/dist/bot/file-validator.js.map +1 -1
  25. package/dist/bot/fw-api.d.ts +8 -0
  26. package/dist/bot/fw-api.d.ts.map +1 -0
  27. package/dist/bot/fw-api.js +12 -0
  28. package/dist/bot/fw-api.js.map +1 -0
  29. package/dist/bot/runner.d.ts +2 -1
  30. package/dist/bot/runner.d.ts.map +1 -1
  31. package/dist/bot/runner.js +8 -0
  32. package/dist/bot/runner.js.map +1 -1
  33. package/dist/bot/step-executor.d.ts +3 -2
  34. package/dist/bot/step-executor.d.ts.map +1 -1
  35. package/dist/bot/step-executor.js +9 -30
  36. package/dist/bot/step-executor.js.map +1 -1
  37. package/dist/bot/system-prompt.d.ts +13 -1
  38. package/dist/bot/system-prompt.d.ts.map +1 -1
  39. package/dist/bot/system-prompt.js +28 -22
  40. package/dist/bot/system-prompt.js.map +1 -1
  41. package/dist/bot/types.d.ts +9 -1
  42. package/dist/bot/types.d.ts.map +1 -1
  43. package/dist/cli-bridge.d.ts.map +1 -1
  44. package/dist/cli-bridge.js +2 -1
  45. package/dist/cli-bridge.js.map +1 -1
  46. package/dist/cli-handlers.d.ts +2 -1
  47. package/dist/cli-handlers.d.ts.map +1 -1
  48. package/dist/cli-handlers.js +69 -0
  49. package/dist/cli-handlers.js.map +1 -1
  50. package/dist/node-types/approval-gate.d.ts.map +1 -1
  51. package/dist/node-types/approval-gate.js +4 -0
  52. package/dist/node-types/approval-gate.js.map +1 -1
  53. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  54. package/dist/node-types/exec-validate-retry.js +10 -4
  55. package/dist/node-types/exec-validate-retry.js.map +1 -1
  56. package/dist/node-types/execute-plan.js +1 -1
  57. package/dist/node-types/execute-plan.js.map +1 -1
  58. package/dist/node-types/git-ops.d.ts.map +1 -1
  59. package/dist/node-types/git-ops.js +2 -0
  60. package/dist/node-types/git-ops.js.map +1 -1
  61. package/dist/node-types/plan-task.d.ts.map +1 -1
  62. package/dist/node-types/plan-task.js +9 -1
  63. package/dist/node-types/plan-task.js.map +1 -1
  64. package/dist/node-types/send-notify.d.ts.map +1 -1
  65. package/dist/node-types/send-notify.js +4 -1
  66. package/dist/node-types/send-notify.js.map +1 -1
  67. package/dist/node-types/validate-result.d.ts +2 -2
  68. package/dist/node-types/validate-result.d.ts.map +1 -1
  69. package/dist/node-types/validate-result.js +2 -2
  70. package/dist/node-types/validate-result.js.map +1 -1
  71. package/dist/workflows/weaver-bot-batch.d.ts +4 -1
  72. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  73. package/dist/workflows/weaver-bot-batch.js +1 -1
  74. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  75. package/dist/workflows/weaver-bot.d.ts +4 -1
  76. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  77. package/dist/workflows/weaver-bot.js +1 -1
  78. package/dist/workflows/weaver-bot.js.map +1 -1
  79. package/flowweaver.manifest.json +1 -1
  80. package/package.json +3 -2
  81. package/src/bot/agent-provider.ts +273 -0
  82. package/src/bot/ai-client.ts +109 -0
  83. package/src/bot/approvals.ts +273 -0
  84. package/src/bot/audit-logger.ts +45 -0
  85. package/src/bot/audit-store.ts +69 -0
  86. package/src/bot/bot-agent-channel.ts +99 -0
  87. package/src/bot/cli-provider.ts +169 -0
  88. package/src/bot/cli-stream-parser.ts +59 -0
  89. package/src/bot/cost-store.ts +92 -0
  90. package/src/bot/cost-tracker.ts +72 -0
  91. package/src/bot/cron-parser.ts +153 -0
  92. package/src/bot/cron-scheduler.ts +48 -0
  93. package/src/bot/dashboard.ts +658 -0
  94. package/src/bot/design-checker.ts +327 -0
  95. package/src/bot/file-lock.ts +73 -0
  96. package/src/bot/file-validator.ts +41 -0
  97. package/src/bot/file-watcher.ts +103 -0
  98. package/src/bot/fw-api.ts +18 -0
  99. package/src/bot/genesis-prompt-context.ts +135 -0
  100. package/src/bot/genesis-store.ts +180 -0
  101. package/src/bot/index.ts +127 -0
  102. package/src/bot/notifications.ts +263 -0
  103. package/src/bot/pipeline-runner.ts +324 -0
  104. package/src/bot/provider-registry.ts +236 -0
  105. package/src/bot/run-store.ts +169 -0
  106. package/src/bot/runner.ts +311 -0
  107. package/src/bot/session-state.ts +73 -0
  108. package/src/bot/steering.ts +44 -0
  109. package/src/bot/step-executor.ts +34 -0
  110. package/src/bot/system-prompt.ts +280 -0
  111. package/src/bot/task-queue.ts +111 -0
  112. package/src/bot/types.ts +571 -0
  113. package/src/bot/utils.ts +17 -0
  114. package/src/bot/watch-daemon.ts +203 -0
  115. package/src/bot/web-approval.ts +240 -0
  116. package/src/cli-bridge.ts +41 -0
  117. package/src/cli-handlers.ts +1271 -0
  118. package/src/docs/weaver-config.md +135 -0
  119. package/src/index.ts +173 -0
  120. package/src/mcp-tools.ts +274 -0
  121. package/src/node-types/abort-task.ts +31 -0
  122. package/src/node-types/approval-gate.ts +75 -0
  123. package/src/node-types/bot-report.ts +82 -0
  124. package/src/node-types/build-context.ts +65 -0
  125. package/src/node-types/detect-provider.ts +75 -0
  126. package/src/node-types/exec-validate-retry.ts +175 -0
  127. package/src/node-types/execute-plan.ts +130 -0
  128. package/src/node-types/execute-target.ts +267 -0
  129. package/src/node-types/fix-errors.ts +68 -0
  130. package/src/node-types/genesis-apply-retry.ts +138 -0
  131. package/src/node-types/genesis-apply.ts +96 -0
  132. package/src/node-types/genesis-approve.ts +73 -0
  133. package/src/node-types/genesis-check-stabilize.ts +37 -0
  134. package/src/node-types/genesis-check-threshold.ts +34 -0
  135. package/src/node-types/genesis-commit.ts +71 -0
  136. package/src/node-types/genesis-compile-validate.ts +77 -0
  137. package/src/node-types/genesis-diff-fingerprint.ts +67 -0
  138. package/src/node-types/genesis-diff-workflow.ts +71 -0
  139. package/src/node-types/genesis-escrow-grace.ts +62 -0
  140. package/src/node-types/genesis-escrow-migrate.ts +138 -0
  141. package/src/node-types/genesis-escrow-recover.ts +99 -0
  142. package/src/node-types/genesis-escrow-stage.ts +104 -0
  143. package/src/node-types/genesis-escrow-validate.ts +120 -0
  144. package/src/node-types/genesis-load-config.ts +44 -0
  145. package/src/node-types/genesis-observe.ts +119 -0
  146. package/src/node-types/genesis-propose.ts +97 -0
  147. package/src/node-types/genesis-report.ts +95 -0
  148. package/src/node-types/genesis-snapshot.ts +30 -0
  149. package/src/node-types/genesis-try-apply.ts +165 -0
  150. package/src/node-types/genesis-update-history.ts +72 -0
  151. package/src/node-types/genesis-validate-proposal.ts +124 -0
  152. package/src/node-types/git-ops.ts +72 -0
  153. package/src/node-types/index.ts +36 -0
  154. package/src/node-types/load-config.ts +27 -0
  155. package/src/node-types/plan-task.ts +77 -0
  156. package/src/node-types/read-workflow.ts +68 -0
  157. package/src/node-types/receive-task.ts +92 -0
  158. package/src/node-types/report.ts +25 -0
  159. package/src/node-types/resolve-target.ts +64 -0
  160. package/src/node-types/route-task.ts +25 -0
  161. package/src/node-types/send-notify.ts +75 -0
  162. package/src/node-types/validate-result.ts +49 -0
  163. package/src/templates/index.ts +5 -0
  164. package/src/templates/weaver-bot-template.ts +106 -0
  165. package/src/workflows/genesis-task.ts +91 -0
  166. package/src/workflows/index.ts +3 -0
  167. package/src/workflows/weaver-bot-batch.ts +65 -0
  168. package/src/workflows/weaver-bot.ts +79 -0
@@ -0,0 +1,1271 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { runWorkflow } from './bot/runner.js';
5
+ import { RunStore } from './bot/run-store.js';
6
+ import { CostStore } from './bot/cost-store.js';
7
+ import { defaultRegistry, discoverProviders } from './bot/provider-registry.js';
8
+ import { WatchDaemon } from './bot/watch-daemon.js';
9
+ import { PipelineRunner } from './bot/pipeline-runner.js';
10
+ import { DashboardServer } from './bot/dashboard.js';
11
+ import { openBrowser } from './bot/utils.js';
12
+ import type { ExecutionEvent, WeaverConfig, RunRecord, RunOutcome, RunCostSummary, CostSummary, StageStatus, WorkflowResult, AuditEvent } from './bot/types.js';
13
+ import { AuditStore } from './bot/audit-store.js';
14
+
15
+ export interface ParsedArgs {
16
+ command: 'run' | 'history' | 'costs' | 'providers' | 'watch' | 'cron' | 'pipeline' | 'dashboard' | 'eject' | 'bot' | 'session' | 'steer' | 'queue' | 'genesis' | 'audit';
17
+ file?: string;
18
+ verbose: boolean;
19
+ dryRun: boolean;
20
+ quiet: boolean;
21
+ params?: Record<string, unknown>;
22
+ configPath?: string;
23
+ showHelp: boolean;
24
+ showVersion: boolean;
25
+ // history
26
+ historyId?: string;
27
+ historyLimit: number;
28
+ historyOutcome?: string;
29
+ historyWorkflow?: string;
30
+ historySince?: string;
31
+ historyJson: boolean;
32
+ historyPrune: boolean;
33
+ historyClear: boolean;
34
+ // costs
35
+ costsSince?: string;
36
+ costsModel?: string;
37
+ // watch/cron
38
+ cronSchedule?: string;
39
+ debounceMs: number;
40
+ logFile?: string;
41
+ // pipeline
42
+ pipelineStage?: string;
43
+ // dashboard
44
+ dashboard: boolean;
45
+ dashboardPort: number;
46
+ dashboardOpen: boolean;
47
+ // approval override
48
+ approvalMode?: string;
49
+ // bot
50
+ botTask?: string;
51
+ botFile?: string;
52
+ botTemplate?: string;
53
+ botBatch?: number;
54
+ autoApprove: boolean;
55
+ // genesis
56
+ genesisInit: boolean;
57
+ genesisWatch: boolean;
58
+ // eject
59
+ ejectWorkflow?: string;
60
+ }
61
+
62
+ export function parseArgs(argv: string[]): ParsedArgs {
63
+ const result: ParsedArgs = {
64
+ command: 'run',
65
+ file: undefined,
66
+ verbose: false,
67
+ dryRun: false,
68
+ quiet: false,
69
+ params: undefined,
70
+ configPath: undefined,
71
+ showHelp: false,
72
+ showVersion: false,
73
+ historyLimit: 20,
74
+ historyJson: false,
75
+ historyPrune: false,
76
+ historyClear: false,
77
+ debounceMs: 500,
78
+ dashboard: false,
79
+ dashboardPort: 4242,
80
+ dashboardOpen: false,
81
+ autoApprove: false,
82
+ genesisInit: false,
83
+ genesisWatch: false,
84
+ };
85
+
86
+ const args = argv.slice(2);
87
+ let i = 0;
88
+
89
+ while (i < args.length) {
90
+ const arg = args[i]!;
91
+
92
+ if (arg === '--help' || arg === '-h') {
93
+ result.showHelp = true;
94
+ } else if (arg === '--version') {
95
+ result.showVersion = true;
96
+ } else if (arg === '--verbose' || arg === '-v') {
97
+ result.verbose = true;
98
+ } else if (arg === '--dry-run' || arg === '-n') {
99
+ result.dryRun = true;
100
+ } else if (arg === '--quiet') {
101
+ result.quiet = true;
102
+ } else if ((arg === '--params' || arg === '-p') && i + 1 < args.length) {
103
+ i++;
104
+ try {
105
+ result.params = JSON.parse(args[i]!);
106
+ } catch {
107
+ console.error(`[weaver] Invalid JSON for --params: ${args[i]}`);
108
+ process.exit(1);
109
+ }
110
+ } else if ((arg === '--config' || arg === '-c') && i + 1 < args.length) {
111
+ i++;
112
+ result.configPath = args[i];
113
+ } else if (arg === '--limit' && i + 1 < args.length) {
114
+ i++;
115
+ result.historyLimit = parseInt(args[i]!, 10) || 20;
116
+ } else if (arg === '--outcome' && i + 1 < args.length) {
117
+ i++;
118
+ result.historyOutcome = args[i];
119
+ } else if (arg === '--workflow' && i + 1 < args.length) {
120
+ i++;
121
+ if (result.command === 'eject') {
122
+ result.ejectWorkflow = args[i];
123
+ } else {
124
+ result.historyWorkflow = args[i];
125
+ }
126
+ } else if (arg === '--since' && i + 1 < args.length) {
127
+ i++;
128
+ if (result.command === 'costs') {
129
+ result.costsSince = args[i];
130
+ } else {
131
+ result.historySince = args[i];
132
+ }
133
+ } else if (arg === '--model' && i + 1 < args.length) {
134
+ i++;
135
+ result.costsModel = args[i];
136
+ } else if (arg === '--json') {
137
+ result.historyJson = true;
138
+ } else if (arg === '--prune') {
139
+ result.historyPrune = true;
140
+ } else if (arg === '--clear') {
141
+ result.historyClear = true;
142
+ } else if (arg === 'history') {
143
+ result.command = 'history';
144
+ } else if (arg === 'costs') {
145
+ result.command = 'costs';
146
+ } else if (arg === 'providers') {
147
+ result.command = 'providers';
148
+ } else if (arg === 'eject') {
149
+ result.command = 'eject';
150
+ } else if (arg === 'watch') {
151
+ result.command = 'watch';
152
+ } else if (arg === 'cron') {
153
+ result.command = 'cron';
154
+ // Next arg is the schedule
155
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
156
+ i++;
157
+ result.cronSchedule = args[i];
158
+ }
159
+ } else if (arg === 'pipeline') {
160
+ result.command = 'pipeline';
161
+ } else if (arg === '--cron' && i + 1 < args.length) {
162
+ i++;
163
+ result.cronSchedule = args[i];
164
+ } else if (arg === '--debounce' && i + 1 < args.length) {
165
+ i++;
166
+ result.debounceMs = parseInt(args[i]!, 10) || 500;
167
+ } else if (arg === '--log' && i + 1 < args.length) {
168
+ i++;
169
+ result.logFile = args[i];
170
+ } else if (arg === '--stage' && i + 1 < args.length) {
171
+ i++;
172
+ result.pipelineStage = args[i];
173
+ } else if (arg === '--dashboard') {
174
+ result.dashboard = true;
175
+ } else if (arg === '--port' && i + 1 < args.length) {
176
+ i++;
177
+ result.dashboardPort = parseInt(args[i]!, 10) || 4242;
178
+ } else if (arg === '--open') {
179
+ result.dashboardOpen = true;
180
+ } else if (arg === '--approval' && i + 1 < args.length) {
181
+ i++;
182
+ result.approvalMode = args[i];
183
+ } else if (arg === 'dashboard') {
184
+ result.command = 'dashboard';
185
+ result.dashboard = true;
186
+ } else if (arg === 'bot') {
187
+ result.command = 'bot';
188
+ // Next non-flag arg is the task string
189
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
190
+ i++;
191
+ result.botTask = args[i];
192
+ }
193
+ } else if (arg === 'session') {
194
+ result.command = 'session';
195
+ } else if (arg === 'steer') {
196
+ result.command = 'steer';
197
+ // Next arg is the subcommand
198
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
199
+ i++;
200
+ result.botTask = args[i];
201
+ // Next arg after redirect/queue is payload
202
+ if ((args[i] === 'redirect' || args[i] === 'queue') && i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
203
+ i++;
204
+ result.botFile = args[i];
205
+ }
206
+ }
207
+ } else if (arg === 'queue') {
208
+ result.command = 'queue';
209
+ // Next arg is action (add/list/clear/remove)
210
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
211
+ i++;
212
+ result.botTask = args[i];
213
+ // Next arg is task/id
214
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
215
+ i++;
216
+ result.botFile = args[i];
217
+ }
218
+ }
219
+ } else if (arg === '--file' && i + 1 < args.length) {
220
+ i++;
221
+ result.botFile = args[i];
222
+ } else if (arg === '--template' && i + 1 < args.length) {
223
+ i++;
224
+ result.botTemplate = args[i];
225
+ } else if (arg === '--batch' && i + 1 < args.length) {
226
+ i++;
227
+ result.botBatch = parseInt(args[i]!, 10) || undefined;
228
+ } else if (arg === '--auto-approve') {
229
+ result.autoApprove = true;
230
+ } else if (arg === 'audit') {
231
+ result.command = 'audit';
232
+ // Next non-flag arg is the runId
233
+ if (i + 1 < args.length && !args[i + 1]!.startsWith('-')) {
234
+ i++;
235
+ result.historyId = args[i];
236
+ }
237
+ } else if (arg === 'genesis') {
238
+ result.command = 'genesis';
239
+ } else if (arg === '--init') {
240
+ result.genesisInit = true;
241
+ } else if (arg === '--watch') {
242
+ result.genesisWatch = true;
243
+ } else if (arg === '--project-dir' && i + 1 < args.length) {
244
+ i++;
245
+ result.file = args[i];
246
+ } else if (arg === 'run') {
247
+ // skip, next arg is the file
248
+ } else if (!arg.startsWith('-')) {
249
+ if (result.command === 'history' && !result.file) {
250
+ result.historyId = arg;
251
+ } else {
252
+ result.file = arg;
253
+ }
254
+ } else {
255
+ console.error(`[weaver] Unknown option: ${arg}`);
256
+ console.error('Run "flow-weaver weaver --help" for usage');
257
+ process.exit(1);
258
+ }
259
+ i++;
260
+ }
261
+
262
+ return result;
263
+ }
264
+
265
+ // --- Formatting helpers ---
266
+
267
+ export function formatDuration(ms: number): string {
268
+ if (ms < 1000) return `${ms}ms`;
269
+ return `${(ms / 1000).toFixed(1)}s`;
270
+ }
271
+
272
+ const STATUS_ICONS: Record<string, string> = {
273
+ 'node-start': '\x1b[36m>\x1b[0m',
274
+ 'node-complete': '\x1b[32m+\x1b[0m',
275
+ 'node-error': '\x1b[31mx\x1b[0m',
276
+ };
277
+
278
+ const OUTCOME_COLORS: Record<string, string> = {
279
+ completed: '\x1b[32m',
280
+ failed: '\x1b[31m',
281
+ error: '\x1b[31m',
282
+ skipped: '\x1b[33m',
283
+ };
284
+ const RESET = '\x1b[0m';
285
+
286
+ const STAGE_ICONS: Record<string, string> = {
287
+ running: '\x1b[36m>\x1b[0m',
288
+ completed: '\x1b[32m+\x1b[0m',
289
+ failed: '\x1b[31mx\x1b[0m',
290
+ skipped: '\x1b[33m-\x1b[0m',
291
+ cancelled: '\x1b[33m~\x1b[0m',
292
+ };
293
+
294
+ function printRunTable(records: RunRecord[]): void {
295
+ console.log(
296
+ 'ID'.padEnd(10) +
297
+ 'OUTCOME'.padEnd(12) +
298
+ 'DURATION'.padEnd(10) +
299
+ 'WORKFLOW'.padEnd(24) +
300
+ 'STARTED',
301
+ );
302
+
303
+ for (const r of records) {
304
+ const id = r.id.slice(0, 8);
305
+ const color = OUTCOME_COLORS[r.outcome] ?? '';
306
+ const outcome = `${color}${r.outcome}${RESET}`;
307
+ const duration = formatDuration(r.durationMs);
308
+ const workflow = path.basename(r.workflowFile);
309
+ const started = r.startedAt.replace('T', ' ').slice(0, 16);
310
+
311
+ console.log(
312
+ id.padEnd(10) +
313
+ outcome.padEnd(12 + color.length + RESET.length) +
314
+ duration.padEnd(10) +
315
+ (workflow.length > 22 ? workflow.slice(0, 21) + '~' : workflow).padEnd(24) +
316
+ started,
317
+ );
318
+ }
319
+ }
320
+
321
+ function printRunDetail(r: RunRecord): void {
322
+ const outcomeColor = r.success ? '\x1b[32m' : '\x1b[31m';
323
+
324
+ console.log(`\nRun ${r.id}\n`);
325
+ console.log(` Workflow: ${r.workflowFile}`);
326
+ if (r.functionName) console.log(` Function: ${r.functionName}`);
327
+ console.log(` Outcome: ${outcomeColor}${r.outcome}${RESET} (${r.success ? 'success' : 'failure'})`);
328
+ console.log(` Started: ${r.startedAt}`);
329
+ console.log(` Finished: ${r.finishedAt}`);
330
+ console.log(` Duration: ${formatDuration(r.durationMs)}`);
331
+ if (r.executionTime !== undefined) {
332
+ console.log(` Execution time: ${formatDuration(r.executionTime)}`);
333
+ }
334
+ if (r.provider) console.log(` Provider: ${r.provider}`);
335
+ console.log(` Dry run: ${r.dryRun ? 'yes' : 'no'}`);
336
+ console.log(` Summary: ${r.summary}`);
337
+ if (r.params) {
338
+ console.log(` Params: ${JSON.stringify(r.params)}`);
339
+ }
340
+ console.log('');
341
+ }
342
+
343
+ function parseSince(spec?: string): number | undefined {
344
+ if (!spec) return undefined;
345
+ const match = spec.match(/^(\d+)([dhm])$/);
346
+ if (match) {
347
+ const n = parseInt(match[1]!, 10);
348
+ const unit = match[2];
349
+ const ms = unit === 'd' ? n * 86_400_000 : unit === 'h' ? n * 3_600_000 : n * 60_000;
350
+ return Date.now() - ms;
351
+ }
352
+ const ts = new Date(spec).getTime();
353
+ return isNaN(ts) ? undefined : ts;
354
+ }
355
+
356
+ function formatCostTable(summary: CostSummary): string {
357
+ const lines: string[] = [];
358
+ lines.push(`Weaver Cost Summary (${summary.totalRuns} runs)`);
359
+ lines.push(`Total: ~$${summary.totalCost.toFixed(4)}`);
360
+ lines.push(`Tokens: ${summary.totalInputTokens.toLocaleString()} in / ${summary.totalOutputTokens.toLocaleString()} out`);
361
+
362
+ const models = Object.entries(summary.byModel);
363
+ if (models.length > 0) {
364
+ lines.push('');
365
+ lines.push('By model:');
366
+ for (const [model, data] of models) {
367
+ lines.push(` ${model}: ${data.runs} runs, ~$${data.cost.toFixed(4)}, ${data.inputTokens.toLocaleString()} in / ${data.outputTokens.toLocaleString()} out`);
368
+ }
369
+ }
370
+
371
+ return lines.join('\n');
372
+ }
373
+
374
+ function formatRunCost(cost: RunCostSummary): string {
375
+ const inp = cost.totalInputTokens.toLocaleString();
376
+ const out = cost.totalOutputTokens.toLocaleString();
377
+ const usd = cost.totalCost < 0.01
378
+ ? `$${cost.totalCost.toFixed(4)}`
379
+ : `$${cost.totalCost.toFixed(2)}`;
380
+ return `tokens: ${inp} in / ${out} out | cost: ~${usd} (${cost.model})`;
381
+ }
382
+
383
+ async function loadConfig(configPath?: string): Promise<WeaverConfig | undefined> {
384
+ if (!configPath) return undefined;
385
+ try {
386
+ const { readFileSync } = await import('node:fs');
387
+ return JSON.parse(readFileSync(path.resolve(configPath), 'utf-8'));
388
+ } catch (err: unknown) {
389
+ const msg = err instanceof Error ? err.message : String(err);
390
+ console.error(`[weaver] Failed to read config: ${msg}`);
391
+ process.exit(1);
392
+ }
393
+ }
394
+
395
+ // --- Handlers ---
396
+
397
+ export async function handleHistory(opts: ParsedArgs): Promise<void> {
398
+ const store = new RunStore();
399
+
400
+ if (opts.historyClear) {
401
+ const deleted = store.clear();
402
+ console.log(deleted ? 'History cleared.' : 'No history to clear.');
403
+ return;
404
+ }
405
+
406
+ if (opts.historyPrune) {
407
+ const pruned = store.prune({ maxRecords: 500, maxAgeDays: 90 });
408
+ console.log(`Pruned ${pruned} record(s).`);
409
+ return;
410
+ }
411
+
412
+ if (opts.historyId) {
413
+ const record = store.get(opts.historyId);
414
+ if (!record) {
415
+ console.error(`[weaver] No run found matching "${opts.historyId}"`);
416
+ process.exit(1);
417
+ }
418
+ if (opts.historyJson) {
419
+ console.log(JSON.stringify(record, null, 2));
420
+ } else {
421
+ printRunDetail(record);
422
+ }
423
+ return;
424
+ }
425
+
426
+ const records = store.list({
427
+ outcome: opts.historyOutcome as RunOutcome | undefined,
428
+ workflowFile: opts.historyWorkflow ? path.resolve(opts.historyWorkflow) : undefined,
429
+ since: opts.historySince,
430
+ limit: opts.historyLimit,
431
+ });
432
+
433
+ if (records.length === 0) {
434
+ console.log('No runs recorded yet.');
435
+ return;
436
+ }
437
+
438
+ if (opts.historyJson) {
439
+ console.log(JSON.stringify(records, null, 2));
440
+ } else {
441
+ printRunTable(records);
442
+ }
443
+ }
444
+
445
+ export async function handleCosts(opts: ParsedArgs): Promise<void> {
446
+ const store = new CostStore();
447
+ const sinceTs = parseSince(opts.costsSince);
448
+ const summary = store.summarize({ since: sinceTs, model: opts.costsModel });
449
+
450
+ if (summary.totalRuns === 0) {
451
+ console.log('No cost data found.');
452
+ return;
453
+ }
454
+
455
+ console.log(formatCostTable(summary));
456
+ }
457
+
458
+ export async function handleWatch(opts: ParsedArgs): Promise<void> {
459
+ if (!opts.file) {
460
+ console.error('[weaver] No workflow file specified for watch');
461
+ process.exit(1);
462
+ }
463
+
464
+ const config = await loadConfig(opts.configPath);
465
+
466
+ const daemon = new WatchDaemon({
467
+ filePath: path.resolve(opts.file),
468
+ watchFile: true,
469
+ cron: opts.cronSchedule,
470
+ debounceMs: opts.debounceMs,
471
+ logFile: opts.logFile,
472
+ verbose: opts.verbose,
473
+ params: opts.params,
474
+ config,
475
+ quiet: opts.quiet,
476
+ });
477
+
478
+ await daemon.start();
479
+ }
480
+
481
+ export async function handleCron(opts: ParsedArgs): Promise<void> {
482
+ if (!opts.cronSchedule) {
483
+ console.error('[weaver] No cron schedule specified');
484
+ console.error('Usage: flow-weaver weaver cron "*/5 * * * *" <file>');
485
+ process.exit(1);
486
+ }
487
+ if (!opts.file) {
488
+ console.error('[weaver] No workflow file specified for cron');
489
+ process.exit(1);
490
+ }
491
+
492
+ const config = await loadConfig(opts.configPath);
493
+
494
+ const daemon = new WatchDaemon({
495
+ filePath: path.resolve(opts.file),
496
+ watchFile: false,
497
+ cron: opts.cronSchedule,
498
+ debounceMs: opts.debounceMs,
499
+ logFile: opts.logFile,
500
+ verbose: opts.verbose,
501
+ params: opts.params,
502
+ config,
503
+ quiet: opts.quiet,
504
+ });
505
+
506
+ await daemon.start();
507
+ }
508
+
509
+ export async function handlePipeline(opts: ParsedArgs): Promise<void> {
510
+ if (!opts.file) {
511
+ console.error('[weaver] No pipeline config specified');
512
+ console.error('Usage: flow-weaver weaver pipeline <config.json>');
513
+ process.exit(1);
514
+ }
515
+
516
+ let config;
517
+ try {
518
+ config = PipelineRunner.load(opts.file);
519
+ } catch (err: unknown) {
520
+ const msg = err instanceof Error ? err.message : String(err);
521
+ console.error(`\x1b[31m[weaver] Pipeline config error: ${msg}\x1b[0m`);
522
+ process.exit(1);
523
+ }
524
+ const runner = new PipelineRunner();
525
+
526
+ if (!opts.quiet) {
527
+ console.log(`Pipeline: ${config.name}`);
528
+ console.log(` ${config.stages.length} stages\n`);
529
+ }
530
+
531
+ const stageTimings = new Map<string, number>();
532
+
533
+ const onStageEvent = opts.quiet
534
+ ? undefined
535
+ : (stageId: string, status: StageStatus, result?: WorkflowResult) => {
536
+ const icon = STAGE_ICONS[status] ?? ' ';
537
+ const stage = config.stages.find((s) => s.id === stageId);
538
+ const label = stage?.label ?? stageId;
539
+
540
+ if (status === 'running') {
541
+ stageTimings.set(stageId, Date.now());
542
+ if (opts.verbose) {
543
+ console.log(` ${icon} ${label}`);
544
+ }
545
+ } else {
546
+ const start = stageTimings.get(stageId);
547
+ const dur = start ? ` (${formatDuration(Date.now() - start)})` : '';
548
+ console.log(` ${icon} ${label}${dur}`);
549
+ }
550
+ };
551
+
552
+ const weaverConfig = await loadConfig(opts.configPath);
553
+
554
+ try {
555
+ const pipelineResult = await runner.run(config, {
556
+ verbose: opts.verbose,
557
+ dryRun: opts.dryRun,
558
+ config: weaverConfig,
559
+ stage: opts.pipelineStage,
560
+ onStageEvent,
561
+ onNotificationError: (channel, _event, error) => {
562
+ console.error(`[weaver] Notification error (${channel}): ${error}`);
563
+ },
564
+ });
565
+
566
+ if (!opts.quiet) {
567
+ const elapsed = formatDuration(pipelineResult.durationMs);
568
+ const color = pipelineResult.success ? '\x1b[32m' : '\x1b[31m';
569
+ console.log(`\n${color}Pipeline: ${pipelineResult.outcome}\x1b[0m (${elapsed})`);
570
+ }
571
+
572
+ process.exit(pipelineResult.success ? 0 : 1);
573
+ } catch (err: unknown) {
574
+ const msg = err instanceof Error ? err.message : String(err);
575
+ console.error(`\x1b[31m[weaver] Pipeline error: ${msg}\x1b[0m`);
576
+ process.exit(1);
577
+ }
578
+ }
579
+
580
+ export async function handleDashboard(opts: ParsedArgs): Promise<void> {
581
+ const dashboard = new DashboardServer({ port: opts.dashboardPort });
582
+ const port = await dashboard.start();
583
+ const url = dashboard.getUrl();
584
+ console.log(`[weaver] Dashboard: ${url}`);
585
+
586
+ if (opts.dashboardOpen) openBrowser(url);
587
+
588
+ if (!opts.file) {
589
+ // Dashboard-only mode: view history, wait for SIGINT
590
+ console.log('[weaver] Dashboard running (Ctrl+C to stop)');
591
+ await new Promise<void>((resolve) => {
592
+ process.on('SIGINT', () => { dashboard.stop().then(resolve); });
593
+ process.on('SIGTERM', () => { dashboard.stop().then(resolve); });
594
+ });
595
+ return;
596
+ }
597
+
598
+ // Run workflow with live dashboard
599
+ const config = await loadConfig(opts.configPath);
600
+ if (opts.approvalMode) {
601
+ const cfg = config ?? { provider: 'auto' as const };
602
+ cfg.approval = { mode: opts.approvalMode as 'web', webOpen: opts.dashboardOpen };
603
+ }
604
+
605
+ dashboard.broadcastWorkflowStart(path.resolve(opts.file));
606
+
607
+ const nodeTimings = new Map<string, number>();
608
+ const onEvent = (event: ExecutionEvent) => {
609
+ dashboard.broadcastExecution(event);
610
+
611
+ if (opts.quiet) return;
612
+ const icon = STATUS_ICONS[event.type] ?? ' ';
613
+ const label = event.nodeType ? `${event.nodeId} (${event.nodeType})` : event.nodeId;
614
+
615
+ if (event.type === 'node-start') {
616
+ nodeTimings.set(event.nodeId, event.timestamp);
617
+ if (opts.verbose) console.log(` ${icon} ${label}`);
618
+ } else if (event.type === 'node-complete') {
619
+ const start = nodeTimings.get(event.nodeId);
620
+ const dur = start ? ` ${formatDuration(event.timestamp - start)}` : '';
621
+ console.log(` ${icon} ${label}${dur}`);
622
+ } else if (event.type === 'node-error') {
623
+ console.log(` ${icon} ${label}: ${event.error ?? 'unknown error'}`);
624
+ }
625
+ };
626
+
627
+ const startTime = Date.now();
628
+
629
+ try {
630
+ const result = await runWorkflow(path.resolve(opts.file), {
631
+ params: opts.params,
632
+ verbose: opts.verbose,
633
+ dryRun: opts.dryRun,
634
+ config,
635
+ onEvent,
636
+ dashboardServer: dashboard,
637
+ onNotificationError: (channel, _event, error) => {
638
+ console.error(`[weaver] Notification error (${channel}): ${error}`);
639
+ },
640
+ });
641
+
642
+ dashboard.broadcastWorkflowComplete(result.summary, result.success);
643
+
644
+ const elapsed = formatDuration(Date.now() - startTime);
645
+ if (!opts.quiet) {
646
+ console.log('');
647
+ const color = result.success ? '\x1b[32m' : '\x1b[31m';
648
+ console.log(`${color}Weaver: ${result.outcome}\x1b[0m (${elapsed})`);
649
+ console.log(` ${result.summary}`);
650
+ if (result.cost && result.cost.totalInputTokens > 0) {
651
+ console.log(` ${formatRunCost(result.cost)}`);
652
+ }
653
+ }
654
+
655
+ // Keep alive for 5 minutes after completion
656
+ console.log(`[weaver] Dashboard at ${url} (5min keep-alive, Ctrl+C to stop)`);
657
+ await new Promise<void>((resolve) => {
658
+ const timer = setTimeout(() => resolve(), 300_000);
659
+ const handler = () => { clearTimeout(timer); resolve(); };
660
+ process.on('SIGINT', handler);
661
+ process.on('SIGTERM', handler);
662
+ });
663
+
664
+ await dashboard.stop();
665
+ process.exit(result.success ? 0 : 1);
666
+ } catch (err: unknown) {
667
+ const msg = err instanceof Error ? err.message : String(err);
668
+ dashboard.broadcastWorkflowError(msg);
669
+ console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
670
+ await dashboard.stop();
671
+ process.exit(1);
672
+ }
673
+ }
674
+
675
+ export async function handleProviders(): Promise<void> {
676
+ await discoverProviders(defaultRegistry);
677
+ const providers = defaultRegistry.list();
678
+
679
+ if (providers.length === 0) {
680
+ console.log('No providers found.');
681
+ return;
682
+ }
683
+
684
+ console.log('Available providers:\n');
685
+ for (const { name, metadata } of providers) {
686
+ const tag = `[${metadata.source}]`;
687
+ console.log(` ${name} ${tag}`);
688
+ if (metadata.description) {
689
+ console.log(` ${metadata.description}`);
690
+ }
691
+ if (metadata.requiredEnvVars && metadata.requiredEnvVars.length > 0) {
692
+ const present = metadata.requiredEnvVars.every((v) => process.env[v]);
693
+ const status = present ? '\x1b[32mset\x1b[0m' : '\x1b[33mnot set\x1b[0m';
694
+ console.log(` env: ${metadata.requiredEnvVars.join(', ')} (${status})`);
695
+ }
696
+ if (metadata.detectCliCommand) {
697
+ console.log(` cli: ${metadata.detectCliCommand}`);
698
+ }
699
+ console.log('');
700
+ }
701
+ }
702
+
703
+ // --- Workflow map for eject and resolution ---
704
+
705
+ const MANAGED_WORKFLOWS: Record<string, string> = {
706
+ bot: 'weaver-bot',
707
+ batch: 'weaver-bot-batch',
708
+ genesis: 'genesis-task',
709
+ };
710
+
711
+ /** Rewrite pack-relative imports to package imports and deduplicate. */
712
+ function rewritePackImports(source: string): string {
713
+ // Rewrite ../node-types/*.js → package/node-types
714
+ // Rewrite ../bot/*.js → package/bot
715
+ const rewritten = source
716
+ .replace(/from\s+['"]\.\.\/node-types\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/node-types'")
717
+ .replace(/from\s+['"]\.\.\/bot\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/bot'");
718
+
719
+ // Deduplicate import lines: collapse multiple imports from the same module
720
+ const lines = rewritten.split('\n');
721
+ const importMap = new Map<string, Set<string>>();
722
+ const nonImportLines: string[] = [];
723
+ let pastImports = false;
724
+
725
+ for (const line of lines) {
726
+ const importMatch = line.match(/^import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?\s*$/);
727
+ if (importMatch && !pastImports) {
728
+ const isType = line.trimStart().startsWith('import type');
729
+ const names = importMatch[1]!.split(',').map((s) => s.trim()).filter(Boolean);
730
+ const mod = importMatch[2]!;
731
+ const key = isType ? `type:${mod}` : mod;
732
+ if (!importMap.has(key)) importMap.set(key, new Set());
733
+ for (const n of names) importMap.get(key)!.add(n);
734
+ } else {
735
+ if (line.trim() !== '' && !line.match(/^import\s/)) pastImports = true;
736
+ nonImportLines.push(line);
737
+ }
738
+ }
739
+
740
+ const dedupedImports: string[] = [];
741
+ for (const [key, names] of importMap) {
742
+ const isType = key.startsWith('type:');
743
+ const mod = isType ? key.slice(5) : key;
744
+ const keyword = isType ? 'import type' : 'import';
745
+ dedupedImports.push(`${keyword} { ${[...names].join(', ')} } from '${mod}';`);
746
+ }
747
+
748
+ return [...dedupedImports, ...nonImportLines].join('\n');
749
+ }
750
+
751
+ /** Read a managed workflow source from the pack. */
752
+ function readPackWorkflowSource(packRoot: URL, workflowBaseName: string): string {
753
+ const candidates = [
754
+ new URL(`src/workflows/${workflowBaseName}.ts`, packRoot),
755
+ new URL(`dist/workflows/${workflowBaseName}.ts`, packRoot),
756
+ new URL(`dist/workflows/${workflowBaseName}.js`, packRoot),
757
+ ];
758
+
759
+ for (const candidate of candidates) {
760
+ try {
761
+ return fs.readFileSync(candidate, 'utf-8');
762
+ } catch { /* try next */ }
763
+ }
764
+
765
+ throw new Error(`Could not find managed workflow: ${workflowBaseName}`);
766
+ }
767
+
768
+ /**
769
+ * Resolve a managed workflow path. Checks for a local ejected override first,
770
+ * then falls back to the pack's own source or dist.
771
+ */
772
+ function resolveWorkflowPath(workflowKey: string, cwd: string): string {
773
+ const baseName = MANAGED_WORKFLOWS[workflowKey];
774
+ if (!baseName) throw new Error(`Unknown workflow: ${workflowKey}`);
775
+
776
+ // Check for ejected override
777
+ const metaPath = path.join(cwd, '.weaver-meta.json');
778
+ if (fs.existsSync(metaPath)) {
779
+ try {
780
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
781
+ if (meta.ejected && meta.workflowFiles?.[workflowKey]) {
782
+ const localPath = path.resolve(cwd, meta.workflowFiles[workflowKey]);
783
+ if (fs.existsSync(localPath)) {
784
+ return localPath;
785
+ }
786
+ }
787
+ } catch { /* fall through to pack */ }
788
+ }
789
+
790
+ // Fall back to pack's managed workflow
791
+ const packRoot = new URL('..', import.meta.url);
792
+ try {
793
+ const srcPath = fileURLToPath(new URL(`src/workflows/${baseName}.ts`, packRoot));
794
+ if (fs.existsSync(srcPath)) return srcPath;
795
+ } catch { /* ignore */ }
796
+
797
+ return fileURLToPath(new URL(`dist/workflows/${baseName}.js`, packRoot));
798
+ }
799
+
800
+ export async function handleEject(opts: ParsedArgs): Promise<void> {
801
+ const packRoot = new URL('..', import.meta.url);
802
+
803
+ // Determine which workflows to eject
804
+ const workflowKeys = opts.ejectWorkflow
805
+ ? [opts.ejectWorkflow]
806
+ : Object.keys(MANAGED_WORKFLOWS);
807
+
808
+ if (opts.ejectWorkflow && !MANAGED_WORKFLOWS[opts.ejectWorkflow]) {
809
+ console.error(`[weaver] Unknown workflow: ${opts.ejectWorkflow}`);
810
+ console.error(`[weaver] Available: ${Object.keys(MANAGED_WORKFLOWS).join(', ')}`);
811
+ process.exit(1);
812
+ return;
813
+ }
814
+
815
+ const ejectedFiles: Record<string, string> = {};
816
+
817
+ for (const key of workflowKeys) {
818
+ const baseName = MANAGED_WORKFLOWS[key]!;
819
+ let source: string;
820
+ try {
821
+ source = readPackWorkflowSource(packRoot, baseName);
822
+ } catch (err: unknown) {
823
+ const msg = err instanceof Error ? err.message : String(err);
824
+ console.error(`[weaver] ${msg}`);
825
+ process.exit(1);
826
+ return;
827
+ }
828
+
829
+ const finalSource = rewritePackImports(source);
830
+ const fileName = `${baseName}.ts`;
831
+ const destPath = path.resolve(process.cwd(), fileName);
832
+ fs.writeFileSync(destPath, finalSource, 'utf-8');
833
+ ejectedFiles[key] = fileName;
834
+ console.log(`[weaver] Ejected ${key} → ${destPath}`);
835
+ }
836
+
837
+ // Read pack version
838
+ let packVersion = 'unknown';
839
+ try {
840
+ const pkgPath = new URL('package.json', packRoot);
841
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
842
+ packVersion = pkg.version;
843
+ } catch { /* ignore */ }
844
+
845
+ // Write/update .weaver-meta.json (merge with existing if present)
846
+ const metaPath = path.resolve(process.cwd(), '.weaver-meta.json');
847
+ let existingMeta: Record<string, unknown> = {};
848
+ try {
849
+ existingMeta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
850
+ } catch { /* start fresh */ }
851
+
852
+ const existingWorkflows = (existingMeta.workflowFiles as Record<string, string>) ?? {};
853
+ const meta = {
854
+ ejected: true,
855
+ packVersion,
856
+ workflowFiles: { ...existingWorkflows, ...ejectedFiles },
857
+ };
858
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n', 'utf-8');
859
+
860
+ console.log(`[weaver] Metadata written to ${metaPath}`);
861
+ console.log('[weaver] You can now customize the ejected workflow(s) freely.');
862
+ console.log('[weaver] The bot will use local files when available.');
863
+ }
864
+
865
+ export async function handleRun(opts: ParsedArgs): Promise<void> {
866
+ if (!opts.file) {
867
+ console.error('[weaver] No workflow file specified');
868
+ console.error('Run "flow-weaver weaver --help" for usage');
869
+ process.exit(1);
870
+ }
871
+
872
+ const filePath = path.resolve(opts.file);
873
+ const config = await loadConfig(opts.configPath);
874
+
875
+ // Apply --approval override
876
+ if (opts.approvalMode && config) {
877
+ config.approval = { mode: opts.approvalMode as 'web' };
878
+ }
879
+
880
+ const nodeTimings = new Map<string, number>();
881
+
882
+ const onEvent = opts.quiet
883
+ ? undefined
884
+ : (event: ExecutionEvent) => {
885
+ const icon = STATUS_ICONS[event.type] ?? ' ';
886
+ const label = event.nodeType
887
+ ? `${event.nodeId} (${event.nodeType})`
888
+ : event.nodeId;
889
+
890
+ if (event.type === 'node-start') {
891
+ nodeTimings.set(event.nodeId, event.timestamp);
892
+ if (opts.verbose) {
893
+ console.log(` ${icon} ${label}`);
894
+ }
895
+ } else if (event.type === 'node-complete') {
896
+ const start = nodeTimings.get(event.nodeId);
897
+ const dur = start ? ` ${formatDuration(event.timestamp - start)}` : '';
898
+ console.log(` ${icon} ${label}${dur}`);
899
+ } else if (event.type === 'node-error') {
900
+ console.log(` ${icon} ${label}: ${event.error ?? 'unknown error'}`);
901
+ }
902
+ };
903
+
904
+ const startTime = Date.now();
905
+
906
+ try {
907
+ const result = await runWorkflow(filePath, {
908
+ params: opts.params,
909
+ verbose: opts.verbose,
910
+ dryRun: opts.dryRun,
911
+ config,
912
+ onEvent,
913
+ onNotificationError: (channel, _event, error) => {
914
+ console.error(`[weaver] Notification error (${channel}): ${error}`);
915
+ },
916
+ });
917
+
918
+ const elapsed = formatDuration(Date.now() - startTime);
919
+
920
+ if (!opts.quiet) {
921
+ console.log('');
922
+ if (result.success) {
923
+ console.log(`\x1b[32mBot: ${result.outcome}\x1b[0m (${elapsed})`);
924
+ } else {
925
+ console.log(`\x1b[31mBot: ${result.outcome}\x1b[0m (${elapsed})`);
926
+ }
927
+ console.log(` ${result.summary}`);
928
+
929
+ if (result.cost && result.cost.totalInputTokens > 0) {
930
+ console.log(` ${formatRunCost(result.cost)}`);
931
+ }
932
+ }
933
+
934
+ process.exit(result.success ? 0 : 1);
935
+ } catch (err: unknown) {
936
+ const msg = err instanceof Error ? err.message : String(err);
937
+ console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
938
+ process.exit(1);
939
+ }
940
+ }
941
+
942
+ export async function handleBot(opts: ParsedArgs): Promise<void> {
943
+ if (!opts.botTask) {
944
+ console.error('[weaver] No task specified');
945
+ console.error('Usage: flow-weaver weaver bot "Create a workflow that..."');
946
+ process.exit(1);
947
+ }
948
+
949
+ const task = {
950
+ instruction: opts.botTask,
951
+ mode: opts.botFile ? 'modify' : 'create',
952
+ targets: opts.botFile ? [opts.botFile] : undefined,
953
+ options: {
954
+ template: opts.botTemplate,
955
+ batchCount: opts.botBatch,
956
+ dryRun: opts.dryRun,
957
+ autoApprove: opts.autoApprove,
958
+ },
959
+ };
960
+
961
+ // Use the batch workflow if --batch specified
962
+ const workflowKey = opts.botBatch ? 'batch' : 'bot';
963
+ const workflowPath = resolveWorkflowPath(workflowKey, opts.file ?? process.cwd());
964
+
965
+ const config = await loadConfig(opts.configPath);
966
+ const nodeTimings = new Map<string, number>();
967
+
968
+ // Start dashboard if requested
969
+ let dashboard: DashboardServer | null = null;
970
+ if (opts.dashboard) {
971
+ dashboard = new DashboardServer({ port: opts.dashboardPort ?? 4242 });
972
+ const port = await dashboard.start();
973
+ console.log(`[weaver] Dashboard: http://127.0.0.1:${port}`);
974
+ openBrowser(`http://127.0.0.1:${port}`);
975
+ dashboard.broadcastWorkflowStart(workflowPath);
976
+ }
977
+
978
+ const onEvent = opts.quiet
979
+ ? undefined
980
+ : (event: ExecutionEvent) => {
981
+ const icon = STATUS_ICONS[event.type] ?? ' ';
982
+ const label = event.nodeType ? `${event.nodeId} (${event.nodeType})` : event.nodeId;
983
+
984
+ if (dashboard) dashboard.broadcastExecution(event);
985
+
986
+ if (event.type === 'node-start') {
987
+ nodeTimings.set(event.nodeId, event.timestamp);
988
+ if (opts.verbose) console.log(` ${icon} ${label}`);
989
+ } else if (event.type === 'node-complete') {
990
+ const start = nodeTimings.get(event.nodeId);
991
+ const dur = start ? ` ${formatDuration(event.timestamp - start)}` : '';
992
+ console.log(` ${icon} ${label}${dur}`);
993
+ } else if (event.type === 'node-error') {
994
+ console.log(` ${icon} ${label}: ${event.error ?? 'unknown error'}`);
995
+ }
996
+ };
997
+
998
+ const startTime = Date.now();
999
+ if (!opts.quiet) {
1000
+ console.log(`[weaver] Bot: ${opts.botTask.slice(0, 80)}`);
1001
+ }
1002
+
1003
+ try {
1004
+ const result = await runWorkflow(workflowPath, {
1005
+ params: { taskJson: JSON.stringify(task), projectDir: opts.file ?? process.cwd() },
1006
+ verbose: opts.verbose,
1007
+ dryRun: opts.dryRun,
1008
+ config,
1009
+ onEvent,
1010
+ onNotificationError: (channel, _event, error) => {
1011
+ console.error(`[weaver] Notification error (${channel}): ${error}`);
1012
+ },
1013
+ });
1014
+
1015
+ const elapsed = formatDuration(Date.now() - startTime);
1016
+ if (!opts.quiet) {
1017
+ const color = result.success ? '\x1b[32m' : '\x1b[31m';
1018
+ console.log(`\n${color}Bot: ${result.outcome}\x1b[0m (${elapsed})`);
1019
+ console.log(` ${result.summary}`);
1020
+ }
1021
+
1022
+ if (dashboard) {
1023
+ dashboard.broadcastWorkflowComplete(result.summary ?? '', result.success);
1024
+ await dashboard.stop();
1025
+ }
1026
+
1027
+ process.exit(result.success ? 0 : 1);
1028
+ } catch (err: unknown) {
1029
+ const msg = err instanceof Error ? err.message : String(err);
1030
+ console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
1031
+ if (dashboard) {
1032
+ dashboard.broadcastWorkflowError(msg);
1033
+ await dashboard.stop();
1034
+ }
1035
+ process.exit(1);
1036
+ }
1037
+ }
1038
+
1039
+ export async function handleSession(opts: ParsedArgs): Promise<void> {
1040
+ const workflowPath = resolveWorkflowPath('bot', opts.file ?? process.cwd());
1041
+
1042
+ const config = await loadConfig(opts.configPath);
1043
+
1044
+ if (!opts.quiet) {
1045
+ console.log('[weaver] Starting bot session (Ctrl+C to stop)');
1046
+ console.log('[weaver] Add tasks with: flow-weaver weaver queue add "task"');
1047
+ }
1048
+
1049
+ try {
1050
+ const result = await runWorkflow(workflowPath, {
1051
+ params: { projectDir: opts.file ?? process.cwd() },
1052
+ verbose: opts.verbose,
1053
+ dryRun: opts.dryRun,
1054
+ config,
1055
+ });
1056
+
1057
+ if (!opts.quiet) {
1058
+ const color = result.success ? '\x1b[32m' : '\x1b[31m';
1059
+ console.log(`${color}Session: ${result.outcome}\x1b[0m`);
1060
+ }
1061
+
1062
+ process.exit(result.success ? 0 : 1);
1063
+ } catch (err: unknown) {
1064
+ const msg = err instanceof Error ? err.message : String(err);
1065
+ console.error(`\x1b[31m[weaver] Fatal: ${msg}\x1b[0m`);
1066
+ process.exit(1);
1067
+ }
1068
+ }
1069
+
1070
+ export async function handleSteer(opts: ParsedArgs): Promise<void> {
1071
+ const { SteeringController } = await import('./bot/steering.js');
1072
+ const controller = new SteeringController();
1073
+
1074
+ const subcommand = opts.botTask;
1075
+ if (!subcommand || !['pause', 'resume', 'cancel', 'redirect', 'queue'].includes(subcommand)) {
1076
+ console.error('[weaver] Usage: flow-weaver weaver steer <pause|resume|cancel|redirect|queue> [payload]');
1077
+ process.exit(1);
1078
+ }
1079
+
1080
+ const command = {
1081
+ command: subcommand as 'pause' | 'resume' | 'cancel' | 'redirect' | 'queue',
1082
+ payload: opts.botFile,
1083
+ timestamp: Date.now(),
1084
+ };
1085
+
1086
+ await controller.write(command);
1087
+ console.log(`[weaver] Steering command sent: ${subcommand}${opts.botFile ? ' "' + opts.botFile + '"' : ''}`);
1088
+ }
1089
+
1090
+ export async function handleQueue(opts: ParsedArgs): Promise<void> {
1091
+ const { TaskQueue } = await import('./bot/task-queue.js');
1092
+ const queue = new TaskQueue();
1093
+
1094
+ const action = opts.botTask;
1095
+ if (!action || !['add', 'list', 'clear', 'remove'].includes(action)) {
1096
+ console.error('[weaver] Usage: flow-weaver weaver queue <add|list|clear|remove> [task|id]');
1097
+ process.exit(1);
1098
+ }
1099
+
1100
+ switch (action) {
1101
+ case 'add': {
1102
+ const instruction = opts.botFile;
1103
+ if (!instruction) {
1104
+ console.error('[weaver] Usage: flow-weaver weaver queue add "task instruction"');
1105
+ process.exit(1);
1106
+ }
1107
+ const id = await queue.add({ instruction, priority: 0 });
1108
+ console.log(`[weaver] Task added: ${id}`);
1109
+ break;
1110
+ }
1111
+ case 'list': {
1112
+ const tasks = await queue.list();
1113
+ if (tasks.length === 0) {
1114
+ console.log('No tasks in queue.');
1115
+ } else {
1116
+ console.log('ID'.padEnd(10) + 'STATUS'.padEnd(12) + 'INSTRUCTION');
1117
+ for (const t of tasks) {
1118
+ console.log(t.id.padEnd(10) + t.status.padEnd(12) + t.instruction.slice(0, 60));
1119
+ }
1120
+ }
1121
+ break;
1122
+ }
1123
+ case 'clear': {
1124
+ const count = await queue.clear();
1125
+ console.log(`Cleared ${count} task(s).`);
1126
+ break;
1127
+ }
1128
+ case 'remove': {
1129
+ const id = opts.botFile;
1130
+ if (!id) {
1131
+ console.error('[weaver] Usage: flow-weaver weaver queue remove <id>');
1132
+ process.exit(1);
1133
+ }
1134
+ const removed = await queue.remove(id);
1135
+ console.log(removed ? `Removed task ${id}.` : `No task found with id "${id}".`);
1136
+ break;
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ export async function handleGenesis(opts: ParsedArgs): Promise<void> {
1142
+ const projectDir = opts.file ?? process.cwd();
1143
+
1144
+ if (opts.genesisInit) {
1145
+ const { GenesisStore } = await import('./bot/genesis-store.js');
1146
+ const store = new GenesisStore(projectDir);
1147
+ store.ensureDirs();
1148
+ const config = store.loadConfig();
1149
+ console.log('[weaver] Created .genesis/config.json');
1150
+ console.log('[weaver] Set "targetWorkflow" to the workflow you want Genesis to evolve');
1151
+ console.log(JSON.stringify(config, null, 2));
1152
+ return;
1153
+ }
1154
+
1155
+ const workflowPath = resolveWorkflowPath('genesis', projectDir);
1156
+
1157
+ const config = await loadConfig(opts.configPath);
1158
+
1159
+ if (opts.genesisWatch) {
1160
+ const { GenesisStore } = await import('./bot/genesis-store.js');
1161
+ const store = new GenesisStore(projectDir);
1162
+ const gConfig = store.loadConfig();
1163
+ const maxCycles = gConfig.maxCyclesPerRun;
1164
+
1165
+ if (!opts.quiet) console.log(`[weaver] Bot genesis watch: up to ${maxCycles} cycles`);
1166
+
1167
+ for (let i = 0; i < maxCycles; i++) {
1168
+ if (!opts.quiet) console.log(`\n[weaver] Bot genesis cycle ${i + 1}/${maxCycles}`);
1169
+ const result = await runWorkflow(workflowPath, {
1170
+ params: { projectDir },
1171
+ verbose: opts.verbose,
1172
+ dryRun: opts.dryRun,
1173
+ config,
1174
+ });
1175
+
1176
+ if (!result.success) {
1177
+ if (!opts.quiet) console.log(`\x1b[33m[weaver] Cycle ${i + 1} ended: ${result.summary}\x1b[0m`);
1178
+ }
1179
+
1180
+ if (i < maxCycles - 1) {
1181
+ await new Promise(r => setTimeout(r, 2000));
1182
+ }
1183
+ }
1184
+ return;
1185
+ }
1186
+
1187
+ // Single cycle
1188
+ if (!opts.quiet) console.log('[weaver] Bot genesis: running cycle');
1189
+
1190
+ const result = await runWorkflow(workflowPath, {
1191
+ params: { projectDir },
1192
+ verbose: opts.verbose,
1193
+ dryRun: opts.dryRun,
1194
+ config,
1195
+ });
1196
+
1197
+ if (!opts.quiet) {
1198
+ const color = result.success ? '\x1b[32m' : '\x1b[33m';
1199
+ console.log(`${color}Bot genesis: ${result.summary}\x1b[0m`);
1200
+ }
1201
+
1202
+ process.exit(result.success ? 0 : 1);
1203
+ }
1204
+
1205
+ export async function handleAudit(opts: ParsedArgs): Promise<void> {
1206
+ const store = new AuditStore();
1207
+
1208
+ if (opts.historyClear) {
1209
+ const deleted = store.clear();
1210
+ console.log(deleted ? 'Audit log cleared.' : 'No audit log to clear.');
1211
+ return;
1212
+ }
1213
+
1214
+ if (opts.historyId) {
1215
+ const events = store.queryByRun(opts.historyId);
1216
+ if (events.length === 0) {
1217
+ // Try prefix match
1218
+ const recent = store.queryRecent(1000);
1219
+ const matched = recent.filter((e) => e.runId.startsWith(opts.historyId!));
1220
+ if (matched.length === 0) {
1221
+ console.error(`[weaver] No audit events found for "${opts.historyId}"`);
1222
+ process.exit(1);
1223
+ }
1224
+ printAuditEvents(matched, opts.historyJson);
1225
+ return;
1226
+ }
1227
+ printAuditEvents(events, opts.historyJson);
1228
+ return;
1229
+ }
1230
+
1231
+ const events = store.queryRecent(opts.historyLimit);
1232
+ if (events.length === 0) {
1233
+ console.log('No audit events recorded yet.');
1234
+ return;
1235
+ }
1236
+
1237
+ if (opts.historyJson) {
1238
+ console.log(JSON.stringify(events, null, 2));
1239
+ return;
1240
+ }
1241
+
1242
+ // Group by runId
1243
+ const byRun = new Map<string, AuditEvent[]>();
1244
+ for (const e of events) {
1245
+ if (!byRun.has(e.runId)) byRun.set(e.runId, []);
1246
+ byRun.get(e.runId)!.push(e);
1247
+ }
1248
+
1249
+ for (const [runId, runEvents] of byRun) {
1250
+ console.log(`\n\x1b[1mRun ${runId.slice(0, 8)}\x1b[0m`);
1251
+ for (const e of runEvents) {
1252
+ const time = e.timestamp.replace('T', ' ').slice(11, 19);
1253
+ const dataStr = e.data ? ' ' + JSON.stringify(e.data) : '';
1254
+ console.log(` ${time} ${e.type}${dataStr}`);
1255
+ }
1256
+ }
1257
+ console.log('');
1258
+ }
1259
+
1260
+ function printAuditEvents(events: AuditEvent[], json: boolean): void {
1261
+ if (json) {
1262
+ console.log(JSON.stringify(events, null, 2));
1263
+ return;
1264
+ }
1265
+
1266
+ for (const e of events) {
1267
+ const time = e.timestamp.replace('T', ' ').slice(0, 19);
1268
+ const dataStr = e.data ? ' ' + JSON.stringify(e.data) : '';
1269
+ console.log(`${time} [${e.type}]${dataStr}`);
1270
+ }
1271
+ }