genbox 1.0.151 → 1.0.153

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.
@@ -47,7 +47,6 @@ const path = __importStar(require("path"));
47
47
  const fs = __importStar(require("fs"));
48
48
  const select_1 = __importDefault(require("@inquirer/select"));
49
49
  const confirm_1 = __importDefault(require("@inquirer/confirm"));
50
- const pty = __importStar(require("node-pty"));
51
50
  function getPrivateSshKey() {
52
51
  const home = os.homedir();
53
52
  const potentialKeys = [
@@ -206,96 +205,12 @@ GENBOX_TMUX_EOF`, { encoding: 'utf-8' });
206
205
  // Ignore errors - config is optional enhancement
207
206
  }
208
207
  }
209
- // Patterns for detecting local file paths (macOS/Linux)
210
- const LOCAL_PATH_PATTERNS = [
211
- /\/var\/folders\/[^\s'"]+/g, // macOS temp folders (screenshots, etc.)
212
- /\/private\/var\/folders\/[^\s'"]+/g, // macOS private temp folders
213
- /\/tmp\/[^\s'"]+/g, // Unix temp
214
- /\/Users\/[^\s'"]+/g, // macOS home directories
215
- /\/home\/(?!dev)[^\s'"]+/g, // Linux home (but not /home/dev which is remote)
216
- /~\/[^\s'"]+/g, // Home directory shorthand
217
- ];
218
- // Image extensions we should auto-upload
219
- const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.ico'];
220
- function isImageFile(filePath) {
221
- const ext = path.extname(filePath).toLowerCase();
222
- return IMAGE_EXTENSIONS.includes(ext);
223
- }
224
- function expandPath(filePath) {
225
- if (filePath.startsWith('~/')) {
226
- return path.join(os.homedir(), filePath.slice(2));
227
- }
228
- return filePath;
229
- }
230
- function uploadFile(localPath, ipAddress, keyPath) {
231
- const expandedPath = expandPath(localPath);
232
- // Check if file exists locally
233
- if (!fs.existsSync(expandedPath)) {
234
- return { localPath, remotePath: '', success: false, error: 'File not found' };
235
- }
236
- const stat = fs.statSync(expandedPath);
237
- if (stat.isDirectory()) {
238
- return { localPath, remotePath: '', success: false, error: 'Is a directory' };
239
- }
240
- // Generate remote path
241
- const fileName = path.basename(expandedPath);
242
- const timestamp = Date.now();
243
- const remotePath = `/home/dev/uploads/${timestamp}-${fileName}`;
244
- try {
245
- // Ensure uploads directory exists
246
- (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "mkdir -p /home/dev/uploads" 2>/dev/null`, { encoding: 'utf-8', timeout: 10000 });
247
- // Upload file
248
- const result = (0, child_process_1.spawnSync)('scp', [
249
- '-i', keyPath,
250
- '-o', 'StrictHostKeyChecking=no',
251
- '-o', 'UserKnownHostsFile=/dev/null',
252
- expandedPath,
253
- `dev@${ipAddress}:${remotePath}`
254
- ], { timeout: 60000 });
255
- if (result.status !== 0) {
256
- return { localPath, remotePath: '', success: false, error: 'SCP failed' };
257
- }
258
- return { localPath, remotePath, success: true };
259
- }
260
- catch (error) {
261
- return { localPath, remotePath: '', success: false, error: error.message };
262
- }
263
- }
264
- function detectAndUploadFiles(input, ipAddress, keyPath) {
265
- const uploads = [];
266
- let modifiedInput = input;
267
- // Find all potential file paths
268
- const allMatches = [];
269
- for (const pattern of LOCAL_PATH_PATTERNS) {
270
- let match;
271
- const regex = new RegExp(pattern.source, pattern.flags);
272
- while ((match = regex.exec(input)) !== null) {
273
- allMatches.push({ path: match[0], index: match.index });
274
- }
275
- }
276
- // Deduplicate and sort by position (reverse to replace from end first)
277
- const uniquePaths = [...new Set(allMatches.map(m => m.path))];
278
- for (const localPath of uniquePaths) {
279
- const expandedPath = expandPath(localPath);
280
- // Only auto-upload if file exists and is an image
281
- if (fs.existsSync(expandedPath) && isImageFile(expandedPath)) {
282
- const result = uploadFile(localPath, ipAddress, keyPath);
283
- uploads.push(result);
284
- if (result.success) {
285
- // Replace all occurrences of this path with remote path
286
- modifiedInput = modifiedInput.split(localPath).join(result.remotePath);
287
- }
288
- }
289
- }
290
- return { modifiedInput, uploads };
291
- }
292
208
  exports.attachCommand = new commander_1.Command('attach')
293
209
  .description('Attach to a Claude session in a Genbox (or create new)')
294
210
  .argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
295
211
  .option('-s, --session <session>', 'Tmux session name to attach to directly')
296
212
  .option('-n, --new', 'Create a new Claude session')
297
213
  .option('-a, --all', 'Select from all genboxes (not just current project)')
298
- .option('--no-upload', 'Disable automatic file upload for local paths')
299
214
  .action(async (name, options) => {
300
215
  try {
301
216
  // 1. Select Genbox (interactive if no name provided)
@@ -325,7 +240,7 @@ exports.attachCommand = new commander_1.Command('attach')
325
240
  if (!created) {
326
241
  return;
327
242
  }
328
- await attachToSession(target.ipAddress, keyPath, sessionName, target.name, options.upload !== false);
243
+ await attachToSession(target.ipAddress, keyPath, sessionName, target.name);
329
244
  return;
330
245
  }
331
246
  // 5. List available tmux sessions
@@ -343,7 +258,7 @@ exports.attachCommand = new commander_1.Command('attach')
343
258
  if (!created) {
344
259
  return;
345
260
  }
346
- await attachToSession(target.ipAddress, keyPath, sessionName, target.name, options.upload !== false);
261
+ await attachToSession(target.ipAddress, keyPath, sessionName, target.name);
347
262
  return;
348
263
  }
349
264
  // 7. Determine which session to attach to
@@ -387,11 +302,11 @@ exports.attachCommand = new commander_1.Command('attach')
387
302
  if (!created) {
388
303
  return;
389
304
  }
390
- await attachToSession(target.ipAddress, keyPath, newSessionName, target.name, options.upload !== false);
305
+ await attachToSession(target.ipAddress, keyPath, newSessionName, target.name);
391
306
  return;
392
307
  }
393
308
  // 9. Attach to existing session
394
- await attachToSession(target.ipAddress, keyPath, sessionName, target.name, options.upload !== false);
309
+ await attachToSession(target.ipAddress, keyPath, sessionName, target.name);
395
310
  }
396
311
  catch (error) {
397
312
  if (error instanceof api_1.AuthenticationError) {
@@ -401,100 +316,26 @@ exports.attachCommand = new commander_1.Command('attach')
401
316
  console.error(chalk_1.default.red(`Error: ${error.message}`));
402
317
  }
403
318
  });
404
- async function attachToSession(ipAddress, keyPath, sessionName, genboxName, autoUpload) {
319
+ async function attachToSession(ipAddress, keyPath, sessionName, genboxName) {
405
320
  console.log(chalk_1.default.dim(`\nAttaching to ${chalk_1.default.bold(sessionName)} on ${chalk_1.default.bold(genboxName)}...`));
406
- if (autoUpload) {
407
- console.log(chalk_1.default.dim('Tip: Local image paths will be auto-uploaded. Detach with Ctrl+b d\n'));
408
- }
409
- else {
410
- console.log(chalk_1.default.dim('Tip: Scroll with mouse wheel, detach with Ctrl+b d\n'));
411
- }
412
- const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
413
- // Build SSH command
414
- const sshCmd = `ssh -t -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "tmux attach -t ${sessionName}"`;
415
- // Create a pseudo-terminal for SSH
416
- const ptyProcess = pty.spawn(shell, ['-c', sshCmd], {
417
- name: 'xterm-256color',
418
- cols: process.stdout.columns || 80,
419
- rows: process.stdout.rows || 24,
420
- cwd: process.cwd(),
421
- env: process.env,
422
- });
423
- // Handle terminal resize
424
- const onResize = () => {
425
- ptyProcess.resize(process.stdout.columns || 80, process.stdout.rows || 24);
426
- };
427
- process.stdout.on('resize', onResize);
428
- // Set raw mode on stdin to capture all input
429
- if (process.stdin.isTTY) {
430
- process.stdin.setRawMode(true);
431
- }
432
- process.stdin.resume();
433
- // Buffer for detecting multi-character pastes (file paths)
434
- let inputBuffer = '';
435
- let bufferTimeout = null;
436
- const flushBuffer = () => {
437
- if (inputBuffer.length === 0)
438
- return;
439
- const input = inputBuffer;
440
- inputBuffer = '';
441
- if (autoUpload && input.length > 10) {
442
- // Check if input contains local file paths
443
- const hasLocalPath = LOCAL_PATH_PATTERNS.some(p => p.test(input));
444
- if (hasLocalPath) {
445
- const { modifiedInput, uploads } = detectAndUploadFiles(input, ipAddress, keyPath);
446
- // Log uploads to stderr (won't interfere with terminal)
447
- for (const upload of uploads) {
448
- if (upload.success) {
449
- process.stderr.write(`\r\n${chalk_1.default.green('↑')} Uploaded: ${chalk_1.default.dim(path.basename(upload.localPath))} → ${chalk_1.default.cyan(upload.remotePath)}\r\n`);
450
- }
451
- }
452
- ptyProcess.write(modifiedInput);
453
- return;
454
- }
455
- }
456
- ptyProcess.write(input);
457
- };
458
- // Handle input from user
459
- process.stdin.on('data', (data) => {
460
- const str = data.toString();
461
- // Add to buffer
462
- inputBuffer += str;
463
- // Clear existing timeout
464
- if (bufferTimeout) {
465
- clearTimeout(bufferTimeout);
466
- }
467
- // For single characters (typing), flush immediately
468
- // For multi-character input (paste), wait briefly to collect full paste
469
- if (str.length === 1 && inputBuffer.length === 1) {
470
- flushBuffer();
471
- }
472
- else {
473
- // Wait 50ms to collect full paste
474
- bufferTimeout = setTimeout(flushBuffer, 50);
475
- }
476
- });
477
- // Output from SSH goes to stdout
478
- ptyProcess.onData((data) => {
479
- process.stdout.write(data);
480
- });
481
- // Handle exit
321
+ console.log(chalk_1.default.dim('Tip: Scroll with mouse wheel, detach with Ctrl+b d'));
322
+ console.log(chalk_1.default.dim(' Use `gb cp <file>` to copy local files to this genbox.\n'));
323
+ const sshArgs = [
324
+ '-t',
325
+ '-i', keyPath,
326
+ '-o', 'StrictHostKeyChecking=no',
327
+ '-o', 'UserKnownHostsFile=/dev/null',
328
+ `dev@${ipAddress}`,
329
+ `tmux attach -t ${sessionName}`
330
+ ];
331
+ const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
482
332
  return new Promise((resolve) => {
483
- ptyProcess.onExit(({ exitCode }) => {
484
- // Cleanup
485
- process.stdout.removeListener('resize', onResize);
486
- if (process.stdin.isTTY) {
487
- process.stdin.setRawMode(false);
488
- }
489
- process.stdin.pause();
490
- if (bufferTimeout) {
491
- clearTimeout(bufferTimeout);
492
- }
493
- if (exitCode === 0) {
333
+ ssh.on('close', (code) => {
334
+ if (code === 0) {
494
335
  console.log(chalk_1.default.dim('\nDetached from session.'));
495
336
  }
496
- else if (exitCode !== null) {
497
- console.log(chalk_1.default.dim(`\nConnection closed (code ${exitCode})`));
337
+ else if (code !== null) {
338
+ console.log(chalk_1.default.dim(`\nConnection closed (code ${code})`));
498
339
  }
499
340
  resolve();
500
341
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.151",
3
+ "version": "1.0.153",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -60,7 +60,6 @@
60
60
  "fast-glob": "^3.3.3",
61
61
  "inquirer": "^13.0.2",
62
62
  "js-yaml": "^4.1.1",
63
- "node-pty": "^1.1.0",
64
63
  "ora": "^9.0.0"
65
64
  }
66
65
  }