genbox 1.0.0

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,103 @@
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.importLegacyScript = importLegacyScript;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ function importLegacyScript(scriptPath, outputDir) {
44
+ console.log(chalk_1.default.blue(`Importing legacy script: ${scriptPath}`));
45
+ const content = fs.readFileSync(scriptPath, 'utf-8');
46
+ const extractedScripts = [];
47
+ const extractedFiles = [];
48
+ // Regex to find write_files blocks
49
+ // Matches: - path: <path>\n ... content: |\n<content>
50
+ // We'll capture path and content until the next indentation change or list item
51
+ // This is a heuristic parser.
52
+ const fileRegex = /-\s+path: ([\w/.-]+)\s+(?:permissions: '(\d+)'\s+)?(?:owner: [\w:]+\s+)?(?:defer: \w+\s+)?content: \|\n((?: {6,}.*\n)+)/g;
53
+ let match;
54
+ while ((match = fileRegex.exec(content)) !== null) {
55
+ const remotePath = match[1];
56
+ const permissions = match[2];
57
+ const rawBody = match[3];
58
+ const fileName = path.basename(remotePath);
59
+ // De-indent
60
+ // Find base indentation of first line
61
+ const firstLineParams = rawBody.match(/^(\s+)/);
62
+ const indent = firstLineParams ? firstLineParams[1].length : 0;
63
+ const cleanBody = rawBody.split('\n').map(line => {
64
+ if (line.trim() === '')
65
+ return '';
66
+ return line.substring(indent);
67
+ }).join('\n');
68
+ // Determine destination
69
+ let localSubDir = 'files';
70
+ if (fileName.endsWith('.sh')) {
71
+ localSubDir = 'scripts';
72
+ }
73
+ const localDir = path.join(outputDir, localSubDir);
74
+ if (!fs.existsSync(localDir)) {
75
+ fs.mkdirSync(localDir, { recursive: true });
76
+ }
77
+ const localPath = path.join(localDir, fileName);
78
+ fs.writeFileSync(localPath, cleanBody);
79
+ console.log(chalk_1.default.dim(` Extracted ${fileName}`));
80
+ const relativeLocalPath = path.join(localSubDir, fileName);
81
+ if (localSubDir === 'scripts') {
82
+ // We'll add this to scripts if it looks like a setup script
83
+ // For parity, the script runs specific ones.
84
+ // setup-goodpass.sh is main. install-caddy.sh is aux.
85
+ // We'll add all .sh files to 'files' bundle, and user can choose which to RUN in 'scripts' config.
86
+ // Wait, genbox config 'scripts' are executed. 'files' are just placed.
87
+ // The legacy script executed 'install-caddy.sh' then 'setup-goodpass.sh'.
88
+ // We should treat them as files first.
89
+ }
90
+ extractedFiles.push({
91
+ source: relativeLocalPath,
92
+ target: remotePath,
93
+ permissions: permissions || '0644'
94
+ });
95
+ if (fileName === 'setup-goodpass.sh') {
96
+ extractedScripts.push(relativeLocalPath); // Mark for execution
97
+ }
98
+ else if (fileName === 'install-caddy.sh') {
99
+ extractedScripts.push(relativeLocalPath);
100
+ }
101
+ }
102
+ return { scripts: extractedScripts, files: extractedFiles };
103
+ }
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const init_1 = require("./commands/init");
6
+ const program = new commander_1.Command();
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
8
+ const { version } = require('../package.json');
9
+ program
10
+ .name('genbox')
11
+ .description('Genbox CLI - AI-Powered Development Environments')
12
+ .version(version, '-v, --version', 'Output the current version');
13
+ const create_1 = require("./commands/create");
14
+ const list_1 = require("./commands/list");
15
+ const destroy_1 = require("./commands/destroy");
16
+ const connect_1 = require("./commands/connect");
17
+ const balance_1 = require("./commands/balance");
18
+ const login_1 = require("./commands/login");
19
+ const urls_1 = require("./commands/urls");
20
+ const push_1 = require("./commands/push");
21
+ const status_1 = require("./commands/status");
22
+ const forward_1 = require("./commands/forward");
23
+ const restore_db_1 = require("./commands/restore-db");
24
+ const help_1 = require("./commands/help");
25
+ program
26
+ .addCommand(init_1.initCommand)
27
+ .addCommand(create_1.createCommand)
28
+ .addCommand(list_1.listCommand)
29
+ .addCommand(destroy_1.destroyCommand)
30
+ .addCommand(connect_1.connectCommand)
31
+ .addCommand(balance_1.balanceCommand)
32
+ .addCommand(login_1.loginCommand)
33
+ .addCommand(urls_1.urlsCommand)
34
+ .addCommand(push_1.pushCommand)
35
+ .addCommand(status_1.statusCommand)
36
+ .addCommand(forward_1.forwardCommand)
37
+ .addCommand(restore_db_1.restoreDbCommand)
38
+ .addCommand(help_1.helpCommand);
39
+ program.parse(process.argv);
package/dist/scan.js ADDED
@@ -0,0 +1,247 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.detectSshKeys = detectSshKeys;
37
+ exports.getGitUrlType = getGitUrlType;
38
+ exports.sshToHttps = sshToHttps;
39
+ exports.httpsToSsh = httpsToSsh;
40
+ exports.scanProject = scanProject;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ /**
45
+ * Detect available SSH keys in ~/.ssh/
46
+ */
47
+ function detectSshKeys() {
48
+ const sshDir = path.join(os.homedir(), '.ssh');
49
+ const keys = [];
50
+ if (!fs.existsSync(sshDir)) {
51
+ return keys;
52
+ }
53
+ const files = fs.readdirSync(sshDir);
54
+ // Common private key patterns (not .pub files)
55
+ const keyPatterns = [
56
+ { pattern: /^id_rsa$/, type: 'rsa' },
57
+ { pattern: /^id_ed25519$/, type: 'ed25519' },
58
+ { pattern: /^id_ecdsa$/, type: 'ecdsa' },
59
+ { pattern: /^.*_rsa$/, type: 'rsa' },
60
+ { pattern: /^.*_ed25519$/, type: 'ed25519' },
61
+ { pattern: /^.*_github$/, type: 'other' },
62
+ { pattern: /^github$/, type: 'other' },
63
+ ];
64
+ for (const file of files) {
65
+ // Skip public keys and known non-key files
66
+ if (file.endsWith('.pub') || file === 'known_hosts' || file === 'config' || file === 'authorized_keys') {
67
+ continue;
68
+ }
69
+ const filePath = path.join(sshDir, file);
70
+ // Check if it's a file (not directory)
71
+ try {
72
+ const stat = fs.statSync(filePath);
73
+ if (!stat.isFile())
74
+ continue;
75
+ // Try to read first line to verify it's a private key
76
+ const content = fs.readFileSync(filePath, 'utf8').substring(0, 100);
77
+ if (!content.includes('PRIVATE KEY') && !content.includes('OPENSSH')) {
78
+ continue;
79
+ }
80
+ // Determine key type
81
+ let keyType = 'other';
82
+ for (const { pattern, type } of keyPatterns) {
83
+ if (pattern.test(file)) {
84
+ keyType = type;
85
+ break;
86
+ }
87
+ }
88
+ keys.push({
89
+ path: filePath,
90
+ name: file,
91
+ type: keyType,
92
+ });
93
+ }
94
+ catch (e) {
95
+ // Skip files we can't read
96
+ }
97
+ }
98
+ return keys;
99
+ }
100
+ /**
101
+ * Check if a URL is SSH or HTTPS
102
+ */
103
+ function getGitUrlType(url) {
104
+ if (url.startsWith('git@') || url.startsWith('ssh://')) {
105
+ return 'ssh';
106
+ }
107
+ return 'https';
108
+ }
109
+ /**
110
+ * Convert SSH URL to HTTPS URL
111
+ */
112
+ function sshToHttps(sshUrl) {
113
+ // git@github.com:user/repo.git -> https://github.com/user/repo.git
114
+ const match = sshUrl.match(/^git@([^:]+):(.+)$/);
115
+ if (match) {
116
+ return `https://${match[1]}/${match[2]}`;
117
+ }
118
+ return sshUrl;
119
+ }
120
+ /**
121
+ * Convert HTTPS URL to SSH URL
122
+ */
123
+ function httpsToSsh(httpsUrl) {
124
+ // https://github.com/user/repo.git -> git@github.com:user/repo.git
125
+ const match = httpsUrl.match(/^https?:\/\/([^/]+)\/(.+)$/);
126
+ if (match) {
127
+ return `git@${match[1]}:${match[2]}`;
128
+ }
129
+ return httpsUrl;
130
+ }
131
+ /**
132
+ * Detect port from various project files
133
+ */
134
+ function detectPort(root) {
135
+ // 1. Check .env or .env.local for PORT
136
+ const envFiles = ['.env.local', '.env', '.env.development'];
137
+ for (const envFile of envFiles) {
138
+ const envPath = path.join(root, envFile);
139
+ if (fs.existsSync(envPath)) {
140
+ const content = fs.readFileSync(envPath, 'utf8');
141
+ const portMatch = content.match(/^PORT\s*=\s*(\d+)/m);
142
+ if (portMatch) {
143
+ return parseInt(portMatch[1], 10);
144
+ }
145
+ }
146
+ }
147
+ // 2. Check package.json scripts for port
148
+ const pkgPath = path.join(root, 'package.json');
149
+ if (fs.existsSync(pkgPath)) {
150
+ try {
151
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
152
+ const scripts = pkg.scripts || {};
153
+ // Check dev/start scripts for --port, -p, or PORT=
154
+ for (const scriptName of ['dev', 'start', 'serve']) {
155
+ const script = scripts[scriptName];
156
+ if (script) {
157
+ // Match patterns like: --port 3000, -p 3000, PORT=3000
158
+ const portMatch = script.match(/(?:--port\s+|(?:^|\s)-p\s+|PORT=)(\d+)/);
159
+ if (portMatch) {
160
+ return parseInt(portMatch[1], 10);
161
+ }
162
+ // Match patterns like: vite --port=5173
163
+ const portEqMatch = script.match(/--port=(\d+)/);
164
+ if (portEqMatch) {
165
+ return parseInt(portEqMatch[1], 10);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ catch (e) {
171
+ // Invalid JSON, skip
172
+ }
173
+ }
174
+ // 3. Check docker-compose for exposed ports
175
+ const dockerComposeFiles = ['docker-compose.yml', 'docker-compose.yaml'];
176
+ for (const dcFile of dockerComposeFiles) {
177
+ const dcPath = path.join(root, dcFile);
178
+ if (fs.existsSync(dcPath)) {
179
+ const content = fs.readFileSync(dcPath, 'utf8');
180
+ // Simple regex to find first ports mapping like "3000:3000" or "- 3000:3000"
181
+ const portMatch = content.match(/ports:\s*\n\s*-\s*["']?(\d+):/m);
182
+ if (portMatch) {
183
+ return parseInt(portMatch[1], 10);
184
+ }
185
+ }
186
+ }
187
+ // 4. Check vite.config.ts/js for server.port
188
+ const viteConfigs = ['vite.config.ts', 'vite.config.js'];
189
+ for (const viteConfig of viteConfigs) {
190
+ const vitePath = path.join(root, viteConfig);
191
+ if (fs.existsSync(vitePath)) {
192
+ const content = fs.readFileSync(vitePath, 'utf8');
193
+ const portMatch = content.match(/port:\s*(\d+)/);
194
+ if (portMatch) {
195
+ return parseInt(portMatch[1], 10);
196
+ }
197
+ }
198
+ }
199
+ // 5. Check next.config.js for custom port (rare, but possible)
200
+ // Next.js typically uses 3000 by default, which we'll fall back to
201
+ return undefined;
202
+ }
203
+ function scanProject(root) {
204
+ const info = {
205
+ name: path.basename(root),
206
+ languages: {},
207
+ hasDocker: false,
208
+ foundScripts: [],
209
+ };
210
+ // 1. Detect Languages/Frameworks
211
+ if (fs.existsSync(path.join(root, 'package.json')))
212
+ info.languages.node = true;
213
+ if (fs.existsSync(path.join(root, 'requirements.txt')))
214
+ info.languages.python = true;
215
+ if (fs.existsSync(path.join(root, 'Gemfile')))
216
+ info.languages.ruby = true;
217
+ if (fs.existsSync(path.join(root, 'go.mod')))
218
+ info.languages.go = true;
219
+ // 2. Detect Docker
220
+ // Check for various docker compose names
221
+ const files = fs.readdirSync(root);
222
+ const dockerFiles = files.filter(f => f.startsWith('docker-compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));
223
+ if (dockerFiles.length > 0)
224
+ info.hasDocker = true;
225
+ // 3. Detect Scripts (Standard)
226
+ const scriptsDir = path.join(root, 'scripts');
227
+ if (fs.existsSync(scriptsDir)) {
228
+ const scriptFiles = fs.readdirSync(scriptsDir);
229
+ info.foundScripts = scriptFiles
230
+ .filter(f => f.endsWith('.sh') && f !== 'hetzner-dev-env.sh')
231
+ .map(f => `scripts/${f}`);
232
+ }
233
+ // 4. Detect Git Repo
234
+ try {
235
+ const remote = require('child_process').execSync('git remote get-url origin', { cwd: root, stdio: 'pipe' }).toString().trim();
236
+ if (remote) {
237
+ info.gitRemote = remote;
238
+ info.gitRemoteType = getGitUrlType(remote);
239
+ }
240
+ }
241
+ catch (e) {
242
+ // Not a git repo or no remote
243
+ }
244
+ // 5. Detect Port
245
+ info.detectedPort = detectPort(root);
246
+ return info;
247
+ }
package/dist/schema.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,216 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.addSshConfigEntry = addSshConfigEntry;
37
+ exports.removeSshConfigEntry = removeSshConfigEntry;
38
+ exports.updateSshConfigEntry = updateSshConfigEntry;
39
+ exports.hasSshConfigEntry = hasSshConfigEntry;
40
+ exports.getSshAlias = getSshAlias;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const SSH_CONFIG_PATH = path.join(os.homedir(), '.ssh', 'config');
45
+ const GENBOX_MARKER_START = '# Genbox Environment:';
46
+ const GENBOX_MARKER_END = '# End Genbox Environment:';
47
+ /**
48
+ * Ensure ~/.ssh directory exists
49
+ */
50
+ function ensureSshDir() {
51
+ const sshDir = path.join(os.homedir(), '.ssh');
52
+ if (!fs.existsSync(sshDir)) {
53
+ fs.mkdirSync(sshDir, { mode: 0o700 });
54
+ }
55
+ }
56
+ /**
57
+ * Read current SSH config content
58
+ */
59
+ function readSshConfig() {
60
+ ensureSshDir();
61
+ if (!fs.existsSync(SSH_CONFIG_PATH)) {
62
+ return '';
63
+ }
64
+ return fs.readFileSync(SSH_CONFIG_PATH, 'utf8');
65
+ }
66
+ /**
67
+ * Write SSH config content
68
+ */
69
+ function writeSshConfig(content) {
70
+ ensureSshDir();
71
+ fs.writeFileSync(SSH_CONFIG_PATH, content, { mode: 0o600 });
72
+ }
73
+ /**
74
+ * Find the best available SSH key
75
+ */
76
+ function findSshKey() {
77
+ const home = os.homedir();
78
+ const keyPaths = [
79
+ path.join(home, '.ssh', 'id_ed25519'),
80
+ path.join(home, '.ssh', 'id_rsa'),
81
+ ];
82
+ for (const keyPath of keyPaths) {
83
+ if (fs.existsSync(keyPath)) {
84
+ return keyPath;
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Add an SSH config entry for a Genbox
91
+ */
92
+ function addSshConfigEntry(entry) {
93
+ try {
94
+ const config = readSshConfig();
95
+ const hostAlias = `genbox-${entry.name}`;
96
+ const marker = `${GENBOX_MARKER_START} ${entry.name}`;
97
+ const endMarker = `${GENBOX_MARKER_END} ${entry.name}`;
98
+ // Check if entry already exists
99
+ if (config.includes(marker)) {
100
+ // Update existing entry
101
+ removeSshConfigEntry(entry.name);
102
+ }
103
+ // Find SSH key
104
+ const keyPath = entry.keyPath || findSshKey();
105
+ // Build config block
106
+ const configBlock = [
107
+ '',
108
+ marker,
109
+ `Host ${hostAlias}`,
110
+ ` HostName ${entry.ipAddress}`,
111
+ ` User ${entry.user || 'dev'}`,
112
+ ' StrictHostKeyChecking no',
113
+ ' UserKnownHostsFile /dev/null',
114
+ ' LogLevel ERROR',
115
+ ];
116
+ if (keyPath) {
117
+ configBlock.push(` IdentityFile ${keyPath}`);
118
+ configBlock.push(' IdentitiesOnly yes');
119
+ }
120
+ configBlock.push(endMarker);
121
+ configBlock.push('');
122
+ // Append to config
123
+ const newConfig = config.trimEnd() + '\n' + configBlock.join('\n');
124
+ writeSshConfig(newConfig);
125
+ return true;
126
+ }
127
+ catch (error) {
128
+ return false;
129
+ }
130
+ }
131
+ /**
132
+ * Remove an SSH config entry for a Genbox
133
+ */
134
+ function removeSshConfigEntry(name) {
135
+ try {
136
+ const config = readSshConfig();
137
+ const marker = `${GENBOX_MARKER_START} ${name}`;
138
+ const endMarker = `${GENBOX_MARKER_END} ${name}`;
139
+ if (!config.includes(marker)) {
140
+ return true; // Nothing to remove
141
+ }
142
+ // Find and remove the block
143
+ const startIdx = config.indexOf(marker);
144
+ let endIdx = config.indexOf(endMarker);
145
+ if (endIdx === -1) {
146
+ // Malformed entry, try to find next Host line
147
+ endIdx = config.indexOf('\nHost ', startIdx + 1);
148
+ if (endIdx === -1) {
149
+ endIdx = config.length;
150
+ }
151
+ }
152
+ else {
153
+ endIdx += endMarker.length;
154
+ }
155
+ // Remove leading newline if present
156
+ let removeStart = startIdx;
157
+ if (removeStart > 0 && config[removeStart - 1] === '\n') {
158
+ removeStart--;
159
+ }
160
+ // Remove trailing newline if present
161
+ let removeEnd = endIdx;
162
+ if (removeEnd < config.length && config[removeEnd] === '\n') {
163
+ removeEnd++;
164
+ }
165
+ const newConfig = config.slice(0, removeStart) + config.slice(removeEnd);
166
+ writeSshConfig(newConfig.trim() + '\n');
167
+ return true;
168
+ }
169
+ catch (error) {
170
+ return false;
171
+ }
172
+ }
173
+ /**
174
+ * Update an existing SSH config entry with a new IP
175
+ */
176
+ function updateSshConfigEntry(name, newIpAddress) {
177
+ const config = readSshConfig();
178
+ const marker = `${GENBOX_MARKER_START} ${name}`;
179
+ if (!config.includes(marker)) {
180
+ return false;
181
+ }
182
+ // Find and update the HostName line
183
+ const lines = config.split('\n');
184
+ let inBlock = false;
185
+ let updated = false;
186
+ for (let i = 0; i < lines.length; i++) {
187
+ if (lines[i].includes(marker)) {
188
+ inBlock = true;
189
+ }
190
+ if (inBlock && lines[i].trim().startsWith('HostName ')) {
191
+ lines[i] = ` HostName ${newIpAddress}`;
192
+ updated = true;
193
+ }
194
+ if (lines[i].includes(`${GENBOX_MARKER_END} ${name}`)) {
195
+ break;
196
+ }
197
+ }
198
+ if (updated) {
199
+ writeSshConfig(lines.join('\n'));
200
+ }
201
+ return updated;
202
+ }
203
+ /**
204
+ * Check if an SSH config entry exists for a Genbox
205
+ */
206
+ function hasSshConfigEntry(name) {
207
+ const config = readSshConfig();
208
+ const marker = `${GENBOX_MARKER_START} ${name}`;
209
+ return config.includes(marker);
210
+ }
211
+ /**
212
+ * Get the SSH alias for a Genbox
213
+ */
214
+ function getSshAlias(name) {
215
+ return `genbox-${name}`;
216
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "genbox",
3
+ "version": "1.0.0",
4
+ "description": "Genbox CLI - AI-Powered Development Environments",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "genbox": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist/**/*"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "dev": "ts-node src/index.ts",
16
+ "prepublishOnly": "npm run build",
17
+ "publish:patch": "./scripts/publish.sh patch",
18
+ "publish:minor": "./scripts/publish.sh minor",
19
+ "publish:major": "./scripts/publish.sh major",
20
+ "release": "./scripts/publish.sh patch",
21
+ "test": "echo \"Error: no test specified\" && exit 1"
22
+ },
23
+ "keywords": [
24
+ "genbox",
25
+ "ai",
26
+ "development",
27
+ "environments",
28
+ "cloud",
29
+ "cli",
30
+ "dev-environment",
31
+ "sandbox",
32
+ "hetzner"
33
+ ],
34
+ "author": "GoodPass",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/goodpass-co/genbox.git",
39
+ "directory": "packages/cli"
40
+ },
41
+ "homepage": "https://genbox.dev",
42
+ "bugs": {
43
+ "url": "https://github.com/goodpass-co/genbox/issues"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/inquirer": "^9.0.9",
50
+ "@types/js-yaml": "^4.0.9",
51
+ "@types/node": "^24.10.1",
52
+ "ts-node": "^10.9.2",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "dependencies": {
56
+ "chalk": "^5.6.2",
57
+ "commander": "^14.0.2",
58
+ "dotenv": "^17.2.3",
59
+ "inquirer": "^13.0.2",
60
+ "js-yaml": "^4.1.1",
61
+ "ora": "^9.0.0"
62
+ }
63
+ }