genbox 1.0.196 → 1.0.197

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.
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -40,6 +73,8 @@ exports.newCommand = new commander_1.Command('new')
40
73
  .argument('[name]', 'Project name')
41
74
  .option('--list', 'List available templates')
42
75
  .option('-s, --size <size>', 'Genbox size: small, medium, large, xl')
76
+ .option('-l, --local', 'Create local genbox (Docker)')
77
+ .option('--cloud', 'Create cloud genbox (default)')
43
78
  .option('-y, --yes', 'Skip interactive prompts, use defaults')
44
79
  .action(async (templateArg, nameArg, options) => {
45
80
  try {
@@ -126,6 +161,24 @@ exports.newCommand = new commander_1.Command('new')
126
161
  projectName = `${template.name}-${(0, random_name_1.generateRandomName)()}`;
127
162
  console.log(chalk_1.default.dim(`Generated name: ${projectName}`));
128
163
  }
164
+ // Local vs Cloud selection
165
+ let isLocal = options.local || false;
166
+ if (!options.local && !options.cloud && !options.yes) {
167
+ const location = await (0, select_1.default)({
168
+ message: 'Where to create genbox?',
169
+ choices: [
170
+ { name: `${chalk_1.default.cyan('☁')} Cloud ${chalk_1.default.dim('- Remote server, access anywhere')}`, value: 'cloud' },
171
+ { name: `${chalk_1.default.green('🖥')} Local ${chalk_1.default.dim('- Docker container on this machine')}`, value: 'local' },
172
+ ],
173
+ default: 'cloud',
174
+ });
175
+ isLocal = location === 'local';
176
+ }
177
+ // Handle local creation
178
+ if (isLocal) {
179
+ await createLocalFromTemplate(template, projectName);
180
+ return;
181
+ }
129
182
  // Size selection
130
183
  let size = options.size || preferences.defaultGenboxSize || template.recommendedSize;
131
184
  if (!options.yes && !options.size) {
@@ -328,6 +381,93 @@ exports.newCommand = new commander_1.Command('new')
328
381
  console.error(chalk_1.default.red('Error:'), error.message);
329
382
  }
330
383
  });
384
+ /**
385
+ * Create a local genbox from a starter template using Docker
386
+ */
387
+ async function createLocalFromTemplate(template, projectName) {
388
+ console.log('');
389
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
390
+ console.log(` ${chalk_1.default.bold('Template:')} ${template.icon} ${template.displayName}`);
391
+ console.log(` ${chalk_1.default.bold('Name:')} ${projectName}`);
392
+ console.log(` ${chalk_1.default.bold('Location:')} ${chalk_1.default.green('Local (Docker)')}`);
393
+ console.log(` ${chalk_1.default.bold('Features:')} ${template.features.join(', ')}`);
394
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
395
+ const confirmed = await (0, confirm_1.default)({
396
+ message: `Create local genbox '${projectName}'?`,
397
+ default: true,
398
+ });
399
+ if (!confirmed) {
400
+ console.log(chalk_1.default.dim('Cancelled.'));
401
+ return;
402
+ }
403
+ const spinner = (0, ora_1.default)('Creating local genbox...').start();
404
+ try {
405
+ // Build the create command for the template
406
+ let createCmd;
407
+ if (template.createCommand) {
408
+ createCmd = template.createCommand.replace(/\{\{name\}\}/g, projectName);
409
+ }
410
+ else if (template.repoUrl) {
411
+ createCmd = `git clone https://${template.repoUrl} ${projectName}`;
412
+ }
413
+ else {
414
+ spinner.fail('Template does not support local creation');
415
+ return;
416
+ }
417
+ // Run the create command locally
418
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
419
+ spinner.text = 'Setting up project...';
420
+ execSync(createCmd, {
421
+ stdio: 'pipe',
422
+ cwd: process.cwd(),
423
+ });
424
+ // Run setup commands if any
425
+ if (template.setupCommands && template.setupCommands.length > 0) {
426
+ const projectPath = `${process.cwd()}/${projectName}`;
427
+ for (const cmd of template.setupCommands) {
428
+ const setupCmd = cmd.replace(/\{\{name\}\}/g, projectName);
429
+ spinner.text = `Running: ${setupCmd}`;
430
+ try {
431
+ execSync(setupCmd, {
432
+ stdio: 'pipe',
433
+ cwd: projectPath,
434
+ });
435
+ }
436
+ catch {
437
+ // Some setup commands may fail, continue
438
+ }
439
+ }
440
+ }
441
+ spinner.succeed(chalk_1.default.green(`Local project '${projectName}' created!`));
442
+ console.log('');
443
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
444
+ console.log(` ${chalk_1.default.bold('Name:')} ${projectName}`);
445
+ console.log(` ${chalk_1.default.bold('Path:')} ${process.cwd()}/${projectName}`);
446
+ console.log(` ${chalk_1.default.bold('Type:')} Local`);
447
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
448
+ console.log('');
449
+ console.log(chalk_1.default.bold('Next steps:'));
450
+ console.log(` 1. ${chalk_1.default.cyan(`cd ${projectName}`)}`);
451
+ console.log(` 2. ${chalk_1.default.cyan('pnpm install')} ${chalk_1.default.dim('(or npm install)')}`);
452
+ console.log(` 3. ${chalk_1.default.cyan('pnpm dev')} ${chalk_1.default.dim('(or npm run dev)')}`);
453
+ console.log('');
454
+ console.log(chalk_1.default.dim('To start a Claude session in this project:'));
455
+ console.log(` ${chalk_1.default.cyan(`cd ${projectName} && claude`)}`);
456
+ console.log('');
457
+ }
458
+ catch (error) {
459
+ spinner.fail(chalk_1.default.red(`Failed to create local project: ${error.message}`));
460
+ if (error.message.includes('command not found')) {
461
+ console.log(chalk_1.default.yellow('\nMake sure the required tools are installed:'));
462
+ if (template.createCommand?.includes('npx')) {
463
+ console.log(chalk_1.default.dim(' - Node.js and npx'));
464
+ }
465
+ if (template.createCommand?.includes('bunx')) {
466
+ console.log(chalk_1.default.dim(' - Bun (https://bun.sh)'));
467
+ }
468
+ }
469
+ }
470
+ }
331
471
  async function listTemplates() {
332
472
  const spinner = (0, ora_1.default)('Fetching templates...').start();
333
473
  try {
@@ -179,13 +179,42 @@ async function getGenboxes(includesStopped = true) {
179
179
  */
180
180
  async function getExistingSessions(provider) {
181
181
  const sessions = [];
182
- // Check local (direct) sessions
182
+ const seenNames = new Set();
183
+ // Check UnifiedSessionManager for registered sessions
184
+ try {
185
+ const sessionManager = (0, unified_session_1.getUnifiedSessionManager)();
186
+ const registeredSessions = sessionManager.listSessions({
187
+ provider: provider,
188
+ status: ['starting', 'running', 'active', 'idle'],
189
+ });
190
+ for (const s of registeredSessions) {
191
+ // Verify the session is still alive (socket exists)
192
+ const socketPath = s.infrastructure?.dtachSocketPath ||
193
+ path.join(getDtachSocketDir(), `${s.name}.sock`);
194
+ if (isDtachSocketAlive(socketPath)) {
195
+ sessions.push({
196
+ name: s.name,
197
+ provider,
198
+ location: 'direct',
199
+ });
200
+ seenNames.add(s.name);
201
+ }
202
+ }
203
+ }
204
+ catch {
205
+ // Registry not available, fall back to socket scanning
206
+ }
207
+ // Also check local (direct) sessions by scanning socket directory
208
+ // (for backwards compatibility with sessions created before the registry)
183
209
  const socketDir = getDtachSocketDir();
184
210
  if (fs.existsSync(socketDir)) {
185
211
  const files = fs.readdirSync(socketDir).filter(f => f.endsWith('.sock'));
186
212
  for (const file of files) {
187
213
  const socketPath = path.join(socketDir, file);
188
214
  const sessionName = file.replace('.sock', '');
215
+ // Skip if already added from registry
216
+ if (seenNames.has(sessionName))
217
+ continue;
189
218
  // Check if this session is for our provider
190
219
  if (sessionName.toLowerCase().includes(provider)) {
191
220
  if (isDtachSocketAlive(socketPath)) {
@@ -318,7 +347,9 @@ async function startSessionOnGenbox(provider, genbox) {
318
347
  // Create session in background using nohup with dtach
319
348
  const socketDir = getRemoteDtachSocketDir();
320
349
  const socketPath = `${socketDir}/${sessionName}.sock`;
321
- (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${targetGenbox.ipAddress} "mkdir -p ${socketDir} && nohup dtach -n ${socketPath} bash -c 'source ~/.nvm/nvm.sh 2>/dev/null; ${cliCommand}' > /dev/null 2>&1 &"`, { timeout: 30000 });
350
+ // Source .bashrc exports (GENBOX_PROJECT_DIR) and nvm, then cd to project directory
351
+ // Note: We use grep to extract just the export line since .bashrc may have early return for non-interactive shells
352
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${targetGenbox.ipAddress} "mkdir -p ${socketDir} && nohup dtach -n ${socketPath} bash -c 'eval \\$(grep GENBOX_PROJECT_DIR ~/.bashrc 2>/dev/null); cd \\$GENBOX_PROJECT_DIR 2>/dev/null; source ~/.nvm/nvm.sh 2>/dev/null; ${cliCommand}' > /dev/null 2>&1 &"`, { timeout: 30000 });
322
353
  // Wait for session to start
323
354
  await new Promise(resolve => setTimeout(resolve, 1000));
324
355
  console.log(chalk_1.default.green(`Session created: ${sessionName}`));
@@ -680,59 +711,321 @@ async function runInteractiveFlow(provider) {
680
711
  break;
681
712
  }
682
713
  }
714
+ /**
715
+ * Create list subcommand for a provider
716
+ */
717
+ function createListSubcommand(provider) {
718
+ return new commander_1.Command('list')
719
+ .description(`List all ${provider} sessions`)
720
+ .action(async () => {
721
+ try {
722
+ console.log(chalk_1.default.dim(`\nFetching ${provider} sessions...`));
723
+ const result = await (0, unified_session_1.listAllSessions)({ provider });
724
+ (0, unified_session_1.displaySimpleList)(result, provider);
725
+ }
726
+ catch (error) {
727
+ if (error instanceof api_1.AuthenticationError) {
728
+ (0, api_1.handleApiError)(error);
729
+ return;
730
+ }
731
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
732
+ }
733
+ });
734
+ }
735
+ /**
736
+ * Create attach subcommand for a provider
737
+ */
738
+ function createAttachSubcommand(provider) {
739
+ return new commander_1.Command('attach')
740
+ .description(`Attach to a ${provider} session`)
741
+ .argument('[session]', 'Session name to attach to')
742
+ .action(async (sessionName) => {
743
+ try {
744
+ const sessions = await getExistingSessions(provider);
745
+ if (sessions.length === 0) {
746
+ console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
747
+ return;
748
+ }
749
+ let session;
750
+ if (sessionName) {
751
+ const found = sessions.find(s => s.name === sessionName || s.name.includes(sessionName));
752
+ if (!found) {
753
+ console.log(chalk_1.default.red(`\nSession not found: ${sessionName}`));
754
+ console.log(chalk_1.default.dim(`Run 'gb ${provider} list' to see available sessions.\n`));
755
+ return;
756
+ }
757
+ session = found;
758
+ }
759
+ else if (sessions.length === 1) {
760
+ // Auto-select if only one session
761
+ session = sessions[0];
762
+ console.log(chalk_1.default.dim(`\nAttaching to ${session.name}...`));
763
+ }
764
+ else {
765
+ // Show selection menu
766
+ const sessionChoices = sessions.map(s => ({
767
+ name: `${s.name} ${chalk_1.default.dim(`(${s.location === 'genbox' ? `on ${s.genboxName}` : 'direct'})`)}`,
768
+ value: s,
769
+ }));
770
+ try {
771
+ session = await (0, select_1.default)({
772
+ message: 'Select session to attach:',
773
+ choices: sessionChoices,
774
+ });
775
+ }
776
+ catch {
777
+ console.log(chalk_1.default.dim('\nCancelled.'));
778
+ return;
779
+ }
780
+ }
781
+ await attachToSession(session);
782
+ }
783
+ catch (error) {
784
+ if (error instanceof api_1.AuthenticationError) {
785
+ (0, api_1.handleApiError)(error);
786
+ return;
787
+ }
788
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
789
+ }
790
+ });
791
+ }
792
+ /**
793
+ * Create stop subcommand for a provider
794
+ */
795
+ function createStopSubcommand(provider) {
796
+ return new commander_1.Command('stop')
797
+ .description(`Stop a ${provider} session`)
798
+ .argument('[session]', 'Session name to stop')
799
+ .action(async (sessionName) => {
800
+ try {
801
+ const sessions = await getExistingSessions(provider);
802
+ if (sessions.length === 0) {
803
+ console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
804
+ return;
805
+ }
806
+ let sessionToStop;
807
+ if (sessionName) {
808
+ const found = sessions.find(s => s.name === sessionName || s.name.includes(sessionName));
809
+ if (!found) {
810
+ console.log(chalk_1.default.red(`\nSession not found: ${sessionName}`));
811
+ console.log(chalk_1.default.dim(`Run 'gb ${provider} list' to see available sessions.\n`));
812
+ return;
813
+ }
814
+ sessionToStop = found;
815
+ }
816
+ else if (sessions.length === 1) {
817
+ sessionToStop = sessions[0];
818
+ console.log(chalk_1.default.dim(`\nStopping ${sessionToStop.name}...`));
819
+ }
820
+ else {
821
+ const sessionChoices = sessions.map(s => ({
822
+ name: `${s.name} ${chalk_1.default.dim(`(${s.location === 'genbox' ? `on ${s.genboxName}` : 'direct'})`)}`,
823
+ value: s,
824
+ }));
825
+ try {
826
+ sessionToStop = await (0, select_1.default)({
827
+ message: 'Select session to stop:',
828
+ choices: sessionChoices,
829
+ });
830
+ }
831
+ catch {
832
+ console.log(chalk_1.default.dim('\nCancelled.'));
833
+ return;
834
+ }
835
+ }
836
+ // Stop is same as kill for dtach sessions
837
+ const success = await killSession(sessionToStop);
838
+ if (success) {
839
+ console.log(chalk_1.default.green(`\n Stopped session: ${sessionToStop.name}\n`));
840
+ }
841
+ else {
842
+ console.log(chalk_1.default.red(`\n Failed to stop session: ${sessionToStop.name}\n`));
843
+ }
844
+ }
845
+ catch (error) {
846
+ if (error instanceof api_1.AuthenticationError) {
847
+ (0, api_1.handleApiError)(error);
848
+ return;
849
+ }
850
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
851
+ }
852
+ });
853
+ }
854
+ /**
855
+ * Create kill subcommand for a provider
856
+ */
857
+ function createKillSubcommand(provider) {
858
+ return new commander_1.Command('kill')
859
+ .description(`Kill a ${provider} session`)
860
+ .argument('[session]', 'Session name to kill')
861
+ .option('-y, --yes', 'Skip confirmation')
862
+ .action(async (sessionName, options) => {
863
+ try {
864
+ const sessions = await getExistingSessions(provider);
865
+ if (sessions.length === 0) {
866
+ console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
867
+ return;
868
+ }
869
+ let sessionToKill;
870
+ if (sessionName) {
871
+ const found = sessions.find(s => s.name === sessionName || s.name.includes(sessionName));
872
+ if (!found) {
873
+ console.log(chalk_1.default.red(`\nSession not found: ${sessionName}`));
874
+ console.log(chalk_1.default.dim(`Run 'gb ${provider} list' to see available sessions.\n`));
875
+ return;
876
+ }
877
+ sessionToKill = found;
878
+ }
879
+ else if (sessions.length === 1) {
880
+ sessionToKill = sessions[0];
881
+ }
882
+ else {
883
+ const sessionChoices = sessions.map(s => ({
884
+ name: `${s.name} ${chalk_1.default.dim(`(${s.location === 'genbox' ? `on ${s.genboxName}` : 'direct'})`)}`,
885
+ value: s,
886
+ }));
887
+ try {
888
+ sessionToKill = await (0, select_1.default)({
889
+ message: 'Select session to kill:',
890
+ choices: sessionChoices,
891
+ });
892
+ }
893
+ catch {
894
+ console.log(chalk_1.default.dim('\nCancelled.'));
895
+ return;
896
+ }
897
+ }
898
+ if (!options.yes) {
899
+ try {
900
+ const confirmed = await (0, prompts_1.confirm)({
901
+ message: `Kill session ${sessionToKill.name}?`,
902
+ default: false,
903
+ });
904
+ if (!confirmed) {
905
+ console.log(chalk_1.default.dim('\nCancelled.'));
906
+ return;
907
+ }
908
+ }
909
+ catch {
910
+ console.log(chalk_1.default.dim('\nCancelled.'));
911
+ return;
912
+ }
913
+ }
914
+ const success = await killSession(sessionToKill);
915
+ if (success) {
916
+ console.log(chalk_1.default.green(`\n Killed session: ${sessionToKill.name}\n`));
917
+ }
918
+ else {
919
+ console.log(chalk_1.default.red(`\n Failed to kill session: ${sessionToKill.name}\n`));
920
+ }
921
+ }
922
+ catch (error) {
923
+ if (error instanceof api_1.AuthenticationError) {
924
+ (0, api_1.handleApiError)(error);
925
+ return;
926
+ }
927
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
928
+ }
929
+ });
930
+ }
931
+ /**
932
+ * Create migrate subcommand for a provider
933
+ */
934
+ function createMigrateSubcommand(provider) {
935
+ return new commander_1.Command('migrate')
936
+ .description(`Migrate a ${provider} session to a different environment`)
937
+ .argument('[session]', 'Session name to migrate')
938
+ .option('--to <target>', 'Target environment (cloud, local, native)')
939
+ .action(async (sessionName, options) => {
940
+ try {
941
+ const sessions = await getExistingSessions(provider);
942
+ if (sessions.length === 0) {
943
+ console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
944
+ return;
945
+ }
946
+ let sessionToMigrate;
947
+ if (sessionName) {
948
+ const found = sessions.find(s => s.name === sessionName || s.name.includes(sessionName));
949
+ if (!found) {
950
+ console.log(chalk_1.default.red(`\nSession not found: ${sessionName}`));
951
+ console.log(chalk_1.default.dim(`Run 'gb ${provider} list' to see available sessions.\n`));
952
+ return;
953
+ }
954
+ sessionToMigrate = found;
955
+ }
956
+ else if (sessions.length === 1) {
957
+ sessionToMigrate = sessions[0];
958
+ }
959
+ else {
960
+ const sessionChoices = sessions.map(s => ({
961
+ name: `${s.name} ${chalk_1.default.dim(`(${s.location === 'genbox' ? `on ${s.genboxName}` : 'direct'})`)}`,
962
+ value: s,
963
+ }));
964
+ try {
965
+ sessionToMigrate = await (0, select_1.default)({
966
+ message: 'Select session to migrate:',
967
+ choices: sessionChoices,
968
+ });
969
+ }
970
+ catch {
971
+ console.log(chalk_1.default.dim('\nCancelled.'));
972
+ return;
973
+ }
974
+ }
975
+ // Determine target
976
+ let target = options.to;
977
+ if (!target) {
978
+ const currentLocation = sessionToMigrate.location === 'genbox' ? 'cloud' : 'local';
979
+ const targetChoices = [
980
+ { name: 'Cloud (Genbox)', value: 'cloud' },
981
+ { name: 'Local (Native)', value: 'native' },
982
+ ].filter(c => c.value !== currentLocation);
983
+ try {
984
+ target = await (0, select_1.default)({
985
+ message: `Migrate ${sessionToMigrate.name} to:`,
986
+ choices: targetChoices,
987
+ });
988
+ }
989
+ catch {
990
+ console.log(chalk_1.default.dim('\nCancelled.'));
991
+ return;
992
+ }
993
+ }
994
+ console.log(chalk_1.default.dim(`\nMigrating ${sessionToMigrate.name} to ${target}...`));
995
+ console.log(chalk_1.default.yellow('\nMigration not yet implemented. Use `gb session migrate` for full migration support.\n'));
996
+ }
997
+ catch (error) {
998
+ if (error instanceof api_1.AuthenticationError) {
999
+ (0, api_1.handleApiError)(error);
1000
+ return;
1001
+ }
1002
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
1003
+ }
1004
+ });
1005
+ }
683
1006
  /**
684
1007
  * Create a provider command (claude, gemini, or codex)
685
1008
  */
686
1009
  function createProviderCommand(provider) {
687
1010
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
688
- return new commander_1.Command(provider)
1011
+ const cmd = new commander_1.Command(provider)
689
1012
  .description(`Start or attach to a ${providerName} AI session`)
690
1013
  .option('--on <genbox>', 'Use specific genbox')
691
1014
  .option('--direct', 'Run directly on this machine (no isolation)')
692
- .option('--attach <session>', 'Attach to existing session')
693
- .option('--list', 'List all sessions')
694
- .option('--kill', 'Kill session')
695
1015
  .option('-y, --yes', 'Skip confirmations')
696
1016
  .addHelpText('after', `
697
1017
  Examples:
698
1018
  gb ${provider} Interactive mode
1019
+ gb ${provider} list List all ${provider} sessions
1020
+ gb ${provider} attach [session] Attach to a session
1021
+ gb ${provider} stop [session] Stop a session
1022
+ gb ${provider} kill [session] Kill a session
1023
+ gb ${provider} migrate [session] Migrate a session
699
1024
  gb ${provider} --on my-genbox Use specific genbox
700
- gb ${provider} --attach swift-fox Attach to existing session
701
1025
  gb ${provider} --direct Run without isolation
702
- gb ${provider} --list List all ${provider} sessions
703
1026
  `)
704
1027
  .action(async (options) => {
705
1028
  try {
706
- // --list: Show all sessions for this provider
707
- if (options.list) {
708
- console.log(chalk_1.default.dim(`\nFetching ${provider} sessions...`));
709
- const sessions = await getExistingSessions(provider);
710
- if (sessions.length === 0) {
711
- console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
712
- return;
713
- }
714
- console.log('');
715
- for (const session of sessions) {
716
- const locationLabel = session.location === 'genbox'
717
- ? chalk_1.default.blue(`on ${session.genboxName}`)
718
- : chalk_1.default.yellow('direct');
719
- console.log(` ${chalk_1.default.green('●')} ${session.name} ${chalk_1.default.dim(`(${locationLabel})`)}`);
720
- }
721
- console.log('');
722
- return;
723
- }
724
- // --attach: Attach to specific session
725
- if (options.attach) {
726
- const sessions = await getExistingSessions(provider);
727
- const session = sessions.find(s => s.name === options.attach || s.name.includes(options.attach));
728
- if (!session) {
729
- console.log(chalk_1.default.red(`\nSession not found: ${options.attach}`));
730
- console.log(chalk_1.default.dim(`Run 'gb ${provider} --list' to see available sessions.\n`));
731
- return;
732
- }
733
- await attachToSession(session);
734
- return;
735
- }
736
1029
  // --on: Use specific genbox
737
1030
  if (options.on) {
738
1031
  const genboxes = await getGenboxes();
@@ -757,61 +1050,6 @@ Examples:
757
1050
  await startDirectSession(provider);
758
1051
  return;
759
1052
  }
760
- // --kill: Kill a session
761
- if (options.kill) {
762
- const sessions = await getExistingSessions(provider);
763
- if (sessions.length === 0) {
764
- console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
765
- return;
766
- }
767
- // If only one session, kill it directly (with confirmation)
768
- let sessionToKill;
769
- if (sessions.length === 1) {
770
- sessionToKill = sessions[0];
771
- }
772
- else {
773
- // Let user select which session to kill
774
- const sessionChoices = sessions.map(s => ({
775
- name: `${s.name} ${chalk_1.default.dim(`(${s.location === 'genbox' ? `on ${s.genboxName}` : 'direct'})`)}`,
776
- value: s,
777
- }));
778
- try {
779
- sessionToKill = await (0, select_1.default)({
780
- message: 'Select session to kill:',
781
- choices: sessionChoices,
782
- });
783
- }
784
- catch {
785
- console.log(chalk_1.default.dim('\nCancelled.'));
786
- return;
787
- }
788
- }
789
- // Confirm unless --yes
790
- if (!options.yes) {
791
- try {
792
- const confirmed = await (0, prompts_1.confirm)({
793
- message: `Kill session ${sessionToKill.name}?`,
794
- default: false,
795
- });
796
- if (!confirmed) {
797
- console.log(chalk_1.default.dim('\nCancelled.'));
798
- return;
799
- }
800
- }
801
- catch {
802
- console.log(chalk_1.default.dim('\nCancelled.'));
803
- return;
804
- }
805
- }
806
- const success = await killSession(sessionToKill);
807
- if (success) {
808
- console.log(chalk_1.default.green(`\n Killed session: ${sessionToKill.name}\n`));
809
- }
810
- else {
811
- console.log(chalk_1.default.red(`\n Failed to kill session: ${sessionToKill.name}\n`));
812
- }
813
- return;
814
- }
815
1053
  // Default: Interactive mode
816
1054
  await runInteractiveFlow(provider);
817
1055
  }
@@ -827,6 +1065,13 @@ Examples:
827
1065
  console.error(chalk_1.default.red(`Error: ${error.message}`));
828
1066
  }
829
1067
  });
1068
+ // Add subcommands
1069
+ cmd.addCommand(createListSubcommand(provider));
1070
+ cmd.addCommand(createAttachSubcommand(provider));
1071
+ cmd.addCommand(createStopSubcommand(provider));
1072
+ cmd.addCommand(createKillSubcommand(provider));
1073
+ cmd.addCommand(createMigrateSubcommand(provider));
1074
+ return cmd;
830
1075
  }
831
1076
  // Export individual commands
832
1077
  exports.claudeCommand = createProviderCommand('claude');