genbox 1.0.224 → 1.0.225
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/dist/commands/session/logs.js +224 -25
- package/dist/commands/session/show.js +200 -6
- package/dist/commands/session/watch.js +19 -5
- package/package.json +1 -1
|
@@ -107,7 +107,33 @@ function getPrivateSshKey() {
|
|
|
107
107
|
* Format timestamp
|
|
108
108
|
*/
|
|
109
109
|
function formatTime(date) {
|
|
110
|
-
|
|
110
|
+
if (date === undefined || date === null) {
|
|
111
|
+
return '--:--:--';
|
|
112
|
+
}
|
|
113
|
+
let d;
|
|
114
|
+
if (date instanceof Date) {
|
|
115
|
+
d = date;
|
|
116
|
+
}
|
|
117
|
+
else if (typeof date === 'number') {
|
|
118
|
+
d = new Date(date);
|
|
119
|
+
}
|
|
120
|
+
else if (typeof date === 'string') {
|
|
121
|
+
d = new Date(date);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Handle MongoDB-style objects like { $date: "..." }
|
|
125
|
+
const dateObj = date;
|
|
126
|
+
if (dateObj.$date) {
|
|
127
|
+
d = new Date(dateObj.$date);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
return '--:--:--';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Check for invalid date
|
|
134
|
+
if (isNaN(d.getTime())) {
|
|
135
|
+
return '--:--:--';
|
|
136
|
+
}
|
|
111
137
|
return d.toLocaleTimeString('en-US', { hour12: false });
|
|
112
138
|
}
|
|
113
139
|
/**
|
|
@@ -131,6 +157,34 @@ async function findSession(nameOrId) {
|
|
|
131
157
|
return { remoteSession: remote };
|
|
132
158
|
}
|
|
133
159
|
}
|
|
160
|
+
// Fallback: If session not found via SSH scan (which can timeout),
|
|
161
|
+
// try checking each running genbox directly via daemon query
|
|
162
|
+
if (result.cloudGenboxes && result.cloudGenboxes.length > 0) {
|
|
163
|
+
for (const genbox of result.cloudGenboxes) {
|
|
164
|
+
if (genbox.status !== 'running')
|
|
165
|
+
continue;
|
|
166
|
+
try {
|
|
167
|
+
// Try to find the session on this genbox
|
|
168
|
+
const stdout = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=3 -o BatchMode=yes genbox-${genbox.name} 'ls -1 /home/dev/.genbox/sockets/*.sock 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
|
|
169
|
+
const sockets = stdout.trim().split('\n').filter(l => l.includes('.sock'));
|
|
170
|
+
for (const socketPath of sockets) {
|
|
171
|
+
const sessionName = socketPath.split('/').pop()?.replace('.sock', '') || '';
|
|
172
|
+
if (sessionName === nameOrId || sessionName.includes(nameOrId)) {
|
|
173
|
+
return {
|
|
174
|
+
remoteSession: {
|
|
175
|
+
name: sessionName,
|
|
176
|
+
genboxName: genbox.name,
|
|
177
|
+
ipAddress: genbox.ipAddress || '',
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// SSH failed for this genbox, continue to next
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
134
188
|
return null;
|
|
135
189
|
}
|
|
136
190
|
/**
|
|
@@ -403,7 +457,7 @@ Examples:
|
|
|
403
457
|
process.exit(0);
|
|
404
458
|
}
|
|
405
459
|
if (options.events) {
|
|
406
|
-
// Show events from daemon
|
|
460
|
+
// Show events from daemon - inline view combining start/complete
|
|
407
461
|
console.log(chalk_1.default.bold(`\nSession Events: ${found.remoteSession.name}`));
|
|
408
462
|
console.log(chalk_1.default.dim(`Running on: ${found.remoteSession.genboxName}`));
|
|
409
463
|
console.log(chalk_1.default.dim('─'.repeat(70)));
|
|
@@ -412,20 +466,110 @@ Examples:
|
|
|
412
466
|
console.log(chalk_1.default.dim('Events will appear as Claude/Gemini processes prompts.\n'));
|
|
413
467
|
}
|
|
414
468
|
else {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
469
|
+
// Build inline view by pairing tool_started with tool_completed
|
|
470
|
+
const toolStarts = new Map();
|
|
471
|
+
const displayEvents = [];
|
|
472
|
+
// Process events in chronological order (oldest first)
|
|
473
|
+
const chronologicalEvents = [...events].reverse();
|
|
474
|
+
for (const evt of chronologicalEvents) {
|
|
475
|
+
const eventType = evt.type || '';
|
|
476
|
+
const toolName = evt.data?.toolName || '';
|
|
477
|
+
const toolId = evt.data?.toolId || toolName;
|
|
478
|
+
if (eventType === 'tool_started') {
|
|
479
|
+
toolStarts.set(toolId, { time: evt.createdAt, data: evt.data });
|
|
480
|
+
}
|
|
481
|
+
else if (eventType === 'tool_completed' || eventType === 'tool_error') {
|
|
482
|
+
const startInfo = toolStarts.get(toolId);
|
|
483
|
+
const startTime = startInfo?.time || evt.createdAt;
|
|
484
|
+
const startData = startInfo?.data || {};
|
|
485
|
+
// Calculate duration
|
|
486
|
+
const startMs = new Date(startTime).getTime();
|
|
487
|
+
const endMs = new Date(evt.createdAt).getTime();
|
|
488
|
+
const durationMs = endMs - startMs;
|
|
489
|
+
const durationStr = durationMs < 1000 ? `${durationMs}ms` :
|
|
490
|
+
durationMs < 60000 ? `${(durationMs / 1000).toFixed(1)}s` :
|
|
491
|
+
`${Math.floor(durationMs / 60000)}m${Math.floor((durationMs % 60000) / 1000)}s`;
|
|
492
|
+
// Format tool name (shorten MCP tool names)
|
|
493
|
+
let displayName = toolName;
|
|
494
|
+
if (toolName.startsWith('mcp__')) {
|
|
495
|
+
// mcp__plugin_playwright_playwright__browser_click -> browser_click
|
|
496
|
+
const parts = toolName.split('__');
|
|
497
|
+
displayName = parts[parts.length - 1];
|
|
498
|
+
}
|
|
499
|
+
// Get tool details from toolInput (command, file path, etc.)
|
|
500
|
+
let details = '';
|
|
501
|
+
const toolInput = startData?.toolInput || evt.data?.toolInput || {};
|
|
502
|
+
if (toolInput.command) {
|
|
503
|
+
// For Bash - show the command (first line, truncated)
|
|
504
|
+
const cmdLine = toolInput.command.split('\n')[0].trim();
|
|
505
|
+
const cmd = cmdLine.length > 60 ? cmdLine.substring(0, 57) + '...' : cmdLine;
|
|
506
|
+
details = chalk_1.default.dim(`: ${cmd}`);
|
|
423
507
|
}
|
|
424
|
-
if (
|
|
425
|
-
|
|
508
|
+
else if (toolInput.file_path) {
|
|
509
|
+
// For Read/Edit/Write - show file path
|
|
510
|
+
const filePath = toolInput.file_path.split('/').slice(-2).join('/'); // last 2 parts
|
|
511
|
+
details = chalk_1.default.dim(`: ${filePath}`);
|
|
426
512
|
}
|
|
513
|
+
else if (toolInput.element) {
|
|
514
|
+
// For browser_click - show element description
|
|
515
|
+
details = chalk_1.default.dim(`: ${toolInput.element}`);
|
|
516
|
+
}
|
|
517
|
+
else if (toolInput.url) {
|
|
518
|
+
// For browser_navigate - show URL
|
|
519
|
+
const url = toolInput.url.length > 50 ? toolInput.url.substring(0, 47) + '...' : toolInput.url;
|
|
520
|
+
details = chalk_1.default.dim(`: ${url}`);
|
|
521
|
+
}
|
|
522
|
+
else if (toolInput.pattern) {
|
|
523
|
+
// For Glob/Grep - show pattern
|
|
524
|
+
details = chalk_1.default.dim(`: ${toolInput.pattern}`);
|
|
525
|
+
}
|
|
526
|
+
else if (toolInput.description) {
|
|
527
|
+
// Fallback to description if available
|
|
528
|
+
const desc = toolInput.description.length > 40 ? toolInput.description.substring(0, 37) + '...' : toolInput.description;
|
|
529
|
+
details = chalk_1.default.dim(`: ${desc}`);
|
|
530
|
+
}
|
|
531
|
+
const icon = eventType === 'tool_error' ? chalk_1.default.red('✗') : chalk_1.default.green('✓');
|
|
532
|
+
const time = chalk_1.default.dim(`[${formatTime(startTime)}]`);
|
|
533
|
+
const duration = chalk_1.default.yellow(`(${durationStr})`);
|
|
534
|
+
displayEvents.push({
|
|
535
|
+
time: startTime,
|
|
536
|
+
output: `${time} ${icon} ${chalk_1.default.cyan(displayName)}${details} ${duration}`
|
|
537
|
+
});
|
|
538
|
+
toolStarts.delete(toolId);
|
|
539
|
+
}
|
|
540
|
+
else if (eventType === 'response_completed') {
|
|
541
|
+
const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
|
|
542
|
+
displayEvents.push({
|
|
543
|
+
time: evt.createdAt,
|
|
544
|
+
output: `${time} ${chalk_1.default.green('✅')} ${chalk_1.default.bold('Response completed')}`
|
|
545
|
+
});
|
|
427
546
|
}
|
|
428
|
-
|
|
547
|
+
else if (eventType === 'prompt_submitted') {
|
|
548
|
+
const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
|
|
549
|
+
const preview = evt.data?.preview ? chalk_1.default.dim(`: "${evt.data.preview.substring(0, 40)}..."`) : '';
|
|
550
|
+
displayEvents.push({
|
|
551
|
+
time: evt.createdAt,
|
|
552
|
+
output: `${time} ${chalk_1.default.blue('💬')} ${chalk_1.default.bold('User prompt')}${preview}`
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Show any tools still running (started but not completed)
|
|
557
|
+
for (const [toolId, info] of toolStarts) {
|
|
558
|
+
const time = chalk_1.default.dim(`[${formatTime(info.time)}]`);
|
|
559
|
+
let displayName = info.data?.toolName || toolId;
|
|
560
|
+
if (displayName.startsWith('mcp__')) {
|
|
561
|
+
const parts = displayName.split('__');
|
|
562
|
+
displayName = parts[parts.length - 1];
|
|
563
|
+
}
|
|
564
|
+
displayEvents.push({
|
|
565
|
+
time: info.time,
|
|
566
|
+
output: `${time} ${chalk_1.default.yellow('⏳')} ${chalk_1.default.cyan(displayName)} ${chalk_1.default.yellow('(running...')}`
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
// Sort by time and display
|
|
570
|
+
displayEvents.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
571
|
+
for (const evt of displayEvents) {
|
|
572
|
+
console.log(evt.output);
|
|
429
573
|
}
|
|
430
574
|
}
|
|
431
575
|
console.log('');
|
|
@@ -451,21 +595,76 @@ Examples:
|
|
|
451
595
|
if (daemonSession.estimatedCostUsd) {
|
|
452
596
|
console.log(` Cost: $${daemonSession.estimatedCostUsd.toFixed(2)}`);
|
|
453
597
|
}
|
|
454
|
-
// Show recent events
|
|
598
|
+
// Show recent events - inline format
|
|
455
599
|
if (events.length > 0) {
|
|
456
|
-
console.log(chalk_1.default.dim('\n─'.repeat(70)));
|
|
600
|
+
console.log(chalk_1.default.dim('\n' + '─'.repeat(70)));
|
|
457
601
|
console.log(chalk_1.default.dim('Recent activity:'));
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
602
|
+
// Build inline view for recent events
|
|
603
|
+
const toolStarts = new Map();
|
|
604
|
+
const recentDisplay = [];
|
|
605
|
+
const recentEvents = events.slice(0, 20); // Get more events to pair
|
|
606
|
+
for (const evt of [...recentEvents].reverse()) {
|
|
607
|
+
const eventType = evt.type || '';
|
|
608
|
+
const toolName = evt.data?.toolName || '';
|
|
609
|
+
const toolId = evt.data?.toolId || toolName;
|
|
610
|
+
if (eventType === 'tool_started') {
|
|
611
|
+
toolStarts.set(toolId, { time: evt.createdAt, data: evt.data });
|
|
612
|
+
}
|
|
613
|
+
else if (eventType === 'tool_completed' || eventType === 'tool_error') {
|
|
614
|
+
const startInfo = toolStarts.get(toolId);
|
|
615
|
+
const startTime = startInfo?.time || evt.createdAt;
|
|
616
|
+
const startMs = new Date(startTime).getTime();
|
|
617
|
+
const endMs = new Date(evt.createdAt).getTime();
|
|
618
|
+
const durationMs = endMs - startMs;
|
|
619
|
+
const durationStr = durationMs < 1000 ? `${durationMs}ms` :
|
|
620
|
+
durationMs < 60000 ? `${(durationMs / 1000).toFixed(1)}s` :
|
|
621
|
+
`${Math.floor(durationMs / 60000)}m${Math.floor((durationMs % 60000) / 1000)}s`;
|
|
622
|
+
let displayName = toolName;
|
|
623
|
+
if (toolName.startsWith('mcp__')) {
|
|
624
|
+
const parts = toolName.split('__');
|
|
625
|
+
displayName = parts[parts.length - 1];
|
|
626
|
+
}
|
|
627
|
+
// Get tool details from toolInput
|
|
628
|
+
let details = '';
|
|
629
|
+
const toolInput = startInfo?.data?.toolInput || evt.data?.toolInput || {};
|
|
630
|
+
if (toolInput.command) {
|
|
631
|
+
const cmdLine = toolInput.command.split('\n')[0].trim();
|
|
632
|
+
const cmd = cmdLine.length > 40 ? cmdLine.substring(0, 37) + '...' : cmdLine;
|
|
633
|
+
details = chalk_1.default.dim(`: ${cmd}`);
|
|
634
|
+
}
|
|
635
|
+
else if (toolInput.file_path) {
|
|
636
|
+
const filePath = toolInput.file_path.split('/').slice(-2).join('/');
|
|
637
|
+
details = chalk_1.default.dim(`: ${filePath}`);
|
|
638
|
+
}
|
|
639
|
+
else if (toolInput.element) {
|
|
640
|
+
details = chalk_1.default.dim(`: ${toolInput.element}`);
|
|
641
|
+
}
|
|
642
|
+
else if (toolInput.pattern) {
|
|
643
|
+
details = chalk_1.default.dim(`: ${toolInput.pattern}`);
|
|
644
|
+
}
|
|
645
|
+
const icon = eventType === 'tool_error' ? chalk_1.default.red('✗') : chalk_1.default.green('✓');
|
|
646
|
+
const time = chalk_1.default.dim(`[${formatTime(startTime)}]`);
|
|
647
|
+
recentDisplay.push({
|
|
648
|
+
time: startTime,
|
|
649
|
+
output: ` ${time} ${icon} ${chalk_1.default.cyan(displayName)}${details} ${chalk_1.default.yellow(`(${durationStr})`)}`
|
|
650
|
+
});
|
|
651
|
+
toolStarts.delete(toolId);
|
|
652
|
+
}
|
|
653
|
+
else if (eventType === 'response_completed') {
|
|
654
|
+
recentDisplay.push({
|
|
655
|
+
time: evt.createdAt,
|
|
656
|
+
output: ` ${chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`)} ${chalk_1.default.green('✅')} Response completed`
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
recentDisplay.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
661
|
+
const toShow = recentDisplay.slice(-8); // Show last 8
|
|
662
|
+
for (const evt of toShow) {
|
|
663
|
+
console.log(evt.output);
|
|
466
664
|
}
|
|
467
|
-
|
|
468
|
-
|
|
665
|
+
const totalTools = events.filter(e => e.type === 'tool_completed').length;
|
|
666
|
+
if (totalTools > 8) {
|
|
667
|
+
console.log(chalk_1.default.dim(` ... and ${totalTools - 8} more tool calls`));
|
|
469
668
|
}
|
|
470
669
|
}
|
|
471
670
|
console.log('');
|
|
@@ -38,6 +38,69 @@ async function queryGenboxDaemon(genboxName, sessionName) {
|
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Query genbox daemon for terminal output via SSH
|
|
43
|
+
* Fetches from /api/sessions/{sessionId}/output endpoint or reads log files directly
|
|
44
|
+
*/
|
|
45
|
+
async function queryGenboxTerminalOutput(genboxName, sessionId, lines = 50) {
|
|
46
|
+
// Strategy 1: Try the daemon API endpoint first
|
|
47
|
+
try {
|
|
48
|
+
const result = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} 'curl -s "http://127.0.0.1:47191/api/sessions/${sessionId}/output?lines=${lines}" 2>/dev/null || curl -s "http://127.0.0.1:47192/api/sessions/${sessionId}/output?lines=${lines}" 2>/dev/null || curl -s "http://127.0.0.1:47193/api/sessions/${sessionId}/output?lines=${lines}" 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 15000 });
|
|
49
|
+
const data = JSON.parse(result);
|
|
50
|
+
if (data.output) {
|
|
51
|
+
return data.output;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Continue to fallbacks
|
|
56
|
+
}
|
|
57
|
+
// Strategy 2: Try reading the log file directly (for daemon-created sessions)
|
|
58
|
+
try {
|
|
59
|
+
const result = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} 'tail -n ${lines} /home/dev/.genbox/sockets/${sessionId}.log 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 15000 });
|
|
60
|
+
if (result && result.trim()) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Continue to next fallback
|
|
66
|
+
}
|
|
67
|
+
// Strategy 3: Use script+dtach to capture a snapshot of the terminal
|
|
68
|
+
// This works for sessions that weren't started with output logging
|
|
69
|
+
try {
|
|
70
|
+
const captureScript = `
|
|
71
|
+
SOCKET="/home/dev/.genbox/sockets/${sessionId}.sock"
|
|
72
|
+
if [ -S "$SOCKET" ]; then
|
|
73
|
+
# Create temp file for capture
|
|
74
|
+
CAPTURE="/tmp/dtach-capture-$$.txt"
|
|
75
|
+
# Use expect/script to attach briefly and capture output
|
|
76
|
+
timeout 2s script -q -c "dtach -a $SOCKET -r none 2>/dev/null" "$CAPTURE" >/dev/null 2>&1 || true
|
|
77
|
+
if [ -f "$CAPTURE" ]; then
|
|
78
|
+
tail -n ${lines} "$CAPTURE" 2>/dev/null
|
|
79
|
+
rm -f "$CAPTURE"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
`;
|
|
83
|
+
const result = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} '${captureScript}' 2>/dev/null`, { encoding: 'utf8', timeout: 20000 });
|
|
84
|
+
if (result && result.trim()) {
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Continue to next fallback
|
|
90
|
+
}
|
|
91
|
+
// Strategy 4: Fallback - try to find any log files for the session
|
|
92
|
+
try {
|
|
93
|
+
const provider = sessionId.split('-')[0] || 'claude';
|
|
94
|
+
const result = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} 'tail -n ${lines} /home/dev/.genbox/sockets/${provider}-*.log 2>/dev/null || tail -n ${lines} /home/dev/.genbox/sockets/*.log 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 15000 });
|
|
95
|
+
if (result && result.trim()) {
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// All strategies failed
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
41
104
|
/**
|
|
42
105
|
* Query genbox daemon for events
|
|
43
106
|
*/
|
|
@@ -159,6 +222,105 @@ function calculateCacheHitRate(cacheRead, totalInput) {
|
|
|
159
222
|
const rate = (cacheRead / totalInput) * 100;
|
|
160
223
|
return `${rate.toFixed(0)}%`;
|
|
161
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Strip ANSI codes for width calculation and display cleaning
|
|
227
|
+
*/
|
|
228
|
+
function stripAnsi(str) {
|
|
229
|
+
// eslint-disable-next-line no-control-regex
|
|
230
|
+
return str
|
|
231
|
+
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '') // Standard ANSI sequences
|
|
232
|
+
.replace(/\x1B\[[\d;]*[A-Za-z]/g, '') // CSI sequences like [38;2;...m
|
|
233
|
+
.replace(/\x1B\[\??\d+[hl]/g, '') // Mode sequences like [?25h
|
|
234
|
+
.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F]/g, ''); // Control chars except \n and \r
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Clean terminal output for display (remove cursor movement, preserve colors)
|
|
238
|
+
*/
|
|
239
|
+
function cleanTerminalOutput(output) {
|
|
240
|
+
return output
|
|
241
|
+
// Remove cursor positioning sequences
|
|
242
|
+
.replace(/\x1B\[\d*[ABCD]/g, '') // Cursor movement (up/down/forward/back)
|
|
243
|
+
.replace(/\x1B\[\d*G/g, '') // Cursor horizontal absolute
|
|
244
|
+
.replace(/\x1B\[[\d;]*H/g, '') // Cursor position
|
|
245
|
+
.replace(/\x1B\[\d*[JK]/g, '') // Erase sequences (clear screen/line)
|
|
246
|
+
.replace(/\x1B\[\?25[hl]/g, '') // Show/hide cursor
|
|
247
|
+
.replace(/\x1B\[\?\d+[hl]/g, '') // Other private mode sequences
|
|
248
|
+
// Remove script header/footer
|
|
249
|
+
.replace(/Script started on.*\n?/g, '')
|
|
250
|
+
.replace(/Script done on.*\n?/g, '')
|
|
251
|
+
// Clean up empty lines and duplicates
|
|
252
|
+
.split('\n')
|
|
253
|
+
.filter((line, index, array) => {
|
|
254
|
+
// Remove lines that are just whitespace
|
|
255
|
+
if (!line.trim())
|
|
256
|
+
return index === 0 || array[index - 1]?.trim();
|
|
257
|
+
return true;
|
|
258
|
+
})
|
|
259
|
+
.join('\n');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Render terminal output in a box
|
|
263
|
+
*/
|
|
264
|
+
function renderTerminalOutput(output, maxLines = 30) {
|
|
265
|
+
const termWidth = process.stdout.columns || 100;
|
|
266
|
+
const boxWidth = Math.min(termWidth - 4, 120);
|
|
267
|
+
const contentWidth = boxWidth - 4;
|
|
268
|
+
// Clean up the terminal output before rendering
|
|
269
|
+
const cleanedOutput = cleanTerminalOutput(output);
|
|
270
|
+
const lines = cleanedOutput.split('\n').slice(-maxLines);
|
|
271
|
+
console.log(chalk_1.default.dim('╭─ ' + chalk_1.default.bold('Terminal Output') + ' ' + '─'.repeat(Math.max(0, boxWidth - 20)) + '╮'));
|
|
272
|
+
for (const line of lines) {
|
|
273
|
+
// Preserve ANSI codes but truncate if too long
|
|
274
|
+
const stripped = stripAnsi(line);
|
|
275
|
+
let displayLine = line;
|
|
276
|
+
if (stripped.length > contentWidth) {
|
|
277
|
+
// Truncate the line, trying to keep ANSI codes intact
|
|
278
|
+
displayLine = line.substring(0, contentWidth + (line.length - stripped.length)) + chalk_1.default.dim('…');
|
|
279
|
+
}
|
|
280
|
+
// Pad to box width
|
|
281
|
+
const padding = ' '.repeat(Math.max(0, contentWidth - stripAnsi(displayLine).length));
|
|
282
|
+
console.log(chalk_1.default.dim('│ ') + displayLine + padding + chalk_1.default.dim(' │'));
|
|
283
|
+
}
|
|
284
|
+
console.log(chalk_1.default.dim('╰' + '─'.repeat(boxWidth - 2) + '╯'));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Follow terminal output in real-time
|
|
288
|
+
*/
|
|
289
|
+
async function followTerminalOutput(genboxName, sessionName, lines = 30) {
|
|
290
|
+
let lastOutput = '';
|
|
291
|
+
let running = true;
|
|
292
|
+
// Hide cursor
|
|
293
|
+
process.stdout.write('\x1B[?25l');
|
|
294
|
+
console.log(chalk_1.default.bold(`\nFollowing: ${sessionName}`));
|
|
295
|
+
console.log(chalk_1.default.dim('Press Ctrl+C to stop\n'));
|
|
296
|
+
const cleanup = () => {
|
|
297
|
+
running = false;
|
|
298
|
+
// Show cursor
|
|
299
|
+
process.stdout.write('\x1B[?25h');
|
|
300
|
+
console.log(chalk_1.default.dim('\nStopped following.\n'));
|
|
301
|
+
};
|
|
302
|
+
process.on('SIGINT', cleanup);
|
|
303
|
+
process.on('SIGTERM', cleanup);
|
|
304
|
+
while (running) {
|
|
305
|
+
try {
|
|
306
|
+
const output = await queryGenboxTerminalOutput(genboxName, sessionName, lines);
|
|
307
|
+
if (output && output !== lastOutput) {
|
|
308
|
+
lastOutput = output;
|
|
309
|
+
// Clear screen and render new output
|
|
310
|
+
process.stdout.write('\x1B[2J\x1B[H');
|
|
311
|
+
console.log(chalk_1.default.bold.inverse(` ${sessionName} `) + chalk_1.default.dim(` on ${genboxName} - ${new Date().toLocaleTimeString()}`));
|
|
312
|
+
console.log('');
|
|
313
|
+
renderTerminalOutput(output, lines);
|
|
314
|
+
console.log('');
|
|
315
|
+
console.log(chalk_1.default.dim(' Press Ctrl+C to stop'));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// Ignore errors, keep trying
|
|
320
|
+
}
|
|
321
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
162
324
|
/**
|
|
163
325
|
* Render boxed section
|
|
164
326
|
*/
|
|
@@ -316,11 +478,16 @@ exports.sessionShowCommand = new commander_1.Command('show')
|
|
|
316
478
|
.description('Show detailed information about a session')
|
|
317
479
|
.argument('<session>', 'Session name or ID')
|
|
318
480
|
.option('--json', 'Output as JSON')
|
|
481
|
+
.option('--live', 'Show live terminal output (like attach, but read-only)')
|
|
482
|
+
.option('-f, --follow', 'Continuously follow terminal output')
|
|
483
|
+
.option('-n, --lines <lines>', 'Number of lines to show (default: 30)')
|
|
319
484
|
.addHelpText('after', `
|
|
320
485
|
Examples:
|
|
321
|
-
gb session show claude-swift-fox
|
|
322
|
-
gb session show
|
|
323
|
-
gb session show swift-fox --
|
|
486
|
+
gb session show claude-swift-fox # Detailed view with metadata
|
|
487
|
+
gb session show swift-fox --live # Show current terminal output
|
|
488
|
+
gb session show swift-fox --live -f # Follow terminal output live
|
|
489
|
+
gb session show swift-fox --live -n 50 # Show last 50 lines
|
|
490
|
+
gb session show swift-fox --json # JSON output
|
|
324
491
|
`)
|
|
325
492
|
.action(async (sessionArg, options) => {
|
|
326
493
|
try {
|
|
@@ -331,6 +498,32 @@ Examples:
|
|
|
331
498
|
process.exit(1);
|
|
332
499
|
}
|
|
333
500
|
if (found.remoteSession) {
|
|
501
|
+
const lines = parseInt(options.lines || '30');
|
|
502
|
+
// Handle --live mode: show terminal output
|
|
503
|
+
if (options.live) {
|
|
504
|
+
if (options.follow) {
|
|
505
|
+
// Follow mode: continuously stream output
|
|
506
|
+
await followTerminalOutput(found.remoteSession.genboxName, found.remoteSession.name, lines);
|
|
507
|
+
process.exit(0);
|
|
508
|
+
}
|
|
509
|
+
// One-shot mode: show current output and exit
|
|
510
|
+
console.log('');
|
|
511
|
+
console.log(chalk_1.default.bold.inverse(` ${found.remoteSession.name} `) + chalk_1.default.dim(` on ${found.remoteSession.genboxName}`));
|
|
512
|
+
console.log('');
|
|
513
|
+
const output = await queryGenboxTerminalOutput(found.remoteSession.genboxName, found.remoteSession.name, lines);
|
|
514
|
+
if (output) {
|
|
515
|
+
renderTerminalOutput(output, lines);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
console.log(chalk_1.default.yellow(' No terminal output available.'));
|
|
519
|
+
console.log(chalk_1.default.dim(' The session may be starting up or the daemon may not be running.'));
|
|
520
|
+
}
|
|
521
|
+
console.log('');
|
|
522
|
+
console.log(chalk_1.default.dim(' Tip: Use --follow (-f) to continuously stream output'));
|
|
523
|
+
console.log(chalk_1.default.dim(' Use gb claude attach to interact with the session'));
|
|
524
|
+
console.log('');
|
|
525
|
+
process.exit(0);
|
|
526
|
+
}
|
|
334
527
|
// Remote session - query daemon for live data
|
|
335
528
|
const daemonSession = await queryGenboxDaemon(found.remoteSession.genboxName, found.remoteSession.name);
|
|
336
529
|
if (options.json) {
|
|
@@ -407,9 +600,10 @@ Examples:
|
|
|
407
600
|
console.log('');
|
|
408
601
|
console.log(chalk_1.default.dim(' Commands:'));
|
|
409
602
|
const provider = found.remoteSession.name.split('-')[0];
|
|
410
|
-
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb
|
|
411
|
-
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session
|
|
412
|
-
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb
|
|
603
|
+
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session show ${found.remoteSession.name} --live`) + chalk_1.default.dim(' # View terminal output'));
|
|
604
|
+
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session show ${found.remoteSession.name} --live -f`) + chalk_1.default.dim(' # Follow output live'));
|
|
605
|
+
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb ${provider} attach ${found.remoteSession.name}`) + chalk_1.default.dim(' # Attach to session'));
|
|
606
|
+
console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session send ${found.remoteSession.name}`) + chalk_1.default.dim(' # Send prompt'));
|
|
413
607
|
console.log('');
|
|
414
608
|
process.exit(0);
|
|
415
609
|
}
|
|
@@ -684,12 +684,11 @@ function renderFooter(termWidth) {
|
|
|
684
684
|
'[q] Quit',
|
|
685
685
|
'[r] Refresh',
|
|
686
686
|
'[↑↓] Navigate',
|
|
687
|
-
'[⏎]
|
|
687
|
+
'[⏎] Live',
|
|
688
|
+
'[d] Details',
|
|
688
689
|
'[a] Attach',
|
|
689
|
-
'[l] Logs',
|
|
690
690
|
'[s] Send',
|
|
691
691
|
'[/] Search',
|
|
692
|
-
'[f] Filter',
|
|
693
692
|
'[c] Clear',
|
|
694
693
|
];
|
|
695
694
|
console.log(chalk_1.default.dim(' ' + controls.join(' ')));
|
|
@@ -1026,7 +1025,21 @@ async function runTuiMode(options) {
|
|
|
1026
1025
|
cleanup();
|
|
1027
1026
|
process.exit(0);
|
|
1028
1027
|
}
|
|
1029
|
-
else if (key === '\r') { // Enter - show
|
|
1028
|
+
else if (key === '\r') { // Enter - show live terminal output
|
|
1029
|
+
const session = cachedSessions[selectedIndex];
|
|
1030
|
+
if (session) {
|
|
1031
|
+
cleanup();
|
|
1032
|
+
console.log(chalk_1.default.cyan(`\nShowing live terminal output for ${session.name}...`));
|
|
1033
|
+
try {
|
|
1034
|
+
(0, child_process_1.execSync)(`gb session show ${session.name} --live -f`, { stdio: 'inherit' });
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
// Session might have ended or user pressed Ctrl+C
|
|
1038
|
+
}
|
|
1039
|
+
process.exit(0);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
else if (key === 'd') { // 'd' for detail view (legacy behavior)
|
|
1030
1043
|
if (cachedSessions[selectedIndex]) {
|
|
1031
1044
|
viewMode = 'detail';
|
|
1032
1045
|
quickRender();
|
|
@@ -1291,8 +1304,9 @@ Examples:
|
|
|
1291
1304
|
|
|
1292
1305
|
TUI Controls:
|
|
1293
1306
|
↑/↓ Navigate sessions
|
|
1307
|
+
Enter View live terminal output (--live -f)
|
|
1308
|
+
d View session details
|
|
1294
1309
|
a Attach to selected session
|
|
1295
|
-
l View logs for selected session
|
|
1296
1310
|
s Send prompt to selected session
|
|
1297
1311
|
/ Search sessions
|
|
1298
1312
|
p Filter by provider
|