genbox 1.0.196 → 1.0.198

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.
@@ -53,6 +53,8 @@ const ssh_config_1 = require("../ssh-config");
53
53
  const schema_v4_1 = require("../schema-v4");
54
54
  const random_name_1 = require("../random-name");
55
55
  const db_utils_1 = require("../db-utils");
56
+ const genbox_progress_1 = require("../lib/genbox-progress");
57
+ const genbox_wizard_1 = require("../lib/genbox-wizard");
56
58
  const utils_1 = require("../utils");
57
59
  const config_warnings_1 = require("../config-warnings");
58
60
  // Credits consumed per hour for each size (matches API billing.config.ts)
@@ -84,28 +86,6 @@ function spawnSshConfigSetup(genboxId, name) {
84
86
  // Allow parent to exit independently
85
87
  child.unref();
86
88
  }
87
- /**
88
- * Wait for genbox to reach a specific status
89
- */
90
- async function waitForGenboxStatus(genboxId, targetStatus, maxAttempts = 120, // 10 minutes with 5s interval
91
- intervalMs = 5000) {
92
- for (let i = 0; i < maxAttempts; i++) {
93
- try {
94
- const genbox = await (0, api_1.fetchApi)(`/genboxes/${genboxId}`);
95
- if (targetStatus.includes(genbox.status)) {
96
- return { success: true, genbox };
97
- }
98
- if (genbox.status === 'failed' || genbox.status === 'error') {
99
- return { success: false, error: `Genbox provisioning failed: ${genbox.statusMessage || 'unknown error'}` };
100
- }
101
- }
102
- catch (error) {
103
- // Ignore fetch errors and continue polling
104
- }
105
- await new Promise(resolve => setTimeout(resolve, intervalMs));
106
- }
107
- return { success: false, error: 'Timeout waiting for genbox to be ready' };
108
- }
109
89
  /**
110
90
  * Run restore script on genbox via SSH
111
91
  */
@@ -593,12 +573,39 @@ exports.createCommand = new commander_1.Command('create')
593
573
  .option('-n, --new-branch <name>', 'Create a new branch with this name (defaults to env name)')
594
574
  .option('-f, --from-branch <branch>', 'Source branch to create new branch from (defaults to config default or main)')
595
575
  .option('-y, --yes', 'Skip interactive prompts')
576
+ .option('-d, --detach', 'Run in background (like docker compose up -d)')
596
577
  .option('--dry-run', 'Show what would be created without actually creating')
597
578
  .option('-r, --restore', 'Restore from backup (uses genbox name to find backup)')
598
579
  .option('--inject-claude-auth', 'Inject local Claude Code credentials for remote execution')
599
580
  .option('--inject-gemini-auth', 'Inject local Gemini CLI credentials for remote execution')
600
581
  .action(async (nameArg, options) => {
601
582
  try {
583
+ // Interactive mode: ask cloud vs local if not specified (use shared wizard)
584
+ if (!options.local && !options.yes && !options.restore && !options.dryRun) {
585
+ const location = await (0, genbox_wizard_1.promptForLocation)();
586
+ if (!location) {
587
+ console.log(chalk_1.default.dim('\nCancelled.'));
588
+ return;
589
+ }
590
+ if (location === 'direct') {
591
+ // Show warning for direct mode
592
+ const proceed = await (0, genbox_wizard_1.showDirectModeWarning)();
593
+ if (!proceed) {
594
+ console.log(chalk_1.default.dim('\nCancelled.'));
595
+ return;
596
+ }
597
+ options.local = true;
598
+ options.native = true;
599
+ }
600
+ else if (location === 'local-docker') {
601
+ options.local = true;
602
+ }
603
+ else if (location === 'local-vm') {
604
+ options.local = true;
605
+ options.vm = true;
606
+ }
607
+ // 'cloud' continues with the default flow
608
+ }
602
609
  // Handle local genbox creation
603
610
  if (options.local) {
604
611
  await createLocalGenbox(nameArg, options);
@@ -1132,36 +1139,50 @@ exports.createCommand = new commander_1.Command('create')
1132
1139
  return;
1133
1140
  }
1134
1141
  spinner.succeed(chalk_1.default.green(`Genbox '${name}' created!`));
1135
- // Add SSH config immediately if IP available, otherwise spawn background process
1136
- if (genbox.ipAddress) {
1137
- const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
1138
- if (sshAdded) {
1139
- console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
1142
+ // Handle detach mode - return immediately without waiting
1143
+ if (options.detach) {
1144
+ // Spawn background process to poll for IP and add SSH config
1145
+ if (genbox._id) {
1146
+ spawnSshConfigSetup(genbox._id, name);
1140
1147
  }
1148
+ console.log('');
1149
+ console.log(chalk_1.default.dim('Running in background (detached mode).'));
1150
+ console.log('');
1151
+ console.log(chalk_1.default.bold('Commands:'));
1152
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${name}`)}`);
1153
+ console.log(` Connect: ${chalk_1.default.cyan(`gb connect ${name}`)}`);
1154
+ console.log(` Logs: ${chalk_1.default.cyan(`gb status ${name} -w`)}`);
1155
+ return;
1141
1156
  }
1142
- else if (genbox._id) {
1143
- // Spawn background process to poll for IP and add SSH config
1144
- spawnSshConfigSetup(genbox._id, name);
1145
- console.log(chalk_1.default.dim(' SSH config will be added once IP is assigned.'));
1157
+ // Wait for genbox to be ready with progress indicator
1158
+ const genboxId = genbox._id || genbox.id;
1159
+ const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genboxId, name, {
1160
+ timeout: 180,
1161
+ showProgress: true,
1162
+ });
1163
+ if (!waitResult.success) {
1164
+ console.log('');
1165
+ console.log(chalk_1.default.bold('Commands:'));
1166
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${name}`)}`);
1167
+ console.log(` Logs: ${chalk_1.default.cyan(`gb status ${name} -w`)}`);
1168
+ return;
1169
+ }
1170
+ const readyGenbox = waitResult.genbox;
1171
+ // Add SSH config now that we have IP
1172
+ if (readyGenbox.ipAddress) {
1173
+ const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: readyGenbox.ipAddress });
1174
+ if (sshAdded) {
1175
+ console.log(chalk_1.default.dim(`SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
1176
+ }
1146
1177
  }
1147
1178
  // Display results
1148
- displayGenboxInfo(genbox, resolved);
1179
+ console.log(chalk_1.default.green(`\nGenbox ${name} is ready!`));
1180
+ displayGenboxInfo(readyGenbox, resolved);
1149
1181
  // Handle restore if backup was specified
1150
- if (restoreBackup && restoreDownloadUrl && genbox._id) {
1182
+ if (restoreBackup && restoreDownloadUrl) {
1151
1183
  console.log('');
1152
1184
  console.log(chalk_1.default.blue('=== Restoring from Backup ==='));
1153
- // Wait for genbox to be ready
1154
- const waitSpinner = (0, ora_1.default)('Waiting for genbox to be ready...').start();
1155
- const waitResult = await waitForGenboxStatus(genbox._id, ['running']);
1156
- if (!waitResult.success) {
1157
- waitSpinner.fail(chalk_1.default.red(waitResult.error || 'Failed to wait for genbox'));
1158
- console.log(chalk_1.default.dim(' You can manually restore later: gb connect, then run the restore script'));
1159
- return;
1160
- }
1161
- waitSpinner.succeed(chalk_1.default.green('Genbox is ready'));
1162
- // Get IP address from the ready genbox
1163
- const readyGenbox = waitResult.genbox;
1164
- const ipAddress = readyGenbox?.ipAddress || genbox.ipAddress;
1185
+ const ipAddress = readyGenbox.ipAddress;
1165
1186
  if (!ipAddress) {
1166
1187
  console.log(chalk_1.default.red('Could not get genbox IP address'));
1167
1188
  console.log(chalk_1.default.dim(' You can manually restore by connecting and downloading the backup'));
@@ -1187,9 +1208,11 @@ exports.createCommand = new commander_1.Command('create')
1187
1208
  }
1188
1209
  }
1189
1210
  else {
1190
- // Inform user about server provisioning
1211
+ // Show next steps
1191
1212
  console.log('');
1192
- console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
1213
+ console.log(chalk_1.default.bold('Next steps:'));
1214
+ console.log(` Connect: ${chalk_1.default.cyan(`gb connect ${name}`)}`);
1215
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${name}`)}`);
1193
1216
  }
1194
1217
  }
1195
1218
  catch (error) {
@@ -82,12 +82,54 @@ async function handleBulkDelete(options) {
82
82
  const projectCloudGenboxes = projectName
83
83
  ? allCloudGenboxes.filter(g => g.project === projectName || g.workspace === projectName)
84
84
  : [];
85
- // Fetch local genboxes
85
+ // Fetch local genboxes from both LocalGenboxProvisioner and UnifiedSessionManager
86
86
  const provisioner = (0, local_genbox_provisioner_1.getLocalGenboxProvisioner)();
87
- const allLocalSessions = provisioner.listSessions();
87
+ const allLocalSessions = [];
88
+ const seenNames = new Set();
89
+ // Get from LocalGenboxProvisioner (legacy)
90
+ for (const s of provisioner.listSessions()) {
91
+ if (!seenNames.has(s.name)) {
92
+ allLocalSessions.push(s);
93
+ seenNames.add(s.name);
94
+ }
95
+ }
96
+ // Get from UnifiedSessionManager (wizard-created VMs)
97
+ try {
98
+ const sessionManager = (0, unified_session_1.getUnifiedSessionManager)();
99
+ const unifiedSessions = sessionManager.listSessions({
100
+ type: ['multipass', 'docker'],
101
+ });
102
+ for (const s of unifiedSessions) {
103
+ if (!seenNames.has(s.name)) {
104
+ allLocalSessions.push({
105
+ id: s.id,
106
+ name: s.name,
107
+ projectName: s.projectPath?.split('/').pop() || '',
108
+ workdir: s.projectPath || '',
109
+ sessionDir: '',
110
+ createdAt: s.createdAt,
111
+ size: 'small',
112
+ isolation: s.type,
113
+ apps: [{
114
+ name: s.provider || 'unknown',
115
+ path: '',
116
+ status: s.status === 'running' || s.status === 'active' ? 'running' : 'stopped',
117
+ }],
118
+ infrastructure: [],
119
+ database: { mode: 'none', seeded: false },
120
+ vmName: s.infrastructure?.vmName,
121
+ vmIpAddress: s.infrastructure?.vmIpAddress,
122
+ });
123
+ seenNames.add(s.name);
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // Ignore errors
129
+ }
88
130
  // Filter local sessions by project if applicable
89
131
  const projectLocalSessions = projectName
90
- ? allLocalSessions.filter(s => s.projectName === projectName)
132
+ ? allLocalSessions.filter(s => s.projectName === projectName || !s.projectName)
91
133
  : allLocalSessions;
92
134
  const totalCloud = allCloudGenboxes.length;
93
135
  const totalLocal = allLocalSessions.length;
@@ -6,9 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.listCommand = void 0;
7
7
  const commander_1 = require("commander");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
+ const child_process_1 = require("child_process");
9
10
  const api_1 = require("../api");
10
11
  const genbox_selector_1 = require("../genbox-selector");
11
12
  const local_genbox_provisioner_1 = require("../lib/local-genbox-provisioner");
13
+ const unified_session_1 = require("../lib/unified-session");
12
14
  exports.listCommand = new commander_1.Command('list')
13
15
  .alias('ls')
14
16
  .description('List genboxes (scoped to current project by default)')
@@ -26,20 +28,85 @@ exports.listCommand = new commander_1.Command('list')
26
28
  all: options.all,
27
29
  includeTerminated: options.terminated,
28
30
  }),
29
- // Local genboxes (sync but wrapped in promise for parallel execution)
31
+ // Local genboxes from both LocalGenboxProvisioner and UnifiedSessionManager
30
32
  Promise.resolve().then(() => {
33
+ const sessions = [];
34
+ const seenNames = new Set();
35
+ // Get sessions from LocalGenboxProvisioner (for legacy local genboxes)
31
36
  try {
32
37
  const provisioner = (0, local_genbox_provisioner_1.getLocalGenboxProvisioner)();
33
- let sessions = provisioner.listSessions();
34
- // Filter by project if applicable
35
- if (projectName && !options.all) {
36
- sessions = sessions.filter(s => s.projectName === projectName || !s.projectName);
38
+ for (const s of provisioner.listSessions()) {
39
+ if (!seenNames.has(s.name)) {
40
+ sessions.push(s);
41
+ seenNames.add(s.name);
42
+ }
37
43
  }
38
- return sessions;
39
44
  }
40
45
  catch {
41
- return [];
46
+ // Ignore errors
42
47
  }
48
+ // Get local sessions from UnifiedSessionManager (multipass/docker VMs created via gb gemini)
49
+ try {
50
+ const sessionManager = (0, unified_session_1.getUnifiedSessionManager)();
51
+ const unifiedSessions = sessionManager.listSessions({
52
+ type: ['multipass', 'docker'],
53
+ });
54
+ for (const s of unifiedSessions) {
55
+ if (!seenNames.has(s.name)) {
56
+ // Check actual VM status for multipass sessions
57
+ let actualStatus = s.status === 'running' || s.status === 'active' ? 'running' : 'stopped';
58
+ let vmIp = s.infrastructure?.vmIpAddress;
59
+ if (s.type === 'multipass') {
60
+ // Use vmName or fall back to session name for older sessions
61
+ const vmName = s.infrastructure?.vmName || s.name;
62
+ try {
63
+ const info = (0, child_process_1.execSync)(`multipass info ${vmName} --format json 2>/dev/null`, { encoding: 'utf8' });
64
+ const vmInfo = JSON.parse(info);
65
+ const vm = vmInfo.info?.[vmName];
66
+ if (vm) {
67
+ actualStatus = vm.state === 'Running' ? 'running' : 'stopped';
68
+ vmIp = vm.ipv4?.[0] || vmIp;
69
+ }
70
+ }
71
+ catch {
72
+ // VM might not exist anymore
73
+ }
74
+ }
75
+ // Convert to LocalGenboxSession format for rendering
76
+ sessions.push({
77
+ id: s.id,
78
+ name: s.name,
79
+ projectName: s.projectPath?.split('/').pop() || '',
80
+ workdir: s.projectPath || '',
81
+ sessionDir: '',
82
+ createdAt: s.createdAt,
83
+ size: 'small',
84
+ isolation: s.type,
85
+ apps: [{
86
+ name: s.provider || 'unknown',
87
+ path: '',
88
+ status: actualStatus,
89
+ }],
90
+ infrastructure: [],
91
+ database: {
92
+ mode: 'none',
93
+ seeded: false,
94
+ },
95
+ vmName: s.infrastructure?.vmName || s.name,
96
+ vmIpAddress: vmIp,
97
+ });
98
+ seenNames.add(s.name);
99
+ }
100
+ }
101
+ }
102
+ catch {
103
+ // Ignore errors
104
+ }
105
+ // Filter by project if applicable
106
+ if (projectName && !options.all) {
107
+ return sessions.filter(s => s.projectName === projectName || !s.projectName);
108
+ }
109
+ return sessions;
43
110
  }),
44
111
  ]);
45
112
  // Extract results
@@ -51,6 +51,7 @@ const path = __importStar(require("path"));
51
51
  const fs = __importStar(require("fs"));
52
52
  const genbox_selector_1 = require("../genbox-selector");
53
53
  const local_genbox_provisioner_1 = require("../lib/local-genbox-provisioner");
54
+ const unified_session_1 = require("../lib/unified-session");
54
55
  function getPrivateSshKey() {
55
56
  const home = os.homedir();
56
57
  const potentialKeys = [
@@ -116,8 +117,41 @@ Examples:
116
117
  * Show logs for a local genbox
117
118
  */
118
119
  async function showLocalGenboxLogs(sessionId, options) {
120
+ // Try LocalGenboxProvisioner first (for legacy sessions)
119
121
  const provisioner = (0, local_genbox_provisioner_1.getLocalGenboxProvisioner)();
120
- const session = provisioner.getSession(sessionId);
122
+ let session = provisioner.getSession(sessionId);
123
+ // If not found, try UnifiedSessionManager (for wizard-created VMs)
124
+ if (!session) {
125
+ try {
126
+ const sessionManager = (0, unified_session_1.getUnifiedSessionManager)();
127
+ const unifiedSession = sessionManager.getSession(sessionId);
128
+ if (unifiedSession && (unifiedSession.type === 'multipass' || unifiedSession.type === 'docker')) {
129
+ // Convert to LocalGenboxSession format
130
+ session = {
131
+ id: unifiedSession.id,
132
+ name: unifiedSession.name,
133
+ projectName: unifiedSession.projectPath?.split('/').pop() || '',
134
+ workdir: unifiedSession.projectPath || '',
135
+ sessionDir: '',
136
+ createdAt: unifiedSession.createdAt,
137
+ size: 'small',
138
+ isolation: unifiedSession.type,
139
+ apps: [{
140
+ name: unifiedSession.provider || 'unknown',
141
+ path: '',
142
+ status: unifiedSession.status === 'running' || unifiedSession.status === 'active' ? 'running' : 'stopped',
143
+ }],
144
+ infrastructure: [],
145
+ database: { mode: 'none', seeded: false },
146
+ vmName: unifiedSession.infrastructure?.vmName,
147
+ vmIpAddress: unifiedSession.infrastructure?.vmIpAddress,
148
+ };
149
+ }
150
+ }
151
+ catch {
152
+ // Ignore errors
153
+ }
154
+ }
121
155
  if (!session) {
122
156
  console.log(chalk_1.default.red('Session not found.'));
123
157
  return;
@@ -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
  };
@@ -16,6 +49,8 @@ const utils_1 = require("../utils");
16
49
  const random_name_1 = require("../random-name");
17
50
  const ssh_config_1 = require("../ssh-config");
18
51
  const child_process_1 = require("child_process");
52
+ const genbox_wizard_1 = require("../lib/genbox-wizard");
53
+ const genbox_progress_1 = require("../lib/genbox-progress");
19
54
  // Credits per hour by size
20
55
  const CREDITS_PER_HOUR = {
21
56
  small: 1,
@@ -40,6 +75,8 @@ exports.newCommand = new commander_1.Command('new')
40
75
  .argument('[name]', 'Project name')
41
76
  .option('--list', 'List available templates')
42
77
  .option('-s, --size <size>', 'Genbox size: small, medium, large, xl')
78
+ .option('-l, --local', 'Create local genbox (Docker)')
79
+ .option('--cloud', 'Create cloud genbox (default)')
43
80
  .option('-y, --yes', 'Skip interactive prompts, use defaults')
44
81
  .action(async (templateArg, nameArg, options) => {
45
82
  try {
@@ -126,6 +163,27 @@ exports.newCommand = new commander_1.Command('new')
126
163
  projectName = `${template.name}-${(0, random_name_1.generateRandomName)()}`;
127
164
  console.log(chalk_1.default.dim(`Generated name: ${projectName}`));
128
165
  }
166
+ // Local vs Cloud selection (use shared wizard)
167
+ let isLocal = options.local || false;
168
+ if (!options.local && !options.cloud && !options.yes) {
169
+ const location = await (0, genbox_wizard_1.promptForLocation)();
170
+ if (!location) {
171
+ console.log(chalk_1.default.dim('\nCancelled.'));
172
+ return;
173
+ }
174
+ // Direct mode not supported for templates - need a genbox
175
+ if (location === 'direct') {
176
+ console.log(chalk_1.default.yellow('\nDirect mode is not available for templates.'));
177
+ console.log(chalk_1.default.dim('Templates require a genbox environment. Choose Cloud or Local Docker/VM.'));
178
+ return;
179
+ }
180
+ isLocal = location === 'local-docker' || location === 'local-vm';
181
+ }
182
+ // Handle local creation
183
+ if (isLocal) {
184
+ await createLocalFromTemplate(template, projectName);
185
+ return;
186
+ }
129
187
  // Size selection
130
188
  let size = options.size || preferences.defaultGenboxSize || template.recommendedSize;
131
189
  if (!options.yes && !options.size) {
@@ -273,41 +331,42 @@ exports.newCommand = new commander_1.Command('new')
273
331
  return;
274
332
  }
275
333
  createSpinner.succeed(chalk_1.default.green(`Genbox '${projectName}' created!`));
276
- // Add SSH config
277
- if (genbox.ipAddress) {
278
- const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name: projectName, ipAddress: genbox.ipAddress });
334
+ // Wait for genbox to be ready with progress indicator
335
+ const genboxId = genbox._id || genbox.id;
336
+ const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genboxId, projectName, {
337
+ timeout: 180,
338
+ showProgress: true,
339
+ });
340
+ if (!waitResult.success) {
341
+ console.log('');
342
+ console.log(chalk_1.default.bold('Commands:'));
343
+ console.log(` Status: ${chalk_1.default.cyan(`gb status ${projectName}`)}`);
344
+ console.log(` Logs: ${chalk_1.default.cyan(`gb status ${projectName} -w`)}`);
345
+ return;
346
+ }
347
+ const readyGenbox = waitResult.genbox;
348
+ // Add SSH config now that we have IP
349
+ if (readyGenbox.ipAddress) {
350
+ const sshAdded = (0, ssh_config_1.addSshConfigEntry)({ name: projectName, ipAddress: readyGenbox.ipAddress });
279
351
  if (sshAdded) {
280
- console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(projectName)}`));
352
+ console.log(chalk_1.default.dim(`SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(projectName)}`));
281
353
  }
282
354
  }
283
- else if (genbox._id) {
284
- spawnSshConfigSetup(genbox._id, projectName);
285
- console.log(chalk_1.default.dim(' SSH config will be added once IP is assigned.'));
286
- }
287
355
  // Display results
356
+ console.log(chalk_1.default.green(`\nGenbox ${projectName} is ready!`));
288
357
  console.log('');
289
358
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
290
- console.log(` ${chalk_1.default.bold('Name:')} ${genbox.name || projectName}`);
291
- console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status || 'provisioning')}`);
292
- if (genbox.ipAddress) {
293
- console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
359
+ console.log(` ${chalk_1.default.bold('Name:')} ${readyGenbox.name || projectName}`);
360
+ console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.green('running')}`);
361
+ if (readyGenbox.ipAddress) {
362
+ console.log(` ${chalk_1.default.bold('IP:')} ${readyGenbox.ipAddress}`);
294
363
  }
295
364
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
296
- // Display URLs
297
- if (genbox.urls && Object.keys(genbox.urls).length > 0) {
298
- console.log('');
299
- console.log(chalk_1.default.bold('Access URLs:'));
300
- for (const [service, url] of Object.entries(genbox.urls)) {
301
- console.log(` ${chalk_1.default.cyan(String(service).padEnd(12))} ${url}`);
302
- }
303
- }
304
365
  console.log('');
305
366
  console.log(chalk_1.default.bold('Next steps:'));
306
- console.log(` 1. Wait for setup: ${chalk_1.default.cyan(`gb status ${projectName} -w`)}`);
307
- console.log(` 2. Connect: ${chalk_1.default.cyan(`gb connect ${projectName}`)}`);
308
- console.log(` 3. Start Claude: ${chalk_1.default.cyan(`gb claude ${projectName}`)}`);
367
+ console.log(` Connect: ${chalk_1.default.cyan(`gb connect ${projectName}`)}`);
368
+ console.log(` Start Claude: ${chalk_1.default.cyan(`gb claude --on ${projectName}`)}`);
309
369
  console.log('');
310
- console.log(chalk_1.default.dim('Server is provisioning. This takes about 2-3 minutes.'));
311
370
  }
312
371
  catch (error) {
313
372
  createSpinner.fail(chalk_1.default.red(`Failed to create genbox: ${error.message}`));
@@ -328,6 +387,93 @@ exports.newCommand = new commander_1.Command('new')
328
387
  console.error(chalk_1.default.red('Error:'), error.message);
329
388
  }
330
389
  });
390
+ /**
391
+ * Create a local genbox from a starter template using Docker
392
+ */
393
+ async function createLocalFromTemplate(template, projectName) {
394
+ console.log('');
395
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
396
+ console.log(` ${chalk_1.default.bold('Template:')} ${template.icon} ${template.displayName}`);
397
+ console.log(` ${chalk_1.default.bold('Name:')} ${projectName}`);
398
+ console.log(` ${chalk_1.default.bold('Location:')} ${chalk_1.default.green('Local (Docker)')}`);
399
+ console.log(` ${chalk_1.default.bold('Features:')} ${template.features.join(', ')}`);
400
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
401
+ const confirmed = await (0, confirm_1.default)({
402
+ message: `Create local genbox '${projectName}'?`,
403
+ default: true,
404
+ });
405
+ if (!confirmed) {
406
+ console.log(chalk_1.default.dim('Cancelled.'));
407
+ return;
408
+ }
409
+ const spinner = (0, ora_1.default)('Creating local genbox...').start();
410
+ try {
411
+ // Build the create command for the template
412
+ let createCmd;
413
+ if (template.createCommand) {
414
+ createCmd = template.createCommand.replace(/\{\{name\}\}/g, projectName);
415
+ }
416
+ else if (template.repoUrl) {
417
+ createCmd = `git clone https://${template.repoUrl} ${projectName}`;
418
+ }
419
+ else {
420
+ spinner.fail('Template does not support local creation');
421
+ return;
422
+ }
423
+ // Run the create command locally
424
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
425
+ spinner.text = 'Setting up project...';
426
+ execSync(createCmd, {
427
+ stdio: 'pipe',
428
+ cwd: process.cwd(),
429
+ });
430
+ // Run setup commands if any
431
+ if (template.setupCommands && template.setupCommands.length > 0) {
432
+ const projectPath = `${process.cwd()}/${projectName}`;
433
+ for (const cmd of template.setupCommands) {
434
+ const setupCmd = cmd.replace(/\{\{name\}\}/g, projectName);
435
+ spinner.text = `Running: ${setupCmd}`;
436
+ try {
437
+ execSync(setupCmd, {
438
+ stdio: 'pipe',
439
+ cwd: projectPath,
440
+ });
441
+ }
442
+ catch {
443
+ // Some setup commands may fail, continue
444
+ }
445
+ }
446
+ }
447
+ spinner.succeed(chalk_1.default.green(`Local project '${projectName}' created!`));
448
+ console.log('');
449
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
450
+ console.log(` ${chalk_1.default.bold('Name:')} ${projectName}`);
451
+ console.log(` ${chalk_1.default.bold('Path:')} ${process.cwd()}/${projectName}`);
452
+ console.log(` ${chalk_1.default.bold('Type:')} Local`);
453
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
454
+ console.log('');
455
+ console.log(chalk_1.default.bold('Next steps:'));
456
+ console.log(` 1. ${chalk_1.default.cyan(`cd ${projectName}`)}`);
457
+ console.log(` 2. ${chalk_1.default.cyan('pnpm install')} ${chalk_1.default.dim('(or npm install)')}`);
458
+ console.log(` 3. ${chalk_1.default.cyan('pnpm dev')} ${chalk_1.default.dim('(or npm run dev)')}`);
459
+ console.log('');
460
+ console.log(chalk_1.default.dim('To start a Claude session in this project:'));
461
+ console.log(` ${chalk_1.default.cyan(`cd ${projectName} && claude`)}`);
462
+ console.log('');
463
+ }
464
+ catch (error) {
465
+ spinner.fail(chalk_1.default.red(`Failed to create local project: ${error.message}`));
466
+ if (error.message.includes('command not found')) {
467
+ console.log(chalk_1.default.yellow('\nMake sure the required tools are installed:'));
468
+ if (template.createCommand?.includes('npx')) {
469
+ console.log(chalk_1.default.dim(' - Node.js and npx'));
470
+ }
471
+ if (template.createCommand?.includes('bunx')) {
472
+ console.log(chalk_1.default.dim(' - Bun (https://bun.sh)'));
473
+ }
474
+ }
475
+ }
476
+ }
331
477
  async function listTemplates() {
332
478
  const spinner = (0, ora_1.default)('Fetching templates...').start();
333
479
  try {