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.
@@ -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.140",
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
+ }