genbox 1.0.211 → 1.0.213

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.
@@ -69,6 +69,7 @@ const watch_1 = require("./watch");
69
69
  const logs_1 = require("./logs");
70
70
  const show_1 = require("./show");
71
71
  const send_1 = require("./send");
72
+ const templates_1 = require("./templates");
72
73
  const child_process_1 = require("child_process");
73
74
  const os = __importStar(require("os"));
74
75
  const path = __importStar(require("path"));
@@ -1776,6 +1777,7 @@ exports.sessionCommand = new commander_1.Command('session')
1776
1777
  .addCommand(logs_1.sessionLogsCommand)
1777
1778
  .addCommand(show_1.sessionShowCommand)
1778
1779
  .addCommand(send_1.sessionSendCommand)
1780
+ .addCommand(templates_1.sessionTemplatesCommand)
1779
1781
  .action(async (sessionArg, providerArgs, options) => {
1780
1782
  try {
1781
1783
  // Clean up stale sockets on startup (silently)
@@ -2,13 +2,13 @@
2
2
  /**
3
3
  * Session Send Command
4
4
  *
5
- * Send a prompt to a running session without attaching.
6
- * Useful for orchestration and automation.
5
+ * Send a prompt to running session(s) without attaching.
6
+ * Supports single session or multi-session broadcasting.
7
7
  *
8
8
  * Usage:
9
- * gb session send <session> "prompt" # Send inline prompt
10
- * gb session send <session> -f file.txt # Send from file
11
- * gb session send <session> -e # Open editor
9
+ * gb session send <session> "prompt" # Send to single session
10
+ * gb session send --all "prompt" # Broadcast to all sessions
11
+ * gb session send --provider claude "prompt" # Send to all Claude sessions
12
12
  */
13
13
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
14
  if (k2 === undefined) k2 = k;
@@ -72,6 +72,56 @@ function getPrivateSshKey() {
72
72
  }
73
73
  return null;
74
74
  }
75
+ /**
76
+ * Find all active sessions matching filters
77
+ */
78
+ async function findAllSessions(options) {
79
+ const result = await (0, unified_session_1.listAllSessions)({ includeEnded: false });
80
+ const targets = [];
81
+ // Process local sessions
82
+ for (const session of result.sessions) {
83
+ if (session.status !== 'running' && session.status !== 'active' && session.status !== 'idle') {
84
+ continue;
85
+ }
86
+ // Filter by provider
87
+ if (options.provider && session.provider !== options.provider)
88
+ continue;
89
+ // Filter by genbox
90
+ if (options.genbox) {
91
+ const genboxName = session.infrastructure?.genboxName ||
92
+ session.infrastructure?.vmName ||
93
+ session.infrastructure?.containerName ||
94
+ session.name;
95
+ if (!genboxName.toLowerCase().includes(options.genbox.toLowerCase()))
96
+ continue;
97
+ }
98
+ targets.push({
99
+ name: session.name,
100
+ type: session.type === 'native' ? 'local' :
101
+ session.type === 'docker' ? 'docker' :
102
+ session.type === 'multipass' ? 'multipass' : 'cloud',
103
+ session,
104
+ });
105
+ }
106
+ // Process remote sessions
107
+ for (const remote of result.remoteSessions) {
108
+ // Filter by provider
109
+ if (options.provider && remote.provider !== options.provider)
110
+ continue;
111
+ // Filter by genbox
112
+ if (options.genbox && !remote.genboxName.toLowerCase().includes(options.genbox.toLowerCase()))
113
+ continue;
114
+ targets.push({
115
+ name: remote.name,
116
+ type: 'remote',
117
+ remoteSession: {
118
+ ...remote,
119
+ provider: remote.provider,
120
+ },
121
+ });
122
+ }
123
+ return targets;
124
+ }
75
125
  /**
76
126
  * Find session by name or ID
77
127
  */
@@ -86,7 +136,13 @@ async function findSession(nameOrId) {
86
136
  session.id === nameOrId ||
87
137
  session.id.startsWith(nameOrId) ||
88
138
  session.name.includes(nameOrId)) {
89
- return { session };
139
+ return {
140
+ name: session.name,
141
+ type: session.type === 'native' ? 'local' :
142
+ session.type === 'docker' ? 'docker' :
143
+ session.type === 'multipass' ? 'multipass' : 'cloud',
144
+ session,
145
+ };
90
146
  }
91
147
  }
92
148
  // Check remote sessions
@@ -94,10 +150,12 @@ async function findSession(nameOrId) {
94
150
  if (remote.name === nameOrId ||
95
151
  remote.name.includes(nameOrId)) {
96
152
  return {
153
+ name: remote.name,
154
+ type: 'remote',
97
155
  remoteSession: {
98
156
  ...remote,
99
157
  provider: remote.provider,
100
- }
158
+ },
101
159
  };
102
160
  }
103
161
  }
@@ -122,6 +180,7 @@ function getPromptFromEditor() {
122
180
  // Create temp file with instructions
123
181
  fs.writeFileSync(tmpFile, `# Enter your prompt below (lines starting with # are ignored)
124
182
  # Save and close the editor when done
183
+ # This prompt will be sent to the session(s)
125
184
 
126
185
  `);
127
186
  // Open editor
@@ -154,11 +213,13 @@ function getDtachSocketDir() {
154
213
  /**
155
214
  * Send prompt to local native session via dtach
156
215
  */
157
- async function sendToLocalSession(session, prompt, options) {
216
+ async function sendToLocalSession(session, prompt) {
217
+ const start = Date.now();
158
218
  const socketPath = session.infrastructure?.dtachSocketPath ||
159
219
  path.join(getDtachSocketDir(), `${session.name}.sock`);
160
220
  if (!fs.existsSync(socketPath)) {
161
221
  return {
222
+ session: session.name,
162
223
  success: false,
163
224
  message: `Socket not found: ${socketPath}. Session may not be running.`,
164
225
  };
@@ -169,8 +230,6 @@ async function sendToLocalSession(session, prompt, options) {
169
230
  .replace(/\\/g, '\\\\')
170
231
  .replace(/'/g, "'\\''")
171
232
  .replace(/\n/g, '\\n');
172
- // Use printf to send prompt to dtach session
173
- // The -p flag attaches briefly to push input
174
233
  const result = (0, child_process_1.spawnSync)('bash', [
175
234
  '-c',
176
235
  `printf '%s\\n' '${escapedPrompt}' | dtach -p "${socketPath}"`,
@@ -180,17 +239,21 @@ async function sendToLocalSession(session, prompt, options) {
180
239
  });
181
240
  if (result.status !== 0) {
182
241
  return {
242
+ session: session.name,
183
243
  success: false,
184
244
  message: `Failed to send prompt: ${result.stderr || 'Unknown error'}`,
185
245
  };
186
246
  }
187
247
  return {
248
+ session: session.name,
188
249
  success: true,
189
- message: `Prompt sent to ${session.name}`,
250
+ message: `Prompt sent`,
251
+ duration: Date.now() - start,
190
252
  };
191
253
  }
192
254
  catch (error) {
193
255
  return {
256
+ session: session.name,
194
257
  success: false,
195
258
  message: `Error: ${error.message}`,
196
259
  };
@@ -200,9 +263,11 @@ async function sendToLocalSession(session, prompt, options) {
200
263
  * Send prompt to remote session via SSH + dtach
201
264
  */
202
265
  async function sendToRemoteSession(ipAddress, sessionName, prompt, sshPort) {
266
+ const start = Date.now();
203
267
  const keyPath = getPrivateSshKey();
204
268
  if (!keyPath) {
205
269
  return {
270
+ session: sessionName,
206
271
  success: false,
207
272
  message: 'SSH key not found. Run `gb login` first.',
208
273
  };
@@ -232,17 +297,21 @@ async function sendToRemoteSession(ipAddress, sessionName, prompt, sshPort) {
232
297
  });
233
298
  if (result.status !== 0) {
234
299
  return {
300
+ session: sessionName,
235
301
  success: false,
236
302
  message: `Failed to send prompt: ${result.stderr || 'Unknown error'}`,
237
303
  };
238
304
  }
239
305
  return {
306
+ session: sessionName,
240
307
  success: true,
241
- message: `Prompt sent to ${sessionName}`,
308
+ message: `Prompt sent`,
309
+ duration: Date.now() - start,
242
310
  };
243
311
  }
244
312
  catch (error) {
245
313
  return {
314
+ session: sessionName,
246
315
  success: false,
247
316
  message: `Error: ${error.message}`,
248
317
  };
@@ -251,7 +320,8 @@ async function sendToRemoteSession(ipAddress, sessionName, prompt, sshPort) {
251
320
  /**
252
321
  * Send prompt via API remote control
253
322
  */
254
- async function sendViaApi(sessionId, prompt) {
323
+ async function sendViaApi(sessionId, sessionName, prompt) {
324
+ const start = Date.now();
255
325
  try {
256
326
  await (0, api_1.fetchApi)(`/sessions/v2/${sessionId}/command`, {
257
327
  method: 'POST',
@@ -262,34 +332,111 @@ async function sendViaApi(sessionId, prompt) {
262
332
  }),
263
333
  });
264
334
  return {
335
+ session: sessionName,
265
336
  success: true,
266
337
  message: 'Prompt queued via API',
338
+ duration: Date.now() - start,
267
339
  };
268
340
  }
269
341
  catch (error) {
270
342
  return {
343
+ session: sessionName,
271
344
  success: false,
272
345
  message: `API error: ${error.message}`,
273
346
  };
274
347
  }
275
348
  }
349
+ /**
350
+ * Send prompt to a single target
351
+ */
352
+ async function sendToTarget(target, prompt) {
353
+ if (target.remoteSession) {
354
+ return sendToRemoteSession(target.remoteSession.ipAddress, target.remoteSession.name, prompt);
355
+ }
356
+ const session = target.session;
357
+ if (target.type === 'local') {
358
+ return sendToLocalSession(session, prompt);
359
+ }
360
+ else if (target.type === 'cloud' && session.infrastructure?.ipAddress) {
361
+ return sendToRemoteSession(session.infrastructure.ipAddress, session.name, prompt);
362
+ }
363
+ else if (target.type === 'docker' || target.type === 'multipass') {
364
+ const ip = target.type === 'docker' ? '127.0.0.1' : session.infrastructure?.vmIpAddress;
365
+ const port = session.infrastructure?.sshPort;
366
+ if (!ip) {
367
+ return {
368
+ session: session.name,
369
+ success: false,
370
+ message: 'Cannot determine session IP address',
371
+ };
372
+ }
373
+ return sendToRemoteSession(ip, session.name, prompt, port);
374
+ }
375
+ else if (session.syncEnabled && session.apiSessionIdV2) {
376
+ return sendViaApi(session.apiSessionIdV2, session.name, prompt);
377
+ }
378
+ return {
379
+ session: session.name,
380
+ success: false,
381
+ message: `Cannot send to session type: ${session.type}`,
382
+ };
383
+ }
384
+ /**
385
+ * Send to multiple sessions
386
+ */
387
+ async function sendToMultipleSessions(targets, prompt, parallel) {
388
+ if (parallel) {
389
+ // Send in parallel
390
+ return Promise.all(targets.map(target => sendToTarget(target, prompt)));
391
+ }
392
+ // Send sequentially
393
+ const results = [];
394
+ for (const target of targets) {
395
+ results.push(await sendToTarget(target, prompt));
396
+ }
397
+ return results;
398
+ }
276
399
  exports.sessionSendCommand = new commander_1.Command('send')
277
- .description('Send a prompt to a running session')
278
- .argument('<session>', 'Session name or ID')
400
+ .description('Send a prompt to running session(s)')
401
+ .argument('[session]', 'Session name or ID (not needed with --all)')
279
402
  .argument('[prompt]', 'Prompt text (or use -f/-e)')
280
403
  .option('-f, --file <path>', 'Read prompt from file')
281
404
  .option('-e, --editor', 'Open $EDITOR to compose prompt')
282
- .option('--wait', 'Wait for response (if API sync enabled)')
405
+ .option('--all', 'Send to ALL active sessions (broadcast)')
406
+ .option('--provider <provider>', 'Filter by provider when using --all (claude, gemini, codex)')
407
+ .option('--genbox <name>', 'Filter by genbox name when using --all')
408
+ .option('--parallel', 'Send to all sessions in parallel (default: sequential)')
283
409
  .option('--json', 'Output result as JSON')
284
410
  .addHelpText('after', `
285
411
  Examples:
412
+ # Single session
286
413
  gb session send claude-fox "Add unit tests" # Inline prompt
287
414
  gb session send claude-fox -f prompt.txt # From file
288
415
  gb session send claude-fox -e # Open editor
289
- gb session send abc123 "Fix bug" --json # JSON output
416
+
417
+ # Multi-session broadcast
418
+ gb session send --all "Run the test suite" # All sessions
419
+ gb session send --all --provider claude "Fix bugs" # All Claude sessions
420
+ gb session send --all --genbox dusty "Deploy" # All on dusty genbox
421
+ gb session send --all --parallel "Check status" # Parallel broadcast
422
+
423
+ # JSON output (for scripting)
424
+ gb session send --all --json "Hello" # JSON results
290
425
  `)
291
426
  .action(async (sessionArg, promptArg, options) => {
292
427
  try {
428
+ // Validate arguments
429
+ if (!options.all && !sessionArg) {
430
+ console.log(chalk_1.default.red('\nNo session specified.'));
431
+ console.log(chalk_1.default.dim('Use: gb session send <session> "prompt"'));
432
+ console.log(chalk_1.default.dim(' or: gb session send --all "prompt"\n'));
433
+ process.exit(1);
434
+ }
435
+ // Handle the case where session arg might actually be the prompt for --all
436
+ let actualPromptArg = promptArg;
437
+ if (options.all && sessionArg && !promptArg) {
438
+ actualPromptArg = sessionArg;
439
+ }
293
440
  // Get the prompt
294
441
  let prompt;
295
442
  if (options.file) {
@@ -298,17 +445,88 @@ Examples:
298
445
  else if (options.editor) {
299
446
  prompt = getPromptFromEditor();
300
447
  }
301
- else if (promptArg) {
302
- prompt = promptArg;
448
+ else if (actualPromptArg) {
449
+ prompt = actualPromptArg;
303
450
  }
304
451
  else {
305
452
  console.log(chalk_1.default.red('\nNo prompt provided.'));
306
453
  console.log(chalk_1.default.dim('Use: gb session send <session> "prompt"'));
307
- console.log(chalk_1.default.dim(' or: gb session send <session> -f file.txt'));
308
- console.log(chalk_1.default.dim(' or: gb session send <session> -e\n'));
454
+ console.log(chalk_1.default.dim(' or: gb session send --all -f prompt.txt'));
455
+ console.log(chalk_1.default.dim(' or: gb session send --all -e\n'));
309
456
  process.exit(1);
310
457
  }
311
- // Find the session
458
+ // Handle multi-session broadcast
459
+ if (options.all) {
460
+ const targets = await findAllSessions({
461
+ provider: options.provider,
462
+ genbox: options.genbox,
463
+ });
464
+ if (targets.length === 0) {
465
+ const output = {
466
+ success: false,
467
+ error: 'No active sessions found matching filters',
468
+ filters: {
469
+ provider: options.provider,
470
+ genbox: options.genbox,
471
+ },
472
+ };
473
+ if (options.json) {
474
+ console.log(JSON.stringify(output));
475
+ }
476
+ else {
477
+ console.log(chalk_1.default.red('\nNo active sessions found matching filters.'));
478
+ if (options.provider)
479
+ console.log(chalk_1.default.dim(` Provider: ${options.provider}`));
480
+ if (options.genbox)
481
+ console.log(chalk_1.default.dim(` Genbox: ${options.genbox}`));
482
+ console.log(chalk_1.default.dim('\nRun `gb session list` to see active sessions.\n'));
483
+ }
484
+ process.exit(1);
485
+ }
486
+ if (!options.json) {
487
+ console.log(chalk_1.default.bold(`\nBroadcasting to ${targets.length} session(s)...`));
488
+ console.log(chalk_1.default.dim(`Prompt: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"`));
489
+ console.log('');
490
+ }
491
+ const results = await sendToMultipleSessions(targets, prompt, !!options.parallel);
492
+ // Output results
493
+ if (options.json) {
494
+ const successCount = results.filter(r => r.success).length;
495
+ console.log(JSON.stringify({
496
+ success: successCount === results.length,
497
+ total: results.length,
498
+ succeeded: successCount,
499
+ failed: results.length - successCount,
500
+ promptLength: prompt.length,
501
+ results,
502
+ }, null, 2));
503
+ }
504
+ else {
505
+ let successCount = 0;
506
+ let failCount = 0;
507
+ for (const result of results) {
508
+ if (result.success) {
509
+ successCount++;
510
+ console.log(chalk_1.default.green(` ✓ ${result.session}`) + chalk_1.default.dim(` (${result.duration}ms)`));
511
+ }
512
+ else {
513
+ failCount++;
514
+ console.log(chalk_1.default.red(` ✗ ${result.session}: ${result.message}`));
515
+ }
516
+ }
517
+ console.log('');
518
+ if (failCount === 0) {
519
+ console.log(chalk_1.default.green(`✓ Sent to all ${successCount} session(s)`));
520
+ }
521
+ else {
522
+ console.log(chalk_1.default.yellow(`Sent to ${successCount}/${results.length} sessions (${failCount} failed)`));
523
+ }
524
+ console.log('');
525
+ }
526
+ const anyFailed = results.some(r => !r.success);
527
+ process.exit(anyFailed ? 1 : 0);
528
+ }
529
+ // Single session send
312
530
  const found = await findSession(sessionArg);
313
531
  if (!found) {
314
532
  const output = {
@@ -324,67 +542,27 @@ Examples:
324
542
  }
325
543
  process.exit(1);
326
544
  }
327
- let result;
328
- if (found.remoteSession) {
329
- // Remote session on cloud genbox
330
- if (!options.json) {
331
- console.log(chalk_1.default.dim(`\nSending to remote session: ${found.remoteSession.name}`));
332
- console.log(chalk_1.default.dim(` Genbox: ${found.remoteSession.genboxName}`));
333
- }
334
- result = await sendToRemoteSession(found.remoteSession.ipAddress, found.remoteSession.name, prompt);
335
- }
336
- else {
337
- const session = found.session;
338
- if (!options.json) {
339
- console.log(chalk_1.default.dim(`\nSending to session: ${session.name}`));
340
- }
341
- // Try different methods based on session type
342
- if (session.type === 'native') {
343
- // Local native session - use dtach directly
344
- result = await sendToLocalSession(session, prompt, options);
345
- }
346
- else if (session.type === 'cloud' && session.infrastructure?.ipAddress) {
347
- // Cloud session - use SSH
348
- result = await sendToRemoteSession(session.infrastructure.ipAddress, session.name, prompt);
349
- }
350
- else if (session.type === 'docker' || session.type === 'multipass') {
351
- // Local VM/Docker - use SSH to localhost with port
352
- const ip = session.type === 'docker' ? '127.0.0.1' : session.infrastructure?.vmIpAddress;
353
- const port = session.infrastructure?.sshPort;
354
- if (!ip) {
355
- result = { success: false, message: 'Cannot determine session IP address' };
356
- }
357
- else {
358
- result = await sendToRemoteSession(ip, session.name, prompt, port);
359
- }
360
- }
361
- else if (session.syncEnabled && session.apiSessionIdV2) {
362
- // Fall back to API remote control
363
- result = await sendViaApi(session.apiSessionIdV2, prompt);
364
- }
365
- else {
366
- result = {
367
- success: false,
368
- message: `Cannot send to session type: ${session.type}`,
369
- };
370
- }
545
+ if (!options.json) {
546
+ console.log(chalk_1.default.dim(`\nSending to session: ${found.name}`));
371
547
  }
548
+ const result = await sendToTarget(found, prompt);
372
549
  // Output result
373
550
  if (options.json) {
374
551
  console.log(JSON.stringify({
375
552
  success: result.success,
376
553
  message: result.message,
377
- session: found.session?.name || found.remoteSession?.name,
554
+ session: result.session,
378
555
  promptLength: prompt.length,
556
+ duration: result.duration,
379
557
  }));
380
558
  }
381
559
  else {
382
560
  if (result.success) {
383
- console.log(chalk_1.default.green(`\n✓ ${result.message}`));
561
+ console.log(chalk_1.default.green(`\n✓ ${result.message} to ${result.session}`));
384
562
  console.log(chalk_1.default.dim(` Prompt: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"`));
385
563
  console.log('');
386
564
  console.log(chalk_1.default.dim('View progress:'));
387
- console.log(chalk_1.default.cyan(` gb session logs ${found.session?.name || found.remoteSession?.name} -f\n`));
565
+ console.log(chalk_1.default.cyan(` gb session logs ${result.session} -f\n`));
388
566
  }
389
567
  else {
390
568
  console.log(chalk_1.default.red(`\n✗ ${result.message}\n`));