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.
@@ -107,7 +107,33 @@ function getPrivateSshKey() {
107
107
  * Format timestamp
108
108
  */
109
109
  function formatTime(date) {
110
- const d = typeof date === 'string' ? new Date(date) : date;
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
- for (const evt of events.reverse()) {
416
- const icon = getEventIcon(evt.type);
417
- const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
418
- const eventLabel = (evt.type || '').replace(/_/g, ' ');
419
- let output = `${time} ${icon} ${eventLabel}`;
420
- if (evt.data) {
421
- if (evt.data.toolName) {
422
- output += chalk_1.default.cyan(` ${evt.data.toolName}`);
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 (evt.data.currentState) {
425
- output += chalk_1.default.yellow(` ${evt.data.currentState}`);
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
- console.log(output);
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
- for (const evt of events.slice(0, 10).reverse()) {
459
- const icon = getEventIcon(evt.type);
460
- const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
461
- const eventLabel = (evt.type || '').replace(/_/g, ' ');
462
- let output = ` ${time} ${icon} ${eventLabel}`;
463
- if (evt.data?.toolName)
464
- output += chalk_1.default.cyan(` ${evt.data.toolName}`);
465
- console.log(output);
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
- if (events.length > 10) {
468
- console.log(chalk_1.default.dim(` ... and ${events.length - 10} more events`));
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 # Detailed view
322
- gb session show abc123 # By ID prefix
323
- gb session show swift-fox --json # JSON output
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 ${provider} attach ${found.remoteSession.name}`) + chalk_1.default.dim(' # Attach to session'));
411
- console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session logs ${found.remoteSession.name} -f`) + chalk_1.default.dim(' # Follow logs'));
412
- console.log(chalk_1.default.dim(' ') + chalk_1.default.cyan(`gb session send ${found.remoteSession.name}`) + chalk_1.default.dim(' # Send prompt'));
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
- '[⏎] Details',
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 detail view
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.224",
3
+ "version": "1.0.225",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {