genbox 1.0.198 → 1.0.199

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.
@@ -43,6 +43,7 @@ const confirm_1 = __importDefault(require("@inquirer/confirm"));
43
43
  const select_1 = __importDefault(require("@inquirer/select"));
44
44
  const prompts = __importStar(require("@inquirer/prompts"));
45
45
  const ora_1 = __importDefault(require("ora"));
46
+ const child_process_1 = require("child_process");
46
47
  const api_1 = require("../api");
47
48
  const genbox_selector_1 = require("../genbox-selector");
48
49
  const ssh_config_1 = require("../ssh-config");
@@ -260,10 +261,51 @@ async function handleBulkDelete(options) {
260
261
  }
261
262
  }
262
263
  // Delete local genboxes
264
+ const sessionManager = (0, unified_session_1.getUnifiedSessionManager)();
263
265
  for (const session of selectedLocalSessions) {
264
266
  const spinner = (0, ora_1.default)(`Destroying ${session.name} (local)...`).start();
265
267
  try {
266
- await provisioner.destroy(session.id);
268
+ // Check if this is a UnifiedSessionManager session (wizard-created VM)
269
+ const unifiedSession = sessionManager.getSession(session.id);
270
+ if (unifiedSession) {
271
+ // Delete the actual VM/container based on type
272
+ if (unifiedSession.type === 'multipass') {
273
+ const vmName = unifiedSession.infrastructure?.vmName || unifiedSession.name;
274
+ try {
275
+ (0, child_process_1.execSync)(`multipass delete ${vmName} --purge`, { stdio: 'pipe' });
276
+ }
277
+ catch {
278
+ // VM might already be deleted
279
+ }
280
+ }
281
+ else if (unifiedSession.type === 'docker') {
282
+ // Try containerId first, then fall back to container name
283
+ const containerId = unifiedSession.infrastructure?.containerId;
284
+ const containerName = unifiedSession.infrastructure?.containerName || unifiedSession.name;
285
+ if (containerId) {
286
+ try {
287
+ (0, child_process_1.execSync)(`docker rm -f ${containerId}`, { stdio: 'pipe' });
288
+ }
289
+ catch {
290
+ // Container might already be deleted
291
+ }
292
+ }
293
+ else if (containerName) {
294
+ try {
295
+ (0, child_process_1.execSync)(`docker rm -f ${containerName}`, { stdio: 'pipe' });
296
+ }
297
+ catch {
298
+ // Container might not exist or already be deleted
299
+ }
300
+ }
301
+ }
302
+ // Delete from UnifiedSessionManager
303
+ await sessionManager.deleteSession(session.id);
304
+ }
305
+ else {
306
+ // Fallback to LocalGenboxProvisioner for legacy sessions
307
+ await provisioner.destroy(session.id);
308
+ }
267
309
  spinner.succeed(chalk_1.default.green(`Destroyed '${session.name}' (local)`));
268
310
  successCount++;
269
311
  }
@@ -42,6 +42,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
42
42
  return (mod && mod.__esModule) ? mod : { "default": mod };
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.promptForSize = promptForSize;
45
46
  exports.showDirectModeWarning = showDirectModeWarning;
46
47
  exports.promptForLocation = promptForLocation;
47
48
  exports.promptForName = promptForName;
@@ -61,6 +62,285 @@ const genbox_progress_1 = require("./genbox-progress");
61
62
  const ssh_keys_1 = require("../utils/ssh-keys");
62
63
  const config_store_1 = require("../config-store");
63
64
  const unified_session_1 = require("./unified-session");
65
+ const VM_SIZE_REQUIREMENTS = {
66
+ small: {
67
+ cpus: 2,
68
+ memoryGB: 4,
69
+ diskGB: 20,
70
+ label: 'Small',
71
+ description: 'Basic development tasks',
72
+ },
73
+ medium: {
74
+ cpus: 4,
75
+ memoryGB: 8,
76
+ diskGB: 40,
77
+ label: 'Medium',
78
+ description: 'Standard development',
79
+ },
80
+ large: {
81
+ cpus: 6,
82
+ memoryGB: 12,
83
+ diskGB: 60,
84
+ label: 'Large',
85
+ description: 'Heavy workloads, multiple services',
86
+ },
87
+ xl: {
88
+ cpus: 8,
89
+ memoryGB: 16,
90
+ diskGB: 80,
91
+ label: 'XL',
92
+ description: 'Intensive tasks, large projects',
93
+ },
94
+ };
95
+ const DOCKER_SIZE_REQUIREMENTS = {
96
+ small: {
97
+ cpus: 1,
98
+ memoryGB: 2,
99
+ diskGB: 5,
100
+ label: 'Small',
101
+ description: 'Basic development tasks',
102
+ },
103
+ medium: {
104
+ cpus: 2,
105
+ memoryGB: 4,
106
+ diskGB: 10,
107
+ label: 'Medium',
108
+ description: 'Standard development',
109
+ },
110
+ large: {
111
+ cpus: 4,
112
+ memoryGB: 8,
113
+ diskGB: 20,
114
+ label: 'Large',
115
+ description: 'Heavy workloads',
116
+ },
117
+ xl: {
118
+ cpus: 6,
119
+ memoryGB: 12,
120
+ diskGB: 30,
121
+ label: 'XL',
122
+ description: 'Intensive tasks',
123
+ },
124
+ };
125
+ /**
126
+ * Get current system resources
127
+ */
128
+ function getSystemResources() {
129
+ const cpuCores = os.cpus().length;
130
+ const totalMemoryGB = Math.round(os.totalmem() / (1024 * 1024 * 1024) * 10) / 10;
131
+ // Get available memory (platform-specific)
132
+ let availableMemoryGB;
133
+ if (process.platform === 'darwin') {
134
+ try {
135
+ // On macOS, use vm_stat for more accurate available memory
136
+ const vmStat = (0, child_process_1.execSync)('vm_stat', { encoding: 'utf8' });
137
+ const pageSize = 16384; // Default page size on Apple Silicon
138
+ const freeMatch = vmStat.match(/Pages free:\s+(\d+)/);
139
+ const inactiveMatch = vmStat.match(/Pages inactive:\s+(\d+)/);
140
+ const speculativeMatch = vmStat.match(/Pages speculative:\s+(\d+)/);
141
+ const freePages = parseInt(freeMatch?.[1] || '0', 10);
142
+ const inactivePages = parseInt(inactiveMatch?.[1] || '0', 10);
143
+ const speculativePages = parseInt(speculativeMatch?.[1] || '0', 10);
144
+ const reclaimableBytes = (freePages + inactivePages + speculativePages) * pageSize;
145
+ availableMemoryGB = Math.round(reclaimableBytes / (1024 * 1024 * 1024) * 10) / 10;
146
+ }
147
+ catch {
148
+ availableMemoryGB = Math.round(os.freemem() / (1024 * 1024 * 1024) * 10) / 10;
149
+ }
150
+ }
151
+ else {
152
+ availableMemoryGB = Math.round(os.freemem() / (1024 * 1024 * 1024) * 10) / 10;
153
+ }
154
+ // Get available disk space
155
+ let diskSpaceGB = 0;
156
+ try {
157
+ if (process.platform === 'darwin' || process.platform === 'linux') {
158
+ const df = (0, child_process_1.execSync)('df -k / | tail -1', { encoding: 'utf8' });
159
+ const parts = df.trim().split(/\s+/);
160
+ const availableKb = parseInt(parts[3], 10);
161
+ diskSpaceGB = Math.round(availableKb / (1024 * 1024) * 10) / 10;
162
+ }
163
+ }
164
+ catch {
165
+ diskSpaceGB = 50; // Assume 50GB if we can't detect
166
+ }
167
+ // Check Docker status
168
+ let dockerRunning = false;
169
+ try {
170
+ (0, child_process_1.execSync)('docker info', { stdio: 'pipe' });
171
+ dockerRunning = true;
172
+ }
173
+ catch {
174
+ dockerRunning = false;
175
+ }
176
+ // Check Multipass status
177
+ let multipassRunning = false;
178
+ try {
179
+ (0, child_process_1.execSync)('multipass version', { stdio: 'pipe' });
180
+ multipassRunning = true;
181
+ }
182
+ catch {
183
+ multipassRunning = false;
184
+ }
185
+ // Count existing VMs
186
+ let existingVms = 0;
187
+ try {
188
+ const vmList = (0, child_process_1.execSync)('multipass list --format json 2>/dev/null', { encoding: 'utf8' });
189
+ const vms = JSON.parse(vmList);
190
+ existingVms = vms.list?.length || 0;
191
+ }
192
+ catch {
193
+ existingVms = 0;
194
+ }
195
+ // Count existing Docker containers (genbox-related)
196
+ let existingContainers = 0;
197
+ try {
198
+ const containerList = (0, child_process_1.execSync)('docker ps -a --filter "name=genbox" --format "{{.Names}}" 2>/dev/null', { encoding: 'utf8' });
199
+ existingContainers = containerList.trim().split('\n').filter(Boolean).length;
200
+ }
201
+ catch {
202
+ existingContainers = 0;
203
+ }
204
+ return {
205
+ cpuCores,
206
+ totalMemoryGB,
207
+ availableMemoryGB,
208
+ diskSpaceGB,
209
+ dockerRunning,
210
+ multipassRunning,
211
+ existingVms,
212
+ existingContainers,
213
+ };
214
+ }
215
+ /**
216
+ * Format bytes to human-readable string
217
+ */
218
+ function formatGB(gb) {
219
+ return `${gb.toFixed(1)} GB`;
220
+ }
221
+ /**
222
+ * Prompt user to select a genbox size
223
+ */
224
+ async function promptForSize(type, resources) {
225
+ const sizeRequirements = type === 'vm' ? VM_SIZE_REQUIREMENTS : DOCKER_SIZE_REQUIREMENTS;
226
+ console.log('');
227
+ console.log(chalk_1.default.cyan(' Select a size for your genbox:'));
228
+ console.log('');
229
+ try {
230
+ const choices = Object.entries(sizeRequirements).map(([size, req]) => {
231
+ // Check if system has enough resources
232
+ const hasEnoughMemory = resources.totalMemoryGB >= req.memoryGB * 1.5;
233
+ const hasEnoughCpus = resources.cpuCores >= req.cpus + 1;
234
+ const hasEnoughDisk = resources.diskSpaceGB >= req.diskGB;
235
+ const canFit = hasEnoughMemory && hasEnoughCpus && hasEnoughDisk;
236
+ const warning = !canFit ? chalk_1.default.yellow(' ⚠') : '';
237
+ const disabled = resources.totalMemoryGB < req.memoryGB || resources.cpuCores < req.cpus;
238
+ return {
239
+ name: `${req.label}${warning} ${chalk_1.default.dim(`(${req.cpus} CPU, ${req.memoryGB}GB RAM, ${req.diskGB}GB disk)`)} ${chalk_1.default.dim('- ' + req.description)}`,
240
+ value: size,
241
+ disabled: disabled ? chalk_1.default.red('(insufficient resources)') : false,
242
+ };
243
+ });
244
+ const selected = await (0, select_1.default)({
245
+ message: 'Size:',
246
+ choices,
247
+ });
248
+ return selected;
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ /**
255
+ * Show resource summary and get user confirmation before creating local genbox
256
+ */
257
+ async function confirmResourceUsage(type, resources, size = 'small') {
258
+ const sizeRequirements = type === 'vm' ? VM_SIZE_REQUIREMENTS : DOCKER_SIZE_REQUIREMENTS;
259
+ const requirements = sizeRequirements[size];
260
+ const warnings = [];
261
+ const isVm = type === 'vm';
262
+ console.log('');
263
+ console.log(chalk_1.default.blue('═══════════════════════════════════════════════════════════'));
264
+ console.log(chalk_1.default.blue.bold(` ${isVm ? 'Multipass VM' : 'Docker Container'} Resource Requirements`));
265
+ console.log(chalk_1.default.blue('═══════════════════════════════════════════════════════════'));
266
+ console.log('');
267
+ // Show what will be allocated
268
+ console.log(chalk_1.default.cyan(' This genbox will use:'));
269
+ console.log(` CPUs: ${chalk_1.default.yellow(requirements.cpus)} cores`);
270
+ console.log(` Memory: ${chalk_1.default.yellow(formatGB(requirements.memoryGB))}`);
271
+ console.log(` Disk: ${chalk_1.default.yellow(formatGB(requirements.diskGB))}`);
272
+ console.log('');
273
+ // Show current system state
274
+ console.log(chalk_1.default.cyan(' Your system:'));
275
+ console.log(` CPUs: ${chalk_1.default.green(resources.cpuCores)} cores`);
276
+ console.log(` Total Memory: ${chalk_1.default.green(formatGB(resources.totalMemoryGB))}`);
277
+ console.log(` Free Memory: ${resources.availableMemoryGB < requirements.memoryGB ? chalk_1.default.red(formatGB(resources.availableMemoryGB)) : chalk_1.default.green(formatGB(resources.availableMemoryGB))}`);
278
+ console.log(` Free Disk: ${resources.diskSpaceGB < requirements.diskGB ? chalk_1.default.red(formatGB(resources.diskSpaceGB)) : chalk_1.default.green(formatGB(resources.diskSpaceGB))}`);
279
+ if (isVm && resources.existingVms > 0) {
280
+ console.log(` Existing VMs: ${chalk_1.default.yellow(resources.existingVms)}`);
281
+ }
282
+ if (!isVm && resources.existingContainers > 0) {
283
+ console.log(` Existing Containers: ${chalk_1.default.yellow(resources.existingContainers)}`);
284
+ }
285
+ console.log('');
286
+ // Check for issues
287
+ if (resources.availableMemoryGB < requirements.memoryGB) {
288
+ warnings.push(`Low memory: ${formatGB(resources.availableMemoryGB)} available, ${formatGB(requirements.memoryGB)} needed`);
289
+ }
290
+ if (resources.diskSpaceGB < requirements.diskGB) {
291
+ warnings.push(`Low disk space: ${formatGB(resources.diskSpaceGB)} available, ${formatGB(requirements.diskGB)} needed`);
292
+ }
293
+ if (resources.cpuCores < requirements.cpus + 2) {
294
+ warnings.push(`Limited CPUs: ${resources.cpuCores} cores total, allocating ${requirements.cpus} may slow your system`);
295
+ }
296
+ if (isVm && !resources.multipassRunning) {
297
+ warnings.push('Multipass is not installed');
298
+ }
299
+ if (!isVm && !resources.dockerRunning) {
300
+ warnings.push('Docker is not running');
301
+ }
302
+ // Show warnings if any
303
+ if (warnings.length > 0) {
304
+ console.log(chalk_1.default.yellow(' ⚠️ Warnings:'));
305
+ for (const warning of warnings) {
306
+ console.log(chalk_1.default.yellow(` • ${warning}`));
307
+ }
308
+ console.log('');
309
+ }
310
+ // Block creation if critical resources missing
311
+ if ((isVm && !resources.multipassRunning) || (!isVm && !resources.dockerRunning)) {
312
+ console.log(chalk_1.default.red(` ✗ Cannot create ${isVm ? 'VM' : 'container'}: ${isVm ? 'Multipass' : 'Docker'} is not available.`));
313
+ if (isVm) {
314
+ console.log(chalk_1.default.dim(' Install from: https://canonical.com/multipass/install'));
315
+ }
316
+ else {
317
+ console.log(chalk_1.default.dim(' Start Docker Desktop or run: sudo systemctl start docker'));
318
+ }
319
+ console.log('');
320
+ return { confirmed: false, cancelled: false };
321
+ }
322
+ // Block if truly insufficient resources
323
+ if (resources.diskSpaceGB < requirements.diskGB * 0.5) {
324
+ console.log(chalk_1.default.red(' ✗ Insufficient disk space. Please free up disk space before creating a genbox.'));
325
+ console.log('');
326
+ return { confirmed: false, cancelled: false };
327
+ }
328
+ console.log(chalk_1.default.blue('═══════════════════════════════════════════════════════════'));
329
+ console.log('');
330
+ // Ask for confirmation
331
+ try {
332
+ const proceed = await (0, prompts_1.confirm)({
333
+ message: warnings.length > 0
334
+ ? 'Continue despite warnings?'
335
+ : 'Create this genbox?',
336
+ default: warnings.length === 0,
337
+ });
338
+ return { confirmed: proceed, cancelled: false };
339
+ }
340
+ catch {
341
+ return { confirmed: false, cancelled: true };
342
+ }
343
+ }
64
344
  /**
65
345
  * Get SSH key paths
66
346
  */
@@ -298,12 +578,22 @@ function showProgress(status, elapsed, percent) {
298
578
  * Create a local Docker genbox
299
579
  */
300
580
  async function createLocalDockerGenbox(name, provider = 'claude') {
301
- // Check Docker availability
302
- if (!isDockerAvailable()) {
303
- console.log(chalk_1.default.red('\nDocker is not running.'));
304
- console.log(chalk_1.default.dim('Please start Docker Desktop and try again.'));
305
- return { success: false, error: 'Docker not available' };
581
+ // Get system resources
582
+ const resources = getSystemResources();
583
+ // Prompt for size selection
584
+ const selectedSize = await promptForSize('docker', resources);
585
+ if (!selectedSize) {
586
+ return { success: false, error: 'Cancelled' };
587
+ }
588
+ // Show resource summary and get confirmation
589
+ const { confirmed, cancelled } = await confirmResourceUsage('docker', resources, selectedSize);
590
+ if (cancelled) {
591
+ return { success: false, error: 'Cancelled' };
592
+ }
593
+ if (!confirmed) {
594
+ return { success: false, error: 'Resource check failed or user declined' };
306
595
  }
596
+ const sizeReqs = DOCKER_SIZE_REQUIREMENTS[selectedSize];
307
597
  const startTime = Date.now();
308
598
  showProgress('Creating Docker container', 0, 10);
309
599
  try {
@@ -330,6 +620,7 @@ async function createLocalDockerGenbox(name, provider = 'claude') {
330
620
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
331
621
  console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
332
622
  console.log(` ${chalk_1.default.bold('Type:')} Docker container`);
623
+ console.log(` ${chalk_1.default.bold('Size:')} ${sizeReqs.label} (${sizeReqs.cpus} CPU, ${sizeReqs.memoryGB}GB RAM)`);
333
624
  console.log(` ${chalk_1.default.bold('Provider:')} ${provider}`);
334
625
  console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.green('running')}`);
335
626
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
@@ -355,12 +646,22 @@ async function createLocalDockerGenbox(name, provider = 'claude') {
355
646
  * Create a local VM genbox (Multipass)
356
647
  */
357
648
  async function createLocalVmGenbox(name, provider = 'claude') {
358
- // Check Multipass availability
359
- if (!isMultipassAvailable()) {
360
- console.log(chalk_1.default.red('\nMultipass is not installed.'));
361
- console.log(chalk_1.default.dim('Install from: https://canonical.com/multipass/install'));
362
- return { success: false, error: 'Multipass not available' };
649
+ // Get system resources
650
+ const resources = getSystemResources();
651
+ // Prompt for size selection
652
+ const selectedSize = await promptForSize('vm', resources);
653
+ if (!selectedSize) {
654
+ return { success: false, error: 'Cancelled' };
655
+ }
656
+ // Show resource summary and get confirmation
657
+ const { confirmed, cancelled } = await confirmResourceUsage('vm', resources, selectedSize);
658
+ if (cancelled) {
659
+ return { success: false, error: 'Cancelled' };
660
+ }
661
+ if (!confirmed) {
662
+ return { success: false, error: 'Resource check failed or user declined' };
363
663
  }
664
+ const sizeReqs = VM_SIZE_REQUIREMENTS[selectedSize];
364
665
  const startTime = Date.now();
365
666
  // Progress stages with estimated percentages
366
667
  const stages = [
@@ -401,13 +702,13 @@ packages:
401
702
  // Write cloud-init to temp file
402
703
  const cloudInitPath = path.join(os.tmpdir(), `genbox-cloud-init-${name}.yaml`);
403
704
  fs.writeFileSync(cloudInitPath, cloudInitConfig);
404
- // Create Multipass VM with cloud-init
705
+ // Create Multipass VM with cloud-init (using selected size)
405
706
  const vmProcess = (0, child_process_1.spawn)('multipass', [
406
707
  'launch',
407
708
  '--name', name,
408
- '--cpus', '2',
409
- '--memory', '4G',
410
- '--disk', '20G',
709
+ '--cpus', String(sizeReqs.cpus),
710
+ '--memory', `${sizeReqs.memoryGB}G`,
711
+ '--disk', `${sizeReqs.diskGB}G`,
411
712
  '--cloud-init', cloudInitPath,
412
713
  ], {
413
714
  stdio: 'pipe',
@@ -490,6 +791,7 @@ packages:
490
791
  console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
491
792
  console.log(` ${chalk_1.default.bold('Name:')} ${session.name}`);
492
793
  console.log(` ${chalk_1.default.bold('Type:')} Multipass VM`);
794
+ console.log(` ${chalk_1.default.bold('Size:')} ${sizeReqs.label} (${sizeReqs.cpus} CPU, ${sizeReqs.memoryGB}GB RAM, ${sizeReqs.diskGB}GB disk)`);
493
795
  console.log(` ${chalk_1.default.bold('Provider:')} ${provider}`);
494
796
  if (vmIp) {
495
797
  console.log(` ${chalk_1.default.bold('IP:')} ${vmIp}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.198",
3
+ "version": "1.0.199",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {