@litmers/cursorflow-orchestrator 0.1.13 → 0.1.15

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