genbox 1.0.209 → 1.0.211

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.
@@ -444,7 +444,7 @@ async function startLocalMultipass(genbox) {
444
444
  * Create and attach to a session on a genbox
445
445
  */
446
446
  async function startSessionOnGenbox(provider, genbox, options = {}) {
447
- const { initialPrompt } = options;
447
+ const { initialPrompt, background = false } = options;
448
448
  let targetGenbox = genbox;
449
449
  let sshTarget;
450
450
  let sshPortArgs = [];
@@ -592,13 +592,32 @@ exec ${cliCommand}
592
592
  // Wait for session to start
593
593
  await new Promise(resolve => setTimeout(resolve, 1000));
594
594
  console.log(chalk_1.default.green(`Session created: ${sessionName}`));
595
- console.log(chalk_1.default.dim(`Image auto-upload: ${chalk_1.default.green('enabled')} - Paste local image paths, they'll be uploaded automatically`));
596
- console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
597
- // Attach to session using SSH proxy with image path interception
598
595
  // Extract IP address from sshTarget (format: dev@IP or dev@localhost)
599
596
  const attachIpAddress = sshTarget.split('@')[1];
600
- // Get port from sshPortArgs if it's a Docker container
601
597
  const sshPort = sshPortArgs.length >= 2 ? parseInt(sshPortArgs[1], 10) : undefined;
598
+ // Background mode: inject prompt and return immediately
599
+ if (background) {
600
+ if (initialPrompt) {
601
+ console.log(chalk_1.default.dim('Injecting prompt in background...'));
602
+ // Spawn background process to attach, inject prompt, then detach
603
+ const { injectPromptInBackground } = await Promise.resolve().then(() => __importStar(require('../utils/ssh-proxy')));
604
+ injectPromptInBackground({
605
+ ipAddress: attachIpAddress === 'localhost' ? '127.0.0.1' : attachIpAddress,
606
+ keyPath,
607
+ genboxName: targetGenbox.name,
608
+ socketPath,
609
+ user: 'dev',
610
+ port: sshPort,
611
+ prompt: initialPrompt,
612
+ });
613
+ }
614
+ console.log(chalk_1.default.dim(`\nSession running in background.`));
615
+ console.log(chalk_1.default.dim(`Attach with: ${chalk_1.default.cyan(`gb ${provider} attach ${sessionName}`)}`));
616
+ return;
617
+ }
618
+ // Interactive mode: attach to session
619
+ console.log(chalk_1.default.dim(`Image auto-upload: ${chalk_1.default.green('enabled')} - Paste local image paths, they'll be uploaded automatically`));
620
+ console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
602
621
  const proc = (0, ssh_proxy_1.attachWithProxy)({
603
622
  ipAddress: attachIpAddress === 'localhost' ? '127.0.0.1' : attachIpAddress,
604
623
  keyPath,
@@ -611,7 +630,7 @@ exec ${cliCommand}
611
630
  });
612
631
  await new Promise(resolve => proc.on('close', () => resolve()));
613
632
  console.log(chalk_1.default.dim('\nDetached from session.'));
614
- console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} --attach ${sessionName}`)}`));
633
+ console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} attach ${sessionName}`)}`));
615
634
  }
616
635
  /**
617
636
  * Attach to an existing session
@@ -944,7 +963,7 @@ async function runInteractiveFlow(provider) {
944
963
  * 5. Starts session with the composed prompt
945
964
  */
946
965
  async function runPromptModeFlow(provider, options = {}) {
947
- const { useEditor = false } = options;
966
+ const { useEditor = false, background = false } = options;
948
967
  console.log(chalk_1.default.bold(`\n${provider.charAt(0).toUpperCase() + provider.slice(1)} - Interactive Prompt Mode`));
949
968
  if (useEditor) {
950
969
  console.log(chalk_1.default.dim('Compose your prompt in $EDITOR with optional images\n'));
@@ -1053,7 +1072,7 @@ async function runPromptModeFlow(provider, options = {}) {
1053
1072
  finalPrompt = text;
1054
1073
  }
1055
1074
  // Start session with prompt
1056
- await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt });
1075
+ await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt, background });
1057
1076
  break;
1058
1077
  }
1059
1078
  case 'create-genbox': {
@@ -1085,7 +1104,7 @@ async function runPromptModeFlow(provider, options = {}) {
1085
1104
  else {
1086
1105
  finalPrompt = text;
1087
1106
  }
1088
- await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt });
1107
+ await startSessionOnGenbox(provider, genbox, { initialPrompt: finalPrompt, background });
1089
1108
  break;
1090
1109
  }
1091
1110
  case 'direct': {
@@ -1410,11 +1429,13 @@ function createProviderCommand(provider) {
1410
1429
  .option('-y, --yes', 'Skip confirmations')
1411
1430
  .option('-p, --prompt-mode', 'Interactive prompt mode: compose prompt with optional images')
1412
1431
  .option('-e, --editor', 'Use $EDITOR for prompt input (with -p)')
1432
+ .option('-b, --background', 'Run session in background (with -p), attach later')
1413
1433
  .addHelpText('after', `
1414
1434
  Examples:
1415
1435
  gb ${provider} Interactive mode
1416
1436
  gb ${provider} -p Prompt mode: inline multiline input
1417
1437
  gb ${provider} -p -e Prompt mode: open $EDITOR for input
1438
+ gb ${provider} -p -b Prompt mode: run in background
1418
1439
  gb ${provider} list List all ${provider} sessions
1419
1440
  gb ${provider} attach [session] Attach to a session
1420
1441
  gb ${provider} stop [session] Stop a session
@@ -1427,7 +1448,10 @@ Examples:
1427
1448
  try {
1428
1449
  // -p / --prompt-mode: Interactive prompt with optional images
1429
1450
  if (options.promptMode) {
1430
- await runPromptModeFlow(provider, { useEditor: options.editor });
1451
+ await runPromptModeFlow(provider, {
1452
+ useEditor: options.editor,
1453
+ background: options.background,
1454
+ });
1431
1455
  return;
1432
1456
  }
1433
1457
  // --on: Use specific genbox
@@ -65,6 +65,10 @@ const stop_1 = require("./stop");
65
65
  const kill_1 = require("./kill");
66
66
  const migrate_1 = require("./migrate");
67
67
  const attach_1 = require("./attach");
68
+ const watch_1 = require("./watch");
69
+ const logs_1 = require("./logs");
70
+ const show_1 = require("./show");
71
+ const send_1 = require("./send");
68
72
  const child_process_1 = require("child_process");
69
73
  const os = __importStar(require("os"));
70
74
  const path = __importStar(require("path"));
@@ -1768,6 +1772,10 @@ exports.sessionCommand = new commander_1.Command('session')
1768
1772
  .addCommand(stop_1.sessionStopCommand)
1769
1773
  .addCommand(kill_1.sessionKillCommand)
1770
1774
  .addCommand(migrate_1.sessionMigrateCommand)
1775
+ .addCommand(watch_1.sessionWatchCommand)
1776
+ .addCommand(logs_1.sessionLogsCommand)
1777
+ .addCommand(show_1.sessionShowCommand)
1778
+ .addCommand(send_1.sessionSendCommand)
1771
1779
  .action(async (sessionArg, providerArgs, options) => {
1772
1780
  try {
1773
1781
  // Clean up stale sockets on startup (silently)
@@ -0,0 +1,387 @@
1
+ "use strict";
2
+ /**
3
+ * Session Logs Command
4
+ *
5
+ * Stream session transcript and activity logs.
6
+ * Shows real-time updates of what the AI is doing.
7
+ *
8
+ * Usage:
9
+ * gb session logs <session> # Show session transcript
10
+ * gb session logs <session> -f # Follow mode (like tail -f)
11
+ * gb session logs <session> --json # JSON output
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ var __importDefault = (this && this.__importDefault) || function (mod) {
47
+ return (mod && mod.__esModule) ? mod : { "default": mod };
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.sessionLogsCommand = void 0;
51
+ const commander_1 = require("commander");
52
+ const chalk_1 = __importDefault(require("chalk"));
53
+ const path = __importStar(require("path"));
54
+ const os = __importStar(require("os"));
55
+ const fs = __importStar(require("fs"));
56
+ const unified_session_1 = require("../../lib/unified-session");
57
+ const api_1 = require("../../api");
58
+ /**
59
+ * Get SSH key path
60
+ */
61
+ function getPrivateSshKey() {
62
+ const home = os.homedir();
63
+ const potentialKeys = [
64
+ path.join(home, '.ssh', 'id_ed25519'),
65
+ path.join(home, '.ssh', 'id_rsa'),
66
+ ];
67
+ for (const keyPath of potentialKeys) {
68
+ if (fs.existsSync(keyPath)) {
69
+ return keyPath;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Format timestamp
76
+ */
77
+ function formatTime(date) {
78
+ const d = typeof date === 'string' ? new Date(date) : date;
79
+ return d.toLocaleTimeString('en-US', { hour12: false });
80
+ }
81
+ /**
82
+ * Find session by name or ID
83
+ */
84
+ async function findSession(nameOrId) {
85
+ const result = await (0, unified_session_1.listAllSessions)({ includeEnded: true });
86
+ // Check local sessions
87
+ for (const session of result.sessions) {
88
+ if (session.name === nameOrId ||
89
+ session.id === nameOrId ||
90
+ session.id.startsWith(nameOrId) ||
91
+ session.name.includes(nameOrId)) {
92
+ return { session };
93
+ }
94
+ }
95
+ // Check remote sessions
96
+ for (const remote of result.remoteSessions) {
97
+ if (remote.name === nameOrId ||
98
+ remote.name.includes(nameOrId)) {
99
+ return { remoteSession: remote };
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * Fetch messages from API
106
+ */
107
+ async function fetchMessages(sessionId, limit = 50) {
108
+ try {
109
+ const response = await (0, api_1.fetchApi)(`/sessions/v2/${sessionId}/messages?limit=${limit}`);
110
+ return response.messages || [];
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ /**
117
+ * Fetch events from API
118
+ */
119
+ async function fetchEvents(sessionId, limit = 100) {
120
+ try {
121
+ const response = await (0, api_1.fetchApi)(`/sessions/v2/${sessionId}/events?limit=${limit}`);
122
+ return response.events || [];
123
+ }
124
+ catch {
125
+ return [];
126
+ }
127
+ }
128
+ /**
129
+ * Get role icon
130
+ */
131
+ function getRoleIcon(role) {
132
+ switch (role) {
133
+ case 'user':
134
+ return '💬';
135
+ case 'assistant':
136
+ return '🤖';
137
+ default:
138
+ return '📝';
139
+ }
140
+ }
141
+ /**
142
+ * Get event icon
143
+ */
144
+ function getEventIcon(event) {
145
+ switch (event) {
146
+ case 'session_start':
147
+ return '🚀';
148
+ case 'session_end':
149
+ return '🏁';
150
+ case 'prompt_submitted':
151
+ return '💬';
152
+ case 'response_started':
153
+ return '🤔';
154
+ case 'response_completed':
155
+ return '✅';
156
+ case 'tool_started':
157
+ return '⚙';
158
+ case 'tool_completed':
159
+ return '✓';
160
+ case 'tool_error':
161
+ return '❌';
162
+ case 'state_change':
163
+ return '🔄';
164
+ case 'error':
165
+ return '⚠';
166
+ default:
167
+ return '•';
168
+ }
169
+ }
170
+ /**
171
+ * Format a message for display
172
+ */
173
+ function formatMessage(msg) {
174
+ const icon = getRoleIcon(msg.role);
175
+ const time = chalk_1.default.dim(`[${formatTime(msg.createdAt)}]`);
176
+ const roleColor = msg.role === 'user' ? chalk_1.default.cyan : chalk_1.default.green;
177
+ const roleLabel = roleColor(msg.role === 'user' ? 'User' : 'Assistant');
178
+ let output = `${time} ${icon} ${roleLabel}:`;
179
+ if (msg.textPreview) {
180
+ // Wrap text for readability
181
+ const preview = msg.textPreview.substring(0, 200);
182
+ output += ` ${preview}${msg.textPreview.length > 200 ? '...' : ''}`;
183
+ }
184
+ if (msg.toolsUsed && msg.toolsUsed.length > 0) {
185
+ output += chalk_1.default.dim(` [Tools: ${msg.toolsUsed.join(', ')}]`);
186
+ }
187
+ return output;
188
+ }
189
+ /**
190
+ * Format an event for display
191
+ */
192
+ function formatEvent(evt) {
193
+ const icon = getEventIcon(evt.event);
194
+ const time = chalk_1.default.dim(`[${formatTime(evt.createdAt)}]`);
195
+ const eventLabel = evt.event.replace(/_/g, ' ');
196
+ let output = `${time} ${icon} ${eventLabel}`;
197
+ if (evt.data) {
198
+ if (evt.data.toolName) {
199
+ output += chalk_1.default.cyan(` ${evt.data.toolName}`);
200
+ }
201
+ if (evt.data.filePath) {
202
+ output += chalk_1.default.dim(` ${evt.data.filePath}`);
203
+ }
204
+ if (evt.data.currentState) {
205
+ output += chalk_1.default.yellow(` → ${evt.data.currentState}`);
206
+ }
207
+ if (evt.data.error) {
208
+ output += chalk_1.default.red(` ${evt.data.error}`);
209
+ }
210
+ }
211
+ return output;
212
+ }
213
+ /**
214
+ * Format for JSON output
215
+ */
216
+ function formatJson(data) {
217
+ console.log(JSON.stringify(data, null, 2));
218
+ }
219
+ /**
220
+ * Display transcript from API
221
+ */
222
+ async function displayApiTranscript(session, options) {
223
+ const sessionId = session.apiSessionIdV2;
224
+ if (!sessionId) {
225
+ console.log(chalk_1.default.yellow('\nSession is not synced to API. No transcript available.'));
226
+ console.log(chalk_1.default.dim('Enable sync with: gb session start --sync'));
227
+ return;
228
+ }
229
+ const limit = parseInt(options.tail || '50');
230
+ if (options.json) {
231
+ const messages = await fetchMessages(sessionId, limit);
232
+ const events = options.events ? await fetchEvents(sessionId, limit) : undefined;
233
+ formatJson({ messages, events });
234
+ return;
235
+ }
236
+ if (options.events) {
237
+ // Show events
238
+ console.log(chalk_1.default.bold(`\nSession Events: ${session.name}`));
239
+ console.log(chalk_1.default.dim('─'.repeat(70)));
240
+ const events = await fetchEvents(sessionId, limit);
241
+ if (events.length === 0) {
242
+ console.log(chalk_1.default.dim('\nNo events recorded.\n'));
243
+ return;
244
+ }
245
+ for (const evt of events.reverse()) {
246
+ console.log(formatEvent(evt));
247
+ }
248
+ console.log('');
249
+ return;
250
+ }
251
+ // Show messages
252
+ console.log(chalk_1.default.bold(`\nSession Transcript: ${session.name}`));
253
+ console.log(chalk_1.default.dim('─'.repeat(70)));
254
+ const messages = await fetchMessages(sessionId, limit);
255
+ if (messages.length === 0) {
256
+ console.log(chalk_1.default.dim('\nNo messages recorded.\n'));
257
+ return;
258
+ }
259
+ for (const msg of messages.reverse()) {
260
+ console.log(formatMessage(msg));
261
+ }
262
+ console.log('');
263
+ }
264
+ /**
265
+ * Follow mode - continuously poll for updates
266
+ */
267
+ async function followMode(session, options) {
268
+ const sessionId = session.apiSessionIdV2;
269
+ if (!sessionId) {
270
+ console.log(chalk_1.default.yellow('\nSession is not synced to API. Follow mode unavailable.'));
271
+ return;
272
+ }
273
+ console.log(chalk_1.default.bold(`\nFollowing: ${session.name}`));
274
+ console.log(chalk_1.default.dim('─'.repeat(70)));
275
+ console.log(chalk_1.default.dim('Press Ctrl+C to stop\n'));
276
+ let lastMessageId = null;
277
+ let lastEventTime = null;
278
+ while (true) {
279
+ try {
280
+ if (options.events || options.tools) {
281
+ // Poll events
282
+ const events = await fetchEvents(sessionId, 20);
283
+ for (const evt of events.reverse()) {
284
+ if (lastEventTime && new Date(evt.createdAt) <= new Date(lastEventTime)) {
285
+ continue;
286
+ }
287
+ // Filter for tools only if --tools specified
288
+ if (options.tools &&
289
+ !evt.event.includes('tool') &&
290
+ evt.event !== 'state_change') {
291
+ continue;
292
+ }
293
+ console.log(formatEvent(evt));
294
+ lastEventTime = evt.createdAt;
295
+ }
296
+ }
297
+ else {
298
+ // Poll messages
299
+ const messages = await fetchMessages(sessionId, 10);
300
+ for (const msg of messages.reverse()) {
301
+ if (lastMessageId === msg.messageId)
302
+ continue;
303
+ if (lastMessageId) {
304
+ console.log(formatMessage(msg));
305
+ }
306
+ lastMessageId = msg.messageId;
307
+ }
308
+ }
309
+ }
310
+ catch (error) {
311
+ // Silently continue on errors
312
+ }
313
+ await new Promise(resolve => setTimeout(resolve, 2000));
314
+ }
315
+ }
316
+ /**
317
+ * Display local session info when no API sync
318
+ */
319
+ function displayLocalSessionInfo(session) {
320
+ console.log(chalk_1.default.bold(`\nSession: ${session.name}`));
321
+ console.log(chalk_1.default.dim('─'.repeat(50)));
322
+ console.log(` Type: ${session.type}`);
323
+ console.log(` Provider: ${session.provider}`);
324
+ console.log(` Status: ${session.status}`);
325
+ console.log(` Created: ${new Date(session.createdAt).toLocaleString()}`);
326
+ if (session.infrastructure?.dtachSocketPath) {
327
+ console.log(` Socket: ${session.infrastructure.dtachSocketPath}`);
328
+ }
329
+ console.log(chalk_1.default.dim('\nThis session is not synced to API. Transcript not available.'));
330
+ console.log(chalk_1.default.dim('To enable recording, start sessions with --sync flag.'));
331
+ console.log('');
332
+ console.log(chalk_1.default.bold('To attach to this session:'));
333
+ console.log(chalk_1.default.cyan(` gb ${session.provider} attach ${session.name}\n`));
334
+ }
335
+ exports.sessionLogsCommand = new commander_1.Command('logs')
336
+ .description('View session transcript and activity logs')
337
+ .argument('<session>', 'Session name or ID')
338
+ .option('-f, --follow', 'Follow mode - continuously show new activity')
339
+ .option('--json', 'Output as JSON')
340
+ .option('-n, --tail <lines>', 'Number of messages to show (default: 50)')
341
+ .option('--events', 'Show events instead of messages')
342
+ .option('--tools', 'Show only tool executions (with -f)')
343
+ .addHelpText('after', `
344
+ Examples:
345
+ gb session logs claude-swift-fox # View transcript
346
+ gb session logs claude-swift-fox -f # Follow mode
347
+ gb session logs abc123 --events # View events
348
+ gb session logs abc123 --json # JSON output
349
+ `)
350
+ .action(async (sessionArg, options) => {
351
+ try {
352
+ const found = await findSession(sessionArg);
353
+ if (!found) {
354
+ console.log(chalk_1.default.red(`\nSession not found: ${sessionArg}`));
355
+ console.log(chalk_1.default.dim('\nRun `gb session list` to see available sessions.\n'));
356
+ process.exit(1);
357
+ }
358
+ if (found.remoteSession) {
359
+ console.log(chalk_1.default.yellow(`\nRemote session: ${found.remoteSession.name}`));
360
+ console.log(chalk_1.default.dim(`Running on: ${found.remoteSession.genboxName}`));
361
+ console.log(chalk_1.default.dim('\nRemote sessions must be attached to view logs.'));
362
+ console.log(chalk_1.default.cyan(`\n gb ${found.remoteSession.name.split('-')[0]} attach ${found.remoteSession.name}\n`));
363
+ process.exit(0);
364
+ }
365
+ const session = found.session;
366
+ // Check if session has API sync
367
+ if (!session.syncEnabled || !session.apiSessionIdV2) {
368
+ displayLocalSessionInfo(session);
369
+ process.exit(0);
370
+ }
371
+ if (options.follow) {
372
+ await followMode(session, options);
373
+ }
374
+ else {
375
+ await displayApiTranscript(session, options);
376
+ }
377
+ process.exit(0);
378
+ }
379
+ catch (error) {
380
+ if (error instanceof api_1.AuthenticationError) {
381
+ console.error(chalk_1.default.red('\nNot logged in. Run `gb login` first.\n'));
382
+ process.exit(1);
383
+ }
384
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
385
+ process.exit(1);
386
+ }
387
+ });