genbox 1.0.183 → 1.0.185

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.
@@ -0,0 +1,576 @@
1
+ "use strict";
2
+ /**
3
+ * Provider Command Factory
4
+ *
5
+ * Creates `gb claude`, `gb gemini`, and `gb codex` commands with a unified,
6
+ * user-friendly interactive experience that encourages genbox usage.
7
+ *
8
+ * Flow:
9
+ * 1. Show interactive menu:
10
+ * - On a Genbox (Recommended)
11
+ * - Attach to existing session (if any exist)
12
+ * - Run directly on this machine (no isolation)
13
+ * 2. Handle user selection
14
+ * 3. Start or attach to session
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.codexCommand = exports.geminiCommand = exports.claudeCommand = void 0;
54
+ exports.createProviderCommand = createProviderCommand;
55
+ const commander_1 = require("commander");
56
+ const chalk_1 = __importDefault(require("chalk"));
57
+ const select_1 = __importDefault(require("@inquirer/select"));
58
+ const prompts_1 = require("@inquirer/prompts");
59
+ const config_store_1 = require("../config-store");
60
+ const api_1 = require("../api");
61
+ const child_process_1 = require("child_process");
62
+ const os = __importStar(require("os"));
63
+ const path = __importStar(require("path"));
64
+ const fs = __importStar(require("fs"));
65
+ // Helper functions
66
+ function getPrivateSshKey() {
67
+ const keyPath = path.join(os.homedir(), '.ssh', 'genbox_key');
68
+ if (!fs.existsSync(keyPath)) {
69
+ throw new Error('SSH key not found. Run `gb login` first.');
70
+ }
71
+ return keyPath;
72
+ }
73
+ function getDtachSocketDir() {
74
+ return path.join(os.homedir(), '.genbox', 'sockets');
75
+ }
76
+ function getRemoteDtachSocketDir() {
77
+ return '/home/dev/.genbox/sockets';
78
+ }
79
+ function ensureLocalSocketDir() {
80
+ const dir = getDtachSocketDir();
81
+ if (!fs.existsSync(dir)) {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ }
85
+ function isDtachSocketAlive(socketPath) {
86
+ if (!fs.existsSync(socketPath)) {
87
+ return false;
88
+ }
89
+ try {
90
+ // Try to check if process is still running by checking socket
91
+ const result = (0, child_process_1.spawnSync)('dtach', ['-a', socketPath, '-e', ''], {
92
+ timeout: 100,
93
+ stdio: 'ignore',
94
+ });
95
+ // If we can't attach, socket might be stale
96
+ return result.status === null || result.status === 0;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ async function ensureDtachInstalled(ipAddress, keyPath) {
103
+ try {
104
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR dev@${ipAddress} "which dtach"`, { timeout: 10000, stdio: 'pipe' });
105
+ }
106
+ catch {
107
+ console.log(chalk_1.default.dim('Installing dtach on genbox...'));
108
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "sudo apt-get update && sudo apt-get install -y dtach"`, { timeout: 60000, stdio: 'pipe' });
109
+ }
110
+ }
111
+ async function ensureLocalDtach() {
112
+ try {
113
+ (0, child_process_1.execSync)('which dtach', { stdio: 'pipe' });
114
+ return 'available';
115
+ }
116
+ catch {
117
+ console.log(chalk_1.default.yellow('\ndtach is not installed (required for session persistence).'));
118
+ const platform = os.platform();
119
+ if (platform === 'darwin') {
120
+ console.log(chalk_1.default.dim('Install with: brew install dtach'));
121
+ }
122
+ else if (platform === 'linux') {
123
+ console.log(chalk_1.default.dim('Install with: sudo apt-get install dtach'));
124
+ }
125
+ try {
126
+ const proceed = await (0, prompts_1.confirm)({
127
+ message: 'Continue without session persistence?',
128
+ default: false,
129
+ });
130
+ return proceed ? 'unavailable' : 'cancel';
131
+ }
132
+ catch {
133
+ return 'cancel';
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Get list of running genboxes
139
+ */
140
+ async function getGenboxes() {
141
+ try {
142
+ const response = await (0, api_1.fetchApi)('/genboxes');
143
+ return (response?.genboxes || []).filter(g => g.status === 'running' && g.ipAddress);
144
+ }
145
+ catch (error) {
146
+ if (error instanceof api_1.AuthenticationError) {
147
+ return [];
148
+ }
149
+ throw error;
150
+ }
151
+ }
152
+ /**
153
+ * Get existing sessions for a provider
154
+ */
155
+ async function getExistingSessions(provider) {
156
+ const sessions = [];
157
+ // Check local (direct) sessions
158
+ const socketDir = getDtachSocketDir();
159
+ if (fs.existsSync(socketDir)) {
160
+ const files = fs.readdirSync(socketDir).filter(f => f.endsWith('.sock'));
161
+ for (const file of files) {
162
+ const socketPath = path.join(socketDir, file);
163
+ const sessionName = file.replace('.sock', '');
164
+ // Check if this session is for our provider
165
+ if (sessionName.toLowerCase().includes(provider)) {
166
+ if (isDtachSocketAlive(socketPath)) {
167
+ sessions.push({
168
+ name: sessionName,
169
+ provider,
170
+ location: 'direct',
171
+ });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ // Check cloud (genbox) sessions
177
+ try {
178
+ const genboxes = await getGenboxes();
179
+ const keyPath = getPrivateSshKey();
180
+ for (const genbox of genboxes) {
181
+ if (!genbox.ipAddress)
182
+ continue;
183
+ try {
184
+ const socketDir = getRemoteDtachSocketDir();
185
+ const output = (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 -o LogLevel=ERROR dev@${genbox.ipAddress} "ls -1 ${socketDir}/*.sock 2>/dev/null" 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
186
+ const lines = output.trim().split('\n').filter(l => l.includes('.sock'));
187
+ for (const line of lines) {
188
+ const sessionName = path.basename(line, '.sock');
189
+ if (sessionName.toLowerCase().includes(provider)) {
190
+ sessions.push({
191
+ name: sessionName,
192
+ provider,
193
+ location: 'genbox',
194
+ genboxName: genbox.name,
195
+ ipAddress: genbox.ipAddress,
196
+ });
197
+ }
198
+ }
199
+ }
200
+ catch {
201
+ // Can't connect or no sessions
202
+ }
203
+ }
204
+ }
205
+ catch {
206
+ // Not logged in or network error
207
+ }
208
+ return sessions;
209
+ }
210
+ /**
211
+ * Show first-time warning for direct mode
212
+ */
213
+ async function showDirectModeWarning() {
214
+ if (config_store_1.ConfigStore.hasDirectModeConsent()) {
215
+ return true;
216
+ }
217
+ console.log('');
218
+ console.log(chalk_1.default.yellow(' ⚠️ Running without isolation'));
219
+ console.log('');
220
+ console.log(chalk_1.default.dim(' The AI will have direct access to your files and system.'));
221
+ console.log(chalk_1.default.dim(' For better security, consider using a Genbox.'));
222
+ console.log('');
223
+ try {
224
+ const proceed = await (0, prompts_1.confirm)({
225
+ message: 'Continue without isolation?',
226
+ default: false,
227
+ });
228
+ if (proceed) {
229
+ config_store_1.ConfigStore.setDirectModeConsent(true);
230
+ console.log(chalk_1.default.dim('\n (This warning won\'t be shown again)\n'));
231
+ }
232
+ return proceed;
233
+ }
234
+ catch {
235
+ return false;
236
+ }
237
+ }
238
+ /**
239
+ * Create and attach to a session on a genbox
240
+ */
241
+ async function startSessionOnGenbox(provider, genbox) {
242
+ const keyPath = getPrivateSshKey();
243
+ const sessionName = `${provider}-${Date.now().toString(36)}`;
244
+ const cliCommand = provider === 'claude'
245
+ ? 'claude --dangerously-skip-permissions'
246
+ : provider === 'gemini'
247
+ ? 'gemini'
248
+ : 'codex';
249
+ console.log(chalk_1.default.dim(`\nStarting ${provider} on ${genbox.name}...`));
250
+ // Ensure dtach is installed
251
+ await ensureDtachInstalled(genbox.ipAddress, keyPath);
252
+ // Create session in background using nohup with dtach
253
+ const socketDir = getRemoteDtachSocketDir();
254
+ const socketPath = `${socketDir}/${sessionName}.sock`;
255
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${genbox.ipAddress} "mkdir -p ${socketDir} && nohup dtach -n ${socketPath} bash -c 'source ~/.nvm/nvm.sh 2>/dev/null; ${cliCommand}' > /dev/null 2>&1 &"`, { timeout: 30000 });
256
+ // Wait for session to start
257
+ await new Promise(resolve => setTimeout(resolve, 1000));
258
+ console.log(chalk_1.default.green(`Session created: ${sessionName}`));
259
+ console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
260
+ // Attach to session
261
+ const sshArgs = [
262
+ '-t',
263
+ '-i', keyPath,
264
+ '-o', 'StrictHostKeyChecking=no',
265
+ '-o', 'UserKnownHostsFile=/dev/null',
266
+ '-o', 'LogLevel=ERROR',
267
+ `dev@${genbox.ipAddress}`,
268
+ `dtach -a ${socketPath}`,
269
+ ];
270
+ const proc = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
271
+ await new Promise(resolve => proc.on('close', () => resolve()));
272
+ console.log(chalk_1.default.dim('\nDetached from session.'));
273
+ console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} --attach ${sessionName}`)}`));
274
+ }
275
+ /**
276
+ * Attach to an existing session
277
+ */
278
+ async function attachToSession(session) {
279
+ if (session.location === 'direct') {
280
+ // Local dtach session
281
+ const socketPath = path.join(getDtachSocketDir(), `${session.name}.sock`);
282
+ console.log(chalk_1.default.dim(`\nAttaching to ${session.name}...`));
283
+ console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
284
+ const proc = (0, child_process_1.spawn)('dtach', ['-a', socketPath], { stdio: 'inherit' });
285
+ await new Promise(resolve => proc.on('close', () => resolve()));
286
+ console.log(chalk_1.default.dim('\nDetached from session.'));
287
+ }
288
+ else {
289
+ // Cloud genbox session
290
+ const keyPath = getPrivateSshKey();
291
+ const socketDir = getRemoteDtachSocketDir();
292
+ const socketPath = `${socketDir}/${session.name}.sock`;
293
+ console.log(chalk_1.default.dim(`\nAttaching to ${session.name} on ${session.genboxName}...`));
294
+ console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
295
+ const sshArgs = [
296
+ '-t',
297
+ '-i', keyPath,
298
+ '-o', 'StrictHostKeyChecking=no',
299
+ '-o', 'UserKnownHostsFile=/dev/null',
300
+ '-o', 'LogLevel=ERROR',
301
+ `dev@${session.ipAddress}`,
302
+ `dtach -a ${socketPath}`,
303
+ ];
304
+ const proc = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
305
+ await new Promise(resolve => proc.on('close', () => resolve()));
306
+ console.log(chalk_1.default.dim('\nDetached from session.'));
307
+ }
308
+ }
309
+ /**
310
+ * Start a direct (no isolation) session
311
+ */
312
+ async function startDirectSession(provider) {
313
+ // Check for dtach
314
+ const dtachStatus = await ensureLocalDtach();
315
+ if (dtachStatus === 'cancel') {
316
+ console.log(chalk_1.default.dim('\nCancelled.'));
317
+ return;
318
+ }
319
+ const useDtach = dtachStatus === 'available';
320
+ ensureLocalSocketDir();
321
+ const sessionName = `${provider}-${Date.now().toString(36)}`;
322
+ const cliCommand = provider === 'claude'
323
+ ? 'claude --dangerously-skip-permissions'
324
+ : provider === 'gemini'
325
+ ? 'gemini'
326
+ : 'codex';
327
+ console.log(chalk_1.default.dim(`\nStarting ${provider} session...`));
328
+ if (useDtach) {
329
+ console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
330
+ }
331
+ else {
332
+ console.log(chalk_1.default.dim('Note: Session will end when you exit (no persistence)\n'));
333
+ }
334
+ const socketPath = path.join(getDtachSocketDir(), `${sessionName}.sock`);
335
+ let cmd;
336
+ if (useDtach) {
337
+ cmd = `dtach -A "${socketPath}" bash -c '${cliCommand}'`;
338
+ }
339
+ else {
340
+ cmd = cliCommand;
341
+ }
342
+ const proc = (0, child_process_1.spawn)('bash', ['-c', cmd], { stdio: 'inherit' });
343
+ await new Promise((resolve) => {
344
+ proc.on('close', () => {
345
+ if (useDtach && fs.existsSync(socketPath)) {
346
+ console.log(chalk_1.default.dim('\nDetached from session (still running in background).'));
347
+ console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} --attach ${sessionName}`)}`));
348
+ }
349
+ else {
350
+ console.log(chalk_1.default.dim('\nSession ended.'));
351
+ }
352
+ resolve();
353
+ });
354
+ });
355
+ }
356
+ /**
357
+ * Main interactive flow
358
+ */
359
+ async function runInteractiveFlow(provider) {
360
+ console.log(chalk_1.default.dim('\nFetching genboxes and sessions...'));
361
+ // Gather data in parallel
362
+ const [genboxes, existingSessions] = await Promise.all([
363
+ getGenboxes(),
364
+ getExistingSessions(provider),
365
+ ]);
366
+ const choices = [];
367
+ // Option 1: On a Genbox (Recommended)
368
+ if (genboxes.length > 0) {
369
+ choices.push({
370
+ name: chalk_1.default.green('●') + ' On a Genbox ' + chalk_1.default.dim('(Recommended)'),
371
+ value: { type: 'select-genbox' },
372
+ });
373
+ }
374
+ else {
375
+ choices.push({
376
+ name: chalk_1.default.green('+') + ' Create a Genbox ' + chalk_1.default.dim('(Recommended)'),
377
+ value: { type: 'create-genbox' },
378
+ });
379
+ }
380
+ // Option 2: Attach to existing session (only if sessions exist)
381
+ if (existingSessions.length > 0) {
382
+ choices.push({
383
+ name: chalk_1.default.dim('─────────────────────────────────'),
384
+ value: { type: 'cancel' },
385
+ disabled: true,
386
+ });
387
+ for (const session of existingSessions) {
388
+ const locationLabel = session.location === 'genbox'
389
+ ? chalk_1.default.blue(`on ${session.genboxName}`)
390
+ : chalk_1.default.yellow('direct');
391
+ choices.push({
392
+ name: ` ${chalk_1.default.cyan('↩')} Attach to ${session.name} ${chalk_1.default.dim(`(${locationLabel})`)}`,
393
+ value: { type: 'attach', session },
394
+ });
395
+ }
396
+ }
397
+ // Option 3: Run directly (no isolation)
398
+ choices.push({
399
+ name: chalk_1.default.dim('─────────────────────────────────'),
400
+ value: { type: 'cancel' },
401
+ disabled: true,
402
+ });
403
+ choices.push({
404
+ name: chalk_1.default.dim(' Run directly on this machine (no isolation)'),
405
+ value: { type: 'direct' },
406
+ });
407
+ // Show menu
408
+ console.log('');
409
+ let action;
410
+ try {
411
+ action = await (0, select_1.default)({
412
+ message: `How would you like to run ${chalk_1.default.bold(provider)}?`,
413
+ choices: choices.filter(c => !c.disabled),
414
+ });
415
+ }
416
+ catch {
417
+ console.log(chalk_1.default.dim('\nCancelled.'));
418
+ return;
419
+ }
420
+ // Handle selection
421
+ switch (action.type) {
422
+ case 'select-genbox': {
423
+ // Show genbox selection
424
+ const genboxChoices = [
425
+ ...genboxes.map(g => ({
426
+ name: `${g.name} ${chalk_1.default.dim(`(${g.ipAddress})`)}`,
427
+ value: g,
428
+ })),
429
+ {
430
+ name: chalk_1.default.dim('─────────────────────────────────'),
431
+ value: null,
432
+ disabled: true,
433
+ },
434
+ {
435
+ name: chalk_1.default.green('+') + ' Create new Genbox',
436
+ value: null,
437
+ },
438
+ ];
439
+ let selectedGenbox;
440
+ try {
441
+ selectedGenbox = await (0, select_1.default)({
442
+ message: 'Select a Genbox:',
443
+ choices: genboxChoices.filter(c => !c.disabled),
444
+ });
445
+ }
446
+ catch {
447
+ console.log(chalk_1.default.dim('\nCancelled.'));
448
+ return;
449
+ }
450
+ if (selectedGenbox === null) {
451
+ // Create new genbox
452
+ console.log(chalk_1.default.yellow('\nTo create a new Genbox, run:'));
453
+ console.log(chalk_1.default.cyan(' gb create <name>\n'));
454
+ return;
455
+ }
456
+ await startSessionOnGenbox(provider, selectedGenbox);
457
+ break;
458
+ }
459
+ case 'create-genbox':
460
+ console.log(chalk_1.default.yellow('\nTo create a Genbox, run:'));
461
+ console.log(chalk_1.default.cyan(' gb create <name>\n'));
462
+ break;
463
+ case 'attach':
464
+ await attachToSession(action.session);
465
+ break;
466
+ case 'direct': {
467
+ const proceed = await showDirectModeWarning();
468
+ if (!proceed) {
469
+ console.log(chalk_1.default.dim('\nCancelled.'));
470
+ return;
471
+ }
472
+ await startDirectSession(provider);
473
+ break;
474
+ }
475
+ case 'cancel':
476
+ console.log(chalk_1.default.dim('\nCancelled.'));
477
+ break;
478
+ }
479
+ }
480
+ /**
481
+ * Create a provider command (claude, gemini, or codex)
482
+ */
483
+ function createProviderCommand(provider) {
484
+ const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
485
+ return new commander_1.Command(provider)
486
+ .description(`Start or attach to a ${providerName} AI session`)
487
+ .option('--on <genbox>', 'Use specific genbox')
488
+ .option('--direct', 'Run directly on this machine (no isolation)')
489
+ .option('--attach <session>', 'Attach to existing session')
490
+ .option('--list', 'List all sessions')
491
+ .option('--kill', 'Kill session')
492
+ .option('-y, --yes', 'Skip confirmations')
493
+ .addHelpText('after', `
494
+ Examples:
495
+ gb ${provider} Interactive mode
496
+ gb ${provider} --on my-genbox Use specific genbox
497
+ gb ${provider} --attach swift-fox Attach to existing session
498
+ gb ${provider} --direct Run without isolation
499
+ gb ${provider} --list List all ${provider} sessions
500
+ `)
501
+ .action(async (options) => {
502
+ try {
503
+ // --list: Show all sessions for this provider
504
+ if (options.list) {
505
+ console.log(chalk_1.default.dim(`\nFetching ${provider} sessions...`));
506
+ const sessions = await getExistingSessions(provider);
507
+ if (sessions.length === 0) {
508
+ console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
509
+ return;
510
+ }
511
+ console.log('');
512
+ for (const session of sessions) {
513
+ const locationLabel = session.location === 'genbox'
514
+ ? chalk_1.default.blue(`on ${session.genboxName}`)
515
+ : chalk_1.default.yellow('direct');
516
+ console.log(` ${chalk_1.default.green('●')} ${session.name} ${chalk_1.default.dim(`(${locationLabel})`)}`);
517
+ }
518
+ console.log('');
519
+ return;
520
+ }
521
+ // --attach: Attach to specific session
522
+ if (options.attach) {
523
+ const sessions = await getExistingSessions(provider);
524
+ const session = sessions.find(s => s.name === options.attach || s.name.includes(options.attach));
525
+ if (!session) {
526
+ console.log(chalk_1.default.red(`\nSession not found: ${options.attach}`));
527
+ console.log(chalk_1.default.dim(`Run 'gb ${provider} --list' to see available sessions.\n`));
528
+ return;
529
+ }
530
+ await attachToSession(session);
531
+ return;
532
+ }
533
+ // --on: Use specific genbox
534
+ if (options.on) {
535
+ const genboxes = await getGenboxes();
536
+ const genbox = genboxes.find(g => g.name === options.on || g.name.includes(options.on));
537
+ if (!genbox) {
538
+ console.log(chalk_1.default.red(`\nGenbox not found: ${options.on}`));
539
+ console.log(chalk_1.default.dim('Run \'gb list\' to see available genboxes.\n'));
540
+ return;
541
+ }
542
+ await startSessionOnGenbox(provider, genbox);
543
+ return;
544
+ }
545
+ // --direct: Run without isolation
546
+ if (options.direct) {
547
+ if (!options.yes) {
548
+ const proceed = await showDirectModeWarning();
549
+ if (!proceed) {
550
+ console.log(chalk_1.default.dim('\nCancelled.'));
551
+ return;
552
+ }
553
+ }
554
+ await startDirectSession(provider);
555
+ return;
556
+ }
557
+ // Default: Interactive mode
558
+ await runInteractiveFlow(provider);
559
+ }
560
+ catch (error) {
561
+ if (error instanceof api_1.AuthenticationError) {
562
+ (0, api_1.handleApiError)(error);
563
+ return;
564
+ }
565
+ if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
566
+ console.log(chalk_1.default.dim('\nCancelled.'));
567
+ return;
568
+ }
569
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
570
+ }
571
+ });
572
+ }
573
+ // Export individual commands
574
+ exports.claudeCommand = createProviderCommand('claude');
575
+ exports.geminiCommand = createProviderCommand('gemini');
576
+ exports.codexCommand = createProviderCommand('codex');
@@ -59,6 +59,7 @@ const local_genbox_provisioner_1 = require("../../lib/local-genbox-provisioner")
59
59
  const native_session_manager_1 = require("../../lib/native-session-manager");
60
60
  const hooks_configurator_1 = require("../../lib/hooks-configurator");
61
61
  const api_1 = require("../../api");
62
+ const genbox_selector_1 = require("../../genbox-selector");
62
63
  const prompts_1 = require("@inquirer/prompts");
63
64
  const list_1 = require("./list");
64
65
  const start_1 = require("./start");
@@ -675,7 +676,7 @@ function isDtachSocketAlive(socketPath) {
675
676
  try {
676
677
  // Try to send an empty string to test if socket is alive
677
678
  // Using timeout to avoid hanging on dead sockets
678
- (0, child_process_1.execSync)(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
679
+ (0, child_process_1.execSync)(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
679
680
  timeout: 2000,
680
681
  stdio: 'ignore'
681
682
  });
@@ -698,7 +699,7 @@ function isDtachSocketAlive(socketPath) {
698
699
  */
699
700
  function isRemoteDtachSocketAlive(ipAddress, keyPath, socketPath) {
700
701
  try {
701
- (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 dev@${ipAddress} "echo -n '' | dtach -p ${socketPath}" 2>/dev/null`, { timeout: 5000, stdio: 'ignore' });
702
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 dev@${ipAddress} "printf '' | dtach -p ${socketPath}" 2>/dev/null`, { timeout: 5000, stdio: 'ignore' });
702
703
  return true;
703
704
  }
704
705
  catch {
@@ -1056,11 +1057,16 @@ async function getAllGenboxesWithSessions(options) {
1056
1057
  }
1057
1058
  // Get cloud genboxes
1058
1059
  try {
1059
- const endpoint = options.all
1060
- ? '/genboxes'
1061
- : `/genboxes?projectPath=${encodeURIComponent(process.cwd())}`;
1062
- const response = await (0, api_1.fetchApi)(endpoint);
1063
- const genboxes = response?.genboxes || [];
1060
+ // Fetch all genboxes and filter by project name (same approach as gb list)
1061
+ const response = await (0, api_1.fetchApi)('/genboxes');
1062
+ let genboxes = response || [];
1063
+ // Filter by project name unless --all is specified
1064
+ if (!options.all) {
1065
+ const projectName = (0, genbox_selector_1.getProjectContext)();
1066
+ if (projectName) {
1067
+ genboxes = genboxes.filter(g => g.project === projectName || g.workspace === projectName);
1068
+ }
1069
+ }
1064
1070
  for (const genbox of genboxes) {
1065
1071
  if (genbox.status !== 'running' || !genbox.ipAddress) {
1066
1072
  continue;
@@ -66,5 +66,14 @@ class ConfigStore {
66
66
  fs.unlinkSync(CONFIG_FILE);
67
67
  }
68
68
  }
69
+ static hasDirectModeConsent() {
70
+ const config = this.load();
71
+ return config.directModeConsent === true;
72
+ }
73
+ static setDirectModeConsent(consent) {
74
+ const config = this.load();
75
+ config.directModeConsent = consent;
76
+ this.save(config);
77
+ }
69
78
  }
70
79
  exports.ConfigStore = ConfigStore;
package/dist/index.js CHANGED
@@ -71,6 +71,7 @@ const start_1 = require("./commands/start");
71
71
  const template_1 = require("./commands/template");
72
72
  const run_prompt_1 = require("./commands/run-prompt");
73
73
  const session_1 = require("./commands/session");
74
+ const provider_command_1 = require("./commands/provider-command");
74
75
  const cp_1 = require("./commands/cp");
75
76
  const completion_1 = require("./commands/completion");
76
77
  const doctor_1 = require("./commands/doctor");
@@ -129,12 +130,17 @@ if (earlyArgs.includes('--version') || earlyArgs.includes('-v')) {
129
130
  }
130
131
  }
131
132
  const commandCategories = {
133
+ 'AI SESSIONS': [
134
+ { name: 'claude', aliases: [], description: 'Start Claude AI session' },
135
+ { name: 'gemini', aliases: [], description: 'Start Gemini AI session' },
136
+ { name: 'codex', aliases: [], description: 'Start Codex AI session' },
137
+ { name: 'session', aliases: [], description: 'Advanced session management' },
138
+ ],
132
139
  'CORE COMMANDS': [
133
140
  { name: 'create', aliases: [], description: 'Create a new Genbox environment' },
134
141
  { name: 'list', aliases: ['ls'], description: 'List genboxes' },
135
142
  { name: 'destroy', aliases: ['delete'], description: 'Destroy a Genbox' },
136
143
  { name: 'connect', aliases: [], description: 'SSH into a Genbox' },
137
- { name: 'session', aliases: [], description: 'Manage AI CLI sessions (local & cloud)' },
138
144
  { name: 'status', aliases: [], description: 'Check Genbox status and services' },
139
145
  ],
140
146
  'LIFECYCLE': [
@@ -262,6 +268,9 @@ program
262
268
  .addCommand(destroy_1.destroyCommand)
263
269
  .addCommand(connect_1.connectCommand)
264
270
  .addCommand(session_1.sessionCommand)
271
+ .addCommand(provider_command_1.claudeCommand)
272
+ .addCommand(provider_command_1.geminiCommand)
273
+ .addCommand(provider_command_1.codexCommand)
265
274
  .addCommand(cp_1.cpCommand)
266
275
  .addCommand(balance_1.balanceCommand)
267
276
  .addCommand(login_1.loginCommand)
@@ -311,7 +311,7 @@ class LocalSessionManager {
311
311
  }
312
312
  try {
313
313
  // Try to send an empty string to test if socket is alive
314
- (0, child_process_1.execSync)(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
314
+ (0, child_process_1.execSync)(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
315
315
  timeout: 2000,
316
316
  stdio: 'ignore'
317
317
  });
@@ -254,7 +254,7 @@ class NativeSessionManager {
254
254
  try {
255
255
  // Try to send an empty string to test if socket is alive
256
256
  const { execSync } = require('child_process');
257
- execSync(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
257
+ execSync(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
258
258
  timeout: 2000,
259
259
  stdio: 'ignore'
260
260
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.183",
3
+ "version": "1.0.185",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {