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.
- package/dist/commands/attach.js +20 -179
- package/package.json +1 -2
package/dist/commands/attach.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
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
|
-
|
|
484
|
-
|
|
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 (
|
|
497
|
-
console.log(chalk_1.default.dim(`\nConnection closed (code ${
|
|
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.
|
|
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
|
}
|