genbox 1.0.177 → 1.0.179

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.
Files changed (35) hide show
  1. package/dist/api.js +13 -1
  2. package/dist/commands/attach.js +16 -16
  3. package/dist/commands/connect.js +24 -1
  4. package/dist/commands/create.js +262 -17
  5. package/dist/commands/destroy.js +95 -27
  6. package/dist/commands/init.js +9 -3
  7. package/dist/commands/list.js +260 -201
  8. package/dist/commands/logs.js +540 -0
  9. package/dist/commands/ps.js +73 -0
  10. package/dist/commands/session/index.js +1168 -81
  11. package/dist/commands/session/list.js +135 -17
  12. package/dist/commands/session/migrate.js +392 -0
  13. package/dist/commands/session/start.js +1 -1
  14. package/dist/commands/setup-local.js +266 -0
  15. package/dist/commands/status.js +175 -2
  16. package/dist/commands/urls.js +15 -2
  17. package/dist/genbox-selector.js +64 -20
  18. package/dist/index.js +9 -0
  19. package/dist/lib/context-exporter.js +314 -0
  20. package/dist/lib/context-importer.js +331 -0
  21. package/dist/lib/db-migrator.js +275 -0
  22. package/dist/lib/env-migrator.js +392 -0
  23. package/dist/lib/hooks-configurator.js +420 -0
  24. package/dist/lib/local-app-runner.js +507 -0
  25. package/dist/lib/local-db-seeder.js +368 -0
  26. package/dist/lib/local-genbox-provisioner.js +746 -0
  27. package/dist/lib/local-infra-provisioner.js +518 -0
  28. package/dist/lib/local-proxy-manager.js +526 -0
  29. package/dist/lib/local-session-manager.js +394 -1
  30. package/dist/lib/local-vm-provisioner.js +894 -0
  31. package/dist/lib/native-session-manager.js +468 -0
  32. package/dist/lib/session-migrator.js +501 -0
  33. package/dist/profile-resolver.js +1 -0
  34. package/dist/utils/env-parser.js +54 -1
  35. package/package.json +3 -2
package/dist/api.js CHANGED
@@ -58,6 +58,7 @@ async function fetchApi(endpoint, options = {}) {
58
58
  const token = config_store_1.ConfigStore.getToken();
59
59
  const headers = {
60
60
  'Content-Type': 'application/json',
61
+ 'Connection': 'close', // Ensure connection closes after request (prevents node from hanging)
61
62
  ...options.headers,
62
63
  };
63
64
  if (token) {
@@ -69,14 +70,25 @@ async function fetchApi(endpoint, options = {}) {
69
70
  console.log(chalk_1.default.dim(`[API DEBUG] POST ${endpoint}`));
70
71
  console.log(chalk_1.default.dim(`[API DEBUG] repos: ${JSON.stringify(bodyObj.repos, null, 2)}`));
71
72
  }
73
+ // Extract custom timeout, default to 15s for mutations, can be lower for reads
74
+ const { timeout = 15000, ...fetchOptions } = options;
72
75
  let response;
73
76
  try {
77
+ // Add timeout to prevent hanging on network issues
78
+ const controller = new AbortController();
79
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
74
80
  response = await fetch(url, {
75
- ...options,
81
+ ...fetchOptions,
76
82
  headers,
83
+ signal: controller.signal,
77
84
  });
85
+ clearTimeout(timeoutId);
78
86
  }
79
87
  catch (error) {
88
+ // Handle timeout
89
+ if (error.name === 'AbortError') {
90
+ throw new Error(`Request to Genbox API timed out. Check your internet connection.`);
91
+ }
80
92
  // Handle network-level errors (DNS, connection refused, timeout, etc.)
81
93
  if (error.cause?.code === 'ECONNREFUSED') {
82
94
  throw new Error(`Cannot connect to Genbox API at ${API_URL}. Is the server running?`);
@@ -377,30 +377,30 @@ function getPrivateSshKey() {
377
377
  }
378
378
  throw new Error('No SSH private key found in ~/.ssh/');
379
379
  }
380
- // Tmux configuration for genbox branding and native terminal feel
380
+ // Minimal tmux config - session management only, no extra features
381
381
  const TMUX_CONFIG = `
382
- # Mouse support for scrolling
383
- set -g mouse on
384
-
385
- # Large scrollback history
386
- set -g history-limit 50000
382
+ # Extended keys (REQUIRED for Shift+Enter in Claude Code)
383
+ set -s extended-keys on
384
+ set -s extended-keys-format csi-u
385
+ set -g extended-keys always
386
+ set -as terminal-features 'xterm*:extkeys'
387
387
 
388
- # No delay for escape key
388
+ # No escape delay (for vim mode)
389
389
  set -g escape-time 0
390
390
 
391
- # Status bar styling - yellow genbox branding
391
+ # Mouse scroll: pass to app if in alternate screen (Claude), else copy-mode
392
+ set -g mouse on
393
+ bind -n WheelUpPane if-shell -F "#{alternate_on}" "send-keys -M" "copy-mode -e; send-keys -M"
394
+ bind -n WheelDownPane send-keys -M
395
+
396
+ # Status bar for Genbox branding and image upload messages
392
397
  set -g status on
393
398
  set -g status-position bottom
394
- set -g status-style 'bg=#f59e0b fg=#000000'
395
- set -g status-left '#[bg=#000000,fg=#f59e0b,bold] GENBOX #[bg=#f59e0b,fg=#000000] #H '
399
+ set -g status-style 'bg=#2d2d2d fg=#888888'
400
+ set -g status-left '#[fg=#888888,bold] GENBOX #[default] #H '
396
401
  set -g status-left-length 30
397
- set -g status-right '#[fg=#000000] %H:%M '
402
+ set -g status-right '#[fg=#666666] %H:%M '
398
403
  set -g status-right-length 20
399
-
400
- # Window styling
401
- set -g window-status-current-style 'bg=#000000,fg=#f59e0b,bold'
402
- set -g window-status-current-format ' #W '
403
- set -g window-status-format ' #W '
404
404
  `.trim();
405
405
  async function listTmuxSessions(ipAddress, keyPath) {
406
406
  try {
@@ -78,7 +78,30 @@ exports.connectCommand = new commander_1.Command('connect')
78
78
  if (!target) {
79
79
  return;
80
80
  }
81
- // Handle local genbox - attach to session
81
+ // Handle local genbox with VM (multipass isolation) - SSH into VM
82
+ if (isLocal && localSession && localSession.isolation === 'multipass' && localSession.ipAddress) {
83
+ const keyPath = getPrivateSshKey();
84
+ // Add SSH config for easy access (addSshConfigEntry adds genbox- prefix)
85
+ if (!(0, ssh_config_1.hasSshConfigEntry)(localSession.name)) {
86
+ (0, ssh_config_1.addSshConfigEntry)({ name: localSession.name, ipAddress: localSession.ipAddress });
87
+ }
88
+ console.log(chalk_1.default.dim(`Connecting to local VM ${chalk_1.default.bold(localSession.name)} (${localSession.ipAddress})...`));
89
+ console.log(chalk_1.default.dim(` You can also use: ssh genbox-${localSession.name}`));
90
+ const sshArgs = [
91
+ '-i', keyPath,
92
+ '-o', 'StrictHostKeyChecking=no',
93
+ '-o', 'UserKnownHostsFile=/dev/null',
94
+ `dev@${localSession.ipAddress}`
95
+ ];
96
+ const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
97
+ ssh.on('close', (code) => {
98
+ if (code !== 0) {
99
+ console.log(chalk_1.default.dim(`Connection closed with code ${code}`));
100
+ }
101
+ });
102
+ return;
103
+ }
104
+ // Handle local genbox without VM - attach to session
82
105
  if (isLocal && localSession) {
83
106
  console.log(chalk_1.default.dim(`Attaching to local genbox ${chalk_1.default.bold(localSession.name)}...`));
84
107
  const manager = (0, local_session_manager_1.getLocalSessionManager)();
@@ -46,6 +46,7 @@ const path = __importStar(require("path"));
46
46
  const child_process_1 = require("child_process");
47
47
  const config_loader_1 = require("../config-loader");
48
48
  const local_session_manager_1 = require("../lib/local-session-manager");
49
+ const local_genbox_provisioner_1 = require("../lib/local-genbox-provisioner");
49
50
  const profile_resolver_1 = require("../profile-resolver");
50
51
  const api_1 = require("../api");
51
52
  const ssh_config_1 = require("../ssh-config");
@@ -329,28 +330,243 @@ async function promptForProfile(profiles) {
329
330
  }
330
331
  /**
331
332
  * Create a local genbox (Docker container or VM)
332
- * This creates the infrastructure without starting a session
333
+ * Supports two modes:
334
+ * 1. Full replica mode (if genbox.yaml exists): Uses same config as cloud
335
+ * 2. Simple mode: Just container/VM with mounted folder
333
336
  */
334
337
  async function createLocalGenbox(nameArg, options) {
338
+ // Check if genbox.yaml exists for full replica mode
339
+ const configLoader = new config_loader_1.ConfigLoader();
340
+ const loadResult = await configLoader.load();
341
+ if (loadResult.config) {
342
+ // Full replica mode - use genbox.yaml configuration
343
+ await createLocalGenboxFullReplica(nameArg, options, loadResult.config, configLoader);
344
+ }
345
+ else {
346
+ // Simple mode - no genbox.yaml
347
+ await createLocalGenboxSimple(nameArg, options);
348
+ }
349
+ }
350
+ /**
351
+ * Create a full replica local genbox using genbox.yaml configuration
352
+ */
353
+ async function createLocalGenboxFullReplica(nameArg, options, config, configLoader) {
354
+ console.log('');
355
+ console.log(chalk_1.default.blue('=== Create Local Genbox (Full Replica) ==='));
356
+ console.log(chalk_1.default.dim('Using genbox.yaml configuration'));
357
+ console.log('');
358
+ // Interactive name prompt if not provided
359
+ let name = nameArg;
360
+ if (!name && !options.yes) {
361
+ name = await promptForName(false);
362
+ }
363
+ else if (!name && options.yes) {
364
+ name = (0, random_name_1.generateRandomName)();
365
+ }
366
+ // Prompt for profile if available and not specified
367
+ let selectedProfile = options.profile;
368
+ if (!selectedProfile && !options.yes && config.profiles && Object.keys(config.profiles).length > 0) {
369
+ selectedProfile = await promptForProfile(config.profiles);
370
+ }
371
+ // Resolve profile and configuration
372
+ const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
373
+ const createOptions = {
374
+ name: name,
375
+ profile: selectedProfile,
376
+ apps: options.apps ? options.apps.split(',').map((a) => a.trim()) : undefined,
377
+ addApps: options.addApps ? options.addApps.split(',').map((a) => a.trim()) : undefined,
378
+ api: options.api,
379
+ db: options.db,
380
+ dbSource: options.dbSource,
381
+ size: options.size,
382
+ branch: options.branch,
383
+ newBranch: options.newBranch,
384
+ sourceBranch: options.fromBranch,
385
+ yes: options.yes,
386
+ };
387
+ const resolved = await profileResolver.resolve(config, createOptions);
388
+ // Check system resources
389
+ const provisioner = (0, local_genbox_provisioner_1.getLocalGenboxProvisioner)();
390
+ const requirements = provisioner.calculateRequirements(resolved);
391
+ const resourceCheck = provisioner.checkResourceAvailability(requirements);
392
+ // Display configuration
393
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
394
+ console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
395
+ console.log(` ${chalk_1.default.bold('Project:')} ${resolved.project.name}`);
396
+ console.log(` ${chalk_1.default.bold('Size:')} ${resolved.size}`);
397
+ console.log(` ${chalk_1.default.bold('Apps:')} ${resolved.apps.map(a => a.name).join(', ') || 'none'}`);
398
+ // Show repos that will be cloned (like cloud)
399
+ if (resolved.repos && resolved.repos.length > 0) {
400
+ console.log(` ${chalk_1.default.bold('Repos:')} ${resolved.repos.map(r => r.name).join(', ')}`);
401
+ }
402
+ const localInfra = resolved.infrastructure.filter(i => i.mode === 'local');
403
+ if (localInfra.length > 0) {
404
+ console.log(` ${chalk_1.default.bold('Infra:')} ${localInfra.map(i => i.name).join(', ')}`);
405
+ }
406
+ console.log(` ${chalk_1.default.bold('Database:')} ${resolved.database.mode}${resolved.database.source ? ` (${resolved.database.source})` : ''}`);
407
+ console.log(` ${chalk_1.default.bold('Directory:')} ${process.cwd()}`);
408
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
409
+ // Show resource warnings
410
+ if (resourceCheck.warnings.length > 0) {
411
+ console.log('');
412
+ console.log(chalk_1.default.yellow('Resource warnings:'));
413
+ for (const warning of resourceCheck.warnings) {
414
+ console.log(chalk_1.default.yellow(` - ${warning}`));
415
+ }
416
+ }
417
+ // Confirm
418
+ if (!options.yes) {
419
+ console.log('');
420
+ const confirm = await prompts.confirm({
421
+ message: `Create local genbox '${name}'?`,
422
+ default: true,
423
+ });
424
+ if (!confirm) {
425
+ console.log(chalk_1.default.dim('Cancelled.'));
426
+ return;
427
+ }
428
+ }
429
+ console.log('');
430
+ // Load GIT_TOKEN for git clone (like cloud)
431
+ const envVars = configLoader.loadEnvVars(process.cwd());
432
+ // Provision
433
+ const result = await provisioner.provision({
434
+ name: name,
435
+ workdir: process.cwd(),
436
+ resolvedConfig: resolved,
437
+ isolation: options.vm ? 'multipass' : options.native ? 'native' : 'docker',
438
+ skipInstall: options.skipInstall,
439
+ skipApps: false,
440
+ skipDatabase: false,
441
+ gitToken: envVars.GIT_TOKEN, // For git clone in VM
442
+ onProgress: (msg) => console.log(msg),
443
+ });
444
+ if (!result.success) {
445
+ console.error(chalk_1.default.red(`\nFailed: ${result.message}`));
446
+ return;
447
+ }
448
+ const session = result.session;
449
+ // Display result
450
+ console.log('');
451
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
452
+ console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
453
+ console.log(` ${chalk_1.default.bold('ID:')} ${session.id}`);
454
+ console.log(` ${chalk_1.default.bold('Size:')} ${session.size}`);
455
+ if (session.infrastructure.length > 0) {
456
+ console.log(` ${chalk_1.default.bold('Infra:')} ${session.infrastructure.map(i => `${i.name}:${i.port}`).join(', ')}`);
457
+ }
458
+ if (session.apps.length > 0) {
459
+ const running = session.apps.filter(a => a.status === 'running');
460
+ console.log(` ${chalk_1.default.bold('Apps:')} ${running.length}/${session.apps.length} running`);
461
+ }
462
+ if (session.domain) {
463
+ console.log(` ${chalk_1.default.bold('Domain:')} ${chalk_1.default.cyan(session.domain)}`);
464
+ }
465
+ if (session.database.seeded) {
466
+ console.log(` ${chalk_1.default.bold('Database:')} ${chalk_1.default.green('seeded')} (${session.database.mode})`);
467
+ }
468
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
469
+ console.log('');
470
+ console.log(chalk_1.default.bold('Next steps:'));
471
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${session.name}`)}`);
472
+ console.log(` Destroy: ${chalk_1.default.cyan(`gb destroy ${session.name}`)}`);
473
+ if (session.previewUrl) {
474
+ console.log('');
475
+ console.log(chalk_1.default.dim(` Preview: ${chalk_1.default.cyan(session.previewUrl)}`));
476
+ }
477
+ // Show infrastructure URLs
478
+ if (session.infrastructure.length > 0) {
479
+ console.log('');
480
+ console.log(chalk_1.default.bold('Infrastructure:'));
481
+ for (const infra of session.infrastructure) {
482
+ console.log(` ${infra.name}: ${chalk_1.default.dim(infra.url)}`);
483
+ }
484
+ }
485
+ console.log('');
486
+ }
487
+ /**
488
+ * Create a simple local genbox (no genbox.yaml)
489
+ * By default creates an isolated session with git worktree
490
+ */
491
+ async function createLocalGenboxSimple(nameArg, options) {
335
492
  const manager = (0, local_session_manager_1.getLocalSessionManager)();
336
493
  const provider = options.gemini ? 'gemini' : 'claude';
337
494
  const isolation = options.vm ? 'multipass' : options.native ? 'native' : 'docker';
495
+ const useWorktree = !options.noWorktree && isolation !== 'native';
496
+ // Interactive name prompt if not provided (same as cloud flow)
497
+ let name = nameArg;
498
+ if (!name && !options.yes) {
499
+ console.log('');
500
+ console.log(chalk_1.default.blue('=== Create Local Genbox ==='));
501
+ console.log(chalk_1.default.dim('No genbox.yaml found - using simple mode'));
502
+ console.log('');
503
+ name = await promptForName(false);
504
+ }
505
+ else if (!name && options.yes) {
506
+ // Auto-generate name in non-interactive mode
507
+ name = (0, random_name_1.generateRandomName)();
508
+ }
509
+ // Native mode doesn't support worktrees
510
+ if (options.native && !options.noWorktree) {
511
+ console.log(chalk_1.default.yellow('Note: Native mode does not support isolated worktrees.'));
512
+ console.log(chalk_1.default.dim('Use --vm or default Docker mode for isolated development.'));
513
+ console.log('');
514
+ }
338
515
  console.log('');
339
- console.log(chalk_1.default.blue('=== Creating Local Genbox ==='));
340
- console.log('');
516
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
517
+ console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
341
518
  console.log(` ${chalk_1.default.bold('Type:')} ${isolation}`);
342
519
  console.log(` ${chalk_1.default.bold('Provider:')} ${provider}`);
343
520
  console.log(` ${chalk_1.default.bold('Directory:')} ${process.cwd()}`);
521
+ if (useWorktree && manager.isGitRepo(process.cwd())) {
522
+ const branchName = options.branch || name;
523
+ console.log(` ${chalk_1.default.bold('Isolation:')} Git worktree (branch: ${branchName})`);
524
+ }
525
+ else {
526
+ console.log(` ${chalk_1.default.bold('Isolation:')} Mounted folder (shared)`);
527
+ }
528
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
344
529
  console.log('');
345
- const spinner = (0, ora_1.default)(`Creating ${isolation} environment...`).start();
346
530
  try {
347
- const session = await manager.createSession({
348
- provider,
349
- isolation,
350
- workdir: process.cwd(),
351
- name: nameArg, // Use provided name if any
352
- });
353
- spinner.succeed(chalk_1.default.green('Local genbox created!'));
531
+ let session;
532
+ if (useWorktree && manager.isGitRepo(process.cwd())) {
533
+ // Create isolated session with worktree
534
+ console.log(chalk_1.default.dim('Creating isolated development environment...'));
535
+ console.log('');
536
+ session = await manager.createIsolatedSession({
537
+ provider,
538
+ isolation: isolation,
539
+ workdir: process.cwd(),
540
+ name,
541
+ branch: options.branch || name,
542
+ sourceBranch: options.fromBranch,
543
+ appPort: options.port ? parseInt(options.port, 10) : 3000,
544
+ appName: options.appName || 'web',
545
+ installDeps: !options.skipInstall,
546
+ });
547
+ }
548
+ else {
549
+ // Create simple session (mounted folder, no worktree)
550
+ if (useWorktree && !manager.isGitRepo(process.cwd())) {
551
+ console.log(chalk_1.default.yellow('Note: Not a git repository. Using mounted folder instead of worktree.'));
552
+ console.log(chalk_1.default.dim('Initialize git to enable isolated development with worktrees.'));
553
+ console.log('');
554
+ }
555
+ const spinner = (0, ora_1.default)(`Creating ${isolation} environment...`).start();
556
+ try {
557
+ session = await manager.createSession({
558
+ provider,
559
+ isolation,
560
+ workdir: process.cwd(),
561
+ name,
562
+ });
563
+ spinner.succeed(chalk_1.default.green('Local genbox created!'));
564
+ }
565
+ catch (err) {
566
+ spinner.fail(chalk_1.default.red('Failed to create environment'));
567
+ throw err;
568
+ }
569
+ }
354
570
  console.log('');
355
571
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
356
572
  console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
@@ -358,6 +574,15 @@ async function createLocalGenbox(nameArg, options) {
358
574
  console.log(` ${chalk_1.default.bold('Provider:')} ${session.provider}`);
359
575
  console.log(` ${chalk_1.default.bold('Isolation:')} ${session.isolation}`);
360
576
  console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.green(session.status)}`);
577
+ if (session.worktreeBranch) {
578
+ console.log(` ${chalk_1.default.bold('Branch:')} ${session.worktreeBranch}`);
579
+ }
580
+ if (session.worktreePath) {
581
+ console.log(` ${chalk_1.default.bold('Worktree:')} ${chalk_1.default.dim(session.worktreePath)}`);
582
+ }
583
+ if (session.domain) {
584
+ console.log(` ${chalk_1.default.bold('Domain:')} ${chalk_1.default.cyan(session.domain)}`);
585
+ }
361
586
  if (session.containerId) {
362
587
  console.log(` ${chalk_1.default.bold('Container:')} ${session.containerId.slice(0, 12)}`);
363
588
  }
@@ -366,22 +591,38 @@ async function createLocalGenbox(nameArg, options) {
366
591
  }
367
592
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
368
593
  console.log('');
594
+ // Show domain setup hint if using worktree
595
+ if (session.domain) {
596
+ console.log(chalk_1.default.yellow('DNS Setup Required:'));
597
+ console.log(chalk_1.default.dim(` Add to /etc/hosts: ${chalk_1.default.cyan(`127.0.0.1 ${session.domain}`)}`));
598
+ console.log(chalk_1.default.dim(` Or run: ${chalk_1.default.cyan('gb setup-local')} for automatic proxy setup`));
599
+ console.log('');
600
+ }
369
601
  console.log(chalk_1.default.bold('Next steps:'));
370
602
  console.log(` Attach: ${chalk_1.default.cyan(`gb session ${session.name}`)}`);
371
- console.log(` List: ${chalk_1.default.cyan('gb session list')}`);
372
- console.log(` Stop: ${chalk_1.default.cyan(`gb session stop ${session.name}`)}`);
373
- console.log(` Destroy: ${chalk_1.default.cyan(`gb session kill ${session.name}`)}`);
603
+ console.log(` Connect: ${chalk_1.default.cyan(`gb connect ${session.name}`)}`);
604
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${session.name}`)}`);
605
+ console.log(` Destroy: ${chalk_1.default.cyan(`gb destroy ${session.name}`)}`);
606
+ if (session.domain) {
607
+ console.log('');
608
+ console.log(chalk_1.default.dim(` Preview: ${chalk_1.default.cyan(`https://${session.domain}`)}`));
609
+ console.log(chalk_1.default.dim(` or ${chalk_1.default.cyan(`http://localhost:${session.ports.preview}`)}`));
610
+ }
374
611
  console.log('');
375
612
  }
376
613
  catch (error) {
377
- spinner.fail(chalk_1.default.red(`Failed to create local genbox: ${error.message}`));
614
+ console.error(chalk_1.default.red(`Failed to create local genbox: ${error.message}`));
378
615
  if (error.message.includes('Docker')) {
379
616
  console.log('');
380
617
  console.log(chalk_1.default.yellow('Tip: Make sure Docker Desktop is running.'));
381
618
  }
382
619
  if (error.message.includes('multipass')) {
383
620
  console.log('');
384
- console.log(chalk_1.default.yellow('Tip: Install Multipass from https://multipass.run'));
621
+ console.log(chalk_1.default.yellow('Tip: Install Multipass from https://canonical.com/multipass/install'));
622
+ }
623
+ if (error.message.includes('worktree')) {
624
+ console.log('');
625
+ console.log(chalk_1.default.yellow('Tip: Use --no-worktree to mount the folder directly instead.'));
385
626
  }
386
627
  }
387
628
  }
@@ -391,6 +632,10 @@ exports.createCommand = new commander_1.Command('create')
391
632
  .option('-l, --local', 'Create local genbox (Docker container or VM)')
392
633
  .option('--vm', 'Use Multipass VM for local genbox')
393
634
  .option('--native', 'Use native mode (no isolation) for local genbox')
635
+ .option('--no-worktree', 'Mount existing folder instead of creating git worktree')
636
+ .option('--skip-install', 'Skip dependency installation for worktree')
637
+ .option('--port <port>', 'App port for local genbox (default: 3000)')
638
+ .option('--app-name <name>', 'App name for domain routing (default: web)')
394
639
  .option('--claude', 'Use Claude provider (default)')
395
640
  .option('--gemini', 'Use Gemini provider')
396
641
  .option('-p, --profile <profile>', 'Use a predefined profile')
@@ -401,7 +646,7 @@ exports.createCommand = new commander_1.Command('create')
401
646
  .option('--db-source <source>', 'Database source: staging, production')
402
647
  .option('--db-dump <path>', 'Use existing mongodump file instead of creating one')
403
648
  .option('--db-copy-remote', 'Copy database on the server (requires publicly accessible DB)')
404
- .option('-s, --size <size>', 'Server size: small, medium, large, xl')
649
+ .option('-s, --size <size>', 'Server size: tiny, mini (local only), small, medium, large, xl')
405
650
  .option('-b, --branch <branch>', 'Use existing git branch (skips new branch creation)')
406
651
  .option('-n, --new-branch <name>', 'Create a new branch with this name (defaults to env name)')
407
652
  .option('-f, --from-branch <branch>', 'Source branch to create new branch from (defaults to config default or main)')