@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.
- package/README.md +1053 -0
- package/bin/openbuilder.js +31 -0
- package/dist/chunks/Banner-D4tqKfzA.js +113 -0
- package/dist/chunks/Banner-D4tqKfzA.js.map +1 -0
- package/dist/chunks/auto-update-Dj3lWPWO.js +350 -0
- package/dist/chunks/auto-update-Dj3lWPWO.js.map +1 -0
- package/dist/chunks/build-D0qYqIq0.js +116 -0
- package/dist/chunks/build-D0qYqIq0.js.map +1 -0
- package/dist/chunks/cleanup-qVTsA3tk.js +141 -0
- package/dist/chunks/cleanup-qVTsA3tk.js.map +1 -0
- package/dist/chunks/cli-error-BjQwvWtK.js +140 -0
- package/dist/chunks/cli-error-BjQwvWtK.js.map +1 -0
- package/dist/chunks/config-BGP1jZJ4.js +167 -0
- package/dist/chunks/config-BGP1jZJ4.js.map +1 -0
- package/dist/chunks/config-manager-BkbjtN-H.js +133 -0
- package/dist/chunks/config-manager-BkbjtN-H.js.map +1 -0
- package/dist/chunks/database-BvAbD4sP.js +68 -0
- package/dist/chunks/database-BvAbD4sP.js.map +1 -0
- package/dist/chunks/database-setup-BYjIRAmT.js +253 -0
- package/dist/chunks/database-setup-BYjIRAmT.js.map +1 -0
- package/dist/chunks/exports-ij9sv4UM.js +7793 -0
- package/dist/chunks/exports-ij9sv4UM.js.map +1 -0
- package/dist/chunks/init-CZoN6soU.js +468 -0
- package/dist/chunks/init-CZoN6soU.js.map +1 -0
- package/dist/chunks/init-tui-BNzk_7Yx.js +1127 -0
- package/dist/chunks/init-tui-BNzk_7Yx.js.map +1 -0
- package/dist/chunks/logger-ZpJi7chw.js +38 -0
- package/dist/chunks/logger-ZpJi7chw.js.map +1 -0
- package/dist/chunks/main-tui-Cq1hLCx-.js +644 -0
- package/dist/chunks/main-tui-Cq1hLCx-.js.map +1 -0
- package/dist/chunks/manager-CvGX9qqe.js +1161 -0
- package/dist/chunks/manager-CvGX9qqe.js.map +1 -0
- package/dist/chunks/port-allocator-BRFzgH9b.js +749 -0
- package/dist/chunks/port-allocator-BRFzgH9b.js.map +1 -0
- package/dist/chunks/process-killer-CaUL7Kpl.js +87 -0
- package/dist/chunks/process-killer-CaUL7Kpl.js.map +1 -0
- package/dist/chunks/prompts-1QbE_bRr.js +128 -0
- package/dist/chunks/prompts-1QbE_bRr.js.map +1 -0
- package/dist/chunks/repo-cloner-CpOQjFSo.js +219 -0
- package/dist/chunks/repo-cloner-CpOQjFSo.js.map +1 -0
- package/dist/chunks/repo-detector-B_oj696o.js +66 -0
- package/dist/chunks/repo-detector-B_oj696o.js.map +1 -0
- package/dist/chunks/run-D23hg4xy.js +630 -0
- package/dist/chunks/run-D23hg4xy.js.map +1 -0
- package/dist/chunks/runner-logger-instance-nDWv2h2T.js +899 -0
- package/dist/chunks/runner-logger-instance-nDWv2h2T.js.map +1 -0
- package/dist/chunks/spinner-BJL9zWAJ.js +53 -0
- package/dist/chunks/spinner-BJL9zWAJ.js.map +1 -0
- package/dist/chunks/start-BygPCbvw.js +1708 -0
- package/dist/chunks/start-BygPCbvw.js.map +1 -0
- package/dist/chunks/start-traditional-uoLZXdxm.js +255 -0
- package/dist/chunks/start-traditional-uoLZXdxm.js.map +1 -0
- package/dist/chunks/status-cS8YwtUx.js +97 -0
- package/dist/chunks/status-cS8YwtUx.js.map +1 -0
- package/dist/chunks/theme-DhorI2Hb.js +44 -0
- package/dist/chunks/theme-DhorI2Hb.js.map +1 -0
- package/dist/chunks/upgrade-CT6w0lKp.js +323 -0
- package/dist/chunks/upgrade-CT6w0lKp.js.map +1 -0
- package/dist/chunks/useBuildState-CdBSu9y_.js +331 -0
- package/dist/chunks/useBuildState-CdBSu9y_.js.map +1 -0
- package/dist/cli/index.js +694 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.js +14358 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.js +64226 -0
- package/dist/instrument.js.map +1 -0
- package/dist/templates.json +295 -0
- package/package.json +98 -0
- package/scripts/install-vendor-deps.js +34 -0
- package/scripts/install-vendor.js +167 -0
- package/scripts/prepare-release.js +71 -0
- package/templates/config.template.json +18 -0
- package/templates.json +295 -0
- package/vendor/ai-sdk-provider-claude-code-LOCAL.tgz +0 -0
- package/vendor/sentry-core-LOCAL.tgz +0 -0
- package/vendor/sentry-nextjs-LOCAL.tgz +0 -0
- package/vendor/sentry-node-LOCAL.tgz +0 -0
- 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
|