genbox 1.0.181 → 1.0.182

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.
@@ -508,6 +508,226 @@ function getDtachSocketDir() {
508
508
  function getDtachSocketPath(sessionName) {
509
509
  return path.join(getDtachSocketDir(), `${sessionName}.sock`);
510
510
  }
511
+ /**
512
+ * Special key mappings (inspired by tmux's key-string.c)
513
+ * Maps key names to their terminal byte sequences
514
+ */
515
+ const SPECIAL_KEYS = {
516
+ 'Enter': '\r',
517
+ 'Return': '\r',
518
+ 'Tab': '\t',
519
+ 'Escape': '\x1b',
520
+ 'Space': ' ',
521
+ 'Backspace': '\x7f',
522
+ 'Delete': '\x1b[3~',
523
+ 'Up': '\x1b[A',
524
+ 'Down': '\x1b[B',
525
+ 'Right': '\x1b[C',
526
+ 'Left': '\x1b[D',
527
+ 'Home': '\x1b[H',
528
+ 'End': '\x1b[F',
529
+ 'PageUp': '\x1b[5~',
530
+ 'PageDown': '\x1b[6~',
531
+ 'Insert': '\x1b[2~',
532
+ 'F1': '\x1bOP',
533
+ 'F2': '\x1bOQ',
534
+ 'F3': '\x1bOR',
535
+ 'F4': '\x1bOS',
536
+ 'F5': '\x1b[15~',
537
+ 'F6': '\x1b[17~',
538
+ 'F7': '\x1b[18~',
539
+ 'F8': '\x1b[19~',
540
+ 'F9': '\x1b[20~',
541
+ 'F10': '\x1b[21~',
542
+ 'F11': '\x1b[23~',
543
+ 'F12': '\x1b[24~',
544
+ };
545
+ /**
546
+ * Convert control key notation to byte (e.g., C-c -> \x03)
547
+ * Follows tmux convention: C-a through C-z map to 0x01-0x1a
548
+ */
549
+ function parseControlKey(key) {
550
+ const match = key.match(/^[Cc]-([a-zA-Z@\[\]\\^_])$/);
551
+ if (!match)
552
+ return null;
553
+ const char = match[1].toLowerCase();
554
+ if (char >= 'a' && char <= 'z') {
555
+ return String.fromCharCode(char.charCodeAt(0) - 96); // a=1, z=26
556
+ }
557
+ // Special control chars
558
+ const specials = {
559
+ '@': 0, '[': 27, '\\': 28, ']': 29, '^': 30, '_': 31
560
+ };
561
+ if (specials[char] !== undefined) {
562
+ return String.fromCharCode(specials[char]);
563
+ }
564
+ return null;
565
+ }
566
+ /**
567
+ * Send keys to a dtach session (inspired by tmux's send-keys)
568
+ *
569
+ * This function writes directly to dtach's stdin, bypassing shell interpretation.
570
+ * Like tmux, it supports:
571
+ * - Literal text
572
+ * - Special keys (Enter, Tab, Escape, etc.)
573
+ * - Control sequences (C-c, C-d, etc.)
574
+ *
575
+ * @param socketPath - Path to the dtach socket
576
+ * @param keys - Array of keys/text to send. Each element can be:
577
+ * - A string of literal text
578
+ * - A special key name like "Enter", "Tab", "Escape"
579
+ * - A control sequence like "C-c"
580
+ * @param options - Optional: addNewline (append Enter after all keys)
581
+ */
582
+ function sendKeysToDtach(socketPath, keys, options = {}) {
583
+ // Build the byte sequence to send
584
+ let data = '';
585
+ for (const key of keys) {
586
+ // Check for special key
587
+ if (SPECIAL_KEYS[key]) {
588
+ data += SPECIAL_KEYS[key];
589
+ continue;
590
+ }
591
+ // Check for control key notation
592
+ const ctrlKey = parseControlKey(key);
593
+ if (ctrlKey) {
594
+ data += ctrlKey;
595
+ continue;
596
+ }
597
+ // Literal text
598
+ data += key;
599
+ }
600
+ // Optionally add newline
601
+ if (options.addNewline) {
602
+ data += '\r';
603
+ }
604
+ // Write directly to dtach via spawn (bypasses shell interpretation)
605
+ // This is the key insight from tmux: write bytes directly, don't go through shell
606
+ const proc = (0, child_process_1.spawnSync)('dtach', ['-p', socketPath], {
607
+ input: data,
608
+ timeout: 5000,
609
+ stdio: ['pipe', 'ignore', 'ignore'],
610
+ });
611
+ if (proc.error) {
612
+ throw new Error(`Failed to send keys: ${proc.error.message}`);
613
+ }
614
+ }
615
+ /**
616
+ * Send keys to a remote dtach session via SSH
617
+ * Uses the same direct-write approach as local, but through SSH stdin forwarding
618
+ */
619
+ function sendKeysToRemoteDtach(ipAddress, keyPath, socketPath, keys, options = {}) {
620
+ // Build the byte sequence
621
+ let data = '';
622
+ for (const key of keys) {
623
+ if (SPECIAL_KEYS[key]) {
624
+ data += SPECIAL_KEYS[key];
625
+ continue;
626
+ }
627
+ const ctrlKey = parseControlKey(key);
628
+ if (ctrlKey) {
629
+ data += ctrlKey;
630
+ continue;
631
+ }
632
+ data += key;
633
+ }
634
+ if (options.addNewline) {
635
+ data += '\r';
636
+ }
637
+ // SSH with stdin forwarding to dtach -p
638
+ const proc = (0, child_process_1.spawnSync)('ssh', [
639
+ '-i', keyPath,
640
+ '-o', 'StrictHostKeyChecking=no',
641
+ '-o', 'UserKnownHostsFile=/dev/null',
642
+ '-o', 'ConnectTimeout=5',
643
+ `dev@${ipAddress}`,
644
+ `dtach -p ${socketPath}`
645
+ ], {
646
+ input: data,
647
+ timeout: 30000,
648
+ stdio: ['pipe', 'ignore', 'ignore'],
649
+ });
650
+ if (proc.error) {
651
+ throw new Error(`Failed to send keys to remote: ${proc.error.message}`);
652
+ }
653
+ }
654
+ /**
655
+ * Legacy escape function for cases where shell command is unavoidable
656
+ * Handles special shell characters that could cause issues
657
+ */
658
+ function escapeForShell(str) {
659
+ return str
660
+ .replace(/\\/g, '\\\\') // Backslashes first
661
+ .replace(/'/g, "'\\''") // Single quotes
662
+ .replace(/\$/g, '\\$') // Dollar signs
663
+ .replace(/`/g, '\\`') // Backticks
664
+ .replace(/!/g, '\\!') // History expansion
665
+ .replace(/"/g, '\\"'); // Double quotes
666
+ }
667
+ /**
668
+ * Check if a dtach socket is actually alive (not just file exists)
669
+ * Attempts a brief connection to verify the process is running
670
+ */
671
+ function isDtachSocketAlive(socketPath) {
672
+ if (!fs.existsSync(socketPath)) {
673
+ return false;
674
+ }
675
+ try {
676
+ // Try to send an empty string to test if socket is alive
677
+ // Using timeout to avoid hanging on dead sockets
678
+ (0, child_process_1.execSync)(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
679
+ timeout: 2000,
680
+ stdio: 'ignore'
681
+ });
682
+ return true;
683
+ }
684
+ catch {
685
+ // Socket exists but is stale - clean it up
686
+ try {
687
+ fs.unlinkSync(socketPath);
688
+ console.log(chalk_1.default.dim(`Cleaned up stale socket: ${path.basename(socketPath)}`));
689
+ }
690
+ catch {
691
+ // Ignore cleanup errors
692
+ }
693
+ return false;
694
+ }
695
+ }
696
+ /**
697
+ * Check if a remote dtach socket is alive via SSH
698
+ */
699
+ function isRemoteDtachSocketAlive(ipAddress, keyPath, socketPath) {
700
+ try {
701
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 dev@${ipAddress} "echo -n '' | dtach -p ${socketPath}" 2>/dev/null`, { timeout: 5000, stdio: 'ignore' });
702
+ return true;
703
+ }
704
+ catch {
705
+ return false;
706
+ }
707
+ }
708
+ /**
709
+ * Clean up all stale sockets in the local sockets directory
710
+ * Called on startup to ensure clean state
711
+ */
712
+ function cleanupAllStaleSockets() {
713
+ const socketsDir = getDtachSocketDir();
714
+ let cleaned = 0;
715
+ try {
716
+ const files = fs.readdirSync(socketsDir);
717
+ for (const file of files) {
718
+ if (file.endsWith('.sock')) {
719
+ const socketPath = path.join(socketsDir, file);
720
+ if (!isDtachSocketAlive(socketPath)) {
721
+ cleaned++;
722
+ }
723
+ }
724
+ }
725
+ }
726
+ catch {
727
+ // Directory might not exist yet
728
+ }
729
+ return cleaned;
730
+ }
511
731
  /**
512
732
  * Ensure local dtach is available or get user choice
513
733
  * Returns: 'available' | 'skip' | 'cancel'
@@ -1603,12 +1823,12 @@ async function createNativeSession(options) {
1603
1823
  });
1604
1824
  }
1605
1825
  /**
1606
- * Check if a dtach session is running locally (by checking if socket file exists)
1826
+ * Check if a dtach session is running locally (verifies socket is actually alive)
1607
1827
  */
1608
1828
  function isDtachSessionRunning(sessionName) {
1609
1829
  try {
1610
1830
  const socketPath = getDtachSocketPath(sessionName);
1611
- return fs.existsSync(socketPath);
1831
+ return isDtachSocketAlive(socketPath);
1612
1832
  }
1613
1833
  catch {
1614
1834
  return false;
@@ -1970,6 +2190,11 @@ exports.sessionCommand = new commander_1.Command('session')
1970
2190
  .addCommand(migrate_1.sessionMigrateCommand)
1971
2191
  .action(async (sessionArg, providerArgs, options) => {
1972
2192
  try {
2193
+ // Clean up stale sockets on startup (silently)
2194
+ const cleaned = cleanupAllStaleSockets();
2195
+ if (cleaned > 0) {
2196
+ console.log(chalk_1.default.dim(`Cleaned up ${cleaned} stale socket${cleaned > 1 ? 's' : ''}`));
2197
+ }
1973
2198
  // Multi-session orchestration mode
1974
2199
  if (options.multi) {
1975
2200
  // If sessionArg looks like a provider spec, treat it as part of providers
@@ -13,7 +13,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.LocalOrchestrator = void 0;
14
14
  exports.parseProviderSpec = parseProviderSpec;
15
15
  exports.getLocalOrchestrator = getLocalOrchestrator;
16
- const child_process_1 = require("child_process");
17
16
  const chalk_1 = __importDefault(require("chalk"));
18
17
  const local_session_manager_1 = require("./local-session-manager");
19
18
  /**
@@ -233,11 +232,81 @@ class LocalOrchestrator {
233
232
  await this.sendPromptToCloudSession(orchSession.session, prompt);
234
233
  }
235
234
  }
235
+ /**
236
+ * Send keys directly to dtach stdin (inspired by tmux's send-keys)
237
+ * Bypasses shell interpretation by writing directly to the process
238
+ */
239
+ sendKeysDirect(socketPath, data) {
240
+ const { spawnSync } = require('child_process');
241
+ const proc = spawnSync('dtach', ['-p', socketPath], {
242
+ input: data + '\r',
243
+ timeout: 5000,
244
+ stdio: ['pipe', 'ignore', 'ignore'],
245
+ });
246
+ if (proc.error) {
247
+ throw new Error(`Failed to send keys: ${proc.error.message}`);
248
+ }
249
+ }
250
+ /**
251
+ * Send keys to Docker container's dtach session
252
+ */
253
+ sendKeysToDocker(containerId, socketPath, data) {
254
+ const { spawnSync } = require('child_process');
255
+ const proc = spawnSync('docker', [
256
+ 'exec', '-i', containerId,
257
+ 'dtach', '-p', socketPath
258
+ ], {
259
+ input: data + '\r',
260
+ timeout: 10000,
261
+ stdio: ['pipe', 'ignore', 'ignore'],
262
+ });
263
+ if (proc.error) {
264
+ throw new Error(`Failed to send keys to docker: ${proc.error.message}`);
265
+ }
266
+ }
267
+ /**
268
+ * Send keys to Multipass VM's dtach session
269
+ */
270
+ sendKeysToMultipass(vmName, socketPath, data) {
271
+ const { spawnSync } = require('child_process');
272
+ const proc = spawnSync('multipass', [
273
+ 'exec', vmName, '--',
274
+ 'dtach', '-p', socketPath
275
+ ], {
276
+ input: data + '\r',
277
+ timeout: 10000,
278
+ stdio: ['pipe', 'ignore', 'ignore'],
279
+ });
280
+ if (proc.error) {
281
+ throw new Error(`Failed to send keys to multipass: ${proc.error.message}`);
282
+ }
283
+ }
284
+ /**
285
+ * Send keys to remote dtach session via SSH stdin forwarding
286
+ */
287
+ sendKeysToRemote(ipAddress, keyPath, socketPath, data) {
288
+ const { spawnSync } = require('child_process');
289
+ const proc = spawnSync('ssh', [
290
+ '-i', keyPath,
291
+ '-o', 'StrictHostKeyChecking=no',
292
+ '-o', 'UserKnownHostsFile=/dev/null',
293
+ '-o', 'ConnectTimeout=5',
294
+ `dev@${ipAddress}`,
295
+ `dtach -p ${socketPath}`
296
+ ], {
297
+ input: data + '\r',
298
+ timeout: 30000,
299
+ stdio: ['pipe', 'ignore', 'ignore'],
300
+ });
301
+ if (proc.error) {
302
+ throw new Error(`Failed to send keys to remote: ${proc.error.message}`);
303
+ }
304
+ }
236
305
  /**
237
306
  * Send a prompt to a local session via dtach -p (push mode)
307
+ * Uses direct stdin write (inspired by tmux's send-keys) to bypass shell interpretation
238
308
  */
239
309
  async sendPromptToLocalSession(session, prompt) {
240
- const escapedPrompt = prompt.replace(/'/g, "'\\''");
241
310
  const os = require('os');
242
311
  const path = require('path');
243
312
  try {
@@ -246,18 +315,18 @@ class LocalOrchestrator {
246
315
  if (!session.containerId)
247
316
  return;
248
317
  await new Promise(resolve => setTimeout(resolve, 2000));
249
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | docker exec -i ${session.containerId} dtach -p /home/dev/.genbox/sockets/${session.name}.sock`, { stdio: 'ignore' });
318
+ this.sendKeysToDocker(session.containerId, `/home/dev/.genbox/sockets/${session.name}.sock`, prompt);
250
319
  break;
251
320
  case 'multipass':
252
321
  if (!session.vmName)
253
322
  return;
254
323
  await new Promise(resolve => setTimeout(resolve, 2000));
255
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | multipass exec ${session.vmName} -- dtach -p /home/ubuntu/.genbox/sockets/${session.name}.sock`, { stdio: 'ignore' });
324
+ this.sendKeysToMultipass(session.vmName, `/home/ubuntu/.genbox/sockets/${session.name}.sock`, prompt);
256
325
  break;
257
326
  case 'native':
258
327
  await new Promise(resolve => setTimeout(resolve, 1000));
259
328
  const socketPath = path.join(os.homedir(), '.genbox', 'sockets', `${session.name}.sock`);
260
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | dtach -p "${socketPath}"`, { stdio: 'ignore' });
329
+ this.sendKeysDirect(socketPath, prompt);
261
330
  break;
262
331
  }
263
332
  }
@@ -267,18 +336,18 @@ class LocalOrchestrator {
267
336
  }
268
337
  /**
269
338
  * Send a prompt to a cloud session via SSH + dtach -p
339
+ * Uses direct stdin forwarding to bypass shell interpretation
270
340
  */
271
341
  async sendPromptToCloudSession(session, prompt) {
272
342
  if (!session.ipAddress) {
273
343
  console.log(chalk_1.default.yellow(`Warning: Cloud session ${session.name} has no IP address`));
274
344
  return;
275
345
  }
276
- const escapedPrompt = prompt.replace(/'/g, "'\\''");
277
346
  const socketPath = `/home/dev/.genbox/sockets/${session.name}.sock`;
278
347
  try {
279
348
  const keyPath = this.getPrivateSshKey();
280
349
  await new Promise(resolve => setTimeout(resolve, 2000));
281
- (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${session.ipAddress} "echo '${escapedPrompt}' | dtach -p ${socketPath}" 2>/dev/null`, { stdio: 'ignore', timeout: 30000 });
350
+ this.sendKeysToRemote(session.ipAddress, keyPath, socketPath, prompt);
282
351
  }
283
352
  catch (error) {
284
353
  console.log(chalk_1.default.yellow(`Warning: Could not send prompt to cloud session ${session.name}: ${error.message}`));
@@ -293,7 +293,7 @@ class LocalSessionManager {
293
293
  checkNativeStatus(session) {
294
294
  try {
295
295
  const socketPath = path.join(os.homedir(), '.genbox', 'sockets', `${session.name}.sock`);
296
- if (fs.existsSync(socketPath)) {
296
+ if (this.isDtachSocketAlive(socketPath)) {
297
297
  return 'running';
298
298
  }
299
299
  return 'stopped';
@@ -302,6 +302,80 @@ class LocalSessionManager {
302
302
  return 'stopped';
303
303
  }
304
304
  }
305
+ /**
306
+ * Check if a dtach socket is actually alive (not just file exists)
307
+ */
308
+ isDtachSocketAlive(socketPath) {
309
+ if (!fs.existsSync(socketPath)) {
310
+ return false;
311
+ }
312
+ try {
313
+ // Try to send an empty string to test if socket is alive
314
+ (0, child_process_1.execSync)(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
315
+ timeout: 2000,
316
+ stdio: 'ignore'
317
+ });
318
+ return true;
319
+ }
320
+ catch {
321
+ // Socket exists but is stale - clean it up
322
+ try {
323
+ fs.unlinkSync(socketPath);
324
+ }
325
+ catch {
326
+ // Ignore cleanup errors
327
+ }
328
+ return false;
329
+ }
330
+ }
331
+ /**
332
+ * Send keys directly to dtach stdin (inspired by tmux's send-keys)
333
+ * Bypasses shell interpretation by writing directly to the process
334
+ */
335
+ sendKeysDirect(socketPath, data) {
336
+ const proc = (0, child_process_1.spawnSync)('dtach', ['-p', socketPath], {
337
+ input: data + '\r', // Add Enter key
338
+ timeout: 5000,
339
+ stdio: ['pipe', 'ignore', 'ignore'],
340
+ });
341
+ if (proc.error) {
342
+ throw new Error(`Failed to send keys: ${proc.error.message}`);
343
+ }
344
+ }
345
+ /**
346
+ * Send keys to Docker container's dtach session
347
+ * Uses docker exec with stdin forwarding
348
+ */
349
+ sendKeysToDocker(containerId, socketPath, data) {
350
+ const proc = (0, child_process_1.spawnSync)('docker', [
351
+ 'exec', '-i', containerId,
352
+ 'dtach', '-p', socketPath
353
+ ], {
354
+ input: data + '\r',
355
+ timeout: 10000,
356
+ stdio: ['pipe', 'ignore', 'ignore'],
357
+ });
358
+ if (proc.error) {
359
+ throw new Error(`Failed to send keys to docker: ${proc.error.message}`);
360
+ }
361
+ }
362
+ /**
363
+ * Send keys to Multipass VM's dtach session
364
+ * Uses multipass exec with stdin forwarding
365
+ */
366
+ sendKeysToMultipass(vmName, socketPath, data) {
367
+ const proc = (0, child_process_1.spawnSync)('multipass', [
368
+ 'exec', vmName, '--',
369
+ 'dtach', '-p', socketPath
370
+ ], {
371
+ input: data + '\r',
372
+ timeout: 10000,
373
+ stdio: ['pipe', 'ignore', 'ignore'],
374
+ });
375
+ if (proc.error) {
376
+ throw new Error(`Failed to send keys to multipass: ${proc.error.message}`);
377
+ }
378
+ }
305
379
  /**
306
380
  * List all local sessions
307
381
  */
@@ -867,25 +941,23 @@ final_message: "Cloud-init completed successfully"
867
941
  }
868
942
  /**
869
943
  * Send a prompt to a session via dtach -p (push mode)
870
- * dtach -p allows writing to the session's stdin without attaching
944
+ * Uses direct stdin write (inspired by tmux's send-keys) to bypass shell interpretation
871
945
  */
872
946
  async sendPromptToSession(session, prompt) {
873
- // Escape the prompt for shell and add Enter key
874
- const escapedPrompt = prompt.replace(/'/g, "'\\''");
875
947
  switch (session.isolation) {
876
948
  case 'docker':
877
949
  if (!session.containerId)
878
950
  throw new Error('Session has no container ID');
879
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | docker exec -i ${session.containerId} dtach -p /home/dev/.genbox/sockets/${session.name}.sock`, { stdio: 'ignore' });
951
+ this.sendKeysToDocker(session.containerId, `/home/dev/.genbox/sockets/${session.name}.sock`, prompt);
880
952
  break;
881
953
  case 'multipass':
882
954
  if (!session.vmName)
883
955
  throw new Error('Session has no VM name');
884
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | multipass exec ${session.vmName} -- dtach -p /home/ubuntu/.genbox/sockets/${session.name}.sock`, { stdio: 'ignore' });
956
+ this.sendKeysToMultipass(session.vmName, `/home/ubuntu/.genbox/sockets/${session.name}.sock`, prompt);
885
957
  break;
886
958
  case 'native':
887
959
  const socketPath = path.join(os.homedir(), '.genbox', 'sockets', `${session.name}.sock`);
888
- (0, child_process_1.execSync)(`echo '${escapedPrompt}' | dtach -p "${socketPath}"`, { stdio: 'ignore' });
960
+ this.sendKeysDirect(socketPath, prompt);
889
961
  break;
890
962
  }
891
963
  // Update last activity
@@ -233,17 +233,44 @@ class NativeSessionManager {
233
233
  return this.listSessions().filter(s => this.isSessionRunning(s.dtachSocketName || s.name));
234
234
  }
235
235
  /**
236
- * Check if a dtach session is running (by checking if socket file exists)
236
+ * Check if a dtach session is running (verifies socket is actually alive)
237
237
  */
238
238
  isSessionRunning(sessionName) {
239
239
  try {
240
240
  const socketPath = path.join(os.homedir(), '.genbox', 'sockets', `${sessionName}.sock`);
241
- return fs.existsSync(socketPath);
241
+ return this.isDtachSocketAlive(socketPath);
242
242
  }
243
243
  catch {
244
244
  return false;
245
245
  }
246
246
  }
247
+ /**
248
+ * Check if a dtach socket is actually alive (not just file exists)
249
+ */
250
+ isDtachSocketAlive(socketPath) {
251
+ if (!fs.existsSync(socketPath)) {
252
+ return false;
253
+ }
254
+ try {
255
+ // Try to send an empty string to test if socket is alive
256
+ const { execSync } = require('child_process');
257
+ execSync(`echo -n "" | dtach -p "${socketPath}" 2>/dev/null`, {
258
+ timeout: 2000,
259
+ stdio: 'ignore'
260
+ });
261
+ return true;
262
+ }
263
+ catch {
264
+ // Socket exists but is stale - clean it up
265
+ try {
266
+ fs.unlinkSync(socketPath);
267
+ }
268
+ catch {
269
+ // Ignore cleanup errors
270
+ }
271
+ return false;
272
+ }
273
+ }
247
274
  /**
248
275
  * Update session status
249
276
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.181",
3
+ "version": "1.0.182",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {