@synergenius/flowweaver-pack-weaver 0.6.0 → 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 (196) 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/design-checker.d.ts +24 -0
  22. package/dist/bot/design-checker.d.ts.map +1 -0
  23. package/dist/bot/design-checker.js +269 -0
  24. package/dist/bot/design-checker.js.map +1 -0
  25. package/dist/bot/file-validator.d.ts +5 -2
  26. package/dist/bot/file-validator.d.ts.map +1 -1
  27. package/dist/bot/file-validator.js +14 -7
  28. package/dist/bot/file-validator.js.map +1 -1
  29. package/dist/bot/fw-api.d.ts +8 -0
  30. package/dist/bot/fw-api.d.ts.map +1 -0
  31. package/dist/bot/fw-api.js +12 -0
  32. package/dist/bot/fw-api.js.map +1 -0
  33. package/dist/bot/runner.d.ts +2 -1
  34. package/dist/bot/runner.d.ts.map +1 -1
  35. package/dist/bot/runner.js +8 -0
  36. package/dist/bot/runner.js.map +1 -1
  37. package/dist/bot/step-executor.d.ts +3 -2
  38. package/dist/bot/step-executor.d.ts.map +1 -1
  39. package/dist/bot/step-executor.js +9 -30
  40. package/dist/bot/step-executor.js.map +1 -1
  41. package/dist/bot/system-prompt.d.ts +13 -1
  42. package/dist/bot/system-prompt.d.ts.map +1 -1
  43. package/dist/bot/system-prompt.js +28 -22
  44. package/dist/bot/system-prompt.js.map +1 -1
  45. package/dist/bot/types.d.ts +9 -1
  46. package/dist/bot/types.d.ts.map +1 -1
  47. package/dist/cli-bridge.d.ts.map +1 -1
  48. package/dist/cli-bridge.js +2 -1
  49. package/dist/cli-bridge.js.map +1 -1
  50. package/dist/cli-handlers.d.ts +4 -2
  51. package/dist/cli-handlers.d.ts.map +1 -1
  52. package/dist/cli-handlers.js +199 -92
  53. package/dist/cli-handlers.js.map +1 -1
  54. package/dist/docs/docs/weaver-config.md +8 -14
  55. package/dist/docs/weaver-config.md +8 -14
  56. package/dist/node-types/approval-gate.d.ts.map +1 -1
  57. package/dist/node-types/approval-gate.js +4 -0
  58. package/dist/node-types/approval-gate.js.map +1 -1
  59. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  60. package/dist/node-types/exec-validate-retry.js +33 -7
  61. package/dist/node-types/exec-validate-retry.js.map +1 -1
  62. package/dist/node-types/execute-plan.js +1 -1
  63. package/dist/node-types/execute-plan.js.map +1 -1
  64. package/dist/node-types/genesis-validate-proposal.d.ts.map +1 -1
  65. package/dist/node-types/genesis-validate-proposal.js +23 -0
  66. package/dist/node-types/genesis-validate-proposal.js.map +1 -1
  67. package/dist/node-types/git-ops.d.ts.map +1 -1
  68. package/dist/node-types/git-ops.js +2 -0
  69. package/dist/node-types/git-ops.js.map +1 -1
  70. package/dist/node-types/plan-task.d.ts.map +1 -1
  71. package/dist/node-types/plan-task.js +9 -1
  72. package/dist/node-types/plan-task.js.map +1 -1
  73. package/dist/node-types/send-notify.d.ts.map +1 -1
  74. package/dist/node-types/send-notify.js +4 -1
  75. package/dist/node-types/send-notify.js.map +1 -1
  76. package/dist/node-types/validate-result.d.ts +2 -2
  77. package/dist/node-types/validate-result.d.ts.map +1 -1
  78. package/dist/node-types/validate-result.js +2 -2
  79. package/dist/node-types/validate-result.js.map +1 -1
  80. package/dist/templates/index.d.ts +2 -4
  81. package/dist/templates/index.d.ts.map +1 -1
  82. package/dist/templates/index.js +1 -3
  83. package/dist/templates/index.js.map +1 -1
  84. package/dist/templates/weaver-bot-template.d.ts +9 -1
  85. package/dist/templates/weaver-bot-template.d.ts.map +1 -1
  86. package/dist/templates/weaver-bot-template.js.map +1 -1
  87. package/dist/workflows/index.d.ts +0 -1
  88. package/dist/workflows/index.d.ts.map +1 -1
  89. package/dist/workflows/index.js +0 -1
  90. package/dist/workflows/index.js.map +1 -1
  91. package/dist/workflows/weaver-bot-batch.d.ts +4 -1
  92. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  93. package/dist/workflows/weaver-bot-batch.js +1 -1
  94. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  95. package/dist/workflows/weaver-bot.d.ts +4 -1
  96. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  97. package/dist/workflows/weaver-bot.js +1 -1
  98. package/dist/workflows/weaver-bot.js.map +1 -1
  99. package/flowweaver.manifest.json +10 -10
  100. package/package.json +4 -3
  101. package/src/bot/agent-provider.ts +273 -0
  102. package/src/bot/ai-client.ts +109 -0
  103. package/src/bot/approvals.ts +273 -0
  104. package/src/bot/audit-logger.ts +45 -0
  105. package/src/bot/audit-store.ts +69 -0
  106. package/src/bot/bot-agent-channel.ts +99 -0
  107. package/src/bot/cli-provider.ts +169 -0
  108. package/src/bot/cli-stream-parser.ts +59 -0
  109. package/src/bot/cost-store.ts +92 -0
  110. package/src/bot/cost-tracker.ts +72 -0
  111. package/src/bot/cron-parser.ts +153 -0
  112. package/src/bot/cron-scheduler.ts +48 -0
  113. package/src/bot/dashboard.ts +658 -0
  114. package/src/bot/design-checker.ts +327 -0
  115. package/src/bot/file-lock.ts +73 -0
  116. package/src/bot/file-validator.ts +41 -0
  117. package/src/bot/file-watcher.ts +103 -0
  118. package/src/bot/fw-api.ts +18 -0
  119. package/src/bot/genesis-prompt-context.ts +135 -0
  120. package/src/bot/genesis-store.ts +180 -0
  121. package/src/bot/index.ts +127 -0
  122. package/src/bot/notifications.ts +263 -0
  123. package/src/bot/pipeline-runner.ts +324 -0
  124. package/src/bot/provider-registry.ts +236 -0
  125. package/src/bot/run-store.ts +169 -0
  126. package/src/bot/runner.ts +311 -0
  127. package/src/bot/session-state.ts +73 -0
  128. package/src/bot/steering.ts +44 -0
  129. package/src/bot/step-executor.ts +34 -0
  130. package/src/bot/system-prompt.ts +280 -0
  131. package/src/bot/task-queue.ts +111 -0
  132. package/src/bot/types.ts +571 -0
  133. package/src/bot/utils.ts +17 -0
  134. package/src/bot/watch-daemon.ts +203 -0
  135. package/src/bot/web-approval.ts +240 -0
  136. package/src/cli-bridge.ts +41 -0
  137. package/src/cli-handlers.ts +1271 -0
  138. package/src/docs/weaver-config.md +135 -0
  139. package/src/index.ts +173 -0
  140. package/src/mcp-tools.ts +274 -0
  141. package/src/node-types/abort-task.ts +31 -0
  142. package/src/node-types/approval-gate.ts +75 -0
  143. package/src/node-types/bot-report.ts +82 -0
  144. package/src/node-types/build-context.ts +65 -0
  145. package/src/node-types/detect-provider.ts +75 -0
  146. package/src/node-types/exec-validate-retry.ts +175 -0
  147. package/src/node-types/execute-plan.ts +130 -0
  148. package/src/node-types/execute-target.ts +267 -0
  149. package/src/node-types/fix-errors.ts +68 -0
  150. package/src/node-types/genesis-apply-retry.ts +138 -0
  151. package/src/node-types/genesis-apply.ts +96 -0
  152. package/src/node-types/genesis-approve.ts +73 -0
  153. package/src/node-types/genesis-check-stabilize.ts +37 -0
  154. package/src/node-types/genesis-check-threshold.ts +34 -0
  155. package/src/node-types/genesis-commit.ts +71 -0
  156. package/src/node-types/genesis-compile-validate.ts +77 -0
  157. package/src/node-types/genesis-diff-fingerprint.ts +67 -0
  158. package/src/node-types/genesis-diff-workflow.ts +71 -0
  159. package/src/node-types/genesis-escrow-grace.ts +62 -0
  160. package/src/node-types/genesis-escrow-migrate.ts +138 -0
  161. package/src/node-types/genesis-escrow-recover.ts +99 -0
  162. package/src/node-types/genesis-escrow-stage.ts +104 -0
  163. package/src/node-types/genesis-escrow-validate.ts +120 -0
  164. package/src/node-types/genesis-load-config.ts +44 -0
  165. package/src/node-types/genesis-observe.ts +119 -0
  166. package/src/node-types/genesis-propose.ts +97 -0
  167. package/src/node-types/genesis-report.ts +95 -0
  168. package/src/node-types/genesis-snapshot.ts +30 -0
  169. package/src/node-types/genesis-try-apply.ts +165 -0
  170. package/src/node-types/genesis-update-history.ts +72 -0
  171. package/src/node-types/genesis-validate-proposal.ts +124 -0
  172. package/src/node-types/git-ops.ts +72 -0
  173. package/src/node-types/index.ts +36 -0
  174. package/src/node-types/load-config.ts +27 -0
  175. package/src/node-types/plan-task.ts +77 -0
  176. package/src/node-types/read-workflow.ts +68 -0
  177. package/src/node-types/receive-task.ts +92 -0
  178. package/src/node-types/report.ts +25 -0
  179. package/src/node-types/resolve-target.ts +64 -0
  180. package/src/node-types/route-task.ts +25 -0
  181. package/src/node-types/send-notify.ts +75 -0
  182. package/src/node-types/validate-result.ts +49 -0
  183. package/src/templates/index.ts +5 -0
  184. package/src/templates/weaver-bot-template.ts +106 -0
  185. package/src/workflows/genesis-task.ts +91 -0
  186. package/src/workflows/index.ts +3 -0
  187. package/src/workflows/weaver-bot-batch.ts +65 -0
  188. package/src/workflows/weaver-bot.ts +79 -0
  189. package/dist/templates/weaver-template.d.ts +0 -11
  190. package/dist/templates/weaver-template.d.ts.map +0 -1
  191. package/dist/templates/weaver-template.js +0 -53
  192. package/dist/templates/weaver-template.js.map +0 -1
  193. package/dist/workflows/weaver.d.ts +0 -24
  194. package/dist/workflows/weaver.d.ts.map +0 -1
  195. package/dist/workflows/weaver.js +0 -28
  196. package/dist/workflows/weaver.js.map +0 -1
@@ -0,0 +1,203 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import type { TriggerSource, WatchDaemonOptions, WatchDaemonState, WorkflowResult, ExecutionEvent } from './types.js';
4
+ import { runWorkflow } from './runner.js';
5
+ import { parseCron } from './cron-parser.js';
6
+ import { FileWatcher } from './file-watcher.js';
7
+ import { CronScheduler } from './cron-scheduler.js';
8
+
9
+ export class WatchDaemon {
10
+ private options: WatchDaemonOptions;
11
+ private fileWatcher: FileWatcher | null = null;
12
+ private cronScheduler: CronScheduler | null = null;
13
+ private logStream: fs.WriteStream | null = null;
14
+ private state: WatchDaemonState;
15
+ private stopping = false;
16
+ private forceExit = false;
17
+
18
+ constructor(options: WatchDaemonOptions) {
19
+ this.options = options;
20
+ this.state = {
21
+ running: false,
22
+ lastRun: null,
23
+ lastTrigger: null,
24
+ lastResult: null,
25
+ runCount: 0,
26
+ errorCount: 0,
27
+ startedAt: new Date(),
28
+ queued: false,
29
+ };
30
+ }
31
+
32
+ async start(): Promise<void> {
33
+ const absPath = path.resolve(this.options.filePath);
34
+ if (!fs.existsSync(absPath)) {
35
+ throw new Error(`Workflow file not found: ${absPath}`);
36
+ }
37
+
38
+ if (this.options.logFile) {
39
+ this.logStream = fs.createWriteStream(this.options.logFile, { flags: 'a' });
40
+ }
41
+
42
+ this.log(`Watching ${absPath}`);
43
+
44
+ if (this.options.watchFile) {
45
+ this.fileWatcher = new FileWatcher({
46
+ filePath: absPath,
47
+ debounceMs: this.options.debounceMs,
48
+ });
49
+ this.fileWatcher.on('change', () => this.onTrigger('file-change'));
50
+ this.fileWatcher.start();
51
+ this.log('File watcher started');
52
+ }
53
+
54
+ if (this.options.cron) {
55
+ const parsed = parseCron(this.options.cron);
56
+ this.cronScheduler = new CronScheduler(parsed);
57
+ this.cronScheduler.on('tick', () => this.onTrigger('cron'));
58
+ this.cronScheduler.start();
59
+ this.log(`Cron scheduler started: ${this.options.cron}`);
60
+ }
61
+
62
+ this.setupSignalHandlers();
63
+
64
+ // Keep alive
65
+ await new Promise<void>((resolve) => {
66
+ const check = setInterval(() => {
67
+ if (this.forceExit) {
68
+ clearInterval(check);
69
+ resolve();
70
+ }
71
+ }, 500);
72
+ });
73
+
74
+ this.cleanup();
75
+ }
76
+
77
+ private onTrigger(source: TriggerSource): void {
78
+ if (this.stopping) return;
79
+
80
+ if (this.state.running) {
81
+ this.state.queued = true;
82
+ this.log(`Run in progress, queuing trigger (${source})`);
83
+ return;
84
+ }
85
+
86
+ this.executeRun(source);
87
+ }
88
+
89
+ private async executeRun(source: TriggerSource): Promise<void> {
90
+ this.state.running = true;
91
+ this.state.queued = false;
92
+ const runNum = this.state.runCount + 1;
93
+
94
+ this.log(`\nRun #${runNum} triggered by ${source}`);
95
+
96
+ const onEvent = this.options.quiet
97
+ ? undefined
98
+ : (event: ExecutionEvent) => {
99
+ if (event.type === 'node-complete') {
100
+ this.log(` + ${event.nodeId}${event.nodeType ? ` (${event.nodeType})` : ''}`);
101
+ } else if (event.type === 'node-error') {
102
+ this.log(` x ${event.nodeId}: ${event.error ?? 'unknown error'}`);
103
+ }
104
+ };
105
+
106
+ try {
107
+ const result = await runWorkflow(path.resolve(this.options.filePath), {
108
+ params: this.options.params,
109
+ verbose: this.options.verbose,
110
+ config: this.options.config,
111
+ onEvent,
112
+ });
113
+
114
+ this.state.lastResult = result;
115
+ this.state.runCount++;
116
+ this.state.lastRun = new Date();
117
+ this.state.lastTrigger = source;
118
+
119
+ if (!result.success) this.state.errorCount++;
120
+
121
+ const statusColor = result.success ? '\x1b[32m' : '\x1b[31m';
122
+ this.log(`${statusColor}Run #${runNum}: ${result.outcome}\x1b[0m - ${result.summary}`);
123
+ } catch (err: unknown) {
124
+ const msg = err instanceof Error ? err.message : String(err);
125
+ this.state.errorCount++;
126
+ this.state.runCount++;
127
+ this.state.lastRun = new Date();
128
+ this.log(`\x1b[31mRun #${runNum}: fatal error\x1b[0m - ${msg}`);
129
+ }
130
+
131
+ this.state.running = false;
132
+
133
+ if (this.state.queued && !this.stopping) {
134
+ this.executeRun(source);
135
+ }
136
+ }
137
+
138
+ private setupSignalHandlers(): void {
139
+ const handler = () => {
140
+ if (this.stopping) {
141
+ // Second signal: force exit after 2s
142
+ this.log('\nForce exit in 2s...');
143
+ setTimeout(() => {
144
+ this.forceExit = true;
145
+ }, 2000);
146
+ return;
147
+ }
148
+
149
+ this.stopping = true;
150
+ this.log('\nStopping daemon...');
151
+
152
+ if (this.fileWatcher) this.fileWatcher.stop();
153
+ if (this.cronScheduler) this.cronScheduler.stop();
154
+
155
+ if (this.state.running) {
156
+ this.log('Waiting for current run to finish (30s timeout, Ctrl+C to force)...');
157
+ const timeout = setTimeout(() => {
158
+ this.forceExit = true;
159
+ }, 30_000);
160
+
161
+ const check = setInterval(() => {
162
+ if (!this.state.running) {
163
+ clearTimeout(timeout);
164
+ clearInterval(check);
165
+ this.printSummary();
166
+ this.forceExit = true;
167
+ }
168
+ }, 500);
169
+ } else {
170
+ this.printSummary();
171
+ this.forceExit = true;
172
+ }
173
+ };
174
+
175
+ process.on('SIGINT', handler);
176
+ process.on('SIGTERM', handler);
177
+ }
178
+
179
+ private printSummary(): void {
180
+ const uptime = Date.now() - this.state.startedAt.getTime();
181
+ const uptimeStr = uptime < 60_000
182
+ ? `${(uptime / 1000).toFixed(0)}s`
183
+ : `${(uptime / 60_000).toFixed(1)}m`;
184
+
185
+ this.log(`\nDaemon summary: ${this.state.runCount} runs, ${this.state.errorCount} errors, uptime ${uptimeStr}`);
186
+ }
187
+
188
+ private log(msg: string): void {
189
+ const ts = new Date().toISOString().slice(11, 19);
190
+ const line = `[${ts}] ${msg}`;
191
+ console.log(line);
192
+ if (this.logStream) {
193
+ this.logStream.write(line + '\n');
194
+ }
195
+ }
196
+
197
+ private cleanup(): void {
198
+ if (this.logStream) {
199
+ this.logStream.end();
200
+ this.logStream = null;
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,240 @@
1
+ import * as http from 'node:http';
2
+ import type { NotificationEvent } from './types.js';
3
+ import type { ApprovalHandler, ApprovalRequest, ApprovalResult } from './approvals.js';
4
+ import type { DashboardServer } from './dashboard.js';
5
+ import { openBrowser } from './utils.js';
6
+
7
+ export class WebApprovalHandler implements ApprovalHandler {
8
+ private timeoutSeconds: number;
9
+ private shouldOpen: boolean;
10
+ private notifier: (event: NotificationEvent) => Promise<void>;
11
+ private dashboardServer?: DashboardServer;
12
+
13
+ constructor(options: {
14
+ timeoutSeconds: number;
15
+ open?: boolean;
16
+ notifier: (event: NotificationEvent) => Promise<void>;
17
+ dashboardServer?: DashboardServer;
18
+ }) {
19
+ this.timeoutSeconds = options.timeoutSeconds;
20
+ this.shouldOpen = options.open ?? true;
21
+ this.notifier = options.notifier;
22
+ this.dashboardServer = options.dashboardServer;
23
+ }
24
+
25
+ async handle(
26
+ request: ApprovalRequest,
27
+ event: NotificationEvent,
28
+ ): Promise<ApprovalResult> {
29
+ await this.notifier(event);
30
+
31
+ // Dashboard mode: register with existing dashboard
32
+ if (this.dashboardServer) {
33
+ return this.handleViaDashboard(request);
34
+ }
35
+
36
+ // Standalone mode: spin up a temporary server
37
+ return this.handleStandalone(request);
38
+ }
39
+
40
+ private handleViaDashboard(request: ApprovalRequest): Promise<ApprovalResult> {
41
+ return new Promise((resolve) => {
42
+ const id = this.dashboardServer!.registerApproval(
43
+ { prompt: request.prompt, context: request.context },
44
+ resolve,
45
+ );
46
+
47
+ const url = `${this.dashboardServer!.getUrl()}`;
48
+ console.log(`[weaver] Approval pending at ${url}`);
49
+
50
+ // Timeout auto-approves
51
+ const timer = setTimeout(() => {
52
+ this.dashboardServer!.removeApproval(id);
53
+ resolve({
54
+ approved: true,
55
+ reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
56
+ });
57
+ }, this.timeoutSeconds * 1000);
58
+
59
+ // Wrap resolve to also clear timeout
60
+ const originalResolve = resolve;
61
+ const wrappedResolve = (result: ApprovalResult) => {
62
+ clearTimeout(timer);
63
+ originalResolve(result);
64
+ };
65
+
66
+ // Re-register with wrapped resolve
67
+ this.dashboardServer!.removeApproval(id);
68
+ this.dashboardServer!.registerApproval(
69
+ { prompt: request.prompt, context: request.context },
70
+ wrappedResolve,
71
+ );
72
+ });
73
+ }
74
+
75
+ private handleStandalone(request: ApprovalRequest): Promise<ApprovalResult> {
76
+ return new Promise((resolve) => {
77
+ let resolved = false;
78
+ let server: http.Server;
79
+
80
+ const doResolve = (result: ApprovalResult) => {
81
+ if (resolved) return;
82
+ resolved = true;
83
+ clearTimeout(timer);
84
+ server.close();
85
+ resolve(result);
86
+ };
87
+
88
+ const timer = setTimeout(() => {
89
+ doResolve({
90
+ approved: true,
91
+ reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
92
+ });
93
+ }, this.timeoutSeconds * 1000);
94
+
95
+ server = http.createServer((req, res) => {
96
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
97
+
98
+ if (req.method === 'GET' && url.pathname === '/') {
99
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
100
+ res.end(getApprovalHtml(request, this.timeoutSeconds));
101
+ return;
102
+ }
103
+
104
+ if (req.method === 'POST' && url.pathname === '/api/approve') {
105
+ res.writeHead(200, { 'Content-Type': 'application/json' });
106
+ res.end(JSON.stringify({ ok: true }));
107
+ doResolve({ approved: true, reason: 'approved via web UI' });
108
+ return;
109
+ }
110
+
111
+ if (req.method === 'POST' && url.pathname === '/api/reject') {
112
+ let body = '';
113
+ req.on('data', (chunk) => { body += chunk; });
114
+ req.on('end', () => {
115
+ let reason = 'rejected via web UI';
116
+ try {
117
+ const parsed = JSON.parse(body);
118
+ if (parsed.reason) reason = parsed.reason;
119
+ } catch { /* use default */ }
120
+ res.writeHead(200, { 'Content-Type': 'application/json' });
121
+ res.end(JSON.stringify({ ok: true }));
122
+ doResolve({ approved: false, reason });
123
+ });
124
+ return;
125
+ }
126
+
127
+ res.writeHead(404);
128
+ res.end('Not found');
129
+ });
130
+
131
+ // Port 0 = OS picks a free port
132
+ server.listen(0, '127.0.0.1', () => {
133
+ const addr = server.address();
134
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
135
+ const url = `http://127.0.0.1:${port}`;
136
+ console.log(`[weaver] Approval page: ${url}`);
137
+ if (this.shouldOpen) openBrowser(url);
138
+ });
139
+ });
140
+ }
141
+ }
142
+
143
+ function getApprovalHtml(request: ApprovalRequest, timeoutSeconds: number): string {
144
+ // Safe injection: double-encode JSON to prevent XSS
145
+ const safeContext = JSON.stringify(JSON.stringify(request.context, null, 2));
146
+ const safePrompt = JSON.stringify(request.prompt);
147
+
148
+ return `<!DOCTYPE html>
149
+ <html lang="en">
150
+ <head>
151
+ <meta charset="utf-8">
152
+ <meta name="viewport" content="width=device-width, initial-scale=1">
153
+ <title>Weaver Approval</title>
154
+ <style>
155
+ * { margin: 0; padding: 0; box-sizing: border-box; }
156
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f6f8fa; color: #24292f; display: flex; justify-content: center; padding: 40px 16px; }
157
+ .card { background: #fff; border: 1px solid #d0d7de; border-radius: 12px; max-width: 640px; width: 100%; padding: 32px; }
158
+ h1 { font-size: 20px; margin-bottom: 8px; }
159
+ .timer { color: #656d76; font-size: 14px; margin-bottom: 20px; }
160
+ .prompt { background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 8px; padding: 16px; margin-bottom: 20px; font-size: 14px; line-height: 1.5; }
161
+ .context { background: #0d1117; color: #c9d1d9; border-radius: 8px; padding: 16px; margin-bottom: 24px; font-family: 'SF Mono', monospace; font-size: 13px; line-height: 1.5; overflow-x: auto; max-height: 400px; overflow-y: auto; white-space: pre-wrap; }
162
+ .actions { display: flex; gap: 12px; }
163
+ .btn { padding: 10px 24px; border: none; border-radius: 8px; font-size: 15px; font-weight: 500; cursor: pointer; flex: 1; }
164
+ .btn-approve { background: #2da44e; color: #fff; }
165
+ .btn-approve:hover { background: #218838; }
166
+ .btn-reject { background: #cf222e; color: #fff; }
167
+ .btn-reject:hover { background: #a40e26; }
168
+ .btn:disabled { opacity: 0.5; cursor: default; }
169
+ .reason { width: 100%; margin-bottom: 12px; padding: 8px 12px; border: 1px solid #d0d7de; border-radius: 6px; font-size: 14px; font-family: inherit; display: none; }
170
+ .done { text-align: center; padding: 20px; font-size: 16px; }
171
+ .done.approved { color: #2da44e; }
172
+ .done.rejected { color: #cf222e; }
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <div class="card">
177
+ <div id="form">
178
+ <h1>Approval Required</h1>
179
+ <div class="timer" id="timer">Auto-approves in ${timeoutSeconds}s</div>
180
+ <div class="prompt" id="prompt"></div>
181
+ <div class="context" id="context"></div>
182
+ <textarea class="reason" id="reason" placeholder="Rejection reason (optional)" rows="2"></textarea>
183
+ <div class="actions">
184
+ <button class="btn btn-approve" id="approveBtn" onclick="doApprove()">Approve</button>
185
+ <button class="btn btn-reject" id="rejectBtn" onclick="showReject()">Reject</button>
186
+ </div>
187
+ </div>
188
+ <div id="result" class="done" style="display:none"></div>
189
+ </div>
190
+ <script>
191
+ var deadline = Date.now() + ${timeoutSeconds * 1000};
192
+ document.getElementById('prompt').textContent = ${safePrompt};
193
+ document.getElementById('context').textContent = JSON.parse(${safeContext});
194
+ var submitted = false;
195
+
196
+ function doApprove() {
197
+ if (submitted) return;
198
+ submitted = true;
199
+ fetch('/api/approve', { method: 'POST' }).then(function() {
200
+ document.getElementById('form').style.display = 'none';
201
+ document.getElementById('result').style.display = 'block';
202
+ document.getElementById('result').className = 'done approved';
203
+ document.getElementById('result').textContent = 'Approved. You can close this tab.';
204
+ });
205
+ }
206
+
207
+ function showReject() {
208
+ document.getElementById('reason').style.display = 'block';
209
+ document.getElementById('rejectBtn').textContent = 'Confirm Reject';
210
+ document.getElementById('rejectBtn').onclick = doReject;
211
+ }
212
+
213
+ function doReject() {
214
+ if (submitted) return;
215
+ submitted = true;
216
+ var reason = document.getElementById('reason').value || 'rejected via web UI';
217
+ fetch('/api/reject', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: reason }) }).then(function() {
218
+ document.getElementById('form').style.display = 'none';
219
+ document.getElementById('result').style.display = 'block';
220
+ document.getElementById('result').className = 'done rejected';
221
+ document.getElementById('result').textContent = 'Rejected. You can close this tab.';
222
+ });
223
+ }
224
+
225
+ setInterval(function() {
226
+ if (submitted) return;
227
+ var remaining = Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
228
+ document.getElementById('timer').textContent = 'Auto-approves in ' + remaining + 's';
229
+ if (remaining <= 0) {
230
+ document.getElementById('form').style.display = 'none';
231
+ document.getElementById('result').style.display = 'block';
232
+ document.getElementById('result').className = 'done approved';
233
+ document.getElementById('result').textContent = 'Auto-approved (timeout). You can close this tab.';
234
+ submitted = true;
235
+ }
236
+ }, 1000);
237
+ </script>
238
+ </body>
239
+ </html>`;
240
+ }
@@ -0,0 +1,41 @@
1
+ import type { ParsedArgs } from './cli-handlers.js';
2
+ import {
3
+ handleRun, handleHistory, handleCosts, handleWatch,
4
+ handleCron, handlePipeline, handleDashboard, handleProviders,
5
+ handleEject, handleBot, handleSession, handleSteer, handleQueue,
6
+ handleGenesis, handleAudit,
7
+ } from './cli-handlers.js';
8
+
9
+ const handlers: Record<string, (opts: ParsedArgs) => Promise<void>> = {
10
+ run: handleRun,
11
+ history: handleHistory,
12
+ costs: handleCosts,
13
+ watch: handleWatch,
14
+ cron: handleCron,
15
+ pipeline: handlePipeline,
16
+ dashboard: handleDashboard,
17
+ providers: handleProviders,
18
+ eject: handleEject,
19
+ bot: handleBot,
20
+ session: handleSession,
21
+ steer: handleSteer,
22
+ queue: handleQueue,
23
+ genesis: handleGenesis,
24
+ audit: handleAudit,
25
+ };
26
+
27
+ export async function handleCommand(
28
+ name: string,
29
+ args: string[],
30
+ ): Promise<void> {
31
+ const handler = handlers[name];
32
+ if (!handler) {
33
+ throw new Error(`Unknown weaver command: ${name}`);
34
+ }
35
+
36
+ // Import parseArgs to handle the remaining flags
37
+ const { parseArgs } = await import('./cli-handlers.js');
38
+ // Prepend dummy entries so parseArgs skips argv[0] and argv[1]
39
+ const opts = parseArgs(['node', 'weaver', name, ...args]);
40
+ await handler(opts);
41
+ }