genbox 1.0.97 → 1.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cleanupSshCommand = void 0;
7
+ exports.cleanupOrphanedSshConfigs = cleanupOrphanedSshConfigs;
8
+ const commander_1 = require("commander");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../api");
12
+ const ssh_config_1 = require("../ssh-config");
13
+ /**
14
+ * Clean up orphaned SSH config entries for genboxes that no longer exist
15
+ * Returns the list of removed entries
16
+ */
17
+ async function cleanupOrphanedSshConfigs(options = {}) {
18
+ const { silent = false } = options;
19
+ // Get all SSH config entries for genboxes
20
+ const sshEntries = (0, ssh_config_1.getAllSshConfigEntries)();
21
+ if (sshEntries.length === 0) {
22
+ return [];
23
+ }
24
+ // Fetch all user's genboxes from API
25
+ let existingGenboxes = [];
26
+ try {
27
+ existingGenboxes = await (0, api_1.fetchApi)('/genboxes');
28
+ }
29
+ catch (error) {
30
+ // If not authenticated, we can't check - skip silently in auto mode
31
+ if (error instanceof api_1.AuthenticationError) {
32
+ if (!silent) {
33
+ throw error;
34
+ }
35
+ return [];
36
+ }
37
+ throw error;
38
+ }
39
+ // Get names of existing genboxes (both running and terminated but recent)
40
+ const existingNames = new Set(existingGenboxes.map(g => g.name));
41
+ // Find orphaned entries (SSH configs for genboxes that don't exist anymore)
42
+ const orphaned = sshEntries.filter(name => !existingNames.has(name));
43
+ if (orphaned.length === 0) {
44
+ return [];
45
+ }
46
+ // Remove orphaned entries
47
+ const removed = [];
48
+ for (const name of orphaned) {
49
+ if ((0, ssh_config_1.removeSshConfigEntry)(name)) {
50
+ removed.push(name);
51
+ }
52
+ }
53
+ return removed;
54
+ }
55
+ exports.cleanupSshCommand = new commander_1.Command('cleanup-ssh')
56
+ .description('Remove SSH config entries for genboxes that no longer exist')
57
+ .option('-q, --quiet', 'Only output if entries were removed')
58
+ .action(async (options) => {
59
+ try {
60
+ const spinner = (0, ora_1.default)('Checking for orphaned SSH configs...').start();
61
+ const sshEntries = (0, ssh_config_1.getAllSshConfigEntries)();
62
+ if (sshEntries.length === 0) {
63
+ spinner.info(chalk_1.default.dim('No genbox SSH config entries found'));
64
+ return;
65
+ }
66
+ spinner.text = `Found ${sshEntries.length} SSH config entries, checking against API...`;
67
+ const removed = await cleanupOrphanedSshConfigs();
68
+ if (removed.length === 0) {
69
+ spinner.succeed(chalk_1.default.dim('No orphaned SSH configs found'));
70
+ if (!options.quiet) {
71
+ console.log(chalk_1.default.dim(` ${sshEntries.length} SSH config entries are all valid`));
72
+ }
73
+ return;
74
+ }
75
+ spinner.succeed(chalk_1.default.green(`Cleaned up ${removed.length} orphaned SSH config${removed.length === 1 ? '' : 's'}`));
76
+ if (!options.quiet) {
77
+ console.log('');
78
+ console.log(chalk_1.default.dim('Removed entries for:'));
79
+ for (const name of removed) {
80
+ console.log(chalk_1.default.dim(` - ${name}`));
81
+ }
82
+ }
83
+ }
84
+ catch (error) {
85
+ if (error instanceof api_1.AuthenticationError) {
86
+ (0, api_1.handleApiError)(error);
87
+ return;
88
+ }
89
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
90
+ }
91
+ });
@@ -234,6 +234,10 @@ async function triggerSaveWork(genbox) {
234
234
  }
235
235
  }
236
236
  }
237
+ // Display Claude history backup status
238
+ if (result.claudeHistoryBackedUp) {
239
+ console.log(` ${chalk_1.default.green('✓')} Claude history backed up to database`);
240
+ }
237
241
  return result;
238
242
  }
239
243
  catch (error) {
package/dist/index.js CHANGED
@@ -1,8 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
5
9
  const init_1 = require("./commands/init");
10
+ const cleanup_ssh_1 = require("./commands/cleanup-ssh");
11
+ /**
12
+ * Auto-cleanup orphaned SSH configs on startup (non-blocking, silent)
13
+ * This runs in the background without affecting CLI startup time
14
+ */
15
+ function autoCleanupSshConfigs() {
16
+ // Run cleanup in background - don't await, don't block startup
17
+ (0, cleanup_ssh_1.cleanupOrphanedSshConfigs)({ silent: true })
18
+ .then(removed => {
19
+ // Optionally log if GENBOX_DEBUG is set
20
+ if (process.env.GENBOX_DEBUG && removed.length > 0) {
21
+ console.error(chalk_1.default.dim(`[Auto-cleanup] Removed ${removed.length} orphaned SSH config(s): ${removed.join(', ')}`));
22
+ }
23
+ })
24
+ .catch(() => {
25
+ // Silently ignore errors - this is a background cleanup
26
+ });
27
+ }
28
+ // Run auto-cleanup on startup (non-blocking)
29
+ autoCleanupSshConfigs();
6
30
  const program = new commander_1.Command();
7
31
  // eslint-disable-next-line @typescript-eslint/no-var-requires
8
32
  const { version } = require('../package.json');
@@ -31,6 +55,7 @@ const migrate_1 = require("./commands/migrate");
31
55
  const ssh_setup_1 = require("./commands/ssh-setup");
32
56
  const rebuild_1 = require("./commands/rebuild");
33
57
  const extend_1 = require("./commands/extend");
58
+ const cleanup_ssh_2 = require("./commands/cleanup-ssh");
34
59
  program
35
60
  .addCommand(init_1.initCommand)
36
61
  .addCommand(create_1.createCommand)
@@ -54,5 +79,6 @@ program
54
79
  .addCommand(migrate_1.deprecationsCommand)
55
80
  .addCommand(ssh_setup_1.sshSetupCommand)
56
81
  .addCommand(rebuild_1.rebuildCommand)
57
- .addCommand(extend_1.extendCommand);
82
+ .addCommand(extend_1.extendCommand)
83
+ .addCommand(cleanup_ssh_2.cleanupSshCommand);
58
84
  program.parse(process.argv);
@@ -170,20 +170,41 @@ class ProjectScanner {
170
170
  const hasStartScript = !!(pkg.scripts?.start || pkg.scripts?.dev || pkg.scripts?.serve);
171
171
  const type = this.inferAppTypeFromPackage(entry.name, pkg, hasStartScript);
172
172
  // Try to detect framework
173
+ // Priority: fullstack frameworks > meta-frameworks > dev servers > UI libraries
173
174
  let framework;
174
175
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
176
+ // 1. Fullstack/meta-frameworks (most specific)
175
177
  if (deps['next'])
176
178
  framework = 'nextjs';
179
+ else if (deps['nuxt'])
180
+ framework = 'nuxt';
181
+ else if (deps['@remix-run/react'])
182
+ framework = 'remix';
183
+ else if (deps['astro'])
184
+ framework = 'astro';
185
+ else if (deps['@sveltejs/kit'])
186
+ framework = 'sveltekit';
187
+ // 2. Backend frameworks
177
188
  else if (deps['@nestjs/core'])
178
189
  framework = 'nestjs';
190
+ else if (deps['fastify'])
191
+ framework = 'fastify';
192
+ else if (deps['express'])
193
+ framework = 'express';
194
+ else if (deps['hono'])
195
+ framework = 'hono';
196
+ // 3. Dev servers (determines port)
197
+ else if (deps['vite'])
198
+ framework = 'vite';
199
+ // 4. UI libraries (least specific - use dev server port if available)
179
200
  else if (deps['react-admin'])
180
- framework = 'react-admin';
201
+ framework = 'vite'; // react-admin uses vite
181
202
  else if (deps['react'])
182
203
  framework = 'react';
183
204
  else if (deps['vue'])
184
205
  framework = 'vue';
185
- else if (deps['express'])
186
- framework = 'express';
206
+ else if (deps['svelte'])
207
+ framework = 'svelte';
187
208
  // Try to detect port from package.json scripts
188
209
  let port;
189
210
  const devScript = pkg.scripts?.dev || pkg.scripts?.start || '';
@@ -370,19 +391,80 @@ class ProjectScanner {
370
391
  if (!app.framework) {
371
392
  app.framework = appFramework.name;
372
393
  }
373
- // Apply default port from framework
374
- if (!app.port && appFramework.defaultPort) {
375
- app.port = appFramework.defaultPort;
394
+ }
395
+ // Priority 1: Detect port from config files (vite.config.ts, etc.)
396
+ if (!app.port) {
397
+ const configPort = this.detectPortFromConfig(appPath);
398
+ if (configPort) {
399
+ app.port = configPort;
400
+ }
401
+ }
402
+ // Priority 2: Use framework default from detector
403
+ if (!app.port && appFramework?.defaultPort) {
404
+ app.port = appFramework.defaultPort;
405
+ }
406
+ // Priority 3: Use hardcoded framework default as last resort
407
+ if (!app.port && app.framework) {
408
+ app.port = this.getDefaultPortForFramework(app.framework);
409
+ }
410
+ }
411
+ }
412
+ /**
413
+ * Detect port from config files (vite.config.ts, next.config.js, etc.)
414
+ */
415
+ detectPortFromConfig(appPath) {
416
+ const fs = require('fs');
417
+ const pathModule = require('path');
418
+ // Try vite.config.ts/js
419
+ for (const configFile of ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs']) {
420
+ const configPath = pathModule.join(appPath, configFile);
421
+ if (fs.existsSync(configPath)) {
422
+ try {
423
+ const content = fs.readFileSync(configPath, 'utf8');
424
+ // Match server.port or server: { port: XXXX }
425
+ const portMatch = content.match(/server\s*:\s*\{[^}]*port\s*:\s*(\d+)/s) ||
426
+ content.match(/port\s*:\s*(\d+)/);
427
+ if (portMatch) {
428
+ return parseInt(portMatch[1], 10);
429
+ }
376
430
  }
431
+ catch { }
377
432
  }
378
- else if (app.framework) {
379
- // App has framework name but we need to look up its default port
380
- const matchingFramework = frameworks.find(f => f.name === app.framework);
381
- if (matchingFramework?.defaultPort && !app.port) {
382
- app.port = matchingFramework.defaultPort;
433
+ }
434
+ // Try next.config.js/mjs (less common to have port there, but check anyway)
435
+ // Next.js port is usually in package.json scripts or CLI args
436
+ // Try nuxt.config.ts/js
437
+ for (const configFile of ['nuxt.config.ts', 'nuxt.config.js']) {
438
+ const configPath = pathModule.join(appPath, configFile);
439
+ if (fs.existsSync(configPath)) {
440
+ try {
441
+ const content = fs.readFileSync(configPath, 'utf8');
442
+ const portMatch = content.match(/devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/s) ||
443
+ content.match(/port\s*:\s*(\d+)/);
444
+ if (portMatch) {
445
+ return parseInt(portMatch[1], 10);
446
+ }
383
447
  }
448
+ catch { }
384
449
  }
385
450
  }
451
+ return undefined;
452
+ }
453
+ /**
454
+ * Get default port for a framework (fallback if config file doesn't specify)
455
+ */
456
+ getDefaultPortForFramework(framework) {
457
+ const portMap = {
458
+ nextjs: 3000,
459
+ nuxt: 3000,
460
+ vite: 5173,
461
+ react: 3000,
462
+ vue: 5173,
463
+ angular: 4200,
464
+ nestjs: 3000,
465
+ express: 3000,
466
+ };
467
+ return portMap[framework];
386
468
  }
387
469
  /**
388
470
  * Ensure all apps have unique ports.
@@ -38,6 +38,7 @@ exports.removeSshConfigEntry = removeSshConfigEntry;
38
38
  exports.updateSshConfigEntry = updateSshConfigEntry;
39
39
  exports.hasSshConfigEntry = hasSshConfigEntry;
40
40
  exports.getSshAlias = getSshAlias;
41
+ exports.getAllSshConfigEntries = getAllSshConfigEntries;
41
42
  const fs = __importStar(require("fs"));
42
43
  const path = __importStar(require("path"));
43
44
  const os = __importStar(require("os"));
@@ -214,3 +215,23 @@ function hasSshConfigEntry(name) {
214
215
  function getSshAlias(name) {
215
216
  return `genbox-${name}`;
216
217
  }
218
+ /**
219
+ * Get all Genbox SSH config entries from ~/.ssh/config
220
+ * Returns array of genbox names that have SSH config entries
221
+ */
222
+ function getAllSshConfigEntries() {
223
+ try {
224
+ const config = readSshConfig();
225
+ const entries = [];
226
+ // Find all genbox markers
227
+ const regex = new RegExp(`${GENBOX_MARKER_START} (.+)`, 'g');
228
+ let match;
229
+ while ((match = regex.exec(config)) !== null) {
230
+ entries.push(match[1]);
231
+ }
232
+ return entries;
233
+ }
234
+ catch (error) {
235
+ return [];
236
+ }
237
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {