genbox 1.0.139 → 1.0.141

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,208 @@
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
+ function getPrivateSshKey() {
49
+ const home = os.homedir();
50
+ const potentialKeys = [
51
+ path.join(home, '.ssh', 'id_rsa'),
52
+ path.join(home, '.ssh', 'id_ed25519'),
53
+ ];
54
+ for (const keyPath of potentialKeys) {
55
+ if (fs.existsSync(keyPath)) {
56
+ return keyPath;
57
+ }
58
+ }
59
+ throw new Error('No SSH private key found in ~/.ssh/');
60
+ }
61
+ // Tmux configuration for genbox branding and native terminal feel
62
+ const TMUX_CONFIG = `
63
+ # Mouse support for scrolling
64
+ set -g mouse on
65
+
66
+ # Large scrollback history
67
+ set -g history-limit 50000
68
+
69
+ # No delay for escape key
70
+ set -g escape-time 0
71
+
72
+ # Status bar styling - yellow genbox branding
73
+ set -g status on
74
+ set -g status-position bottom
75
+ set -g status-style 'bg=#f59e0b fg=#000000'
76
+ set -g status-left '#[bg=#000000,fg=#f59e0b,bold] GENBOX #[bg=#f59e0b,fg=#000000] #H '
77
+ set -g status-left-length 30
78
+ set -g status-right '#[fg=#000000] %H:%M '
79
+ set -g status-right-length 20
80
+
81
+ # Window styling
82
+ set -g window-status-current-style 'bg=#000000,fg=#f59e0b,bold'
83
+ set -g window-status-current-format ' #W '
84
+ set -g window-status-format ' #W '
85
+ `.trim();
86
+ async function listTmuxSessions(ipAddress, keyPath) {
87
+ try {
88
+ 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}'" 2>/dev/null`, { encoding: 'utf-8' });
89
+ return result.trim().split('\n').filter(s => s.length > 0);
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
95
+ async function ensureTmuxConfig(ipAddress, keyPath) {
96
+ const configPath = '/home/dev/.tmux.conf';
97
+ const marker = '# GENBOX_BRANDED_CONFIG';
98
+ try {
99
+ // Check if config already has our branding
100
+ 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();
101
+ if (checkResult === 'exists') {
102
+ return; // Already configured
103
+ }
104
+ // Add our config
105
+ const fullConfig = `${marker}\n${TMUX_CONFIG}`;
106
+ (0, child_process_1.execSync)(`ssh -i "${keyPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dev@${ipAddress} "cat >> ${configPath}" << 'GENBOX_TMUX_EOF'
107
+ ${fullConfig}
108
+ GENBOX_TMUX_EOF`, { encoding: 'utf-8' });
109
+ // Reload tmux config if tmux server is running
110
+ (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' });
111
+ }
112
+ catch {
113
+ // Ignore errors - config is optional enhancement
114
+ }
115
+ }
116
+ exports.attachCommand = new commander_1.Command('attach')
117
+ .description('Attach to an active Claude/AI session in a Genbox')
118
+ .argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
119
+ .option('-s, --session <session>', 'Tmux session name to attach to')
120
+ .option('-a, --all', 'Select from all genboxes (not just current project)')
121
+ .option('-l, --list', 'List available sessions without attaching')
122
+ .action(async (name, options) => {
123
+ try {
124
+ // 1. Select Genbox
125
+ const { genbox: target, cancelled } = await (0, genbox_selector_1.selectGenbox)(name, {
126
+ all: options.all,
127
+ selectMessage: 'Select a genbox to attach to:',
128
+ });
129
+ if (cancelled) {
130
+ console.log(chalk_1.default.dim('Cancelled.'));
131
+ return;
132
+ }
133
+ if (!target) {
134
+ return;
135
+ }
136
+ if (!target.ipAddress) {
137
+ console.error(chalk_1.default.yellow(`Genbox '${target.name}' is still provisioning (no IP). Please wait.`));
138
+ return;
139
+ }
140
+ // 2. Get SSH key
141
+ const keyPath = getPrivateSshKey();
142
+ // 3. List available tmux sessions
143
+ const sessions = await listTmuxSessions(target.ipAddress, keyPath);
144
+ if (sessions.length === 0) {
145
+ console.log(chalk_1.default.yellow('No active sessions found.'));
146
+ console.log(chalk_1.default.dim('Start a new Claude session with: gb run-prompt <genbox>'));
147
+ return;
148
+ }
149
+ if (options.list) {
150
+ console.log(chalk_1.default.bold(`\nActive sessions in ${target.name}:\n`));
151
+ for (const session of sessions) {
152
+ console.log(` ${chalk_1.default.cyan('•')} ${session}`);
153
+ }
154
+ console.log('');
155
+ console.log(chalk_1.default.dim(`Attach with: gb attach ${target.name} -s <session>`));
156
+ return;
157
+ }
158
+ // 4. Determine which session to attach to
159
+ let sessionName = options.session;
160
+ if (!sessionName) {
161
+ // Default to claude-session if it exists, otherwise first session
162
+ if (sessions.includes('claude-session')) {
163
+ sessionName = 'claude-session';
164
+ }
165
+ else {
166
+ sessionName = sessions[0];
167
+ }
168
+ }
169
+ if (!sessions.includes(sessionName)) {
170
+ console.error(chalk_1.default.red(`Session '${sessionName}' not found.`));
171
+ console.log(chalk_1.default.dim('Available sessions:'));
172
+ for (const s of sessions) {
173
+ console.log(` ${chalk_1.default.cyan('•')} ${s}`);
174
+ }
175
+ return;
176
+ }
177
+ // 5. Ensure tmux is configured with branding
178
+ console.log(chalk_1.default.dim(`Setting up terminal...`));
179
+ await ensureTmuxConfig(target.ipAddress, keyPath);
180
+ // 6. Attach to session
181
+ console.log(chalk_1.default.dim(`Attaching to ${chalk_1.default.bold(sessionName)} on ${chalk_1.default.bold(target.name)}...`));
182
+ console.log(chalk_1.default.dim('Tip: Scroll with mouse wheel, detach with Ctrl+b d\n'));
183
+ const sshArgs = [
184
+ '-t', // Force pseudo-terminal allocation
185
+ '-i', keyPath,
186
+ '-o', 'StrictHostKeyChecking=no',
187
+ '-o', 'UserKnownHostsFile=/dev/null',
188
+ `dev@${target.ipAddress}`,
189
+ `tmux attach -t ${sessionName}`
190
+ ];
191
+ const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
192
+ ssh.on('close', (code) => {
193
+ if (code === 0) {
194
+ console.log(chalk_1.default.dim('\nDetached from session.'));
195
+ }
196
+ else if (code !== null) {
197
+ console.log(chalk_1.default.dim(`\nConnection closed (code ${code})`));
198
+ }
199
+ });
200
+ }
201
+ catch (error) {
202
+ if (error instanceof api_1.AuthenticationError) {
203
+ (0, api_1.handleApiError)(error);
204
+ return;
205
+ }
206
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
207
+ }
208
+ });
@@ -350,11 +350,20 @@ exports.statusCommand = new commander_1.Command('status')
350
350
  const currentHourStart = new Date(currentHourEnd.getTime() - 60 * 60 * 1000);
351
351
  const minutesIntoBillingHour = Math.floor((now.getTime() - currentHourStart.getTime()) / (60 * 1000));
352
352
  const minutesUntilDestroy = Math.max(0, 58 - minutesIntoBillingHour);
353
- if (minutesInactive >= 5) {
353
+ // Check if there was activity after the 50 min mark (this is what prevents auto-destroy)
354
+ const fiftyMinMark = new Date(currentHourStart.getTime() + 50 * 60 * 1000);
355
+ const lastActivity = billing.lastActivityAt ? new Date(billing.lastActivityAt) : null;
356
+ const wasActiveAfter50Min = lastActivity ? lastActivity.getTime() >= fiftyMinMark.getTime() : false;
357
+ // Auto-destroy is paused if we're past 50min mark AND there was activity after 50min
358
+ const isAutoDestroyPaused = minutesIntoBillingHour >= 50 && wasActiveAfter50Min;
359
+ if (isAutoDestroyPaused) {
360
+ console.log(chalk_1.default.green(` Auto-destroy: paused`));
361
+ }
362
+ else if (minutesInactive >= 5) {
354
363
  console.log(chalk_1.default.yellow(` Auto-destroy in: ${minutesUntilDestroy} min (inactive)`));
355
364
  }
356
365
  else {
357
- console.log(chalk_1.default.green(` Auto-destroy: paused (activity detected)`));
366
+ console.log(chalk_1.default.dim(` Auto-destroy: ${minutesUntilDestroy} min`));
358
367
  }
359
368
  }
360
369
  }
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.139",
3
+ "version": "1.0.141",
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
+ }