genbox 1.0.225 → 1.0.226
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/show.js +301 -57
- package/dist/commands/session/watch.js +1020 -30
- package/package.json +1 -1
|
@@ -103,17 +103,120 @@ fi
|
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Query genbox daemon for events
|
|
106
|
+
* First finds the matching session by provider (from CLI session name prefix),
|
|
107
|
+
* then queries events using the daemon's internal session UUID
|
|
106
108
|
*/
|
|
107
|
-
async function queryGenboxDaemonEvents(genboxName, limit = 50) {
|
|
109
|
+
async function queryGenboxDaemonEvents(genboxName, cliSessionName, limit = 50) {
|
|
108
110
|
try {
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
111
|
+
// First get all sessions to find the UUID
|
|
112
|
+
const sessionsResult = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} 'curl -s http://127.0.0.1:47191/api/storage/sessions 2>/dev/null || curl -s http://127.0.0.1:47192/api/storage/sessions 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 10000 });
|
|
113
|
+
const sessionsData = JSON.parse(sessionsResult);
|
|
114
|
+
const sessions = sessionsData.sessions || [];
|
|
115
|
+
if (sessions.length === 0) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
// Extract provider from CLI session name (e.g., "claude-mjy6oh8l" -> "claude")
|
|
119
|
+
const provider = cliSessionName.split('-')[0];
|
|
120
|
+
// Find matching session by provider, preferring the most recent active one
|
|
121
|
+
let matchingSession = sessions
|
|
122
|
+
.filter((s) => s.provider === provider)
|
|
123
|
+
.sort((a, b) => (b.lastActivityAt || 0) - (a.lastActivityAt || 0))[0];
|
|
124
|
+
// Fallback to most recent session if no provider match
|
|
125
|
+
if (!matchingSession && sessions.length > 0) {
|
|
126
|
+
matchingSession = sessions
|
|
127
|
+
.sort((a, b) => (b.lastActivityAt || 0) - (a.lastActivityAt || 0))[0];
|
|
128
|
+
}
|
|
129
|
+
if (!matchingSession) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
// Now query events using the daemon's session UUID
|
|
133
|
+
const eventsResult = (0, child_process_1.execSync)(`ssh -o ConnectTimeout=5 -o BatchMode=yes genbox-${genboxName} 'curl -s "http://127.0.0.1:47191/api/storage/events?sessionId=${matchingSession.id}&limit=${limit}" 2>/dev/null || curl -s "http://127.0.0.1:47192/api/storage/events?sessionId=${matchingSession.id}&limit=${limit}" 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 10000 });
|
|
134
|
+
const eventsData = JSON.parse(eventsResult);
|
|
135
|
+
return eventsData.events || [];
|
|
112
136
|
}
|
|
113
137
|
catch {
|
|
114
138
|
return [];
|
|
115
139
|
}
|
|
116
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Format events into a readable activity log
|
|
143
|
+
*/
|
|
144
|
+
function formatEventsAsActivityLog(events) {
|
|
145
|
+
if (!events || events.length === 0) {
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
const lines = [];
|
|
149
|
+
// Sort events by timestamp (oldest first for reading order)
|
|
150
|
+
const sortedEvents = [...events].sort((a, b) => {
|
|
151
|
+
const timeA = new Date(a.timestamp || a.createdAt || 0).getTime();
|
|
152
|
+
const timeB = new Date(b.timestamp || b.createdAt || 0).getTime();
|
|
153
|
+
return timeA - timeB;
|
|
154
|
+
});
|
|
155
|
+
for (const event of sortedEvents.slice(-30)) { // Last 30 events
|
|
156
|
+
const eventType = event.type || event.eventType || '';
|
|
157
|
+
const timestamp = event.timestamp || event.createdAt;
|
|
158
|
+
const timeStr = timestamp ? new Date(timestamp).toLocaleTimeString() : '';
|
|
159
|
+
switch (eventType) {
|
|
160
|
+
case 'before_prompt':
|
|
161
|
+
case 'user_prompt':
|
|
162
|
+
case 'prompt_submitted':
|
|
163
|
+
const prompt = event.data?.prompt || event.prompt || event.data?.message || '';
|
|
164
|
+
if (prompt) {
|
|
165
|
+
const truncated = prompt.substring(0, 80).replace(/\n/g, ' ');
|
|
166
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.cyan(' You: ') + truncated + (prompt.length > 80 ? '...' : ''));
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case 'after_prompt':
|
|
170
|
+
case 'response_completed':
|
|
171
|
+
case 'response':
|
|
172
|
+
// For response_completed, extract meaningful info if available
|
|
173
|
+
const response = event.data?.response || event.data?.message || event.response || '';
|
|
174
|
+
const stopReason = event.data?.stopReason || '';
|
|
175
|
+
if (response) {
|
|
176
|
+
const truncated = response.substring(0, 80).replace(/\n/g, ' ');
|
|
177
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.green(' Claude: ') + truncated + (response.length > 80 ? '...' : ''));
|
|
178
|
+
}
|
|
179
|
+
else if (stopReason === '' || stopReason === 'end_turn') {
|
|
180
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.green(' ✓ Response completed'));
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'before_tool':
|
|
184
|
+
case 'tool_started':
|
|
185
|
+
const toolName = event.data?.toolName || event.toolName || event.data?.tool_name || '';
|
|
186
|
+
if (toolName) {
|
|
187
|
+
// Shorten long MCP tool names
|
|
188
|
+
const shortName = toolName.replace(/^mcp__plugin_playwright_playwright__/, 'pw:');
|
|
189
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.yellow(' ⚙ Tool: ') + shortName);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
case 'after_tool':
|
|
193
|
+
case 'tool_completed':
|
|
194
|
+
const completedTool = event.data?.toolName || event.toolName || event.data?.tool_name || '';
|
|
195
|
+
const success = event.data?.success !== false;
|
|
196
|
+
if (completedTool) {
|
|
197
|
+
const shortName = completedTool.replace(/^mcp__plugin_playwright_playwright__/, 'pw:');
|
|
198
|
+
const statusIcon = success ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
|
|
199
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + ` ${statusIcon} ${shortName} completed`);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case 'notification':
|
|
203
|
+
const message = event.data?.message || '';
|
|
204
|
+
if (message) {
|
|
205
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.blue(' ℹ ') + message);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
case 'session_start':
|
|
209
|
+
case 'session_started':
|
|
210
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.blue(' ▶ Session started'));
|
|
211
|
+
break;
|
|
212
|
+
case 'session_end':
|
|
213
|
+
case 'session_ended':
|
|
214
|
+
lines.push(chalk_1.default.dim(`[${timeStr}]`) + chalk_1.default.red(' ■ Session ended'));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
117
220
|
/**
|
|
118
221
|
* Find session by name or ID
|
|
119
222
|
*/
|
|
@@ -228,97 +331,235 @@ function calculateCacheHitRate(cacheRead, totalInput) {
|
|
|
228
331
|
function stripAnsi(str) {
|
|
229
332
|
// eslint-disable-next-line no-control-regex
|
|
230
333
|
return str
|
|
334
|
+
// Proper ANSI sequences (with ESC character)
|
|
231
335
|
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '') // Standard ANSI sequences
|
|
232
336
|
.replace(/\x1B\[[\d;]*[A-Za-z]/g, '') // CSI sequences like [38;2;...m
|
|
233
337
|
.replace(/\x1B\[\??\d+[hl]/g, '') // Mode sequences like [?25h
|
|
234
|
-
|
|
338
|
+
// Broken ANSI sequences (missing ESC char, common from dtach capture)
|
|
339
|
+
.replace(/\[([0-9]+;)*[0-9]*m/g, '') // SGR codes like [2m, [38;2;153;153;153m
|
|
340
|
+
.replace(/\[\?[0-9]+[hl]/g, '') // Mode codes like [?25h
|
|
341
|
+
.replace(/\[[0-9]*[ABCDJKH]/g, '') // Cursor movement like [1A, [2K, [H
|
|
342
|
+
.replace(/\[[0-9]*G/g, '') // Column positioning
|
|
343
|
+
// Control chars except \n and \r
|
|
344
|
+
.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F]/g, '')
|
|
345
|
+
.replace(/\x1B/g, ''); // Leftover ESC characters
|
|
235
346
|
}
|
|
236
347
|
/**
|
|
237
348
|
* Clean terminal output for display (remove cursor movement, preserve colors)
|
|
349
|
+
*
|
|
350
|
+
* Terminal captures include screen-clear and line-erase sequences that would
|
|
351
|
+
* result in empty output if processed literally. We extract meaningful content
|
|
352
|
+
* by finding lines with actual visible text.
|
|
238
353
|
*/
|
|
239
354
|
function cleanTerminalOutput(output) {
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
355
|
+
// Remove script command header/footer first
|
|
356
|
+
let cleaned = output
|
|
249
357
|
.replace(/Script started on.*\n?/g, '')
|
|
250
|
-
.replace(/Script done on.*\n?/g, '')
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
358
|
+
.replace(/Script done on.*\n?/g, '');
|
|
359
|
+
// The dtach/script capture often loses the ESC character, leaving raw codes like [2m or [38;2;...
|
|
360
|
+
// We need to handle both proper ANSI sequences (\x1B[...) and broken ones ([...)
|
|
361
|
+
// Find the last occurrence of screen clear - check both proper and broken formats
|
|
362
|
+
const lastClearIndex = Math.max(cleaned.lastIndexOf('\x1B[H\x1B[J'), cleaned.lastIndexOf('\x1B[2J'), cleaned.lastIndexOf('[H[J'), cleaned.lastIndexOf('[2J'));
|
|
363
|
+
if (lastClearIndex > 0) {
|
|
364
|
+
// Take content after the last screen clear
|
|
365
|
+
cleaned = cleaned.substring(lastClearIndex);
|
|
366
|
+
}
|
|
367
|
+
// Strip ALL ANSI sequences - both proper (\x1B[...) and broken ([...)
|
|
368
|
+
// This is more aggressive but ensures clean output
|
|
369
|
+
cleaned = cleaned
|
|
370
|
+
// Proper ANSI CSI sequences
|
|
371
|
+
.replace(/\x1B\[[0-9;]*[A-Za-z]/g, '')
|
|
372
|
+
.replace(/\x1B\[\?[0-9;]+[hl]/g, '')
|
|
373
|
+
.replace(/\x1B\][^\x07]*\x07/g, '') // OSC sequences
|
|
374
|
+
.replace(/\x1B[()][AB012]/g, '') // Character set sequences
|
|
375
|
+
.replace(/\x1B[=>]/g, '') // Keypad modes
|
|
376
|
+
// Broken ANSI sequences (missing ESC character) - very common from dtach capture
|
|
377
|
+
.replace(/\[([0-9]+;)*[0-9]*m/g, '') // SGR codes like [2m, [38;2;153;153;153m
|
|
378
|
+
.replace(/\[\?[0-9]+[hl]/g, '') // Mode codes like [?25h
|
|
379
|
+
.replace(/\[[0-9]*[ABCDJKH]/g, '') // Cursor movement like [1A, [2K, [H
|
|
380
|
+
.replace(/\[[0-9]*G/g, '') // Column positioning
|
|
381
|
+
// Control characters
|
|
382
|
+
.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F]/g, '')
|
|
383
|
+
.replace(/\x1B/g, ''); // Leftover ESC characters
|
|
384
|
+
// Split into lines and filter
|
|
385
|
+
const lines = cleaned.split('\n');
|
|
386
|
+
const resultLines = [];
|
|
387
|
+
for (const line of lines) {
|
|
388
|
+
const trimmed = line.trim();
|
|
389
|
+
// Keep lines that have actual visible content (not just dashes/decoration)
|
|
390
|
+
if (trimmed.length > 0 && !/^[-─═]+$/.test(trimmed)) {
|
|
391
|
+
resultLines.push(line);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// If we got nothing meaningful, try extracting any recognizable text
|
|
395
|
+
if (resultLines.length === 0) {
|
|
396
|
+
const words = cleaned.match(/[a-zA-Z][a-zA-Z0-9\s]{2,}/g);
|
|
397
|
+
if (words && words.length > 0) {
|
|
398
|
+
return words.join(' ').substring(0, 200);
|
|
399
|
+
}
|
|
400
|
+
return '(no displayable content)';
|
|
401
|
+
}
|
|
402
|
+
return resultLines.join('\n');
|
|
260
403
|
}
|
|
261
404
|
/**
|
|
262
|
-
* Render terminal output
|
|
405
|
+
* Render terminal output - display raw terminal content with colors preserved
|
|
406
|
+
* This matches the Claude Code display style with full-width output
|
|
263
407
|
*/
|
|
264
408
|
function renderTerminalOutput(output, maxLines = 30) {
|
|
265
409
|
const termWidth = process.stdout.columns || 100;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
410
|
+
// Remove script headers
|
|
411
|
+
let display = output
|
|
412
|
+
.replace(/Script started on.*\n?/g, '')
|
|
413
|
+
.replace(/Script done on.*\n?/g, '');
|
|
414
|
+
// Fix broken ANSI codes (ESC character lost during dtach capture)
|
|
415
|
+
// Convert [32m to \x1B[32m (add back the ESC character)
|
|
416
|
+
// Handle various SGR patterns: [0m, [1m, [22m, [38;2;r;g;bm, [7m, [27m, etc.
|
|
417
|
+
display = display.replace(/(?<!\x1B)\[(\d+(?:;\d+)*)?m/g, '\x1B[$1m');
|
|
418
|
+
// Also fix broken CSI sequences for bold, dim, etc.
|
|
419
|
+
// [1m (bold), [2m (dim), [7m (reverse), [22m (normal), [27m (no reverse)
|
|
420
|
+
display = display.replace(/(?<!\x1B)\[([0-9]+)m/g, '\x1B[$1m');
|
|
421
|
+
// Find the last complete screen frame (after last clear or cursor home)
|
|
422
|
+
const clearPatterns = [
|
|
423
|
+
'\x1B[H\x1B[J', // Home + Clear
|
|
424
|
+
'\x1B[2J', // Clear screen
|
|
425
|
+
'\x1B[H\x1B[2J', // Home + Clear all
|
|
426
|
+
];
|
|
427
|
+
let lastClearIndex = -1;
|
|
428
|
+
for (const pattern of clearPatterns) {
|
|
429
|
+
const idx = display.lastIndexOf(pattern);
|
|
430
|
+
if (idx > lastClearIndex) {
|
|
431
|
+
lastClearIndex = idx + pattern.length;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// If we found a clear, start from there; otherwise use all content
|
|
435
|
+
let lastFrame = lastClearIndex > 0 ? display.substring(lastClearIndex) : display;
|
|
436
|
+
// Remove control sequences that cause display issues, but KEEP color codes
|
|
437
|
+
lastFrame = lastFrame
|
|
438
|
+
// Remove cursor visibility commands
|
|
439
|
+
.replace(/\x1B\[\?25[hl]/g, '')
|
|
440
|
+
.replace(/\[\?25[hl]/g, '')
|
|
441
|
+
// Remove alternate screen buffer commands
|
|
442
|
+
.replace(/\x1B\[\?1049[hl]/g, '')
|
|
443
|
+
.replace(/\[\?1049[hl]/g, '')
|
|
444
|
+
// Remove other private mode commands
|
|
445
|
+
.replace(/\x1B\[\?\d+[hl]/g, '')
|
|
446
|
+
.replace(/\[\?\d+[hl]/g, '')
|
|
447
|
+
// Remove cursor movement
|
|
448
|
+
.replace(/\x1B\[\d*G/g, '') // Cursor to column
|
|
449
|
+
.replace(/\[\d*G/g, '')
|
|
450
|
+
.replace(/\x1B\[\d*A/g, '') // Cursor up
|
|
451
|
+
.replace(/\[\d*A/g, '')
|
|
452
|
+
.replace(/\x1B\[\d*B/g, '') // Cursor down
|
|
453
|
+
.replace(/\[\d*B/g, '')
|
|
454
|
+
.replace(/\x1B\[\d*C/g, '') // Cursor forward
|
|
455
|
+
.replace(/\[\d*C/g, '')
|
|
456
|
+
.replace(/\x1B\[\d*D/g, '') // Cursor back
|
|
457
|
+
.replace(/\[\d*D/g, '')
|
|
458
|
+
.replace(/\x1B\[\d*H/g, '') // Cursor position
|
|
459
|
+
.replace(/\[\d*H/g, '')
|
|
460
|
+
.replace(/\x1B\[\d*;\d*H/g, '') // Cursor position with row;col
|
|
461
|
+
.replace(/\[\d*;\d*H/g, '')
|
|
462
|
+
// Remove line clearing
|
|
463
|
+
.replace(/\x1B\[\d*K/g, '')
|
|
464
|
+
.replace(/\[\d*K/g, '')
|
|
465
|
+
// Remove screen clearing
|
|
466
|
+
.replace(/\x1B\[\d*J/g, '')
|
|
467
|
+
.replace(/\[\d*J/g, '')
|
|
468
|
+
// Clean up carriage returns and normalize newlines
|
|
469
|
+
.replace(/\r\n/g, '\n')
|
|
470
|
+
.replace(/\r/g, '\n')
|
|
471
|
+
// Collapse multiple newlines
|
|
472
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
473
|
+
// Split into lines and filter out empty ones
|
|
474
|
+
const rawLines = lastFrame.split('\n')
|
|
475
|
+
.filter(l => l.trim().length > 0);
|
|
476
|
+
// Deduplicate similar lines (spinner frames show same content with different spinner chars)
|
|
477
|
+
const lines = [];
|
|
478
|
+
const seenSignatures = new Set();
|
|
479
|
+
for (let i = rawLines.length - 1; i >= 0 && lines.length < maxLines; i--) {
|
|
480
|
+
const line = rawLines[i];
|
|
481
|
+
// Create a signature by removing spinner characters and ANSI codes
|
|
482
|
+
const sig = line
|
|
483
|
+
.replace(/[✢✶✻·*⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/g, '') // spinner chars
|
|
484
|
+
.replace(/\x1B\[[0-9;]*m/g, '') // ANSI color codes
|
|
485
|
+
.trim()
|
|
486
|
+
.substring(0, 60);
|
|
487
|
+
// For short signatures (likely just spinner), always add
|
|
488
|
+
// For longer signatures, dedupe
|
|
489
|
+
if (sig.length <= 10 || !seenSignatures.has(sig)) {
|
|
490
|
+
lines.unshift(line);
|
|
491
|
+
if (sig.length > 10) {
|
|
492
|
+
seenSignatures.add(sig);
|
|
493
|
+
}
|
|
279
494
|
}
|
|
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
495
|
}
|
|
284
|
-
|
|
496
|
+
// Output with full width, no left padding
|
|
497
|
+
console.log(chalk_1.default.dim('─'.repeat(termWidth - 1)));
|
|
498
|
+
if (lines.length === 0) {
|
|
499
|
+
console.log(chalk_1.default.dim(' (no displayable content)'));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
for (const line of lines) {
|
|
503
|
+
// Ensure line doesn't exceed terminal width
|
|
504
|
+
const visibleLength = stripAnsi(line).length;
|
|
505
|
+
if (visibleLength > termWidth) {
|
|
506
|
+
console.log(line.substring(0, termWidth + (line.length - visibleLength)));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.log(line);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
console.log(chalk_1.default.dim('─'.repeat(termWidth - 1)));
|
|
285
514
|
}
|
|
286
515
|
/**
|
|
287
|
-
* Follow terminal output in real-time
|
|
516
|
+
* Follow terminal output in real-time - full screen like Claude Code
|
|
288
517
|
*/
|
|
289
518
|
async function followTerminalOutput(genboxName, sessionName, lines = 30) {
|
|
290
519
|
let lastOutput = '';
|
|
291
520
|
let running = true;
|
|
521
|
+
const termWidth = process.stdout.columns || 100;
|
|
522
|
+
const termHeight = process.stdout.rows || 24;
|
|
523
|
+
// Enter alternate screen buffer (like vim/htop)
|
|
524
|
+
process.stdout.write('\x1B[?1049h');
|
|
292
525
|
// Hide cursor
|
|
293
526
|
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
527
|
const cleanup = () => {
|
|
297
528
|
running = false;
|
|
298
529
|
// Show cursor
|
|
299
530
|
process.stdout.write('\x1B[?25h');
|
|
300
|
-
|
|
531
|
+
// Exit alternate screen buffer
|
|
532
|
+
process.stdout.write('\x1B[?1049l');
|
|
533
|
+
console.log(chalk_1.default.dim('Stopped following.\n'));
|
|
301
534
|
};
|
|
302
535
|
process.on('SIGINT', cleanup);
|
|
303
536
|
process.on('SIGTERM', cleanup);
|
|
304
537
|
while (running) {
|
|
305
538
|
try {
|
|
306
539
|
const output = await queryGenboxTerminalOutput(genboxName, sessionName, lines);
|
|
540
|
+
// Check if output changed
|
|
307
541
|
if (output && output !== lastOutput) {
|
|
308
542
|
lastOutput = output;
|
|
309
|
-
// Clear screen and
|
|
310
|
-
process.stdout.write('\x1B[
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
543
|
+
// Clear screen and move to home
|
|
544
|
+
process.stdout.write('\x1B[H\x1B[J');
|
|
545
|
+
// Status bar at top - minimal, full width
|
|
546
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
547
|
+
const leftPart = `● ${sessionName}`;
|
|
548
|
+
const rightPart = `${genboxName} • ${timestamp}`;
|
|
549
|
+
const padding = Math.max(0, termWidth - leftPart.length - rightPart.length - 2);
|
|
550
|
+
console.log(chalk_1.default.inverse(` ${leftPart}${' '.repeat(padding)}${rightPart} `));
|
|
551
|
+
// Calculate available lines for content
|
|
552
|
+
const contentLines = Math.max(10, termHeight - 3);
|
|
553
|
+
renderTerminalOutput(output, contentLines);
|
|
554
|
+
// Footer at bottom
|
|
555
|
+
const controls = chalk_1.default.dim('[Ctrl+C]') + ' Stop';
|
|
556
|
+
console.log(chalk_1.default.inverse(' ' + controls + ' '.repeat(Math.max(0, termWidth - stripAnsi(controls).length - 1))));
|
|
316
557
|
}
|
|
317
558
|
}
|
|
318
559
|
catch {
|
|
319
560
|
// Ignore errors, keep trying
|
|
320
561
|
}
|
|
321
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
562
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
322
563
|
}
|
|
323
564
|
}
|
|
324
565
|
/**
|
|
@@ -506,20 +747,23 @@ Examples:
|
|
|
506
747
|
await followTerminalOutput(found.remoteSession.genboxName, found.remoteSession.name, lines);
|
|
507
748
|
process.exit(0);
|
|
508
749
|
}
|
|
509
|
-
// One-shot mode: show
|
|
750
|
+
// One-shot mode: show full terminal output
|
|
510
751
|
console.log('');
|
|
511
752
|
console.log(chalk_1.default.bold.inverse(` ${found.remoteSession.name} `) + chalk_1.default.dim(` on ${found.remoteSession.genboxName}`));
|
|
512
753
|
console.log('');
|
|
754
|
+
// Fetch terminal output
|
|
513
755
|
const output = await queryGenboxTerminalOutput(found.remoteSession.genboxName, found.remoteSession.name, lines);
|
|
514
|
-
if (output) {
|
|
756
|
+
if (output && output.trim()) {
|
|
515
757
|
renderTerminalOutput(output, lines);
|
|
516
758
|
}
|
|
517
759
|
else {
|
|
518
760
|
console.log(chalk_1.default.yellow(' No terminal output available.'));
|
|
519
|
-
console.log(chalk_1.default.dim(' The session may be starting up or
|
|
761
|
+
console.log(chalk_1.default.dim(' The session may be starting up or waiting for input.'));
|
|
762
|
+
console.log('');
|
|
520
763
|
}
|
|
521
764
|
console.log('');
|
|
522
765
|
console.log(chalk_1.default.dim(' Tip: Use --follow (-f) to continuously stream output'));
|
|
766
|
+
console.log(chalk_1.default.dim(' Use gb session logs <session> to view activity log'));
|
|
523
767
|
console.log(chalk_1.default.dim(' Use gb claude attach to interact with the session'));
|
|
524
768
|
console.log('');
|
|
525
769
|
process.exit(0);
|