@litmers/cursorflow-orchestrator 0.1.20 → 0.1.28
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/CHANGELOG.md +20 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +87 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +125 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +397 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +97 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +154 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1136 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +1443 -1217
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +871 -499
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
package/src/cli/logs.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
JsonLogEntry
|
|
15
15
|
} from '../utils/enhanced-logger';
|
|
16
16
|
import { formatPotentialJsonMessage } from '../utils/log-formatter';
|
|
17
|
+
import { startLogViewer } from '../ui/log-viewer';
|
|
17
18
|
|
|
18
19
|
interface LogsOptions {
|
|
19
20
|
runDir?: string;
|
|
@@ -23,6 +24,7 @@ interface LogsOptions {
|
|
|
23
24
|
output?: string;
|
|
24
25
|
tail?: number;
|
|
25
26
|
follow: boolean;
|
|
27
|
+
interactive: boolean;
|
|
26
28
|
filter?: string;
|
|
27
29
|
level?: string;
|
|
28
30
|
clean: boolean;
|
|
@@ -46,12 +48,14 @@ View and export lane logs.
|
|
|
46
48
|
|
|
47
49
|
Options:
|
|
48
50
|
[run-dir] Run directory (default: latest)
|
|
51
|
+
--run <id> Specific run directory
|
|
49
52
|
--lane <name> Filter to specific lane
|
|
50
53
|
--all, -a View all lanes merged (sorted by timestamp)
|
|
51
54
|
--format <fmt> Output format: text, json, markdown, html (default: text)
|
|
52
55
|
--output <path> Write output to file instead of stdout
|
|
53
56
|
--tail <n> Show last n lines/entries (default: all)
|
|
54
57
|
--follow, -f Follow log output in real-time
|
|
58
|
+
--interactive, -i Open interactive log viewer
|
|
55
59
|
--filter <pattern> Filter entries by regex pattern
|
|
56
60
|
--level <level> Filter by log level: stdout, stderr, info, error, debug
|
|
57
61
|
--readable, -r Show readable log (parsed AI output) (default)
|
|
@@ -68,23 +72,26 @@ Examples:
|
|
|
68
72
|
cursorflow logs --all --format json # Export all lanes as JSON
|
|
69
73
|
cursorflow logs --all --filter "error" # Filter all lanes for errors
|
|
70
74
|
cursorflow logs --format json --output out.json # Export as JSON
|
|
75
|
+
cursorflow logs -i # Interactive log viewer
|
|
76
|
+
cursorflow logs -i --run <id> # Specific run in interactive mode
|
|
71
77
|
`);
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
function parseArgs(args: string[]): LogsOptions {
|
|
75
81
|
const laneIdx = args.indexOf('--lane');
|
|
82
|
+
const runIdx = args.indexOf('--run');
|
|
76
83
|
const formatIdx = args.indexOf('--format');
|
|
77
84
|
const outputIdx = args.indexOf('--output');
|
|
78
85
|
const tailIdx = args.indexOf('--tail');
|
|
79
86
|
const filterIdx = args.indexOf('--filter');
|
|
80
87
|
const levelIdx = args.indexOf('--level');
|
|
81
88
|
|
|
82
|
-
// Find run directory (first non-option argument)
|
|
83
|
-
|
|
89
|
+
// Find run directory (first non-option argument or --run value)
|
|
90
|
+
let runDir = runIdx >= 0 ? args[runIdx + 1] : args.find((arg, i) => {
|
|
84
91
|
if (arg.startsWith('--') || arg.startsWith('-')) return false;
|
|
85
92
|
// Skip values for options
|
|
86
93
|
const prevArg = args[i - 1];
|
|
87
|
-
if (prevArg && ['--lane', '--format', '--output', '--tail', '--filter', '--level'].includes(prevArg)) {
|
|
94
|
+
if (prevArg && ['--lane', '--run', '--format', '--output', '--tail', '--filter', '--level'].includes(prevArg)) {
|
|
88
95
|
return false;
|
|
89
96
|
}
|
|
90
97
|
return true;
|
|
@@ -102,6 +109,7 @@ function parseArgs(args: string[]): LogsOptions {
|
|
|
102
109
|
output: outputIdx >= 0 ? args[outputIdx + 1] : undefined,
|
|
103
110
|
tail: tailIdx >= 0 ? parseInt(args[tailIdx + 1] || '50') : undefined,
|
|
104
111
|
follow: args.includes('--follow') || args.includes('-f'),
|
|
112
|
+
interactive: args.includes('--interactive') || args.includes('-i'),
|
|
105
113
|
filter: filterIdx >= 0 ? args[filterIdx + 1] : undefined,
|
|
106
114
|
level: levelIdx >= 0 ? args[levelIdx + 1] : undefined,
|
|
107
115
|
raw,
|
|
@@ -217,7 +225,7 @@ function displayJsonLogs(
|
|
|
217
225
|
if (options.filter) {
|
|
218
226
|
const filterLower = options.filter.toLowerCase();
|
|
219
227
|
entries = entries.filter(e =>
|
|
220
|
-
e.message.toLowerCase().includes(filterLower) ||
|
|
228
|
+
(e.message || '').toLowerCase().includes(filterLower) ||
|
|
221
229
|
(e.task && e.task.toLowerCase().includes(filterLower))
|
|
222
230
|
);
|
|
223
231
|
}
|
|
@@ -232,10 +240,12 @@ function displayJsonLogs(
|
|
|
232
240
|
} else {
|
|
233
241
|
// Display as formatted text
|
|
234
242
|
for (const entry of entries) {
|
|
235
|
-
const
|
|
243
|
+
const level = entry.level || 'info';
|
|
244
|
+
const message = entry.message || '';
|
|
245
|
+
const levelColor = getLevelColor(level);
|
|
236
246
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
237
|
-
const formattedMsg = formatPotentialJsonMessage(
|
|
238
|
-
console.log(`${levelColor}[${ts}] [${
|
|
247
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
248
|
+
console.log(`${levelColor}[${ts}] [${level.toUpperCase().padEnd(6)}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
239
249
|
}
|
|
240
250
|
}
|
|
241
251
|
}
|
|
@@ -343,7 +353,7 @@ function displayMergedLogs(runDir: string, options: LogsOptions): void {
|
|
|
343
353
|
if (options.filter) {
|
|
344
354
|
const filterLower = options.filter.toLowerCase();
|
|
345
355
|
entries = entries.filter(e =>
|
|
346
|
-
e.message.toLowerCase().includes(filterLower) ||
|
|
356
|
+
(e.message || '').toLowerCase().includes(filterLower) ||
|
|
347
357
|
(e.task && e.task.toLowerCase().includes(filterLower)) ||
|
|
348
358
|
e.laneName.toLowerCase().includes(filterLower)
|
|
349
359
|
);
|
|
@@ -378,22 +388,25 @@ function displayMergedLogs(runDir: string, options: LogsOptions): void {
|
|
|
378
388
|
// Display entries
|
|
379
389
|
for (const entry of entries) {
|
|
380
390
|
const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
381
|
-
const
|
|
391
|
+
const level = entry.level || 'info';
|
|
392
|
+
const levelColor = getLevelColor(level);
|
|
382
393
|
const laneColor = entry.laneColor;
|
|
383
394
|
const lanePad = entry.laneName.substring(0, 12).padEnd(12);
|
|
384
|
-
const levelPad =
|
|
395
|
+
const levelPad = level.toUpperCase().padEnd(6);
|
|
396
|
+
|
|
397
|
+
const message = entry.message || '';
|
|
385
398
|
|
|
386
399
|
// Skip session entries for cleaner output unless they're important
|
|
387
|
-
if (
|
|
400
|
+
if (level === 'session' && message === 'Session started') {
|
|
388
401
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
|
|
389
402
|
continue;
|
|
390
403
|
}
|
|
391
|
-
if (
|
|
404
|
+
if (level === 'session' && message === 'Session ended') {
|
|
392
405
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
|
|
393
406
|
continue;
|
|
394
407
|
}
|
|
395
408
|
|
|
396
|
-
const formattedMsg = formatPotentialJsonMessage(
|
|
409
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
397
410
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
398
411
|
}
|
|
399
412
|
|
|
@@ -436,14 +449,14 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
436
449
|
const laneDir = safeJoin(runDir, 'lanes', lane);
|
|
437
450
|
const jsonLogPath = safeJoin(laneDir, 'terminal.jsonl');
|
|
438
451
|
|
|
452
|
+
let fd: number | null = null;
|
|
439
453
|
try {
|
|
440
|
-
// Use
|
|
441
|
-
|
|
454
|
+
// Use fstat on open fd to avoid TOCTOU race condition
|
|
455
|
+
fd = fs.openSync(jsonLogPath, 'r');
|
|
456
|
+
const stats = fs.fstatSync(fd);
|
|
442
457
|
if (stats.size > lastPositions[lane]!) {
|
|
443
|
-
const fd = fs.openSync(jsonLogPath, 'r');
|
|
444
458
|
const buffer = Buffer.alloc(stats.size - lastPositions[lane]!);
|
|
445
459
|
fs.readSync(fd, buffer, 0, buffer.length, lastPositions[lane]!);
|
|
446
|
-
fs.closeSync(fd);
|
|
447
460
|
|
|
448
461
|
const content = buffer.toString();
|
|
449
462
|
const lines = content.split('\n').filter(l => l.trim());
|
|
@@ -465,6 +478,10 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
465
478
|
}
|
|
466
479
|
} catch {
|
|
467
480
|
// Ignore errors
|
|
481
|
+
} finally {
|
|
482
|
+
if (fd !== null) {
|
|
483
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
484
|
+
}
|
|
468
485
|
}
|
|
469
486
|
}
|
|
470
487
|
|
|
@@ -477,13 +494,16 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
477
494
|
|
|
478
495
|
// Apply filters and display
|
|
479
496
|
for (let entry of newEntries) {
|
|
497
|
+
const level = entry.level || 'info';
|
|
498
|
+
const message = entry.message || '';
|
|
499
|
+
|
|
480
500
|
// Apply level filter
|
|
481
|
-
if (options.level &&
|
|
501
|
+
if (options.level && level !== options.level) continue;
|
|
482
502
|
|
|
483
503
|
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
484
504
|
if (options.filter) {
|
|
485
505
|
const filterLower = options.filter.toLowerCase();
|
|
486
|
-
if (!
|
|
506
|
+
if (!message.toLowerCase().includes(filterLower) &&
|
|
487
507
|
!(entry.task && entry.task.toLowerCase().includes(filterLower)) &&
|
|
488
508
|
!entry.laneName.toLowerCase().includes(filterLower)) {
|
|
489
509
|
continue;
|
|
@@ -491,21 +511,21 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
491
511
|
}
|
|
492
512
|
|
|
493
513
|
const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
494
|
-
const levelColor = getLevelColor(
|
|
514
|
+
const levelColor = getLevelColor(level);
|
|
495
515
|
const lanePad = entry.laneName.substring(0, 12).padEnd(12);
|
|
496
|
-
const levelPad =
|
|
516
|
+
const levelPad = level.toUpperCase().padEnd(6);
|
|
497
517
|
|
|
498
518
|
// Skip verbose session entries
|
|
499
|
-
if (
|
|
500
|
-
if (
|
|
519
|
+
if (level === 'session') {
|
|
520
|
+
if (message === 'Session started') {
|
|
501
521
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
|
|
502
|
-
} else if (
|
|
522
|
+
} else if (message === 'Session ended') {
|
|
503
523
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
|
|
504
524
|
}
|
|
505
525
|
continue;
|
|
506
526
|
}
|
|
507
527
|
|
|
508
|
-
const formattedMsg = formatPotentialJsonMessage(
|
|
528
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
509
529
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
510
530
|
}
|
|
511
531
|
}, 100);
|
|
@@ -542,7 +562,9 @@ function exportMergedLogs(runDir: string, format: string, outputPath?: string):
|
|
|
542
562
|
// Text format
|
|
543
563
|
for (const entry of entries) {
|
|
544
564
|
const ts = new Date(entry.timestamp).toISOString();
|
|
545
|
-
|
|
565
|
+
const level = entry.level || 'info';
|
|
566
|
+
const message = entry.message || '';
|
|
567
|
+
output += `[${ts}] [${entry.laneName}] [${level.toUpperCase()}] ${message}\n`;
|
|
546
568
|
}
|
|
547
569
|
}
|
|
548
570
|
|
|
@@ -570,8 +592,14 @@ function exportMergedToMarkdown(entries: MergedLogEntry[], runDir: string): stri
|
|
|
570
592
|
|
|
571
593
|
for (const entry of entries) {
|
|
572
594
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
573
|
-
const
|
|
574
|
-
|
|
595
|
+
const level = entry.level || 'info';
|
|
596
|
+
// Escape markdown table special characters: pipe, backslash, and newlines
|
|
597
|
+
const message = (entry.message || '')
|
|
598
|
+
.replace(/\\/g, '\\\\')
|
|
599
|
+
.replace(/\|/g, '\\|')
|
|
600
|
+
.replace(/\n/g, ' ')
|
|
601
|
+
.substring(0, 80);
|
|
602
|
+
md += `| ${ts} | ${entry.laneName} | ${level} | ${message} |\n`;
|
|
575
603
|
}
|
|
576
604
|
|
|
577
605
|
return md;
|
|
@@ -625,12 +653,14 @@ function exportMergedToHtml(entries: MergedLogEntry[], runDir: string): string {
|
|
|
625
653
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
626
654
|
const laneIndex = lanes.indexOf(entry.laneName);
|
|
627
655
|
const color = colors[laneIndex % colors.length];
|
|
656
|
+
const level = entry.level || 'info';
|
|
657
|
+
const message = entry.message || '';
|
|
628
658
|
|
|
629
|
-
html += ` <div class="entry ${
|
|
659
|
+
html += ` <div class="entry ${level}">
|
|
630
660
|
<span class="time">${ts}</span>
|
|
631
661
|
<span class="lane" style="color: ${color}">${entry.laneName}</span>
|
|
632
|
-
<span class="level">[${
|
|
633
|
-
<span class="message">${escapeHtml(
|
|
662
|
+
<span class="level">[${level.toUpperCase()}]</span>
|
|
663
|
+
<span class="message">${escapeHtml(message)}</span>
|
|
634
664
|
</div>\n`;
|
|
635
665
|
}
|
|
636
666
|
|
|
@@ -685,15 +715,14 @@ function followLogs(laneDir: string, options: LogsOptions): void {
|
|
|
685
715
|
console.log(`${logger.COLORS.cyan}Following ${logFile}... (Ctrl+C to stop)${logger.COLORS.reset}\n`);
|
|
686
716
|
|
|
687
717
|
const checkInterval = setInterval(() => {
|
|
718
|
+
// Use fstat on open fd to avoid TOCTOU race condition
|
|
719
|
+
let fd: number | null = null;
|
|
688
720
|
try {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const stats = fs.statSync(logFile);
|
|
721
|
+
fd = fs.openSync(logFile, 'r');
|
|
722
|
+
const stats = fs.fstatSync(fd);
|
|
692
723
|
if (stats.size > lastSize) {
|
|
693
|
-
const fd = fs.openSync(logFile, 'r');
|
|
694
724
|
const buffer = Buffer.alloc(stats.size - lastSize);
|
|
695
725
|
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
696
|
-
fs.closeSync(fd);
|
|
697
726
|
|
|
698
727
|
let content = buffer.toString();
|
|
699
728
|
|
|
@@ -715,8 +744,12 @@ function followLogs(laneDir: string, options: LogsOptions): void {
|
|
|
715
744
|
|
|
716
745
|
lastSize = stats.size;
|
|
717
746
|
}
|
|
718
|
-
} catch
|
|
747
|
+
} catch {
|
|
719
748
|
// Ignore errors (file might be rotating)
|
|
749
|
+
} finally {
|
|
750
|
+
if (fd !== null) {
|
|
751
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
752
|
+
}
|
|
720
753
|
}
|
|
721
754
|
}, 100);
|
|
722
755
|
|
|
@@ -804,13 +837,17 @@ async function logs(args: string[]): Promise<void> {
|
|
|
804
837
|
let runDir = options.runDir;
|
|
805
838
|
if (!runDir || runDir === 'latest') {
|
|
806
839
|
runDir = findLatestRunDir(config.logsDir) || undefined;
|
|
807
|
-
if (!runDir) {
|
|
808
|
-
throw new Error('No run directories found');
|
|
809
|
-
}
|
|
810
840
|
}
|
|
811
841
|
|
|
812
|
-
if (!fs.existsSync(runDir)) {
|
|
813
|
-
|
|
842
|
+
if (!runDir || !fs.existsSync(runDir)) {
|
|
843
|
+
console.error('No run found');
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Handle interactive mode
|
|
848
|
+
if (options.interactive) {
|
|
849
|
+
await startLogViewer(runDir);
|
|
850
|
+
return;
|
|
814
851
|
}
|
|
815
852
|
|
|
816
853
|
// Handle --all option (view all lanes merged)
|