genbox 1.0.140 → 1.0.142
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 +238 -0
- package/dist/index.js +3 -0
- package/package.json +12 -13
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.attachCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const api_1 = require("../api");
|
|
43
|
+
const genbox_selector_1 = require("../genbox-selector");
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
49
|
+
function getPrivateSshKey() {
|
|
50
|
+
const home = os.homedir();
|
|
51
|
+
const potentialKeys = [
|
|
52
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
53
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
54
|
+
];
|
|
55
|
+
for (const keyPath of potentialKeys) {
|
|
56
|
+
if (fs.existsSync(keyPath)) {
|
|
57
|
+
return keyPath;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new Error('No SSH private key found in ~/.ssh/');
|
|
61
|
+
}
|
|
62
|
+
// Tmux configuration for genbox branding and native terminal feel
|
|
63
|
+
const TMUX_CONFIG = `
|
|
64
|
+
# Mouse support for scrolling
|
|
65
|
+
set -g mouse on
|
|
66
|
+
|
|
67
|
+
# Large scrollback history
|
|
68
|
+
set -g history-limit 50000
|
|
69
|
+
|
|
70
|
+
# No delay for escape key
|
|
71
|
+
set -g escape-time 0
|
|
72
|
+
|
|
73
|
+
# Status bar styling - yellow genbox branding
|
|
74
|
+
set -g status on
|
|
75
|
+
set -g status-position bottom
|
|
76
|
+
set -g status-style 'bg=#f59e0b fg=#000000'
|
|
77
|
+
set -g status-left '#[bg=#000000,fg=#f59e0b,bold] GENBOX #[bg=#f59e0b,fg=#000000] #H '
|
|
78
|
+
set -g status-left-length 30
|
|
79
|
+
set -g status-right '#[fg=#000000] %H:%M '
|
|
80
|
+
set -g status-right-length 20
|
|
81
|
+
|
|
82
|
+
# Window styling
|
|
83
|
+
set -g window-status-current-style 'bg=#000000,fg=#f59e0b,bold'
|
|
84
|
+
set -g window-status-current-format ' #W '
|
|
85
|
+
set -g window-status-format ' #W '
|
|
86
|
+
`.trim();
|
|
87
|
+
async function listTmuxSessions(ipAddress, keyPath) {
|
|
88
|
+
try {
|
|
89
|
+
const result = (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "tmux list-sessions -F '#{session_name}|#{session_windows}|#{session_created}|#{session_attached}'" 2>/dev/null`, { encoding: 'utf-8' });
|
|
90
|
+
return result.trim().split('\n').filter(s => s.length > 0).map(line => {
|
|
91
|
+
const [name, windows, created, attached] = line.split('|');
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
windows: parseInt(windows) || 1,
|
|
95
|
+
created: new Date(parseInt(created) * 1000).toLocaleTimeString(),
|
|
96
|
+
attached: attached === '1',
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function selectSession(sessions) {
|
|
105
|
+
const choices = sessions.map(s => ({
|
|
106
|
+
name: `${s.name}${s.attached ? chalk_1.default.green(' (attached)') : ''} ${chalk_1.default.dim(`- ${s.windows} window(s), started ${s.created}`)}`,
|
|
107
|
+
value: s.name,
|
|
108
|
+
}));
|
|
109
|
+
choices.push({
|
|
110
|
+
name: chalk_1.default.dim('Cancel'),
|
|
111
|
+
value: '__cancel__',
|
|
112
|
+
});
|
|
113
|
+
const { session } = await inquirer_1.default.prompt([
|
|
114
|
+
{
|
|
115
|
+
type: 'list',
|
|
116
|
+
name: 'session',
|
|
117
|
+
message: 'Select a session to attach:',
|
|
118
|
+
choices,
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
return session === '__cancel__' ? null : session;
|
|
122
|
+
}
|
|
123
|
+
async function ensureTmuxConfig(ipAddress, keyPath) {
|
|
124
|
+
const configPath = '/home/dev/.tmux.conf';
|
|
125
|
+
const marker = '# GENBOX_BRANDED_CONFIG';
|
|
126
|
+
try {
|
|
127
|
+
// Check if config already has our branding
|
|
128
|
+
const checkResult = (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "grep -q '${marker}' ${configPath} 2>/dev/null && echo 'exists' || echo 'missing'"`, { encoding: 'utf-8' }).trim();
|
|
129
|
+
if (checkResult === 'exists') {
|
|
130
|
+
return; // Already configured
|
|
131
|
+
}
|
|
132
|
+
// Add our config
|
|
133
|
+
const fullConfig = `${marker}\n${TMUX_CONFIG}`;
|
|
134
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "cat >> ${configPath}" << 'GENBOX_TMUX_EOF'
|
|
135
|
+
${fullConfig}
|
|
136
|
+
GENBOX_TMUX_EOF`, { encoding: 'utf-8' });
|
|
137
|
+
// Reload tmux config if tmux server is running
|
|
138
|
+
(0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "tmux source-file ${configPath} 2>/dev/null || true"`, { encoding: 'utf-8' });
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Ignore errors - config is optional enhancement
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.attachCommand = new commander_1.Command('attach')
|
|
145
|
+
.description('Attach to an active Claude/AI session in a Genbox')
|
|
146
|
+
.argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
|
|
147
|
+
.option('-s, --session <session>', 'Tmux session name to attach to directly')
|
|
148
|
+
.option('-a, --all', 'Select from all genboxes (not just current project)')
|
|
149
|
+
.action(async (name, options) => {
|
|
150
|
+
try {
|
|
151
|
+
// 1. Select Genbox (interactive if no name provided)
|
|
152
|
+
const { genbox: target, cancelled } = await (0, genbox_selector_1.selectGenbox)(name, {
|
|
153
|
+
all: options.all,
|
|
154
|
+
selectMessage: 'Select a genbox to attach to:',
|
|
155
|
+
});
|
|
156
|
+
if (cancelled) {
|
|
157
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!target) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!target.ipAddress) {
|
|
164
|
+
console.error(chalk_1.default.yellow(`Genbox '${target.name}' is still provisioning (no IP). Please wait.`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// 2. Get SSH key
|
|
168
|
+
const keyPath = getPrivateSshKey();
|
|
169
|
+
// 3. List available tmux sessions
|
|
170
|
+
console.log(chalk_1.default.dim(`Checking sessions on ${target.name}...`));
|
|
171
|
+
const sessions = await listTmuxSessions(target.ipAddress, keyPath);
|
|
172
|
+
if (sessions.length === 0) {
|
|
173
|
+
console.log(chalk_1.default.yellow('\nNo active sessions found.'));
|
|
174
|
+
console.log(chalk_1.default.dim('Start a new Claude session with: gb run-prompt ' + target.name));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// 4. Determine which session to attach to
|
|
178
|
+
let sessionName = options.session;
|
|
179
|
+
if (!sessionName) {
|
|
180
|
+
if (sessions.length === 1) {
|
|
181
|
+
// Only one session - attach directly
|
|
182
|
+
sessionName = sessions[0].name;
|
|
183
|
+
console.log(chalk_1.default.dim(`Found session: ${sessionName}`));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Multiple sessions - show interactive picker
|
|
187
|
+
console.log('');
|
|
188
|
+
sessionName = await selectSession(sessions);
|
|
189
|
+
if (!sessionName) {
|
|
190
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Validate the provided session name
|
|
197
|
+
const sessionNames = sessions.map(s => s.name);
|
|
198
|
+
if (!sessionNames.includes(sessionName)) {
|
|
199
|
+
console.error(chalk_1.default.red(`\nSession '${sessionName}' not found.`));
|
|
200
|
+
console.log(chalk_1.default.dim('Available sessions:'));
|
|
201
|
+
for (const s of sessions) {
|
|
202
|
+
console.log(` ${chalk_1.default.cyan('•')} ${s.name}`);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// 5. Ensure tmux is configured with branding
|
|
208
|
+
console.log(chalk_1.default.dim(`Setting up terminal...`));
|
|
209
|
+
await ensureTmuxConfig(target.ipAddress, keyPath);
|
|
210
|
+
// 6. Attach to session
|
|
211
|
+
console.log(chalk_1.default.dim(`\nAttaching to ${chalk_1.default.bold(sessionName)} on ${chalk_1.default.bold(target.name)}...`));
|
|
212
|
+
console.log(chalk_1.default.dim('Tip: Scroll with mouse wheel, detach with Ctrl+b d\n'));
|
|
213
|
+
const sshArgs = [
|
|
214
|
+
'-t', // Force pseudo-terminal allocation
|
|
215
|
+
'-i', keyPath,
|
|
216
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
217
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
218
|
+
`dev@${target.ipAddress}`,
|
|
219
|
+
`tmux attach -t ${sessionName}`
|
|
220
|
+
];
|
|
221
|
+
const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
222
|
+
ssh.on('close', (code) => {
|
|
223
|
+
if (code === 0) {
|
|
224
|
+
console.log(chalk_1.default.dim('\nDetached from session.'));
|
|
225
|
+
}
|
|
226
|
+
else if (code !== null) {
|
|
227
|
+
console.log(chalk_1.default.dim(`\nConnection closed (code ${code})`));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
233
|
+
(0, api_1.handleApiError)(error);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
237
|
+
}
|
|
238
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,7 @@ const stop_1 = require("./commands/stop");
|
|
|
70
70
|
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
|
+
const attach_1 = require("./commands/attach");
|
|
73
74
|
const completion_1 = require("./commands/completion");
|
|
74
75
|
const doctor_1 = require("./commands/doctor");
|
|
75
76
|
const update_1 = require("./commands/update");
|
|
@@ -129,6 +130,7 @@ const commandCategories = {
|
|
|
129
130
|
{ name: 'list', aliases: ['ls'], description: 'List genboxes' },
|
|
130
131
|
{ name: 'destroy', aliases: ['delete'], description: 'Destroy a Genbox' },
|
|
131
132
|
{ name: 'connect', aliases: [], description: 'SSH into a Genbox' },
|
|
133
|
+
{ name: 'attach', aliases: [], description: 'Attach to an active Claude session' },
|
|
132
134
|
{ name: 'status', aliases: [], description: 'Check Genbox status and services' },
|
|
133
135
|
],
|
|
134
136
|
'LIFECYCLE': [
|
|
@@ -251,6 +253,7 @@ program
|
|
|
251
253
|
.addCommand(list_1.listCommand)
|
|
252
254
|
.addCommand(destroy_1.destroyCommand)
|
|
253
255
|
.addCommand(connect_1.connectCommand)
|
|
256
|
+
.addCommand(attach_1.attachCommand)
|
|
254
257
|
.addCommand(balance_1.balanceCommand)
|
|
255
258
|
.addCommand(login_1.loginCommand)
|
|
256
259
|
.addCommand(logout_1.logoutCommand)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genbox",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.142",
|
|
4
4
|
"description": "Genbox CLI - AI-Powered Development Environments",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,17 +11,6 @@
|
|
|
11
11
|
"dist/**/*",
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
-
"start": "node dist/index.js",
|
|
17
|
-
"dev": "ts-node src/index.ts",
|
|
18
|
-
"prepublishOnly": "npm run build",
|
|
19
|
-
"publish:patch": "./scripts/publish.sh patch",
|
|
20
|
-
"publish:minor": "./scripts/publish.sh minor",
|
|
21
|
-
"publish:major": "./scripts/publish.sh major",
|
|
22
|
-
"release": "./scripts/publish.sh patch",
|
|
23
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
-
},
|
|
25
14
|
"keywords": [
|
|
26
15
|
"genbox",
|
|
27
16
|
"ai",
|
|
@@ -61,5 +50,15 @@
|
|
|
61
50
|
"inquirer": "^13.0.2",
|
|
62
51
|
"js-yaml": "^4.1.1",
|
|
63
52
|
"ora": "^9.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc",
|
|
56
|
+
"start": "node dist/index.js",
|
|
57
|
+
"dev": "ts-node src/index.ts",
|
|
58
|
+
"publish:patch": "./scripts/publish.sh patch",
|
|
59
|
+
"publish:minor": "./scripts/publish.sh minor",
|
|
60
|
+
"publish:major": "./scripts/publish.sh major",
|
|
61
|
+
"release": "./scripts/publish.sh patch",
|
|
62
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
64
63
|
}
|
|
65
|
-
}
|
|
64
|
+
}
|