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.
- package/dist/commands/attach.js +208 -0
- package/dist/commands/status.js +11 -2
- package/dist/index.js +3 -0
- package/package.json +12 -13
|
@@ -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
|
+
});
|
package/dist/commands/status.js
CHANGED
|
@@ -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 (
|
|
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.
|
|
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.
|
|
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
|
+
}
|