@openbuilder/cli 0.31.11

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 (78) hide show
  1. package/README.md +1053 -0
  2. package/bin/openbuilder.js +31 -0
  3. package/dist/chunks/Banner-D4tqKfzA.js +113 -0
  4. package/dist/chunks/Banner-D4tqKfzA.js.map +1 -0
  5. package/dist/chunks/auto-update-Dj3lWPWO.js +350 -0
  6. package/dist/chunks/auto-update-Dj3lWPWO.js.map +1 -0
  7. package/dist/chunks/build-D0qYqIq0.js +116 -0
  8. package/dist/chunks/build-D0qYqIq0.js.map +1 -0
  9. package/dist/chunks/cleanup-qVTsA3tk.js +141 -0
  10. package/dist/chunks/cleanup-qVTsA3tk.js.map +1 -0
  11. package/dist/chunks/cli-error-BjQwvWtK.js +140 -0
  12. package/dist/chunks/cli-error-BjQwvWtK.js.map +1 -0
  13. package/dist/chunks/config-BGP1jZJ4.js +167 -0
  14. package/dist/chunks/config-BGP1jZJ4.js.map +1 -0
  15. package/dist/chunks/config-manager-BkbjtN-H.js +133 -0
  16. package/dist/chunks/config-manager-BkbjtN-H.js.map +1 -0
  17. package/dist/chunks/database-BvAbD4sP.js +68 -0
  18. package/dist/chunks/database-BvAbD4sP.js.map +1 -0
  19. package/dist/chunks/database-setup-BYjIRAmT.js +253 -0
  20. package/dist/chunks/database-setup-BYjIRAmT.js.map +1 -0
  21. package/dist/chunks/exports-ij9sv4UM.js +7793 -0
  22. package/dist/chunks/exports-ij9sv4UM.js.map +1 -0
  23. package/dist/chunks/init-CZoN6soU.js +468 -0
  24. package/dist/chunks/init-CZoN6soU.js.map +1 -0
  25. package/dist/chunks/init-tui-BNzk_7Yx.js +1127 -0
  26. package/dist/chunks/init-tui-BNzk_7Yx.js.map +1 -0
  27. package/dist/chunks/logger-ZpJi7chw.js +38 -0
  28. package/dist/chunks/logger-ZpJi7chw.js.map +1 -0
  29. package/dist/chunks/main-tui-Cq1hLCx-.js +644 -0
  30. package/dist/chunks/main-tui-Cq1hLCx-.js.map +1 -0
  31. package/dist/chunks/manager-CvGX9qqe.js +1161 -0
  32. package/dist/chunks/manager-CvGX9qqe.js.map +1 -0
  33. package/dist/chunks/port-allocator-BRFzgH9b.js +749 -0
  34. package/dist/chunks/port-allocator-BRFzgH9b.js.map +1 -0
  35. package/dist/chunks/process-killer-CaUL7Kpl.js +87 -0
  36. package/dist/chunks/process-killer-CaUL7Kpl.js.map +1 -0
  37. package/dist/chunks/prompts-1QbE_bRr.js +128 -0
  38. package/dist/chunks/prompts-1QbE_bRr.js.map +1 -0
  39. package/dist/chunks/repo-cloner-CpOQjFSo.js +219 -0
  40. package/dist/chunks/repo-cloner-CpOQjFSo.js.map +1 -0
  41. package/dist/chunks/repo-detector-B_oj696o.js +66 -0
  42. package/dist/chunks/repo-detector-B_oj696o.js.map +1 -0
  43. package/dist/chunks/run-D23hg4xy.js +630 -0
  44. package/dist/chunks/run-D23hg4xy.js.map +1 -0
  45. package/dist/chunks/runner-logger-instance-nDWv2h2T.js +899 -0
  46. package/dist/chunks/runner-logger-instance-nDWv2h2T.js.map +1 -0
  47. package/dist/chunks/spinner-BJL9zWAJ.js +53 -0
  48. package/dist/chunks/spinner-BJL9zWAJ.js.map +1 -0
  49. package/dist/chunks/start-BygPCbvw.js +1708 -0
  50. package/dist/chunks/start-BygPCbvw.js.map +1 -0
  51. package/dist/chunks/start-traditional-uoLZXdxm.js +255 -0
  52. package/dist/chunks/start-traditional-uoLZXdxm.js.map +1 -0
  53. package/dist/chunks/status-cS8YwtUx.js +97 -0
  54. package/dist/chunks/status-cS8YwtUx.js.map +1 -0
  55. package/dist/chunks/theme-DhorI2Hb.js +44 -0
  56. package/dist/chunks/theme-DhorI2Hb.js.map +1 -0
  57. package/dist/chunks/upgrade-CT6w0lKp.js +323 -0
  58. package/dist/chunks/upgrade-CT6w0lKp.js.map +1 -0
  59. package/dist/chunks/useBuildState-CdBSu9y_.js +331 -0
  60. package/dist/chunks/useBuildState-CdBSu9y_.js.map +1 -0
  61. package/dist/cli/index.js +694 -0
  62. package/dist/cli/index.js.map +1 -0
  63. package/dist/index.js +14358 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/instrument.js +64226 -0
  66. package/dist/instrument.js.map +1 -0
  67. package/dist/templates.json +295 -0
  68. package/package.json +98 -0
  69. package/scripts/install-vendor-deps.js +34 -0
  70. package/scripts/install-vendor.js +167 -0
  71. package/scripts/prepare-release.js +71 -0
  72. package/templates/config.template.json +18 -0
  73. package/templates.json +295 -0
  74. package/vendor/ai-sdk-provider-claude-code-LOCAL.tgz +0 -0
  75. package/vendor/sentry-core-LOCAL.tgz +0 -0
  76. package/vendor/sentry-nextjs-LOCAL.tgz +0 -0
  77. package/vendor/sentry-node-LOCAL.tgz +0 -0
  78. package/vendor/sentry-node-core-LOCAL.tgz +0 -0
@@ -0,0 +1,899 @@
1
+ // OpenBuilder CLI - Built with Rollup
2
+ import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { EventEmitter } from 'node:events';
5
+ import chalk from 'chalk';
6
+
7
+ /**
8
+ * File-based logger for debugging when TUI console interception interferes
9
+ * Writes to /logs directory for easy tail -f monitoring
10
+ */
11
+ const LOGS_DIR = join(process.cwd(), 'logs');
12
+ const RUNNER_LOG = join(LOGS_DIR, 'runner.log');
13
+ const STREAM_LOG = join(LOGS_DIR, 'stream.log');
14
+ const ERRORS_LOG = join(LOGS_DIR, 'errors.log');
15
+ // Ensure logs directory exists
16
+ if (!existsSync(LOGS_DIR)) {
17
+ mkdirSync(LOGS_DIR, { recursive: true });
18
+ }
19
+ function timestamp() {
20
+ return new Date().toISOString();
21
+ }
22
+ function writeLog(file, level, ...args) {
23
+ const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ');
24
+ const logLine = `[${timestamp()}] [${level}] ${message}\n`;
25
+ try {
26
+ appendFileSync(file, logLine);
27
+ }
28
+ catch (err) {
29
+ // Silently fail - don't want logging to crash the app
30
+ console.error('Failed to write log:', err);
31
+ }
32
+ }
33
+ /**
34
+ * General runner logs - everything goes here
35
+ */
36
+ const fileLog = {
37
+ info: (...args) => writeLog(RUNNER_LOG, 'INFO', ...args),
38
+ warn: (...args) => writeLog(RUNNER_LOG, 'WARN', ...args),
39
+ error: (...args) => {
40
+ writeLog(RUNNER_LOG, 'ERROR', ...args);
41
+ writeLog(ERRORS_LOG, 'ERROR', ...args);
42
+ },
43
+ debug: (...args) => writeLog(RUNNER_LOG, 'DEBUG', ...args),
44
+ };
45
+ /**
46
+ * Stream-specific logs - for tracking AI SDK events
47
+ */
48
+ const streamLog = {
49
+ info: (...args) => writeLog(STREAM_LOG, 'INFO', ...args),
50
+ warn: (...args) => writeLog(STREAM_LOG, 'WARN', ...args),
51
+ event: (eventNumber, eventType, data) => {
52
+ writeLog(STREAM_LOG, 'EVENT', `#${eventNumber} type="${eventType}"`, data);
53
+ },
54
+ yield: (messageType, data) => {
55
+ writeLog(STREAM_LOG, 'YIELD', `type="${messageType}"`, data);
56
+ },
57
+ error: (...args) => {
58
+ writeLog(STREAM_LOG, 'ERROR', ...args);
59
+ writeLog(ERRORS_LOG, 'ERROR', ...args);
60
+ },
61
+ };
62
+ // TUI mode flag - when true, don't output to terminal (only to file)
63
+ let tuiModeEnabled = false;
64
+ /**
65
+ * Enable TUI mode - suppresses terminal output, only writes to files
66
+ * Also sets SILENT_MODE env var to suppress agent-core build-logger output
67
+ */
68
+ function setFileLoggerTuiMode(enabled) {
69
+ tuiModeEnabled = enabled;
70
+ // Set env var so agent-core's build-logger also silences itself
71
+ {
72
+ process.env.SILENT_MODE = '1';
73
+ }
74
+ }
75
+ // Intercept console methods to also write to file
76
+ const originalConsole = {
77
+ log: console.log,
78
+ error: console.error,
79
+ warn: console.warn,
80
+ info: console.info,
81
+ debug: console.debug,
82
+ };
83
+ // Helper to check if we should suppress terminal output
84
+ // Check both the runtime flag AND the environment variable (set early in CLI)
85
+ function shouldSuppressTerminal() {
86
+ return tuiModeEnabled || process.env.SILENT_MODE === '1';
87
+ }
88
+ // Override console methods to write to file (and optionally terminal)
89
+ console.log = (...args) => {
90
+ if (!shouldSuppressTerminal()) {
91
+ originalConsole.log(...args);
92
+ }
93
+ fileLog.info(...args);
94
+ };
95
+ console.error = (...args) => {
96
+ if (!shouldSuppressTerminal()) {
97
+ originalConsole.error(...args);
98
+ }
99
+ fileLog.error(...args);
100
+ };
101
+ console.warn = (...args) => {
102
+ if (!shouldSuppressTerminal()) {
103
+ originalConsole.warn(...args);
104
+ }
105
+ fileLog.warn(...args);
106
+ };
107
+ console.info = (...args) => {
108
+ if (!shouldSuppressTerminal()) {
109
+ originalConsole.info(...args);
110
+ }
111
+ fileLog.info(...args);
112
+ };
113
+ console.debug = (...args) => {
114
+ if (!shouldSuppressTerminal()) {
115
+ originalConsole.debug(...args);
116
+ }
117
+ fileLog.debug(...args);
118
+ };
119
+ // Log startup
120
+ fileLog.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
121
+ fileLog.info('Runner started - console interception enabled');
122
+ fileLog.info('Log files:');
123
+ fileLog.info(` - General: ${RUNNER_LOG}`);
124
+ fileLog.info(` - Stream: ${STREAM_LOG}`);
125
+ fileLog.info(` - Errors: ${ERRORS_LOG}`);
126
+ fileLog.info('All console.log/error/warn/info/debug will be captured here');
127
+ fileLog.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
128
+
129
+ /**
130
+ * LogBuffer - File-backed circular buffer for log entries
131
+ *
132
+ * Maintains an in-memory buffer of the most recent 100 entries
133
+ * and persists all logs to a file for full history access.
134
+ */
135
+ const DEFAULT_BUFFER_SIZE = 100;
136
+ const LOG_DIR = 'logs';
137
+ const LOG_FILE = 'runner-tui.log';
138
+ class LogBuffer extends EventEmitter {
139
+ constructor(options) {
140
+ super();
141
+ this.buffer = [];
142
+ this.maxSize = options?.maxSize ?? DEFAULT_BUFFER_SIZE;
143
+ this.sessionStartTime = Date.now();
144
+ // Set up log file path
145
+ const logDir = options?.logDir ?? LOG_DIR;
146
+ if (!existsSync(logDir)) {
147
+ mkdirSync(logDir, { recursive: true });
148
+ }
149
+ this.logFilePath = join(logDir, LOG_FILE);
150
+ // Write session start marker
151
+ this.writeToFile(`\n${'='.repeat(60)}\n`);
152
+ this.writeToFile(`SESSION START: ${new Date().toISOString()}\n`);
153
+ this.writeToFile(`${'='.repeat(60)}\n\n`);
154
+ }
155
+ /**
156
+ * Add a log entry to the buffer and file
157
+ */
158
+ add(entry) {
159
+ // Add to in-memory buffer (circular)
160
+ this.buffer.push(entry);
161
+ if (this.buffer.length > this.maxSize) {
162
+ this.buffer.shift();
163
+ }
164
+ // Write to file
165
+ this.writeToFile(this.formatEntryForFile(entry));
166
+ // Emit event for subscribers (TUI components)
167
+ this.emit('log', entry);
168
+ }
169
+ /**
170
+ * Get the most recent N entries from memory
171
+ */
172
+ getRecent(count) {
173
+ const n = count ?? this.maxSize;
174
+ return this.buffer.slice(-n);
175
+ }
176
+ /**
177
+ * Get all entries currently in memory
178
+ */
179
+ getAll() {
180
+ return [...this.buffer];
181
+ }
182
+ /**
183
+ * Get filtered entries from memory
184
+ */
185
+ getFiltered(filter) {
186
+ return this.buffer.filter(entry => this.matchesFilter(entry, filter));
187
+ }
188
+ /**
189
+ * Read entries from the log file (for full history)
190
+ * Returns parsed log entries from the current session
191
+ */
192
+ readFromFile(maxLines) {
193
+ if (!existsSync(this.logFilePath)) {
194
+ return [];
195
+ }
196
+ try {
197
+ const content = readFileSync(this.logFilePath, 'utf-8');
198
+ const lines = content.split('\n').filter(line => line.trim());
199
+ // Find the last session marker and only read from there
200
+ let sessionStart = 0;
201
+ for (let i = lines.length - 1; i >= 0; i--) {
202
+ if (lines[i].includes('SESSION START:')) {
203
+ sessionStart = i + 1;
204
+ break;
205
+ }
206
+ }
207
+ const sessionLines = lines.slice(sessionStart);
208
+ const entries = [];
209
+ for (const line of sessionLines) {
210
+ const parsed = this.parseLogLine(line);
211
+ if (parsed) {
212
+ entries.push(parsed);
213
+ }
214
+ }
215
+ if (maxLines && entries.length > maxLines) {
216
+ return entries.slice(-maxLines);
217
+ }
218
+ return entries;
219
+ }
220
+ catch (error) {
221
+ console.error('Failed to read log file:', error);
222
+ return [];
223
+ }
224
+ }
225
+ /**
226
+ * Get the log file path for external access
227
+ */
228
+ getLogFilePath() {
229
+ return this.logFilePath;
230
+ }
231
+ /**
232
+ * Convert buffer contents to plain text (for copying)
233
+ */
234
+ toText(entries) {
235
+ const items = entries ?? this.buffer;
236
+ return items.map(entry => this.formatEntryForDisplay(entry)).join('\n');
237
+ }
238
+ /**
239
+ * Clear the in-memory buffer (file is preserved)
240
+ */
241
+ clear() {
242
+ this.buffer = [];
243
+ this.emit('clear');
244
+ }
245
+ /**
246
+ * Subscribe to new log entries
247
+ */
248
+ onLog(callback) {
249
+ this.on('log', callback);
250
+ return () => this.off('log', callback);
251
+ }
252
+ // Private methods
253
+ writeToFile(content) {
254
+ try {
255
+ appendFileSync(this.logFilePath, content);
256
+ }
257
+ catch (error) {
258
+ // Silently fail - don't break logging if file write fails
259
+ }
260
+ }
261
+ formatEntryForFile(entry) {
262
+ const timestamp = new Date(entry.timestamp).toISOString();
263
+ const level = entry.level.toUpperCase().padEnd(7);
264
+ const category = entry.category.padEnd(12);
265
+ let line = `[${timestamp}] [${level}] [${category}] ${entry.message}`;
266
+ if (entry.toolName) {
267
+ line += ` | tool=${entry.toolName}`;
268
+ if (entry.toolArgs) {
269
+ line += ` args=${entry.toolArgs}`;
270
+ }
271
+ }
272
+ if (entry.buildId) {
273
+ line += ` | build=${entry.buildId}`;
274
+ }
275
+ if (entry.data && Object.keys(entry.data).length > 0) {
276
+ line += ` | data=${JSON.stringify(entry.data)}`;
277
+ }
278
+ return line + '\n';
279
+ }
280
+ formatEntryForDisplay(entry) {
281
+ const time = new Date(entry.timestamp).toLocaleTimeString('en-US', {
282
+ hour12: false,
283
+ hour: '2-digit',
284
+ minute: '2-digit',
285
+ second: '2-digit',
286
+ });
287
+ const levelIcon = {
288
+ debug: ' ',
289
+ info: '●',
290
+ success: '✓',
291
+ warn: '⚠',
292
+ error: '✗',
293
+ }[entry.level];
294
+ if (entry.toolName) {
295
+ return `${time} 🔧 ${entry.toolName}${entry.toolArgs ? ` ${entry.toolArgs}` : ''}`;
296
+ }
297
+ return `${time} ${levelIcon} ${entry.message}`;
298
+ }
299
+ parseLogLine(line) {
300
+ // Parse format: [ISO_TIMESTAMP] [LEVEL ] [CATEGORY ] message | key=value...
301
+ const match = line.match(/^\[([^\]]+)\] \[([^\]]+)\] \[([^\]]+)\] (.+)$/);
302
+ if (!match) {
303
+ return null;
304
+ }
305
+ const [, timestamp, levelRaw, categoryRaw, rest] = match;
306
+ const level = levelRaw.trim().toLowerCase();
307
+ const category = categoryRaw.trim();
308
+ // Parse message and optional fields
309
+ const parts = rest.split(' | ');
310
+ const message = parts[0];
311
+ const entry = {
312
+ id: `${Date.parse(timestamp)}-${Math.random().toString(36).slice(2, 8)}`,
313
+ timestamp: Date.parse(timestamp),
314
+ level,
315
+ category,
316
+ message,
317
+ };
318
+ // Parse optional key=value pairs
319
+ for (let i = 1; i < parts.length; i++) {
320
+ const [key, ...valueParts] = parts[i].split('=');
321
+ const value = valueParts.join('=');
322
+ switch (key) {
323
+ case 'tool':
324
+ entry.toolName = value;
325
+ break;
326
+ case 'args':
327
+ entry.toolArgs = value;
328
+ break;
329
+ case 'build':
330
+ entry.buildId = value;
331
+ break;
332
+ case 'data':
333
+ try {
334
+ entry.data = JSON.parse(value);
335
+ }
336
+ catch {
337
+ // Ignore parse errors
338
+ }
339
+ break;
340
+ }
341
+ }
342
+ return entry;
343
+ }
344
+ matchesFilter(entry, filter) {
345
+ if (filter.levels && !filter.levels.includes(entry.level)) {
346
+ return false;
347
+ }
348
+ if (filter.categories && !filter.categories.includes(entry.category)) {
349
+ return false;
350
+ }
351
+ if (filter.buildId && entry.buildId !== filter.buildId) {
352
+ return false;
353
+ }
354
+ if (filter.verbose === false && entry.verbose) {
355
+ return false;
356
+ }
357
+ if (filter.search) {
358
+ const searchLower = filter.search.toLowerCase();
359
+ const messageMatch = entry.message.toLowerCase().includes(searchLower);
360
+ const toolMatch = entry.toolName?.toLowerCase().includes(searchLower);
361
+ const argsMatch = entry.toolArgs?.toLowerCase().includes(searchLower);
362
+ if (!messageMatch && !toolMatch && !argsMatch) {
363
+ return false;
364
+ }
365
+ }
366
+ return true;
367
+ }
368
+ }
369
+ // Singleton instance
370
+ let instance$1 = null;
371
+ function getLogBuffer() {
372
+ if (!instance$1) {
373
+ instance$1 = new LogBuffer();
374
+ }
375
+ return instance$1;
376
+ }
377
+ function createLogBuffer(options) {
378
+ instance$1 = new LogBuffer(options);
379
+ return instance$1;
380
+ }
381
+
382
+ /**
383
+ * RunnerLogger - Unified logging for the OpenBuilder Runner
384
+ *
385
+ * Provides structured logging with:
386
+ * - Verbose mode filtering
387
+ * - Build lifecycle tracking
388
+ * - Tool call logging with truncated args
389
+ * - Event emission for TUI components
390
+ * - File persistence via LogBuffer
391
+ */
392
+ const MAX_ARG_LENGTH = 40;
393
+ const MAX_MESSAGE_LENGTH = 80;
394
+ class RunnerLogger extends EventEmitter {
395
+ constructor(options) {
396
+ super();
397
+ this.builds = new Map();
398
+ this.currentBuildId = null;
399
+ this.connected = false;
400
+ this.verbose = options?.verbose ?? false;
401
+ this.tuiMode = options?.tuiMode ?? true;
402
+ this.buffer = options?.logDir
403
+ ? createLogBuffer({ logDir: options.logDir })
404
+ : getLogBuffer();
405
+ }
406
+ // ============================================
407
+ // Configuration
408
+ // ============================================
409
+ setVerbose(verbose) {
410
+ this.verbose = verbose;
411
+ this.emit('verboseChange', verbose);
412
+ }
413
+ isVerbose() {
414
+ return this.verbose;
415
+ }
416
+ setTuiMode(tuiMode) {
417
+ this.tuiMode = tuiMode;
418
+ }
419
+ // ============================================
420
+ // Connection Status
421
+ // ============================================
422
+ setConnected(connected) {
423
+ this.connected = connected;
424
+ if (connected) {
425
+ this.success('system', 'Connected to server');
426
+ this.emit('connected');
427
+ }
428
+ else {
429
+ this.warn('system', 'Disconnected from server');
430
+ this.emit('disconnected');
431
+ }
432
+ }
433
+ isConnected() {
434
+ return this.connected;
435
+ }
436
+ // ============================================
437
+ // Startup / Header
438
+ // ============================================
439
+ /**
440
+ * Print the startup header with runner configuration
441
+ */
442
+ header(config) {
443
+ if (this.tuiMode) {
444
+ // In TUI mode, header is rendered by the dashboard
445
+ // Just emit the config for the TUI to use
446
+ this.emit('config', config);
447
+ return;
448
+ }
449
+ // Plain text mode - print formatted header
450
+ const line = '━'.repeat(60);
451
+ console.log(chalk.cyan(line));
452
+ console.log(chalk.cyan.bold(' OpenBuilder Runner'));
453
+ console.log(chalk.cyan(line));
454
+ console.log(chalk.gray(' Runner ID '), chalk.white(config.runnerId));
455
+ console.log(chalk.gray(' Server '), chalk.white(config.serverUrl));
456
+ console.log(chalk.gray(' Workspace '), chalk.white(config.workspace));
457
+ if (config.apiUrl) {
458
+ console.log(chalk.gray(' API '), chalk.white(config.apiUrl));
459
+ }
460
+ console.log(chalk.cyan(line));
461
+ console.log();
462
+ }
463
+ // ============================================
464
+ // Generic Log Methods
465
+ // ============================================
466
+ info(category, message, data) {
467
+ this.log('info', category, message, data);
468
+ }
469
+ success(category, message, data) {
470
+ this.log('success', category, message, data);
471
+ }
472
+ warn(category, message, data) {
473
+ this.log('warn', category, message, data);
474
+ }
475
+ error(category, message, data) {
476
+ this.log('error', category, message, data);
477
+ }
478
+ debug(category, message, data) {
479
+ this.log('debug', category, message, data, true);
480
+ }
481
+ // ============================================
482
+ // Build Lifecycle
483
+ // ============================================
484
+ /**
485
+ * Log when a build command is received
486
+ */
487
+ buildReceived(build) {
488
+ const buildInfo = {
489
+ id: build.commandId,
490
+ projectId: build.projectId,
491
+ projectSlug: build.projectSlug,
492
+ projectName: build.projectName,
493
+ prompt: build.prompt,
494
+ operation: build.operation,
495
+ agent: build.agent,
496
+ model: build.model,
497
+ startTime: Date.now(),
498
+ status: 'pending',
499
+ todos: [],
500
+ toolCallCount: 0,
501
+ };
502
+ this.builds.set(build.commandId, buildInfo);
503
+ this.currentBuildId = build.commandId;
504
+ // Log the build received event
505
+ this.info('build', `Build received: ${build.projectSlug}`, { buildId: build.commandId });
506
+ // Log the prompt (truncated in normal mode, full in verbose)
507
+ const truncatedPrompt = this.truncate(build.prompt, this.verbose ? 200 : 60);
508
+ this.log('info', 'build', `Prompt: "${truncatedPrompt}"`, undefined, !this.verbose && build.prompt.length > 60);
509
+ this.log('info', 'build', `Operation: ${build.operation}`, undefined, true);
510
+ this.emit('buildStart', buildInfo);
511
+ }
512
+ /**
513
+ * Log template selection and download
514
+ */
515
+ template(info) {
516
+ switch (info.status) {
517
+ case 'selected':
518
+ this.info('template', `Template: ${info.name}${info.id ? ` (${info.id})` : ''}`);
519
+ break;
520
+ case 'downloading':
521
+ this.log('info', 'template', `Downloading from ${info.source}...`, undefined, true);
522
+ break;
523
+ case 'downloaded':
524
+ this.info('template', `Downloaded (${info.fileCount} files)`);
525
+ break;
526
+ }
527
+ if (this.currentBuildId) {
528
+ const build = this.builds.get(this.currentBuildId);
529
+ if (build) {
530
+ build.template = info.name;
531
+ build.templateId = info.id;
532
+ this.emit('buildUpdate', build);
533
+ }
534
+ }
535
+ }
536
+ /**
537
+ * Log when build execution starts
538
+ */
539
+ buildStart(info) {
540
+ this.info('build', 'Build started');
541
+ if (info?.agent) {
542
+ this.log('info', 'build', `Agent: ${info.agent}${info.model ? ` (${info.model})` : ''}`, undefined, true);
543
+ }
544
+ if (info?.directory) {
545
+ this.log('info', 'build', `Directory: ${info.directory}`, undefined, true);
546
+ }
547
+ if (this.currentBuildId) {
548
+ const build = this.builds.get(this.currentBuildId);
549
+ if (build) {
550
+ build.status = 'running';
551
+ build.directory = info?.directory;
552
+ this.emit('buildUpdate', build);
553
+ }
554
+ }
555
+ }
556
+ /**
557
+ * Log build completion
558
+ */
559
+ buildComplete(stats) {
560
+ const elapsed = this.formatDuration(stats.elapsedTime);
561
+ this.success('build', `Build complete (${elapsed})`);
562
+ this.info('build', `Tool calls: ${stats.toolCallCount} | Tokens: ${stats.totalTokens.toLocaleString()}`);
563
+ this.log('info', 'build', `Directory: ${stats.directory}`, undefined, true);
564
+ if (this.currentBuildId) {
565
+ const build = this.builds.get(this.currentBuildId);
566
+ if (build) {
567
+ build.status = 'completed';
568
+ build.endTime = Date.now();
569
+ build.totalTokens = stats.totalTokens;
570
+ this.emit('buildComplete', build, stats);
571
+ }
572
+ this.currentBuildId = null;
573
+ }
574
+ }
575
+ /**
576
+ * Log build failure
577
+ */
578
+ buildFailed(error) {
579
+ const message = error instanceof Error ? error.message : error;
580
+ this.error('build', `Build failed: ${message}`);
581
+ if (this.currentBuildId) {
582
+ const build = this.builds.get(this.currentBuildId);
583
+ if (build) {
584
+ build.status = 'failed';
585
+ build.endTime = Date.now();
586
+ build.error = message;
587
+ this.emit('buildComplete', build, {
588
+ elapsedTime: (build.endTime - build.startTime) / 1000,
589
+ toolCallCount: build.toolCallCount,
590
+ totalTokens: build.totalTokens ?? 0,
591
+ directory: build.directory ?? '',
592
+ });
593
+ }
594
+ this.currentBuildId = null;
595
+ }
596
+ }
597
+ // ============================================
598
+ // Tool Calls
599
+ // ============================================
600
+ /**
601
+ * Log a tool call with truncated arguments
602
+ */
603
+ tool(toolName, args) {
604
+ const truncatedArgs = this.formatToolArgs(toolName, args);
605
+ const entry = {
606
+ id: this.generateId(),
607
+ timestamp: Date.now(),
608
+ level: 'info',
609
+ category: 'tool',
610
+ message: `${toolName}${truncatedArgs ? ` ${truncatedArgs}` : ''}`,
611
+ toolName,
612
+ toolArgs: truncatedArgs,
613
+ buildId: this.currentBuildId ?? undefined,
614
+ };
615
+ this.buffer.add(entry);
616
+ // Increment tool count
617
+ if (this.currentBuildId) {
618
+ const build = this.builds.get(this.currentBuildId);
619
+ if (build) {
620
+ build.toolCallCount++;
621
+ this.emit('buildUpdate', build);
622
+ }
623
+ }
624
+ if (!this.tuiMode) {
625
+ this.printEntry(entry);
626
+ }
627
+ this.emit('log', entry);
628
+ }
629
+ /**
630
+ * Format tool arguments for display
631
+ */
632
+ formatToolArgs(toolName, args) {
633
+ if (!args)
634
+ return '';
635
+ // If already a string, just truncate it
636
+ if (typeof args === 'string') {
637
+ return `(${this.truncate(args, MAX_ARG_LENGTH)})`;
638
+ }
639
+ // Format based on tool type
640
+ switch (toolName) {
641
+ case 'Read':
642
+ case 'read':
643
+ case 'Write':
644
+ case 'write':
645
+ case 'Edit':
646
+ case 'edit':
647
+ // Try various path field names used by different SDKs
648
+ const pathValue = args.filePath || args.file_path || args.path || args.target || args.file;
649
+ if (pathValue) {
650
+ const path = String(pathValue);
651
+ // Show just the filename or last part of path
652
+ const fileName = path.split('/').pop() || path;
653
+ return fileName;
654
+ }
655
+ break;
656
+ case 'Bash':
657
+ if (args.command) {
658
+ return `(${this.truncate(String(args.command), MAX_ARG_LENGTH)})`;
659
+ }
660
+ break;
661
+ case 'Glob':
662
+ case 'Grep':
663
+ if (args.pattern) {
664
+ return `(${this.truncate(String(args.pattern), MAX_ARG_LENGTH)})`;
665
+ }
666
+ break;
667
+ case 'TodoWrite':
668
+ if (Array.isArray(args.todos)) {
669
+ return `(${args.todos.length} items)`;
670
+ }
671
+ break;
672
+ default:
673
+ // For other tools, try to show the first string value
674
+ for (const [key, value] of Object.entries(args)) {
675
+ if (typeof value === 'string' && value.length > 0) {
676
+ return `(${this.truncate(value, MAX_ARG_LENGTH)})`;
677
+ }
678
+ }
679
+ }
680
+ return '';
681
+ }
682
+ // ============================================
683
+ // Agent Messages
684
+ // ============================================
685
+ /**
686
+ * Log agent thinking/message
687
+ */
688
+ agent(message) {
689
+ const truncated = this.truncate(message, MAX_MESSAGE_LENGTH);
690
+ this.log('info', 'agent', truncated, undefined, message.length > MAX_MESSAGE_LENGTH);
691
+ }
692
+ // ============================================
693
+ // Todo List Updates
694
+ // ============================================
695
+ /**
696
+ * Update the todo list for the current build
697
+ */
698
+ updateTodos(todos) {
699
+ if (!this.currentBuildId)
700
+ return;
701
+ const build = this.builds.get(this.currentBuildId);
702
+ if (build) {
703
+ build.todos = todos;
704
+ this.emit('todoUpdate', this.currentBuildId, todos);
705
+ this.emit('buildUpdate', build);
706
+ }
707
+ }
708
+ // ============================================
709
+ // Dev Server / Tunnel
710
+ // ============================================
711
+ devServer(info) {
712
+ switch (info.status) {
713
+ case 'starting':
714
+ this.info('server', `Starting dev server on port ${info.port}...`);
715
+ break;
716
+ case 'started':
717
+ this.success('server', `Dev server running on port ${info.port}`);
718
+ if (info.url) {
719
+ this.info('server', `URL: ${info.url}`);
720
+ }
721
+ break;
722
+ case 'stopped':
723
+ this.info('server', 'Dev server stopped');
724
+ break;
725
+ case 'error':
726
+ this.error('server', `Dev server error: ${info.error}`);
727
+ break;
728
+ }
729
+ }
730
+ tunnel(info) {
731
+ switch (info.status) {
732
+ case 'creating':
733
+ this.log('info', 'server', `Creating tunnel for port ${info.port}...`, undefined, true);
734
+ break;
735
+ case 'created':
736
+ this.success('server', `Tunnel: ${info.url} → localhost:${info.port}`);
737
+ break;
738
+ case 'closed':
739
+ this.info('server', 'Tunnel closed');
740
+ break;
741
+ case 'error':
742
+ this.error('server', `Tunnel error: ${info.error}`);
743
+ break;
744
+ }
745
+ }
746
+ // ============================================
747
+ // Build Access
748
+ // ============================================
749
+ getCurrentBuild() {
750
+ if (!this.currentBuildId)
751
+ return null;
752
+ return this.builds.get(this.currentBuildId) ?? null;
753
+ }
754
+ getAllBuilds() {
755
+ return Array.from(this.builds.values());
756
+ }
757
+ getBuild(buildId) {
758
+ return this.builds.get(buildId) ?? null;
759
+ }
760
+ // ============================================
761
+ // Buffer Access
762
+ // ============================================
763
+ getBuffer() {
764
+ return this.buffer;
765
+ }
766
+ // ============================================
767
+ // Private Helpers
768
+ // ============================================
769
+ log(level, category, message, data, verbose = false) {
770
+ // Skip verbose logs when not in verbose mode
771
+ if (verbose && !this.verbose) {
772
+ return;
773
+ }
774
+ const entry = {
775
+ id: this.generateId(),
776
+ timestamp: Date.now(),
777
+ level,
778
+ category,
779
+ message,
780
+ data,
781
+ buildId: this.currentBuildId ?? undefined,
782
+ verbose,
783
+ };
784
+ this.buffer.add(entry);
785
+ if (!this.tuiMode) {
786
+ this.printEntry(entry);
787
+ }
788
+ this.emit('log', entry);
789
+ }
790
+ printEntry(entry) {
791
+ const time = new Date(entry.timestamp).toLocaleTimeString('en-US', {
792
+ hour12: false,
793
+ hour: '2-digit',
794
+ minute: '2-digit',
795
+ second: '2-digit',
796
+ });
797
+ const levelStyles = {
798
+ debug: chalk.gray,
799
+ info: chalk.blue,
800
+ success: chalk.green,
801
+ warn: chalk.yellow,
802
+ error: chalk.red,
803
+ };
804
+ const levelIcons = {
805
+ debug: ' ',
806
+ info: '●',
807
+ success: '✓',
808
+ warn: '⚠',
809
+ error: '✗',
810
+ };
811
+ const style = levelStyles[entry.level];
812
+ const icon = levelIcons[entry.level];
813
+ if (entry.toolName) {
814
+ // Tool calls get special formatting
815
+ console.log(chalk.gray(time), ' ', chalk.cyan('🔧'), chalk.white(entry.toolName), entry.toolArgs ? chalk.gray(entry.toolArgs) : '');
816
+ }
817
+ else {
818
+ // Regular log entries
819
+ console.log(chalk.gray(time), style(icon), style(entry.message));
820
+ }
821
+ }
822
+ truncate(str, maxLength) {
823
+ if (str.length <= maxLength)
824
+ return str;
825
+ return str.substring(0, maxLength - 3) + '...';
826
+ }
827
+ formatDuration(seconds) {
828
+ if (seconds < 60) {
829
+ return `${Math.round(seconds)}s`;
830
+ }
831
+ const mins = Math.floor(seconds / 60);
832
+ const secs = Math.round(seconds % 60);
833
+ return `${mins}m ${secs}s`;
834
+ }
835
+ generateId() {
836
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
837
+ }
838
+ }
839
+ // Singleton instance
840
+ let instance = null;
841
+ function getRunnerLogger() {
842
+ if (!instance) {
843
+ instance = new RunnerLogger();
844
+ }
845
+ return instance;
846
+ }
847
+ function createRunnerLogger(options) {
848
+ instance = new RunnerLogger(options);
849
+ return instance;
850
+ }
851
+
852
+ /**
853
+ * RunnerLogger Instance Management
854
+ *
855
+ * This module provides the singleton logger instance that can be:
856
+ * - Configured at startup (via initRunnerLogger)
857
+ * - Accessed from anywhere in the runner (via getLogger)
858
+ * - Integrated with the TUI dashboard
859
+ *
860
+ * Note: Uses the same singleton as runner-logger.ts to ensure
861
+ * all code shares the same instance.
862
+ */
863
+ let loggerOptions = {};
864
+ let initialized = false;
865
+ /**
866
+ * Initialize the logger with options
867
+ * Call this early in the runner startup
868
+ *
869
+ * If already initialized (e.g., by TUI before startRunner), returns the existing instance
870
+ * to preserve event subscriptions.
871
+ */
872
+ function initRunnerLogger(options) {
873
+ if (initialized) {
874
+ // Already initialized - return existing instance to preserve subscriptions
875
+ // But update options if needed
876
+ const logger = getRunnerLogger();
877
+ if (options.verbose !== undefined) {
878
+ logger.setVerbose(options.verbose);
879
+ }
880
+ return logger;
881
+ }
882
+ loggerOptions = options;
883
+ initialized = true;
884
+ return createRunnerLogger(options);
885
+ }
886
+ /**
887
+ * Get the current logger instance
888
+ * Uses the shared singleton from runner-logger.ts
889
+ */
890
+ function getLogger() {
891
+ if (!initialized) {
892
+ // Create with default options if not initialized
893
+ return createRunnerLogger(loggerOptions);
894
+ }
895
+ return getRunnerLogger();
896
+ }
897
+
898
+ export { streamLog as a, getLogger as b, fileLog as f, getLogBuffer as g, initRunnerLogger as i, setFileLoggerTuiMode as s };
899
+ //# sourceMappingURL=runner-logger-instance-nDWv2h2T.js.map