genbox 1.0.2 → 1.0.4

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.
@@ -38,27 +38,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.createCommand = void 0;
40
40
  const commander_1 = require("commander");
41
- const inquirer_1 = __importDefault(require("inquirer"));
41
+ const prompts = __importStar(require("@inquirer/prompts"));
42
42
  const chalk_1 = __importDefault(require("chalk"));
43
43
  const ora_1 = __importDefault(require("ora"));
44
- const config_1 = require("../config");
45
44
  const fs = __importStar(require("fs"));
46
45
  const path = __importStar(require("path"));
47
46
  const os = __importStar(require("os"));
47
+ const config_loader_1 = require("../config-loader");
48
+ const profile_resolver_1 = require("../profile-resolver");
48
49
  const api_1 = require("../api");
49
50
  const ssh_config_1 = require("../ssh-config");
50
- // Real API Client
51
- async function provisionGenbox(options) {
51
+ async function provisionGenbox(payload) {
52
52
  return (0, api_1.fetchApi)('/genboxes', {
53
53
  method: 'POST',
54
- body: JSON.stringify(options),
54
+ body: JSON.stringify(payload),
55
55
  });
56
56
  }
57
57
  function getPublicSshKey() {
58
58
  const home = os.homedir();
59
59
  const potentialKeys = [
60
- path.join(home, '.ssh', 'id_rsa.pub'),
61
60
  path.join(home, '.ssh', 'id_ed25519.pub'),
61
+ path.join(home, '.ssh', 'id_rsa.pub'),
62
62
  ];
63
63
  for (const keyPath of potentialKeys) {
64
64
  if (fs.existsSync(keyPath)) {
@@ -67,172 +67,411 @@ function getPublicSshKey() {
67
67
  return content;
68
68
  }
69
69
  }
70
- throw new Error('No public SSH key found in ~/.ssh/ (checked id_rsa.pub and id_ed25519.pub)');
70
+ throw new Error('No public SSH key found in ~/.ssh/');
71
71
  }
72
72
  function getPrivateSshKey() {
73
73
  const home = os.homedir();
74
74
  const potentialKeys = [
75
- path.join(home, '.ssh', 'id_rsa'),
76
75
  path.join(home, '.ssh', 'id_ed25519'),
76
+ path.join(home, '.ssh', 'id_rsa'),
77
77
  ];
78
78
  for (const keyPath of potentialKeys) {
79
79
  if (fs.existsSync(keyPath)) {
80
- return keyPath;
80
+ return fs.readFileSync(keyPath, 'utf-8');
81
81
  }
82
82
  }
83
- throw new Error('No SSH private key found in ~/.ssh/');
83
+ return undefined;
84
84
  }
85
85
  exports.createCommand = new commander_1.Command('create')
86
- .description('Create a new Genbox')
86
+ .description('Create a new Genbox environment')
87
87
  .argument('<name>', 'Name of the Genbox')
88
- .option('--size <size>', 'Size of the Genbox (small, medium, large, xl)')
88
+ .option('-p, --profile <profile>', 'Use a predefined profile')
89
+ .option('-a, --apps <apps>', 'Comma-separated list of apps to include')
90
+ .option('--add-apps <apps>', 'Add apps to the profile')
91
+ .option('--api <mode>', 'API mode: local, staging, production')
92
+ .option('--db <mode>', 'Database mode: none, local, copy, remote')
93
+ .option('--db-source <source>', 'Database source: staging, production')
94
+ .option('-s, --size <size>', 'Server size: small, medium, large, xl')
95
+ .option('-b, --branch <branch>', 'Git branch to checkout')
96
+ .option('-y, --yes', 'Skip interactive prompts')
97
+ .option('--dry-run', 'Show what would be created without actually creating')
89
98
  .action(async (name, options) => {
90
99
  try {
91
- const config = (0, config_1.loadConfig)();
92
- const size = options.size || 'small';
100
+ // Load configuration
101
+ const configLoader = new config_loader_1.ConfigLoader();
102
+ const loadResult = await configLoader.load();
103
+ if (!loadResult.config || loadResult.config.version !== '3.0') {
104
+ // Fall back to legacy v1/v2 handling
105
+ await createLegacy(name, options);
106
+ return;
107
+ }
108
+ const config = loadResult.config;
109
+ const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
110
+ // Build create options
111
+ const createOptions = {
112
+ name,
113
+ profile: options.profile,
114
+ apps: options.apps ? options.apps.split(',').map((a) => a.trim()) : undefined,
115
+ addApps: options.addApps ? options.addApps.split(',').map((a) => a.trim()) : undefined,
116
+ api: options.api,
117
+ db: options.db,
118
+ dbSource: options.dbSource,
119
+ size: options.size,
120
+ branch: options.branch,
121
+ yes: options.yes,
122
+ dryRun: options.dryRun,
123
+ };
124
+ // Resolve configuration
125
+ console.log(chalk_1.default.blue('Resolving configuration...'));
126
+ console.log('');
127
+ const resolved = await profileResolver.resolve(config, createOptions);
128
+ // Display resolved configuration
129
+ displayResolvedConfig(resolved);
130
+ // Ask to save as profile (if not using one already)
131
+ if (!options.profile && !options.yes && !options.dryRun) {
132
+ await profileResolver.askSaveProfile(config, resolved);
133
+ }
134
+ // Dry run mode
135
+ if (options.dryRun) {
136
+ console.log(chalk_1.default.yellow('\nDry run mode - no genbox created'));
137
+ return;
138
+ }
139
+ // Confirm creation
140
+ if (!options.yes) {
141
+ const confirm = await prompts.confirm({
142
+ message: `Create genbox '${name}'?`,
143
+ default: true,
144
+ });
145
+ if (!confirm) {
146
+ console.log(chalk_1.default.dim('Cancelled.'));
147
+ return;
148
+ }
149
+ }
150
+ // Get SSH keys
93
151
  const publicKey = getPublicSshKey();
94
- // Check if SSH auth is needed for git repos (only ask if using SSH, not PAT)
152
+ // Check if SSH auth is needed for git
95
153
  let privateKeyContent;
96
- const usesSSHAuth = config.git_auth?.method === 'ssh' ||
97
- (config.repos && Object.values(config.repos).some(r => r.auth === 'ssh'));
98
- if (usesSSHAuth) {
99
- const { injectKey } = await inquirer_1.default.prompt([{
100
- type: 'confirm',
101
- name: 'injectKey',
102
- message: 'Inject local SSH Private Key for git cloning?',
103
- default: true
104
- }]);
154
+ const usesSSH = config.git_auth?.method === 'ssh' ||
155
+ Object.values(config.repos || {}).some(r => r.auth === 'ssh');
156
+ if (usesSSH && !options.yes) {
157
+ const injectKey = await prompts.confirm({
158
+ message: 'Inject SSH private key for git cloning?',
159
+ default: true,
160
+ });
105
161
  if (injectKey) {
106
- const home = os.homedir();
107
- const privKeyPath = [path.join(home, '.ssh', 'id_rsa'), path.join(home, '.ssh', 'id_ed25519')].find(kv => fs.existsSync(kv));
108
- if (privKeyPath) {
109
- privateKeyContent = fs.readFileSync(privKeyPath, 'utf-8');
110
- console.log(chalk_1.default.dim(` Using private key: ${privKeyPath}`));
111
- }
112
- else {
113
- console.warn(chalk_1.default.yellow('No private key found to inject!'));
162
+ privateKeyContent = getPrivateSshKey();
163
+ if (privateKeyContent) {
164
+ console.log(chalk_1.default.dim(' Using local SSH private key'));
114
165
  }
115
166
  }
116
167
  }
117
- const spinner = (0, ora_1.default)(`Creating Genbox '${name}' for project '${config.project_name}'...`).start();
168
+ // Build payload
169
+ const payload = buildPayload(resolved, config, publicKey, privateKeyContent, configLoader);
170
+ // Create genbox
171
+ const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
118
172
  try {
119
- // Prepare files bundle
120
- const filesBundle = [];
121
- if (config.files) {
122
- for (const f of config.files) {
123
- const sourcePath = path.resolve(process.cwd(), f.source);
124
- if (fs.existsSync(sourcePath)) {
125
- const content = fs.readFileSync(sourcePath, 'utf-8');
126
- filesBundle.push({
127
- path: f.target,
128
- content: content,
129
- permissions: f.permissions || '0644'
130
- });
131
- }
132
- else {
133
- console.warn(chalk_1.default.yellow(`Warning: File ${f.source} not found. Skipping.`));
134
- }
135
- }
136
- }
137
- // Scripts handling (simplistic: upload and run)
138
- const postDetails = [];
139
- if (config.scripts) {
140
- for (const s of config.scripts) {
141
- const scriptName = path.basename(s);
142
- const targetPath = `/home/dev/${scriptName}`;
143
- const sourcePath = path.resolve(process.cwd(), s);
144
- if (fs.existsSync(sourcePath)) {
145
- const content = fs.readFileSync(sourcePath, 'utf-8');
146
- filesBundle.push({
147
- path: targetPath,
148
- content: content,
149
- permissions: '0755'
150
- });
151
- postDetails.push(`su - dev -c "${targetPath}"`);
152
- }
153
- }
154
- }
155
- // project_name from config becomes 'workspace' in the API
156
- const workspace = config.project_name || 'default';
157
- // Load environment variables (including GIT_TOKEN for PAT auth)
158
- const envVars = (0, config_1.loadEnvVars)();
159
- const gitToken = envVars.GIT_TOKEN;
160
- const genbox = await provisionGenbox({
161
- name,
162
- size,
163
- publicKey,
164
- workspace,
165
- services: config.services,
166
- files: filesBundle,
167
- postDetails,
168
- repos: config.repos,
169
- privateKey: privateKeyContent,
170
- gitToken,
171
- });
173
+ const genbox = await provisionGenbox(payload);
172
174
  spinner.succeed(chalk_1.default.green(`Genbox '${name}' created successfully!`));
173
- // Add SSH config entry for easy access
175
+ // Add SSH config
174
176
  if (genbox.ipAddress) {
175
- const sshConfigAdded = (0, ssh_config_1.addSshConfigEntry)({
177
+ const sshAdded = (0, ssh_config_1.addSshConfigEntry)({
176
178
  name,
177
179
  ipAddress: genbox.ipAddress,
178
180
  });
179
- if (sshConfigAdded) {
181
+ if (sshAdded) {
180
182
  console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
181
183
  }
182
184
  }
183
- console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
184
- console.log(` ${chalk_1.default.bold('Environment:')} ${name}`);
185
- console.log(` ${chalk_1.default.bold('Workspace:')} ${genbox.workspace || workspace}`);
186
- console.log(` ${chalk_1.default.bold('Size:')} ${genbox.size}`);
187
- console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status)}`);
188
- if (genbox.ipAddress) {
189
- console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
190
- }
191
- console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
192
- // Display URLs
193
- if (genbox.urls && Object.keys(genbox.urls).length > 0) {
194
- console.log('\n' + chalk_1.default.bold('Access URLs:'));
195
- for (const [service, url] of Object.entries(genbox.urls)) {
196
- console.log(` ${chalk_1.default.cyan(service.padEnd(10))} ${url}`);
197
- }
198
- }
199
- console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
200
- console.log(`\n${chalk_1.default.bold('Quick Commands:')}`);
201
- console.log(` SSH: ${chalk_1.default.cyan(`genbox connect ${name}`)}`);
202
- console.log(` Status: ${chalk_1.default.cyan(`genbox status ${name}`)}`);
203
- console.log(` URLs: ${chalk_1.default.cyan(`genbox urls ${name}`)}`);
204
- console.log(` Destroy: ${chalk_1.default.cyan(`genbox destroy ${name}`)}`);
205
- }
206
- catch (innerError) {
207
- if (innerError instanceof api_1.AuthenticationError) {
208
- spinner.fail(chalk_1.default.red('Not logged in'));
209
- console.error('');
210
- console.error(chalk_1.default.yellow(' Please authenticate first:'));
211
- console.error(chalk_1.default.cyan(' $ genbox login'));
212
- console.error('');
213
- }
214
- else {
215
- spinner.fail(chalk_1.default.red(`Failed to create Genbox '${name}': ${innerError.message}`));
185
+ // Display results
186
+ displayGenboxInfo(genbox, resolved);
187
+ }
188
+ catch (error) {
189
+ spinner.fail(chalk_1.default.red(`Failed to create Genbox: ${error.message}`));
190
+ if (error instanceof api_1.AuthenticationError) {
191
+ console.log('');
192
+ console.log(chalk_1.default.yellow(' Please authenticate first:'));
193
+ console.log(chalk_1.default.cyan(' $ genbox login'));
216
194
  }
217
195
  }
218
196
  }
219
197
  catch (error) {
220
- // Handle user cancellation (Ctrl+C) gracefully
221
198
  if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
222
199
  console.log('');
223
200
  console.log(chalk_1.default.dim('Cancelled.'));
224
201
  return;
225
202
  }
226
- // Handle authentication errors with a helpful message
227
203
  if (error instanceof api_1.AuthenticationError) {
228
- console.error('');
229
- console.error(chalk_1.default.red('✖ Not logged in'));
230
- console.error('');
231
- console.error(chalk_1.default.yellow(' Please authenticate first:'));
232
- console.error(chalk_1.default.cyan(' $ genbox login'));
233
- console.error('');
204
+ console.log(chalk_1.default.red('✖ Not logged in'));
205
+ console.log('');
206
+ console.log(chalk_1.default.yellow(' Please authenticate first:'));
207
+ console.log(chalk_1.default.cyan(' $ genbox login'));
234
208
  return;
235
209
  }
236
210
  console.error(chalk_1.default.red(`Error: ${error.message}`));
237
211
  }
238
212
  });
213
+ /**
214
+ * Display resolved configuration
215
+ */
216
+ function displayResolvedConfig(resolved) {
217
+ console.log(chalk_1.default.bold('Configuration:'));
218
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
219
+ console.log(` ${chalk_1.default.bold('Name:')} ${resolved.name}`);
220
+ console.log(` ${chalk_1.default.bold('Project:')} ${resolved.project.name}`);
221
+ console.log(` ${chalk_1.default.bold('Size:')} ${resolved.size}`);
222
+ if (resolved.profile) {
223
+ console.log(` ${chalk_1.default.bold('Profile:')} ${resolved.profile}`);
224
+ }
225
+ console.log('');
226
+ console.log(` ${chalk_1.default.bold('Apps:')}`);
227
+ for (const app of resolved.apps) {
228
+ const deps = Object.entries(app.dependencies)
229
+ .map(([name, d]) => `${name}:${d.mode}`)
230
+ .join(', ');
231
+ console.log(` • ${app.name} (${app.type}${app.framework ? `, ${app.framework}` : ''})`);
232
+ if (deps) {
233
+ console.log(chalk_1.default.dim(` deps: ${deps}`));
234
+ }
235
+ }
236
+ if (resolved.infrastructure.length > 0) {
237
+ console.log('');
238
+ console.log(` ${chalk_1.default.bold('Infrastructure:')}`);
239
+ for (const infra of resolved.infrastructure) {
240
+ console.log(` • ${infra.name} (${infra.mode})`);
241
+ }
242
+ }
243
+ console.log('');
244
+ console.log(` ${chalk_1.default.bold('Database:')} ${resolved.database.mode}${resolved.database.source ? ` (from ${resolved.database.source})` : ''}`);
245
+ if (resolved.warnings.length > 0) {
246
+ console.log('');
247
+ console.log(chalk_1.default.yellow(' Warnings:'));
248
+ for (const warning of resolved.warnings) {
249
+ console.log(chalk_1.default.dim(` - ${warning}`));
250
+ }
251
+ }
252
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
253
+ console.log('');
254
+ }
255
+ /**
256
+ * Build API payload from resolved config
257
+ */
258
+ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
259
+ // Load env vars
260
+ const envVars = configLoader.loadEnvVars(process.cwd());
261
+ // Build services map
262
+ const services = {};
263
+ for (const app of resolved.apps) {
264
+ if (app.services) {
265
+ for (const [name, svc] of Object.entries(app.services)) {
266
+ services[name] = { port: svc.port, healthcheck: svc.healthcheck };
267
+ }
268
+ }
269
+ else if (app.port) {
270
+ services[app.name] = { port: app.port };
271
+ }
272
+ }
273
+ // Build files bundle
274
+ const files = [];
275
+ // Add setup script if generated
276
+ const setupScript = generateSetupScript(resolved, config);
277
+ if (setupScript) {
278
+ files.push({
279
+ path: '/home/dev/setup-genbox.sh',
280
+ content: setupScript,
281
+ permissions: '0755',
282
+ });
283
+ }
284
+ // Build post details (scripts to run)
285
+ const postDetails = [];
286
+ if (setupScript) {
287
+ postDetails.push('su - dev -c "/home/dev/setup-genbox.sh"');
288
+ }
289
+ // Build repos
290
+ const repos = {};
291
+ for (const repo of resolved.repos) {
292
+ repos[repo.name] = {
293
+ url: repo.url,
294
+ path: repo.path,
295
+ branch: repo.branch,
296
+ };
297
+ }
298
+ return {
299
+ name: resolved.name,
300
+ size: resolved.size,
301
+ publicKey,
302
+ workspace: resolved.project.name,
303
+ services,
304
+ files,
305
+ postDetails,
306
+ repos,
307
+ privateKey,
308
+ gitToken: envVars.GIT_TOKEN,
309
+ envVars: resolved.env,
310
+ apps: resolved.apps.map(a => a.name),
311
+ infrastructure: resolved.infrastructure.map(i => ({
312
+ name: i.name,
313
+ type: i.type,
314
+ mode: i.mode,
315
+ })),
316
+ database: resolved.database,
317
+ };
318
+ }
319
+ /**
320
+ * Generate setup script
321
+ */
322
+ function generateSetupScript(resolved, config) {
323
+ const lines = [
324
+ '#!/bin/bash',
325
+ '# Generated by genbox create',
326
+ 'set -e',
327
+ '',
328
+ ];
329
+ // Change to project directory
330
+ if (resolved.repos.length > 0) {
331
+ lines.push(`cd ${resolved.repos[0].path} || exit 1`);
332
+ lines.push('');
333
+ }
334
+ // Install dependencies - detect package manager from lockfiles
335
+ lines.push('# Install dependencies');
336
+ lines.push('if [ -f "pnpm-lock.yaml" ]; then');
337
+ lines.push(' echo "Installing dependencies with pnpm..."');
338
+ lines.push(' pnpm install --frozen-lockfile');
339
+ lines.push('elif [ -f "yarn.lock" ]; then');
340
+ lines.push(' echo "Installing dependencies with yarn..."');
341
+ lines.push(' yarn install --frozen-lockfile');
342
+ lines.push('elif [ -f "bun.lockb" ]; then');
343
+ lines.push(' echo "Installing dependencies with bun..."');
344
+ lines.push(' bun install --frozen-lockfile');
345
+ lines.push('elif [ -f "package-lock.json" ]; then');
346
+ lines.push(' echo "Installing dependencies with npm..."');
347
+ lines.push(' npm ci');
348
+ lines.push('fi');
349
+ // Start Docker if API is included locally
350
+ const hasLocalApi = resolved.apps.some(a => a.name === 'api' || a.type === 'backend');
351
+ if (hasLocalApi) {
352
+ lines.push('');
353
+ lines.push('echo "Starting Docker services..."');
354
+ lines.push('if [ -f "docker-compose.yml" ] || [ -f "compose.yml" ]; then');
355
+ lines.push(' docker compose up -d');
356
+ lines.push('fi');
357
+ }
358
+ lines.push('');
359
+ lines.push('echo "Setup complete!"');
360
+ return lines.join('\n');
361
+ }
362
+ /**
363
+ * Display genbox info after creation
364
+ */
365
+ function displayGenboxInfo(genbox, resolved) {
366
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
367
+ console.log(` ${chalk_1.default.bold('Environment:')} ${genbox.name || resolved.name}`);
368
+ console.log(` ${chalk_1.default.bold('Workspace:')} ${genbox.workspace || resolved.project.name}`);
369
+ console.log(` ${chalk_1.default.bold('Size:')} ${genbox.size || resolved.size}`);
370
+ console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status || 'creating')}`);
371
+ if (genbox.ipAddress) {
372
+ console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
373
+ }
374
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
375
+ // Display URLs
376
+ if (genbox.urls && Object.keys(genbox.urls).length > 0) {
377
+ console.log('');
378
+ console.log(chalk_1.default.bold('Access URLs:'));
379
+ for (const [service, url] of Object.entries(genbox.urls)) {
380
+ console.log(` ${chalk_1.default.cyan(service.padEnd(12))} ${url}`);
381
+ }
382
+ }
383
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
384
+ console.log('');
385
+ console.log(chalk_1.default.bold('Quick Commands:'));
386
+ console.log(` SSH: ${chalk_1.default.cyan(`genbox connect ${resolved.name}`)}`);
387
+ console.log(` Status: ${chalk_1.default.cyan(`genbox status ${resolved.name}`)}`);
388
+ console.log(` URLs: ${chalk_1.default.cyan(`genbox urls ${resolved.name}`)}`);
389
+ console.log(` Destroy: ${chalk_1.default.cyan(`genbox destroy ${resolved.name}`)}`);
390
+ }
391
+ /**
392
+ * Legacy v1/v2 create handling
393
+ */
394
+ async function createLegacy(name, options) {
395
+ // Import legacy config handling
396
+ const { loadConfig, loadEnvVars } = await Promise.resolve().then(() => __importStar(require('../config')));
397
+ const config = loadConfig();
398
+ const size = options.size || config.system?.server_size || 'small';
399
+ const publicKey = getPublicSshKey();
400
+ // Check if SSH auth needed
401
+ let privateKeyContent;
402
+ const usesSSH = config.git_auth?.method === 'ssh' ||
403
+ (config.repos && Object.values(config.repos).some((r) => r.auth === 'ssh'));
404
+ if (usesSSH && !options.yes) {
405
+ const injectKey = await prompts.confirm({
406
+ message: 'Inject SSH private key for git cloning?',
407
+ default: true,
408
+ });
409
+ if (injectKey) {
410
+ privateKeyContent = getPrivateSshKey();
411
+ }
412
+ }
413
+ const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
414
+ try {
415
+ // Prepare files
416
+ const filesBundle = [];
417
+ if (config.files) {
418
+ for (const f of config.files) {
419
+ const sourcePath = path.resolve(process.cwd(), f.source);
420
+ if (fs.existsSync(sourcePath)) {
421
+ filesBundle.push({
422
+ path: f.target,
423
+ content: fs.readFileSync(sourcePath, 'utf-8'),
424
+ permissions: f.permissions || '0644',
425
+ });
426
+ }
427
+ }
428
+ }
429
+ // Prepare scripts
430
+ const postDetails = [];
431
+ if (config.scripts) {
432
+ for (const s of config.scripts) {
433
+ const scriptName = path.basename(s);
434
+ const targetPath = `/home/dev/${scriptName}`;
435
+ const sourcePath = path.resolve(process.cwd(), s);
436
+ if (fs.existsSync(sourcePath)) {
437
+ filesBundle.push({
438
+ path: targetPath,
439
+ content: fs.readFileSync(sourcePath, 'utf-8'),
440
+ permissions: '0755',
441
+ });
442
+ postDetails.push(`su - dev -c "${targetPath}"`);
443
+ }
444
+ }
445
+ }
446
+ const envVars = loadEnvVars();
447
+ const genbox = await provisionGenbox({
448
+ name,
449
+ size,
450
+ publicKey,
451
+ workspace: config.project_name || 'default',
452
+ services: config.services,
453
+ files: filesBundle,
454
+ postDetails,
455
+ repos: config.repos,
456
+ privateKey: privateKeyContent,
457
+ gitToken: envVars.GIT_TOKEN,
458
+ });
459
+ spinner.succeed(chalk_1.default.green(`Genbox '${name}' created!`));
460
+ if (genbox.ipAddress) {
461
+ (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
462
+ }
463
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
464
+ console.log(` ${chalk_1.default.bold('Environment:')} ${name}`);
465
+ console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status)}`);
466
+ if (genbox.ipAddress) {
467
+ console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
468
+ }
469
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
470
+ }
471
+ catch (error) {
472
+ spinner.fail(chalk_1.default.red(`Failed: ${error.message}`));
473
+ if (error instanceof api_1.AuthenticationError) {
474
+ console.log(chalk_1.default.yellow('\n Run: genbox login'));
475
+ }
476
+ }
477
+ }