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.
@@ -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
- const result = (0, child_process_1.execSync)(`ssh genbox-${genboxName} 'curl -s "http://127.0.0.1:47191/api/storage/events?limit=${limit}" 2>/dev/null || curl -s "http://127.0.0.1:47192/api/storage/events?limit=${limit}" 2>/dev/null' 2>/dev/null`, { encoding: 'utf8', timeout: 10000 });
110
- const data = JSON.parse(result);
111
- return data.events || [];
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
- .replace(/[\x00-\x09\x0B\x0C\x0E-\x1F]/g, ''); // Control chars except \n and \r
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
- 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
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
- // 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');
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 in a box
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
- 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('…');
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
- console.log(chalk_1.default.dim('╰' + '─'.repeat(boxWidth - 2) + '╯'));
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
- console.log(chalk_1.default.dim('\nStopped following.\n'));
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 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'));
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, 1000));
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 current output and exit
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 the daemon may not be running.'));
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);