@litmers/cursorflow-orchestrator 0.1.12 → 0.1.14

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 (71) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +83 -2
  3. package/commands/cursorflow-clean.md +20 -6
  4. package/commands/cursorflow-prepare.md +1 -1
  5. package/commands/cursorflow-resume.md +127 -6
  6. package/commands/cursorflow-run.md +2 -2
  7. package/commands/cursorflow-signal.md +11 -4
  8. package/dist/cli/clean.js +164 -12
  9. package/dist/cli/clean.js.map +1 -1
  10. package/dist/cli/index.d.ts +1 -0
  11. package/dist/cli/index.js +6 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/cli/logs.d.ts +8 -0
  14. package/dist/cli/logs.js +746 -0
  15. package/dist/cli/logs.js.map +1 -0
  16. package/dist/cli/monitor.js +113 -30
  17. package/dist/cli/monitor.js.map +1 -1
  18. package/dist/cli/prepare.js +1 -1
  19. package/dist/cli/resume.js +367 -18
  20. package/dist/cli/resume.js.map +1 -1
  21. package/dist/cli/run.js +2 -0
  22. package/dist/cli/run.js.map +1 -1
  23. package/dist/cli/signal.js +34 -20
  24. package/dist/cli/signal.js.map +1 -1
  25. package/dist/core/orchestrator.d.ts +11 -1
  26. package/dist/core/orchestrator.js +257 -35
  27. package/dist/core/orchestrator.js.map +1 -1
  28. package/dist/core/reviewer.js +20 -0
  29. package/dist/core/reviewer.js.map +1 -1
  30. package/dist/core/runner.js +113 -13
  31. package/dist/core/runner.js.map +1 -1
  32. package/dist/utils/config.js +34 -0
  33. package/dist/utils/config.js.map +1 -1
  34. package/dist/utils/doctor.js +9 -2
  35. package/dist/utils/doctor.js.map +1 -1
  36. package/dist/utils/enhanced-logger.d.ts +209 -0
  37. package/dist/utils/enhanced-logger.js +963 -0
  38. package/dist/utils/enhanced-logger.js.map +1 -0
  39. package/dist/utils/events.d.ts +59 -0
  40. package/dist/utils/events.js +37 -0
  41. package/dist/utils/events.js.map +1 -0
  42. package/dist/utils/git.d.ts +5 -0
  43. package/dist/utils/git.js +25 -0
  44. package/dist/utils/git.js.map +1 -1
  45. package/dist/utils/types.d.ts +122 -1
  46. package/dist/utils/webhook.d.ts +5 -0
  47. package/dist/utils/webhook.js +109 -0
  48. package/dist/utils/webhook.js.map +1 -0
  49. package/examples/README.md +1 -1
  50. package/package.json +1 -1
  51. package/scripts/simple-logging-test.sh +97 -0
  52. package/scripts/test-real-logging.sh +289 -0
  53. package/scripts/test-streaming-multi-task.sh +247 -0
  54. package/src/cli/clean.ts +170 -13
  55. package/src/cli/index.ts +4 -1
  56. package/src/cli/logs.ts +848 -0
  57. package/src/cli/monitor.ts +123 -30
  58. package/src/cli/prepare.ts +1 -1
  59. package/src/cli/resume.ts +463 -22
  60. package/src/cli/run.ts +2 -0
  61. package/src/cli/signal.ts +43 -27
  62. package/src/core/orchestrator.ts +303 -37
  63. package/src/core/reviewer.ts +22 -0
  64. package/src/core/runner.ts +128 -12
  65. package/src/utils/config.ts +36 -0
  66. package/src/utils/doctor.ts +12 -2
  67. package/src/utils/enhanced-logger.ts +1097 -0
  68. package/src/utils/events.ts +117 -0
  69. package/src/utils/git.ts +25 -0
  70. package/src/utils/types.ts +150 -1
  71. package/src/utils/webhook.ts +85 -0
@@ -0,0 +1,746 @@
1
+ "use strict";
2
+ /**
3
+ * CursorFlow logs command - View and export logs
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const logger = __importStar(require("../utils/logger"));
41
+ const config_1 = require("../utils/config");
42
+ const enhanced_logger_1 = require("../utils/enhanced-logger");
43
+ function printHelp() {
44
+ console.log(`
45
+ Usage: cursorflow logs [run-dir] [options]
46
+
47
+ View and export lane logs.
48
+
49
+ Options:
50
+ [run-dir] Run directory (default: latest)
51
+ --lane <name> Filter to specific lane
52
+ --all, -a View all lanes merged (sorted by timestamp)
53
+ --format <fmt> Output format: text, json, markdown, html (default: text)
54
+ --output <path> Write output to file instead of stdout
55
+ --tail <n> Show last n lines/entries (default: all)
56
+ --follow, -f Follow log output in real-time
57
+ --filter <pattern> Filter entries by regex pattern
58
+ --level <level> Filter by log level: stdout, stderr, info, error, debug
59
+ --clean Show clean logs without ANSI codes (default)
60
+ --raw Show raw logs with ANSI codes
61
+ --readable, -r Show readable log (parsed streaming output)
62
+ --help, -h Show help
63
+
64
+ Examples:
65
+ cursorflow logs # View latest run logs summary
66
+ cursorflow logs --lane api-setup # View specific lane logs
67
+ cursorflow logs --lane api-setup --readable # View readable parsed log
68
+ cursorflow logs --all # View all lanes merged by time
69
+ cursorflow logs --all --follow # Follow all lanes in real-time
70
+ cursorflow logs --all --format json # Export all lanes as JSON
71
+ cursorflow logs --all --filter "error" # Filter all lanes for errors
72
+ cursorflow logs --format json --output out.json # Export as JSON
73
+ `);
74
+ }
75
+ function parseArgs(args) {
76
+ const laneIdx = args.indexOf('--lane');
77
+ const formatIdx = args.indexOf('--format');
78
+ const outputIdx = args.indexOf('--output');
79
+ const tailIdx = args.indexOf('--tail');
80
+ const filterIdx = args.indexOf('--filter');
81
+ const levelIdx = args.indexOf('--level');
82
+ // Find run directory (first non-option argument)
83
+ const runDir = args.find((arg, i) => {
84
+ if (arg.startsWith('--') || arg.startsWith('-'))
85
+ return false;
86
+ // Skip values for options
87
+ const prevArg = args[i - 1];
88
+ if (prevArg && ['--lane', '--format', '--output', '--tail', '--filter', '--level'].includes(prevArg)) {
89
+ return false;
90
+ }
91
+ return true;
92
+ });
93
+ return {
94
+ runDir,
95
+ lane: laneIdx >= 0 ? args[laneIdx + 1] : undefined,
96
+ all: args.includes('--all') || args.includes('-a'),
97
+ format: (formatIdx >= 0 ? args[formatIdx + 1] : 'text'),
98
+ output: outputIdx >= 0 ? args[outputIdx + 1] : undefined,
99
+ tail: tailIdx >= 0 ? parseInt(args[tailIdx + 1] || '50') : undefined,
100
+ follow: args.includes('--follow') || args.includes('-f'),
101
+ filter: filterIdx >= 0 ? args[filterIdx + 1] : undefined,
102
+ level: levelIdx >= 0 ? args[levelIdx + 1] : undefined,
103
+ clean: !args.includes('--raw') && !args.includes('--readable') && !args.includes('-r'),
104
+ raw: args.includes('--raw'),
105
+ readable: args.includes('--readable') || args.includes('-r'),
106
+ help: args.includes('--help') || args.includes('-h'),
107
+ };
108
+ }
109
+ /**
110
+ * Find the latest run directory
111
+ */
112
+ function findLatestRunDir(logsDir) {
113
+ const runsDir = path.join(logsDir, 'runs');
114
+ if (!fs.existsSync(runsDir))
115
+ return null;
116
+ const runs = fs.readdirSync(runsDir)
117
+ .filter(d => d.startsWith('run-'))
118
+ .map(d => ({
119
+ name: d,
120
+ path: path.join(runsDir, d),
121
+ mtime: fs.statSync(path.join(runsDir, d)).mtime.getTime()
122
+ }))
123
+ .sort((a, b) => b.mtime - a.mtime);
124
+ return runs.length > 0 ? runs[0].path : null;
125
+ }
126
+ /**
127
+ * List lanes in a run directory
128
+ */
129
+ function listLanes(runDir) {
130
+ const lanesDir = path.join(runDir, 'lanes');
131
+ if (!fs.existsSync(lanesDir))
132
+ return [];
133
+ return fs.readdirSync(lanesDir)
134
+ .filter(d => fs.statSync(path.join(lanesDir, d)).isDirectory());
135
+ }
136
+ /**
137
+ * Read and display text logs
138
+ */
139
+ function displayTextLogs(laneDir, options) {
140
+ let logFile;
141
+ if (options.readable) {
142
+ logFile = path.join(laneDir, 'terminal-readable.log');
143
+ }
144
+ else if (options.raw) {
145
+ logFile = path.join(laneDir, 'terminal-raw.log');
146
+ }
147
+ else {
148
+ logFile = path.join(laneDir, 'terminal.log');
149
+ }
150
+ if (!fs.existsSync(logFile)) {
151
+ if (options.readable) {
152
+ console.log('Readable log not found. This log is only available for runs with streaming output enabled.');
153
+ }
154
+ else {
155
+ console.log('No log file found.');
156
+ }
157
+ return;
158
+ }
159
+ let content = fs.readFileSync(logFile, 'utf8');
160
+ let lines = content.split('\n');
161
+ // Apply filter
162
+ if (options.filter) {
163
+ const regex = new RegExp(options.filter, 'i');
164
+ lines = lines.filter(line => regex.test(line));
165
+ }
166
+ // Apply tail
167
+ if (options.tail && lines.length > options.tail) {
168
+ lines = lines.slice(-options.tail);
169
+ }
170
+ // Clean ANSI if needed (for clean mode)
171
+ if (options.clean && !options.raw) {
172
+ lines = lines.map(line => (0, enhanced_logger_1.stripAnsi)(line));
173
+ }
174
+ console.log(lines.join('\n'));
175
+ }
176
+ /**
177
+ * Read and display JSON logs
178
+ */
179
+ function displayJsonLogs(laneDir, options) {
180
+ const logFile = path.join(laneDir, 'terminal.jsonl');
181
+ if (!fs.existsSync(logFile)) {
182
+ console.log('No JSON log file found.');
183
+ return;
184
+ }
185
+ let entries = (0, enhanced_logger_1.readJsonLog)(logFile);
186
+ // Apply level filter
187
+ if (options.level) {
188
+ entries = entries.filter(e => e.level === options.level);
189
+ }
190
+ // Apply regex filter
191
+ if (options.filter) {
192
+ const regex = new RegExp(options.filter, 'i');
193
+ entries = entries.filter(e => regex.test(e.message) || regex.test(e.task || ''));
194
+ }
195
+ // Apply tail
196
+ if (options.tail && entries.length > options.tail) {
197
+ entries = entries.slice(-options.tail);
198
+ }
199
+ if (options.format === 'json') {
200
+ console.log(JSON.stringify(entries, null, 2));
201
+ }
202
+ else {
203
+ // Display as formatted text
204
+ for (const entry of entries) {
205
+ const levelColor = getLevelColor(entry.level);
206
+ const ts = new Date(entry.timestamp).toLocaleTimeString();
207
+ console.log(`${levelColor}[${ts}] [${entry.level.toUpperCase().padEnd(6)}]${logger.COLORS.reset} ${entry.message}`);
208
+ }
209
+ }
210
+ }
211
+ /**
212
+ * Get color for log level
213
+ */
214
+ function getLevelColor(level) {
215
+ switch (level) {
216
+ case 'error':
217
+ return logger.COLORS.red;
218
+ case 'stderr':
219
+ return logger.COLORS.yellow;
220
+ case 'info':
221
+ case 'session':
222
+ return logger.COLORS.cyan;
223
+ case 'debug':
224
+ return logger.COLORS.gray;
225
+ default:
226
+ return logger.COLORS.reset;
227
+ }
228
+ }
229
+ /**
230
+ * Lane color palette for distinguishing lanes in merged view
231
+ */
232
+ const LANE_COLORS = [
233
+ '\x1b[38;5;39m', // Blue
234
+ '\x1b[38;5;208m', // Orange
235
+ '\x1b[38;5;156m', // Light Green
236
+ '\x1b[38;5;213m', // Pink
237
+ '\x1b[38;5;87m', // Cyan
238
+ '\x1b[38;5;228m', // Yellow
239
+ '\x1b[38;5;183m', // Light Purple
240
+ '\x1b[38;5;121m', // Sea Green
241
+ ];
242
+ /**
243
+ * Get consistent color for a lane name
244
+ */
245
+ function getLaneColor(laneName, laneIndex) {
246
+ return LANE_COLORS[laneIndex % LANE_COLORS.length];
247
+ }
248
+ /**
249
+ * Read and merge all lane logs
250
+ */
251
+ function readAllLaneLogs(runDir) {
252
+ const lanes = listLanes(runDir);
253
+ const allEntries = [];
254
+ lanes.forEach((laneName, index) => {
255
+ const laneDir = path.join(runDir, 'lanes', laneName);
256
+ const jsonLogPath = path.join(laneDir, 'terminal.jsonl');
257
+ if (fs.existsSync(jsonLogPath)) {
258
+ const entries = (0, enhanced_logger_1.readJsonLog)(jsonLogPath);
259
+ const laneColor = getLaneColor(laneName, index);
260
+ for (const entry of entries) {
261
+ allEntries.push({
262
+ ...entry,
263
+ laneName,
264
+ laneColor,
265
+ });
266
+ }
267
+ }
268
+ });
269
+ // Sort by timestamp
270
+ allEntries.sort((a, b) => {
271
+ const timeA = new Date(a.timestamp).getTime();
272
+ const timeB = new Date(b.timestamp).getTime();
273
+ return timeA - timeB;
274
+ });
275
+ return allEntries;
276
+ }
277
+ /**
278
+ * Display merged logs from all lanes
279
+ */
280
+ function displayMergedLogs(runDir, options) {
281
+ let entries = readAllLaneLogs(runDir);
282
+ if (entries.length === 0) {
283
+ console.log('No log entries found in any lane.');
284
+ return;
285
+ }
286
+ // Apply level filter
287
+ if (options.level) {
288
+ entries = entries.filter(e => e.level === options.level);
289
+ }
290
+ // Apply regex filter
291
+ if (options.filter) {
292
+ const regex = new RegExp(options.filter, 'i');
293
+ entries = entries.filter(e => regex.test(e.message) ||
294
+ regex.test(e.task || '') ||
295
+ regex.test(e.laneName));
296
+ }
297
+ // Apply tail
298
+ if (options.tail && entries.length > options.tail) {
299
+ entries = entries.slice(-options.tail);
300
+ }
301
+ // Get unique lanes for legend
302
+ const lanes = [...new Set(entries.map(e => e.laneName))];
303
+ // Print header
304
+ console.log(`\n${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
305
+ console.log(`${logger.COLORS.cyan} 🔀 Merged Logs - ${path.basename(runDir)} (${entries.length} entries from ${lanes.length} lanes)${logger.COLORS.reset}`);
306
+ console.log(`${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
307
+ // Print lane legend
308
+ console.log('\n Lanes: ' + lanes.map((lane, i) => {
309
+ const color = getLaneColor(lane, lanes.indexOf(lane));
310
+ return `${color}■${logger.COLORS.reset} ${lane}`;
311
+ }).join(' '));
312
+ console.log('');
313
+ // Format output based on format option
314
+ if (options.format === 'json') {
315
+ console.log(JSON.stringify(entries, null, 2));
316
+ return;
317
+ }
318
+ // Display entries
319
+ for (const entry of entries) {
320
+ const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
321
+ const levelColor = getLevelColor(entry.level);
322
+ const laneColor = entry.laneColor;
323
+ const lanePad = entry.laneName.substring(0, 12).padEnd(12);
324
+ const levelPad = entry.level.toUpperCase().padEnd(6);
325
+ // Skip session entries for cleaner output unless they're important
326
+ if (entry.level === 'session' && entry.message === 'Session started') {
327
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
328
+ continue;
329
+ }
330
+ if (entry.level === 'session' && entry.message === 'Session ended') {
331
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
332
+ continue;
333
+ }
334
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${entry.message}`);
335
+ }
336
+ console.log(`\n${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
337
+ }
338
+ /**
339
+ * Follow all lanes in real-time
340
+ */
341
+ function followAllLogs(runDir, options) {
342
+ const lanes = listLanes(runDir);
343
+ if (lanes.length === 0) {
344
+ console.log('No lanes found.');
345
+ return;
346
+ }
347
+ // Track last read position for each lane
348
+ const lastPositions = {};
349
+ const laneColors = {};
350
+ lanes.forEach((lane, index) => {
351
+ lastPositions[lane] = 0;
352
+ laneColors[lane] = getLaneColor(lane, index);
353
+ });
354
+ // Print header
355
+ console.log(`\n${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
356
+ console.log(`${logger.COLORS.cyan} 🔴 Following All Lanes - ${path.basename(runDir)} (Ctrl+C to stop)${logger.COLORS.reset}`);
357
+ console.log(`${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
358
+ console.log('\n Lanes: ' + lanes.map((lane, i) => {
359
+ return `${laneColors[lane]}■${logger.COLORS.reset} ${lane}`;
360
+ }).join(' '));
361
+ console.log('');
362
+ const checkInterval = setInterval(() => {
363
+ const newEntries = [];
364
+ for (const lane of lanes) {
365
+ const laneDir = path.join(runDir, 'lanes', lane);
366
+ const jsonLogPath = path.join(laneDir, 'terminal.jsonl');
367
+ if (!fs.existsSync(jsonLogPath))
368
+ continue;
369
+ try {
370
+ const stats = fs.statSync(jsonLogPath);
371
+ if (stats.size > lastPositions[lane]) {
372
+ const fd = fs.openSync(jsonLogPath, 'r');
373
+ const buffer = Buffer.alloc(stats.size - lastPositions[lane]);
374
+ fs.readSync(fd, buffer, 0, buffer.length, lastPositions[lane]);
375
+ fs.closeSync(fd);
376
+ const content = buffer.toString();
377
+ const lines = content.split('\n').filter(l => l.trim());
378
+ for (const line of lines) {
379
+ try {
380
+ const entry = JSON.parse(line);
381
+ newEntries.push({
382
+ ...entry,
383
+ laneName: lane,
384
+ laneColor: laneColors[lane],
385
+ });
386
+ }
387
+ catch {
388
+ // Skip invalid lines
389
+ }
390
+ }
391
+ lastPositions[lane] = stats.size;
392
+ }
393
+ }
394
+ catch {
395
+ // Ignore errors
396
+ }
397
+ }
398
+ // Sort new entries by timestamp
399
+ newEntries.sort((a, b) => {
400
+ const timeA = new Date(a.timestamp).getTime();
401
+ const timeB = new Date(b.timestamp).getTime();
402
+ return timeA - timeB;
403
+ });
404
+ // Apply filters and display
405
+ for (let entry of newEntries) {
406
+ // Apply level filter
407
+ if (options.level && entry.level !== options.level)
408
+ continue;
409
+ // Apply regex filter
410
+ if (options.filter) {
411
+ const regex = new RegExp(options.filter, 'i');
412
+ if (!regex.test(entry.message) && !regex.test(entry.task || '') && !regex.test(entry.laneName)) {
413
+ continue;
414
+ }
415
+ }
416
+ const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
417
+ const levelColor = getLevelColor(entry.level);
418
+ const lanePad = entry.laneName.substring(0, 12).padEnd(12);
419
+ const levelPad = entry.level.toUpperCase().padEnd(6);
420
+ // Skip verbose session entries
421
+ if (entry.level === 'session') {
422
+ if (entry.message === 'Session started') {
423
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
424
+ }
425
+ else if (entry.message === 'Session ended') {
426
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
427
+ }
428
+ continue;
429
+ }
430
+ console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${entry.message}`);
431
+ }
432
+ }, 100);
433
+ // Handle Ctrl+C
434
+ process.on('SIGINT', () => {
435
+ clearInterval(checkInterval);
436
+ console.log('\n\nStopped following logs.');
437
+ process.exit(0);
438
+ });
439
+ }
440
+ /**
441
+ * Export merged logs to various formats
442
+ */
443
+ function exportMergedLogs(runDir, format, outputPath) {
444
+ let entries = readAllLaneLogs(runDir);
445
+ let output = '';
446
+ switch (format) {
447
+ case 'json':
448
+ output = JSON.stringify(entries, null, 2);
449
+ break;
450
+ case 'markdown':
451
+ output = exportMergedToMarkdown(entries, runDir);
452
+ break;
453
+ case 'html':
454
+ output = exportMergedToHtml(entries, runDir);
455
+ break;
456
+ default:
457
+ // Text format
458
+ for (const entry of entries) {
459
+ const ts = new Date(entry.timestamp).toISOString();
460
+ output += `[${ts}] [${entry.laneName}] [${entry.level.toUpperCase()}] ${entry.message}\n`;
461
+ }
462
+ }
463
+ if (outputPath) {
464
+ fs.writeFileSync(outputPath, output, 'utf8');
465
+ }
466
+ return output;
467
+ }
468
+ /**
469
+ * Export merged logs to Markdown
470
+ */
471
+ function exportMergedToMarkdown(entries, runDir) {
472
+ const lanes = [...new Set(entries.map(e => e.laneName))];
473
+ let md = `# CursorFlow Merged Logs\n\n`;
474
+ md += `**Run:** ${path.basename(runDir)}\n`;
475
+ md += `**Lanes:** ${lanes.join(', ')}\n`;
476
+ md += `**Entries:** ${entries.length}\n\n`;
477
+ md += `## Timeline\n\n`;
478
+ md += '| Time | Lane | Level | Message |\n';
479
+ md += '|------|------|-------|--------|\n';
480
+ for (const entry of entries) {
481
+ const ts = new Date(entry.timestamp).toLocaleTimeString();
482
+ const message = entry.message.replace(/\|/g, '\\|').substring(0, 80);
483
+ md += `| ${ts} | ${entry.laneName} | ${entry.level} | ${message} |\n`;
484
+ }
485
+ return md;
486
+ }
487
+ /**
488
+ * Export merged logs to HTML
489
+ */
490
+ function exportMergedToHtml(entries, runDir) {
491
+ const lanes = [...new Set(entries.map(e => e.laneName))];
492
+ let html = `<!DOCTYPE html>
493
+ <html>
494
+ <head>
495
+ <title>CursorFlow Merged Logs - ${path.basename(runDir)}</title>
496
+ <style>
497
+ body { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }
498
+ h1, h2 { color: #569cd6; }
499
+ .legend { margin: 10px 0; padding: 10px; background: #252526; border-radius: 4px; }
500
+ .legend-item { display: inline-block; margin-right: 15px; }
501
+ .legend-color { display: inline-block; width: 12px; height: 12px; margin-right: 5px; border-radius: 2px; }
502
+ .entry { padding: 4px 8px; margin: 2px 0; border-radius: 4px; display: flex; }
503
+ .entry .time { color: #808080; width: 80px; flex-shrink: 0; }
504
+ .entry .lane { width: 120px; flex-shrink: 0; font-weight: bold; }
505
+ .entry .level { width: 70px; flex-shrink: 0; }
506
+ .entry .message { flex: 1; white-space: pre-wrap; }
507
+ .entry.stdout { background: #252526; }
508
+ .entry.stderr { background: #3c1f1f; }
509
+ .entry.stderr .level { color: #f48771; }
510
+ .entry.info { background: #1e3a5f; }
511
+ .entry.error { background: #5f1e1e; }
512
+ .entry.session { background: #1e4620; color: #6a9955; }
513
+ </style>
514
+ </head>
515
+ <body>
516
+ <h1>🔀 CursorFlow Merged Logs</h1>
517
+ <p><strong>Run:</strong> ${path.basename(runDir)} | <strong>Entries:</strong> ${entries.length}</p>
518
+
519
+ <div class="legend">
520
+ <strong>Lanes:</strong> `;
521
+ const colors = ['#5dade2', '#f39c12', '#58d68d', '#af7ac5', '#48c9b0', '#f7dc6f', '#bb8fce', '#76d7c4'];
522
+ lanes.forEach((lane, i) => {
523
+ const color = colors[i % colors.length];
524
+ html += `<span class="legend-item"><span class="legend-color" style="background: ${color}"></span>${lane}</span>`;
525
+ });
526
+ html += `</div>\n`;
527
+ for (const entry of entries) {
528
+ const ts = new Date(entry.timestamp).toLocaleTimeString();
529
+ const laneIndex = lanes.indexOf(entry.laneName);
530
+ const color = colors[laneIndex % colors.length];
531
+ html += ` <div class="entry ${entry.level}">
532
+ <span class="time">${ts}</span>
533
+ <span class="lane" style="color: ${color}">${entry.laneName}</span>
534
+ <span class="level">[${entry.level.toUpperCase()}]</span>
535
+ <span class="message">${escapeHtml(entry.message)}</span>
536
+ </div>\n`;
537
+ }
538
+ html += '</body></html>';
539
+ return html;
540
+ }
541
+ function escapeHtml(text) {
542
+ return text
543
+ .replace(/&/g, '&amp;')
544
+ .replace(/</g, '&lt;')
545
+ .replace(/>/g, '&gt;')
546
+ .replace(/"/g, '&quot;')
547
+ .replace(/'/g, '&#039;');
548
+ }
549
+ /**
550
+ * Follow logs in real-time
551
+ */
552
+ function followLogs(laneDir, options) {
553
+ let logFile;
554
+ if (options.readable) {
555
+ logFile = path.join(laneDir, 'terminal-readable.log');
556
+ }
557
+ else if (options.raw) {
558
+ logFile = path.join(laneDir, 'terminal-raw.log');
559
+ }
560
+ else {
561
+ logFile = path.join(laneDir, 'terminal.log');
562
+ }
563
+ if (!fs.existsSync(logFile)) {
564
+ console.log('Waiting for log file...');
565
+ }
566
+ let lastSize = 0;
567
+ try {
568
+ lastSize = fs.existsSync(logFile) ? fs.statSync(logFile).size : 0;
569
+ }
570
+ catch {
571
+ // Ignore
572
+ }
573
+ console.log(`${logger.COLORS.cyan}Following ${logFile}... (Ctrl+C to stop)${logger.COLORS.reset}\n`);
574
+ const checkInterval = setInterval(() => {
575
+ try {
576
+ if (!fs.existsSync(logFile))
577
+ return;
578
+ const stats = fs.statSync(logFile);
579
+ if (stats.size > lastSize) {
580
+ const fd = fs.openSync(logFile, 'r');
581
+ const buffer = Buffer.alloc(stats.size - lastSize);
582
+ fs.readSync(fd, buffer, 0, buffer.length, lastSize);
583
+ fs.closeSync(fd);
584
+ let content = buffer.toString();
585
+ // Apply filter
586
+ if (options.filter) {
587
+ const regex = new RegExp(options.filter, 'i');
588
+ const lines = content.split('\n');
589
+ content = lines.filter(line => regex.test(line)).join('\n');
590
+ }
591
+ // Clean ANSI if needed
592
+ if (options.clean && !options.raw) {
593
+ content = (0, enhanced_logger_1.stripAnsi)(content);
594
+ }
595
+ if (content.trim()) {
596
+ process.stdout.write(content);
597
+ }
598
+ lastSize = stats.size;
599
+ }
600
+ }
601
+ catch (e) {
602
+ // Ignore errors (file might be rotating)
603
+ }
604
+ }, 100);
605
+ // Handle Ctrl+C
606
+ process.on('SIGINT', () => {
607
+ clearInterval(checkInterval);
608
+ console.log('\n\nStopped following logs.');
609
+ process.exit(0);
610
+ });
611
+ }
612
+ /**
613
+ * Display logs summary for all lanes
614
+ */
615
+ function displaySummary(runDir) {
616
+ const lanes = listLanes(runDir);
617
+ console.log(`\n${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
618
+ console.log(`${logger.COLORS.cyan} 📋 Logs Summary - ${path.basename(runDir)}${logger.COLORS.reset}`);
619
+ console.log(`${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}\n`);
620
+ if (lanes.length === 0) {
621
+ console.log(' No lanes found.');
622
+ return;
623
+ }
624
+ for (const lane of lanes) {
625
+ const laneDir = path.join(runDir, 'lanes', lane);
626
+ const cleanLog = path.join(laneDir, 'terminal.log');
627
+ const rawLog = path.join(laneDir, 'terminal-raw.log');
628
+ const jsonLog = path.join(laneDir, 'terminal.jsonl');
629
+ const readableLog = path.join(laneDir, 'terminal-readable.log');
630
+ console.log(` ${logger.COLORS.green}📁 ${lane}${logger.COLORS.reset}`);
631
+ if (fs.existsSync(cleanLog)) {
632
+ const stats = fs.statSync(cleanLog);
633
+ console.log(` └─ terminal.log ${formatSize(stats.size)}`);
634
+ }
635
+ if (fs.existsSync(rawLog)) {
636
+ const stats = fs.statSync(rawLog);
637
+ console.log(` └─ terminal-raw.log ${formatSize(stats.size)}`);
638
+ }
639
+ if (fs.existsSync(readableLog)) {
640
+ const stats = fs.statSync(readableLog);
641
+ console.log(` └─ terminal-readable.log ${formatSize(stats.size)} ${logger.COLORS.yellow}(parsed AI output)${logger.COLORS.reset}`);
642
+ }
643
+ if (fs.existsSync(jsonLog)) {
644
+ const stats = fs.statSync(jsonLog);
645
+ const entries = (0, enhanced_logger_1.readJsonLog)(jsonLog);
646
+ const errors = entries.filter(e => e.level === 'error' || e.level === 'stderr').length;
647
+ console.log(` └─ terminal.jsonl ${formatSize(stats.size)} (${entries.length} entries, ${errors} errors)`);
648
+ }
649
+ console.log('');
650
+ }
651
+ }
652
+ /**
653
+ * Format file size
654
+ */
655
+ function formatSize(bytes) {
656
+ if (bytes < 1024)
657
+ return `${bytes} B`;
658
+ if (bytes < 1024 * 1024)
659
+ return `${(bytes / 1024).toFixed(1)} KB`;
660
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
661
+ }
662
+ /**
663
+ * Main logs command
664
+ */
665
+ async function logs(args) {
666
+ const options = parseArgs(args);
667
+ if (options.help) {
668
+ printHelp();
669
+ return;
670
+ }
671
+ const config = (0, config_1.loadConfig)();
672
+ // Find run directory
673
+ let runDir = options.runDir;
674
+ if (!runDir || runDir === 'latest') {
675
+ runDir = findLatestRunDir(config.logsDir) || undefined;
676
+ if (!runDir) {
677
+ throw new Error('No run directories found');
678
+ }
679
+ }
680
+ if (!fs.existsSync(runDir)) {
681
+ throw new Error(`Run directory not found: ${runDir}`);
682
+ }
683
+ // Handle --all option (view all lanes merged)
684
+ if (options.all) {
685
+ // Handle follow mode for all lanes
686
+ if (options.follow) {
687
+ followAllLogs(runDir, options);
688
+ return;
689
+ }
690
+ // Handle export for all lanes
691
+ if (options.output) {
692
+ exportMergedLogs(runDir, options.format, options.output);
693
+ console.log(`Exported merged logs to: ${options.output}`);
694
+ return;
695
+ }
696
+ // Display merged logs
697
+ if (options.format === 'json') {
698
+ const entries = readAllLaneLogs(runDir);
699
+ console.log(JSON.stringify(entries, null, 2));
700
+ }
701
+ else if (options.format === 'markdown' || options.format === 'html') {
702
+ const content = exportMergedLogs(runDir, options.format);
703
+ console.log(content);
704
+ }
705
+ else {
706
+ displayMergedLogs(runDir, options);
707
+ }
708
+ return;
709
+ }
710
+ // If no lane specified, show summary
711
+ if (!options.lane) {
712
+ displaySummary(runDir);
713
+ console.log(`${logger.COLORS.gray}Use --lane <name> to view logs, --readable for parsed AI output, or --all to view all lanes merged${logger.COLORS.reset}`);
714
+ return;
715
+ }
716
+ // Find lane directory
717
+ const laneDir = path.join(runDir, 'lanes', options.lane);
718
+ if (!fs.existsSync(laneDir)) {
719
+ const lanes = listLanes(runDir);
720
+ throw new Error(`Lane not found: ${options.lane}\nAvailable lanes: ${lanes.join(', ')}`);
721
+ }
722
+ // Handle follow mode
723
+ if (options.follow) {
724
+ followLogs(laneDir, options);
725
+ return;
726
+ }
727
+ // Handle export
728
+ if (options.output) {
729
+ const content = (0, enhanced_logger_1.exportLogs)(laneDir, options.format, options.output);
730
+ console.log(`Exported logs to: ${options.output}`);
731
+ return;
732
+ }
733
+ // Display logs
734
+ if (options.format === 'json' || options.level) {
735
+ displayJsonLogs(laneDir, options);
736
+ }
737
+ else if (options.format === 'markdown' || options.format === 'html') {
738
+ const content = (0, enhanced_logger_1.exportLogs)(laneDir, options.format);
739
+ console.log(content);
740
+ }
741
+ else {
742
+ displayTextLogs(laneDir, options);
743
+ }
744
+ }
745
+ module.exports = logs;
746
+ //# sourceMappingURL=logs.js.map