genbox 1.0.183 → 1.0.185
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,576 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Provider Command Factory
|
|
4
|
+
*
|
|
5
|
+
* Creates `gb claude`, `gb gemini`, and `gb codex` commands with a unified,
|
|
6
|
+
* user-friendly interactive experience that encourages genbox usage.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Show interactive menu:
|
|
10
|
+
* - On a Genbox (Recommended)
|
|
11
|
+
* - Attach to existing session (if any exist)
|
|
12
|
+
* - Run directly on this machine (no isolation)
|
|
13
|
+
* 2. Handle user selection
|
|
14
|
+
* 3. Start or attach to session
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.codexCommand = exports.geminiCommand = exports.claudeCommand = void 0;
|
|
54
|
+
exports.createProviderCommand = createProviderCommand;
|
|
55
|
+
const commander_1 = require("commander");
|
|
56
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
57
|
+
const select_1 = __importDefault(require("@inquirer/select"));
|
|
58
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
59
|
+
const config_store_1 = require("../config-store");
|
|
60
|
+
const api_1 = require("../api");
|
|
61
|
+
const child_process_1 = require("child_process");
|
|
62
|
+
const os = __importStar(require("os"));
|
|
63
|
+
const path = __importStar(require("path"));
|
|
64
|
+
const fs = __importStar(require("fs"));
|
|
65
|
+
// Helper functions
|
|
66
|
+
function getPrivateSshKey() {
|
|
67
|
+
const keyPath = path.join(os.homedir(), '.ssh', 'genbox_key');
|
|
68
|
+
if (!fs.existsSync(keyPath)) {
|
|
69
|
+
throw new Error('SSH key not found. Run `gb login` first.');
|
|
70
|
+
}
|
|
71
|
+
return keyPath;
|
|
72
|
+
}
|
|
73
|
+
function getDtachSocketDir() {
|
|
74
|
+
return path.join(os.homedir(), '.genbox', 'sockets');
|
|
75
|
+
}
|
|
76
|
+
function getRemoteDtachSocketDir() {
|
|
77
|
+
return '/home/dev/.genbox/sockets';
|
|
78
|
+
}
|
|
79
|
+
function ensureLocalSocketDir() {
|
|
80
|
+
const dir = getDtachSocketDir();
|
|
81
|
+
if (!fs.existsSync(dir)) {
|
|
82
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isDtachSocketAlive(socketPath) {
|
|
86
|
+
if (!fs.existsSync(socketPath)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
// Try to check if process is still running by checking socket
|
|
91
|
+
const result = (0, child_process_1.spawnSync)('dtach', ['-a', socketPath, '-e', ''], {
|
|
92
|
+
timeout: 100,
|
|
93
|
+
stdio: 'ignore',
|
|
94
|
+
});
|
|
95
|
+
// If we can't attach, socket might be stale
|
|
96
|
+
return result.status === null || result.status === 0;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function ensureDtachInstalled(ipAddress, keyPath) {
|
|
103
|
+
try {
|
|
104
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR dev@${ipAddress} "which dtach"`, { timeout: 10000, stdio: 'pipe' });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
console.log(chalk_1.default.dim('Installing dtach on genbox...'));
|
|
108
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "sudo apt-get update && sudo apt-get install -y dtach"`, { timeout: 60000, stdio: 'pipe' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function ensureLocalDtach() {
|
|
112
|
+
try {
|
|
113
|
+
(0, child_process_1.execSync)('which dtach', { stdio: 'pipe' });
|
|
114
|
+
return 'available';
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
console.log(chalk_1.default.yellow('\ndtach is not installed (required for session persistence).'));
|
|
118
|
+
const platform = os.platform();
|
|
119
|
+
if (platform === 'darwin') {
|
|
120
|
+
console.log(chalk_1.default.dim('Install with: brew install dtach'));
|
|
121
|
+
}
|
|
122
|
+
else if (platform === 'linux') {
|
|
123
|
+
console.log(chalk_1.default.dim('Install with: sudo apt-get install dtach'));
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const proceed = await (0, prompts_1.confirm)({
|
|
127
|
+
message: 'Continue without session persistence?',
|
|
128
|
+
default: false,
|
|
129
|
+
});
|
|
130
|
+
return proceed ? 'unavailable' : 'cancel';
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return 'cancel';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get list of running genboxes
|
|
139
|
+
*/
|
|
140
|
+
async function getGenboxes() {
|
|
141
|
+
try {
|
|
142
|
+
const response = await (0, api_1.fetchApi)('/genboxes');
|
|
143
|
+
return (response?.genboxes || []).filter(g => g.status === 'running' && g.ipAddress);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get existing sessions for a provider
|
|
154
|
+
*/
|
|
155
|
+
async function getExistingSessions(provider) {
|
|
156
|
+
const sessions = [];
|
|
157
|
+
// Check local (direct) sessions
|
|
158
|
+
const socketDir = getDtachSocketDir();
|
|
159
|
+
if (fs.existsSync(socketDir)) {
|
|
160
|
+
const files = fs.readdirSync(socketDir).filter(f => f.endsWith('.sock'));
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const socketPath = path.join(socketDir, file);
|
|
163
|
+
const sessionName = file.replace('.sock', '');
|
|
164
|
+
// Check if this session is for our provider
|
|
165
|
+
if (sessionName.toLowerCase().includes(provider)) {
|
|
166
|
+
if (isDtachSocketAlive(socketPath)) {
|
|
167
|
+
sessions.push({
|
|
168
|
+
name: sessionName,
|
|
169
|
+
provider,
|
|
170
|
+
location: 'direct',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Check cloud (genbox) sessions
|
|
177
|
+
try {
|
|
178
|
+
const genboxes = await getGenboxes();
|
|
179
|
+
const keyPath = getPrivateSshKey();
|
|
180
|
+
for (const genbox of genboxes) {
|
|
181
|
+
if (!genbox.ipAddress)
|
|
182
|
+
continue;
|
|
183
|
+
try {
|
|
184
|
+
const socketDir = getRemoteDtachSocketDir();
|
|
185
|
+
const output = (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 -o LogLevel=ERROR dev@${genbox.ipAddress} "ls -1 ${socketDir}/*.sock 2>/dev/null" 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
|
|
186
|
+
const lines = output.trim().split('\n').filter(l => l.includes('.sock'));
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
const sessionName = path.basename(line, '.sock');
|
|
189
|
+
if (sessionName.toLowerCase().includes(provider)) {
|
|
190
|
+
sessions.push({
|
|
191
|
+
name: sessionName,
|
|
192
|
+
provider,
|
|
193
|
+
location: 'genbox',
|
|
194
|
+
genboxName: genbox.name,
|
|
195
|
+
ipAddress: genbox.ipAddress,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Can't connect or no sessions
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Not logged in or network error
|
|
207
|
+
}
|
|
208
|
+
return sessions;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Show first-time warning for direct mode
|
|
212
|
+
*/
|
|
213
|
+
async function showDirectModeWarning() {
|
|
214
|
+
if (config_store_1.ConfigStore.hasDirectModeConsent()) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(chalk_1.default.yellow(' ⚠️ Running without isolation'));
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log(chalk_1.default.dim(' The AI will have direct access to your files and system.'));
|
|
221
|
+
console.log(chalk_1.default.dim(' For better security, consider using a Genbox.'));
|
|
222
|
+
console.log('');
|
|
223
|
+
try {
|
|
224
|
+
const proceed = await (0, prompts_1.confirm)({
|
|
225
|
+
message: 'Continue without isolation?',
|
|
226
|
+
default: false,
|
|
227
|
+
});
|
|
228
|
+
if (proceed) {
|
|
229
|
+
config_store_1.ConfigStore.setDirectModeConsent(true);
|
|
230
|
+
console.log(chalk_1.default.dim('\n (This warning won\'t be shown again)\n'));
|
|
231
|
+
}
|
|
232
|
+
return proceed;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Create and attach to a session on a genbox
|
|
240
|
+
*/
|
|
241
|
+
async function startSessionOnGenbox(provider, genbox) {
|
|
242
|
+
const keyPath = getPrivateSshKey();
|
|
243
|
+
const sessionName = `${provider}-${Date.now().toString(36)}`;
|
|
244
|
+
const cliCommand = provider === 'claude'
|
|
245
|
+
? 'claude --dangerously-skip-permissions'
|
|
246
|
+
: provider === 'gemini'
|
|
247
|
+
? 'gemini'
|
|
248
|
+
: 'codex';
|
|
249
|
+
console.log(chalk_1.default.dim(`\nStarting ${provider} on ${genbox.name}...`));
|
|
250
|
+
// Ensure dtach is installed
|
|
251
|
+
await ensureDtachInstalled(genbox.ipAddress, keyPath);
|
|
252
|
+
// Create session in background using nohup with dtach
|
|
253
|
+
const socketDir = getRemoteDtachSocketDir();
|
|
254
|
+
const socketPath = `${socketDir}/${sessionName}.sock`;
|
|
255
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${genbox.ipAddress} "mkdir -p ${socketDir} && nohup dtach -n ${socketPath} bash -c 'source ~/.nvm/nvm.sh 2>/dev/null; ${cliCommand}' > /dev/null 2>&1 &"`, { timeout: 30000 });
|
|
256
|
+
// Wait for session to start
|
|
257
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
258
|
+
console.log(chalk_1.default.green(`Session created: ${sessionName}`));
|
|
259
|
+
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
260
|
+
// Attach to session
|
|
261
|
+
const sshArgs = [
|
|
262
|
+
'-t',
|
|
263
|
+
'-i', keyPath,
|
|
264
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
265
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
266
|
+
'-o', 'LogLevel=ERROR',
|
|
267
|
+
`dev@${genbox.ipAddress}`,
|
|
268
|
+
`dtach -a ${socketPath}`,
|
|
269
|
+
];
|
|
270
|
+
const proc = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
271
|
+
await new Promise(resolve => proc.on('close', () => resolve()));
|
|
272
|
+
console.log(chalk_1.default.dim('\nDetached from session.'));
|
|
273
|
+
console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} --attach ${sessionName}`)}`));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Attach to an existing session
|
|
277
|
+
*/
|
|
278
|
+
async function attachToSession(session) {
|
|
279
|
+
if (session.location === 'direct') {
|
|
280
|
+
// Local dtach session
|
|
281
|
+
const socketPath = path.join(getDtachSocketDir(), `${session.name}.sock`);
|
|
282
|
+
console.log(chalk_1.default.dim(`\nAttaching to ${session.name}...`));
|
|
283
|
+
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
284
|
+
const proc = (0, child_process_1.spawn)('dtach', ['-a', socketPath], { stdio: 'inherit' });
|
|
285
|
+
await new Promise(resolve => proc.on('close', () => resolve()));
|
|
286
|
+
console.log(chalk_1.default.dim('\nDetached from session.'));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Cloud genbox session
|
|
290
|
+
const keyPath = getPrivateSshKey();
|
|
291
|
+
const socketDir = getRemoteDtachSocketDir();
|
|
292
|
+
const socketPath = `${socketDir}/${session.name}.sock`;
|
|
293
|
+
console.log(chalk_1.default.dim(`\nAttaching to ${session.name} on ${session.genboxName}...`));
|
|
294
|
+
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
295
|
+
const sshArgs = [
|
|
296
|
+
'-t',
|
|
297
|
+
'-i', keyPath,
|
|
298
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
299
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
300
|
+
'-o', 'LogLevel=ERROR',
|
|
301
|
+
`dev@${session.ipAddress}`,
|
|
302
|
+
`dtach -a ${socketPath}`,
|
|
303
|
+
];
|
|
304
|
+
const proc = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
305
|
+
await new Promise(resolve => proc.on('close', () => resolve()));
|
|
306
|
+
console.log(chalk_1.default.dim('\nDetached from session.'));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Start a direct (no isolation) session
|
|
311
|
+
*/
|
|
312
|
+
async function startDirectSession(provider) {
|
|
313
|
+
// Check for dtach
|
|
314
|
+
const dtachStatus = await ensureLocalDtach();
|
|
315
|
+
if (dtachStatus === 'cancel') {
|
|
316
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const useDtach = dtachStatus === 'available';
|
|
320
|
+
ensureLocalSocketDir();
|
|
321
|
+
const sessionName = `${provider}-${Date.now().toString(36)}`;
|
|
322
|
+
const cliCommand = provider === 'claude'
|
|
323
|
+
? 'claude --dangerously-skip-permissions'
|
|
324
|
+
: provider === 'gemini'
|
|
325
|
+
? 'gemini'
|
|
326
|
+
: 'codex';
|
|
327
|
+
console.log(chalk_1.default.dim(`\nStarting ${provider} session...`));
|
|
328
|
+
if (useDtach) {
|
|
329
|
+
console.log(chalk_1.default.dim('Tip: Detach with Ctrl+\\\n'));
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log(chalk_1.default.dim('Note: Session will end when you exit (no persistence)\n'));
|
|
333
|
+
}
|
|
334
|
+
const socketPath = path.join(getDtachSocketDir(), `${sessionName}.sock`);
|
|
335
|
+
let cmd;
|
|
336
|
+
if (useDtach) {
|
|
337
|
+
cmd = `dtach -A "${socketPath}" bash -c '${cliCommand}'`;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
cmd = cliCommand;
|
|
341
|
+
}
|
|
342
|
+
const proc = (0, child_process_1.spawn)('bash', ['-c', cmd], { stdio: 'inherit' });
|
|
343
|
+
await new Promise((resolve) => {
|
|
344
|
+
proc.on('close', () => {
|
|
345
|
+
if (useDtach && fs.existsSync(socketPath)) {
|
|
346
|
+
console.log(chalk_1.default.dim('\nDetached from session (still running in background).'));
|
|
347
|
+
console.log(chalk_1.default.dim(`Reattach with: ${chalk_1.default.cyan(`gb ${provider} --attach ${sessionName}`)}`));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.log(chalk_1.default.dim('\nSession ended.'));
|
|
351
|
+
}
|
|
352
|
+
resolve();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Main interactive flow
|
|
358
|
+
*/
|
|
359
|
+
async function runInteractiveFlow(provider) {
|
|
360
|
+
console.log(chalk_1.default.dim('\nFetching genboxes and sessions...'));
|
|
361
|
+
// Gather data in parallel
|
|
362
|
+
const [genboxes, existingSessions] = await Promise.all([
|
|
363
|
+
getGenboxes(),
|
|
364
|
+
getExistingSessions(provider),
|
|
365
|
+
]);
|
|
366
|
+
const choices = [];
|
|
367
|
+
// Option 1: On a Genbox (Recommended)
|
|
368
|
+
if (genboxes.length > 0) {
|
|
369
|
+
choices.push({
|
|
370
|
+
name: chalk_1.default.green('●') + ' On a Genbox ' + chalk_1.default.dim('(Recommended)'),
|
|
371
|
+
value: { type: 'select-genbox' },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
choices.push({
|
|
376
|
+
name: chalk_1.default.green('+') + ' Create a Genbox ' + chalk_1.default.dim('(Recommended)'),
|
|
377
|
+
value: { type: 'create-genbox' },
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// Option 2: Attach to existing session (only if sessions exist)
|
|
381
|
+
if (existingSessions.length > 0) {
|
|
382
|
+
choices.push({
|
|
383
|
+
name: chalk_1.default.dim('─────────────────────────────────'),
|
|
384
|
+
value: { type: 'cancel' },
|
|
385
|
+
disabled: true,
|
|
386
|
+
});
|
|
387
|
+
for (const session of existingSessions) {
|
|
388
|
+
const locationLabel = session.location === 'genbox'
|
|
389
|
+
? chalk_1.default.blue(`on ${session.genboxName}`)
|
|
390
|
+
: chalk_1.default.yellow('direct');
|
|
391
|
+
choices.push({
|
|
392
|
+
name: ` ${chalk_1.default.cyan('↩')} Attach to ${session.name} ${chalk_1.default.dim(`(${locationLabel})`)}`,
|
|
393
|
+
value: { type: 'attach', session },
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Option 3: Run directly (no isolation)
|
|
398
|
+
choices.push({
|
|
399
|
+
name: chalk_1.default.dim('─────────────────────────────────'),
|
|
400
|
+
value: { type: 'cancel' },
|
|
401
|
+
disabled: true,
|
|
402
|
+
});
|
|
403
|
+
choices.push({
|
|
404
|
+
name: chalk_1.default.dim(' Run directly on this machine (no isolation)'),
|
|
405
|
+
value: { type: 'direct' },
|
|
406
|
+
});
|
|
407
|
+
// Show menu
|
|
408
|
+
console.log('');
|
|
409
|
+
let action;
|
|
410
|
+
try {
|
|
411
|
+
action = await (0, select_1.default)({
|
|
412
|
+
message: `How would you like to run ${chalk_1.default.bold(provider)}?`,
|
|
413
|
+
choices: choices.filter(c => !c.disabled),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
// Handle selection
|
|
421
|
+
switch (action.type) {
|
|
422
|
+
case 'select-genbox': {
|
|
423
|
+
// Show genbox selection
|
|
424
|
+
const genboxChoices = [
|
|
425
|
+
...genboxes.map(g => ({
|
|
426
|
+
name: `${g.name} ${chalk_1.default.dim(`(${g.ipAddress})`)}`,
|
|
427
|
+
value: g,
|
|
428
|
+
})),
|
|
429
|
+
{
|
|
430
|
+
name: chalk_1.default.dim('─────────────────────────────────'),
|
|
431
|
+
value: null,
|
|
432
|
+
disabled: true,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: chalk_1.default.green('+') + ' Create new Genbox',
|
|
436
|
+
value: null,
|
|
437
|
+
},
|
|
438
|
+
];
|
|
439
|
+
let selectedGenbox;
|
|
440
|
+
try {
|
|
441
|
+
selectedGenbox = await (0, select_1.default)({
|
|
442
|
+
message: 'Select a Genbox:',
|
|
443
|
+
choices: genboxChoices.filter(c => !c.disabled),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (selectedGenbox === null) {
|
|
451
|
+
// Create new genbox
|
|
452
|
+
console.log(chalk_1.default.yellow('\nTo create a new Genbox, run:'));
|
|
453
|
+
console.log(chalk_1.default.cyan(' gb create <name>\n'));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
await startSessionOnGenbox(provider, selectedGenbox);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case 'create-genbox':
|
|
460
|
+
console.log(chalk_1.default.yellow('\nTo create a Genbox, run:'));
|
|
461
|
+
console.log(chalk_1.default.cyan(' gb create <name>\n'));
|
|
462
|
+
break;
|
|
463
|
+
case 'attach':
|
|
464
|
+
await attachToSession(action.session);
|
|
465
|
+
break;
|
|
466
|
+
case 'direct': {
|
|
467
|
+
const proceed = await showDirectModeWarning();
|
|
468
|
+
if (!proceed) {
|
|
469
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
await startDirectSession(provider);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case 'cancel':
|
|
476
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Create a provider command (claude, gemini, or codex)
|
|
482
|
+
*/
|
|
483
|
+
function createProviderCommand(provider) {
|
|
484
|
+
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
485
|
+
return new commander_1.Command(provider)
|
|
486
|
+
.description(`Start or attach to a ${providerName} AI session`)
|
|
487
|
+
.option('--on <genbox>', 'Use specific genbox')
|
|
488
|
+
.option('--direct', 'Run directly on this machine (no isolation)')
|
|
489
|
+
.option('--attach <session>', 'Attach to existing session')
|
|
490
|
+
.option('--list', 'List all sessions')
|
|
491
|
+
.option('--kill', 'Kill session')
|
|
492
|
+
.option('-y, --yes', 'Skip confirmations')
|
|
493
|
+
.addHelpText('after', `
|
|
494
|
+
Examples:
|
|
495
|
+
gb ${provider} Interactive mode
|
|
496
|
+
gb ${provider} --on my-genbox Use specific genbox
|
|
497
|
+
gb ${provider} --attach swift-fox Attach to existing session
|
|
498
|
+
gb ${provider} --direct Run without isolation
|
|
499
|
+
gb ${provider} --list List all ${provider} sessions
|
|
500
|
+
`)
|
|
501
|
+
.action(async (options) => {
|
|
502
|
+
try {
|
|
503
|
+
// --list: Show all sessions for this provider
|
|
504
|
+
if (options.list) {
|
|
505
|
+
console.log(chalk_1.default.dim(`\nFetching ${provider} sessions...`));
|
|
506
|
+
const sessions = await getExistingSessions(provider);
|
|
507
|
+
if (sessions.length === 0) {
|
|
508
|
+
console.log(chalk_1.default.dim(`\n No ${provider} sessions found.\n`));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
console.log('');
|
|
512
|
+
for (const session of sessions) {
|
|
513
|
+
const locationLabel = session.location === 'genbox'
|
|
514
|
+
? chalk_1.default.blue(`on ${session.genboxName}`)
|
|
515
|
+
: chalk_1.default.yellow('direct');
|
|
516
|
+
console.log(` ${chalk_1.default.green('●')} ${session.name} ${chalk_1.default.dim(`(${locationLabel})`)}`);
|
|
517
|
+
}
|
|
518
|
+
console.log('');
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
// --attach: Attach to specific session
|
|
522
|
+
if (options.attach) {
|
|
523
|
+
const sessions = await getExistingSessions(provider);
|
|
524
|
+
const session = sessions.find(s => s.name === options.attach || s.name.includes(options.attach));
|
|
525
|
+
if (!session) {
|
|
526
|
+
console.log(chalk_1.default.red(`\nSession not found: ${options.attach}`));
|
|
527
|
+
console.log(chalk_1.default.dim(`Run 'gb ${provider} --list' to see available sessions.\n`));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
await attachToSession(session);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
// --on: Use specific genbox
|
|
534
|
+
if (options.on) {
|
|
535
|
+
const genboxes = await getGenboxes();
|
|
536
|
+
const genbox = genboxes.find(g => g.name === options.on || g.name.includes(options.on));
|
|
537
|
+
if (!genbox) {
|
|
538
|
+
console.log(chalk_1.default.red(`\nGenbox not found: ${options.on}`));
|
|
539
|
+
console.log(chalk_1.default.dim('Run \'gb list\' to see available genboxes.\n'));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
await startSessionOnGenbox(provider, genbox);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// --direct: Run without isolation
|
|
546
|
+
if (options.direct) {
|
|
547
|
+
if (!options.yes) {
|
|
548
|
+
const proceed = await showDirectModeWarning();
|
|
549
|
+
if (!proceed) {
|
|
550
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
await startDirectSession(provider);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
// Default: Interactive mode
|
|
558
|
+
await runInteractiveFlow(provider);
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
562
|
+
(0, api_1.handleApiError)(error);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
566
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
// Export individual commands
|
|
574
|
+
exports.claudeCommand = createProviderCommand('claude');
|
|
575
|
+
exports.geminiCommand = createProviderCommand('gemini');
|
|
576
|
+
exports.codexCommand = createProviderCommand('codex');
|
|
@@ -59,6 +59,7 @@ const local_genbox_provisioner_1 = require("../../lib/local-genbox-provisioner")
|
|
|
59
59
|
const native_session_manager_1 = require("../../lib/native-session-manager");
|
|
60
60
|
const hooks_configurator_1 = require("../../lib/hooks-configurator");
|
|
61
61
|
const api_1 = require("../../api");
|
|
62
|
+
const genbox_selector_1 = require("../../genbox-selector");
|
|
62
63
|
const prompts_1 = require("@inquirer/prompts");
|
|
63
64
|
const list_1 = require("./list");
|
|
64
65
|
const start_1 = require("./start");
|
|
@@ -675,7 +676,7 @@ function isDtachSocketAlive(socketPath) {
|
|
|
675
676
|
try {
|
|
676
677
|
// Try to send an empty string to test if socket is alive
|
|
677
678
|
// Using timeout to avoid hanging on dead sockets
|
|
678
|
-
(0, child_process_1.execSync)(`
|
|
679
|
+
(0, child_process_1.execSync)(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
|
|
679
680
|
timeout: 2000,
|
|
680
681
|
stdio: 'ignore'
|
|
681
682
|
});
|
|
@@ -698,7 +699,7 @@ function isDtachSocketAlive(socketPath) {
|
|
|
698
699
|
*/
|
|
699
700
|
function isRemoteDtachSocketAlive(ipAddress, keyPath, socketPath) {
|
|
700
701
|
try {
|
|
701
|
-
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 dev@${ipAddress} "
|
|
702
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 dev@${ipAddress} "printf '' | dtach -p ${socketPath}" 2>/dev/null`, { timeout: 5000, stdio: 'ignore' });
|
|
702
703
|
return true;
|
|
703
704
|
}
|
|
704
705
|
catch {
|
|
@@ -1056,11 +1057,16 @@ async function getAllGenboxesWithSessions(options) {
|
|
|
1056
1057
|
}
|
|
1057
1058
|
// Get cloud genboxes
|
|
1058
1059
|
try {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1060
|
+
// Fetch all genboxes and filter by project name (same approach as gb list)
|
|
1061
|
+
const response = await (0, api_1.fetchApi)('/genboxes');
|
|
1062
|
+
let genboxes = response || [];
|
|
1063
|
+
// Filter by project name unless --all is specified
|
|
1064
|
+
if (!options.all) {
|
|
1065
|
+
const projectName = (0, genbox_selector_1.getProjectContext)();
|
|
1066
|
+
if (projectName) {
|
|
1067
|
+
genboxes = genboxes.filter(g => g.project === projectName || g.workspace === projectName);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1064
1070
|
for (const genbox of genboxes) {
|
|
1065
1071
|
if (genbox.status !== 'running' || !genbox.ipAddress) {
|
|
1066
1072
|
continue;
|
package/dist/config-store.js
CHANGED
|
@@ -66,5 +66,14 @@ class ConfigStore {
|
|
|
66
66
|
fs.unlinkSync(CONFIG_FILE);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
static hasDirectModeConsent() {
|
|
70
|
+
const config = this.load();
|
|
71
|
+
return config.directModeConsent === true;
|
|
72
|
+
}
|
|
73
|
+
static setDirectModeConsent(consent) {
|
|
74
|
+
const config = this.load();
|
|
75
|
+
config.directModeConsent = consent;
|
|
76
|
+
this.save(config);
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
exports.ConfigStore = ConfigStore;
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,7 @@ const start_1 = require("./commands/start");
|
|
|
71
71
|
const template_1 = require("./commands/template");
|
|
72
72
|
const run_prompt_1 = require("./commands/run-prompt");
|
|
73
73
|
const session_1 = require("./commands/session");
|
|
74
|
+
const provider_command_1 = require("./commands/provider-command");
|
|
74
75
|
const cp_1 = require("./commands/cp");
|
|
75
76
|
const completion_1 = require("./commands/completion");
|
|
76
77
|
const doctor_1 = require("./commands/doctor");
|
|
@@ -129,12 +130,17 @@ if (earlyArgs.includes('--version') || earlyArgs.includes('-v')) {
|
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
const commandCategories = {
|
|
133
|
+
'AI SESSIONS': [
|
|
134
|
+
{ name: 'claude', aliases: [], description: 'Start Claude AI session' },
|
|
135
|
+
{ name: 'gemini', aliases: [], description: 'Start Gemini AI session' },
|
|
136
|
+
{ name: 'codex', aliases: [], description: 'Start Codex AI session' },
|
|
137
|
+
{ name: 'session', aliases: [], description: 'Advanced session management' },
|
|
138
|
+
],
|
|
132
139
|
'CORE COMMANDS': [
|
|
133
140
|
{ name: 'create', aliases: [], description: 'Create a new Genbox environment' },
|
|
134
141
|
{ name: 'list', aliases: ['ls'], description: 'List genboxes' },
|
|
135
142
|
{ name: 'destroy', aliases: ['delete'], description: 'Destroy a Genbox' },
|
|
136
143
|
{ name: 'connect', aliases: [], description: 'SSH into a Genbox' },
|
|
137
|
-
{ name: 'session', aliases: [], description: 'Manage AI CLI sessions (local & cloud)' },
|
|
138
144
|
{ name: 'status', aliases: [], description: 'Check Genbox status and services' },
|
|
139
145
|
],
|
|
140
146
|
'LIFECYCLE': [
|
|
@@ -262,6 +268,9 @@ program
|
|
|
262
268
|
.addCommand(destroy_1.destroyCommand)
|
|
263
269
|
.addCommand(connect_1.connectCommand)
|
|
264
270
|
.addCommand(session_1.sessionCommand)
|
|
271
|
+
.addCommand(provider_command_1.claudeCommand)
|
|
272
|
+
.addCommand(provider_command_1.geminiCommand)
|
|
273
|
+
.addCommand(provider_command_1.codexCommand)
|
|
265
274
|
.addCommand(cp_1.cpCommand)
|
|
266
275
|
.addCommand(balance_1.balanceCommand)
|
|
267
276
|
.addCommand(login_1.loginCommand)
|
|
@@ -311,7 +311,7 @@ class LocalSessionManager {
|
|
|
311
311
|
}
|
|
312
312
|
try {
|
|
313
313
|
// Try to send an empty string to test if socket is alive
|
|
314
|
-
(0, child_process_1.execSync)(`
|
|
314
|
+
(0, child_process_1.execSync)(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
|
|
315
315
|
timeout: 2000,
|
|
316
316
|
stdio: 'ignore'
|
|
317
317
|
});
|
|
@@ -254,7 +254,7 @@ class NativeSessionManager {
|
|
|
254
254
|
try {
|
|
255
255
|
// Try to send an empty string to test if socket is alive
|
|
256
256
|
const { execSync } = require('child_process');
|
|
257
|
-
execSync(`
|
|
257
|
+
execSync(`printf '' | dtach -p "${socketPath}" 2>/dev/null`, {
|
|
258
258
|
timeout: 2000,
|
|
259
259
|
stdio: 'ignore'
|
|
260
260
|
});
|