genbox 1.0.200 → 1.0.202
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/create.js
CHANGED
|
@@ -1157,7 +1157,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
1157
1157
|
// Wait for genbox to be ready with progress indicator
|
|
1158
1158
|
const genboxId = genbox._id || genbox.id;
|
|
1159
1159
|
const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genboxId, name, {
|
|
1160
|
-
timeout:
|
|
1160
|
+
timeout: 600,
|
|
1161
1161
|
showProgress: true,
|
|
1162
1162
|
});
|
|
1163
1163
|
if (!waitResult.success) {
|
package/dist/commands/new.js
CHANGED
|
@@ -334,7 +334,7 @@ exports.newCommand = new commander_1.Command('new')
|
|
|
334
334
|
// Wait for genbox to be ready with progress indicator
|
|
335
335
|
const genboxId = genbox._id || genbox.id;
|
|
336
336
|
const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genboxId, projectName, {
|
|
337
|
-
timeout:
|
|
337
|
+
timeout: 600,
|
|
338
338
|
showProgress: true,
|
|
339
339
|
});
|
|
340
340
|
if (!waitResult.success) {
|
|
@@ -368,7 +368,7 @@ async function startGenbox(genbox) {
|
|
|
368
368
|
});
|
|
369
369
|
// Wait for genbox to be ready with progress indicator
|
|
370
370
|
const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genbox.id, genbox.name, {
|
|
371
|
-
timeout:
|
|
371
|
+
timeout: 600,
|
|
372
372
|
showProgress: true,
|
|
373
373
|
});
|
|
374
374
|
if (!waitResult.success) {
|
|
@@ -495,7 +495,16 @@ async function startSessionOnGenbox(provider, genbox) {
|
|
|
495
495
|
const socketDir = getRemoteDtachSocketDir();
|
|
496
496
|
const socketPath = `${socketDir}/${sessionName}.sock`;
|
|
497
497
|
// Use projectPath from genbox if available, otherwise detect
|
|
498
|
-
|
|
498
|
+
// For local Docker/VM, the workspace is mounted at /home/dev/workspace
|
|
499
|
+
let knownProjectPath = '';
|
|
500
|
+
if (genbox._isLocal) {
|
|
501
|
+
// Local genboxes mount workspace at /home/dev/workspace
|
|
502
|
+
knownProjectPath = '/home/dev/workspace';
|
|
503
|
+
}
|
|
504
|
+
else if (targetGenbox.projectPath) {
|
|
505
|
+
// Cloud genboxes use the path from API
|
|
506
|
+
knownProjectPath = targetGenbox.projectPath;
|
|
507
|
+
}
|
|
499
508
|
const startScript = `#!/bin/bash
|
|
500
509
|
# Set terminal environment for color support
|
|
501
510
|
export TERM=xterm-256color
|
|
@@ -51,7 +51,7 @@ function estimateProgress(elapsedSecs, hasIp) {
|
|
|
51
51
|
* Wait for a genbox to be ready with real-time progress indicator
|
|
52
52
|
*/
|
|
53
53
|
async function waitForGenboxReady(genboxId, name, options = {}) {
|
|
54
|
-
const { timeout =
|
|
54
|
+
const { timeout = 600, interval = 2000, showProgress = true, onStatusChange, } = options;
|
|
55
55
|
const maxAttempts = Math.ceil((timeout * 1000) / interval);
|
|
56
56
|
const startTime = Date.now();
|
|
57
57
|
let lastStatus = '';
|
|
@@ -57,6 +57,7 @@ const fs = __importStar(require("fs"));
|
|
|
57
57
|
const os = __importStar(require("os"));
|
|
58
58
|
const path = __importStar(require("path"));
|
|
59
59
|
const child_process_1 = require("child_process");
|
|
60
|
+
const ora_1 = __importDefault(require("ora"));
|
|
60
61
|
const api_1 = require("../api");
|
|
61
62
|
const genbox_progress_1 = require("./genbox-progress");
|
|
62
63
|
const ssh_keys_1 = require("../utils/ssh-keys");
|
|
@@ -64,6 +65,10 @@ const config_store_1 = require("../config-store");
|
|
|
64
65
|
const unified_session_1 = require("./unified-session");
|
|
65
66
|
const gemini_auth_1 = require("../utils/gemini-auth");
|
|
66
67
|
const claude_auth_1 = require("../utils/claude-auth");
|
|
68
|
+
const config_loader_1 = require("../config-loader");
|
|
69
|
+
const profile_resolver_1 = require("../profile-resolver");
|
|
70
|
+
const utils_1 = require("../utils");
|
|
71
|
+
const random_name_1 = require("../random-name");
|
|
67
72
|
const VM_SIZE_REQUIREMENTS = {
|
|
68
73
|
small: {
|
|
69
74
|
cpus: 2,
|
|
@@ -585,23 +590,50 @@ async function promptForLocation(includeDirect = true) {
|
|
|
585
590
|
}
|
|
586
591
|
}
|
|
587
592
|
/**
|
|
588
|
-
* Prompt for genbox name
|
|
593
|
+
* Prompt for genbox name with random suggestions
|
|
594
|
+
* Matches the UX from `gb create` command
|
|
589
595
|
*/
|
|
590
596
|
async function promptForName(defaultName) {
|
|
591
597
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
598
|
+
// Generate 3 random name suggestions
|
|
599
|
+
const suggestions = (0, random_name_1.generateNameSuggestions)(3);
|
|
600
|
+
const nameChoice = await (0, select_1.default)({
|
|
601
|
+
message: 'Environment name:',
|
|
602
|
+
choices: [
|
|
603
|
+
{
|
|
604
|
+
name: `${suggestions[0]} ${chalk_1.default.dim('(random)')}`,
|
|
605
|
+
value: suggestions[0],
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: `${suggestions[1]} ${chalk_1.default.dim('(random)')}`,
|
|
609
|
+
value: suggestions[1],
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
name: `${suggestions[2]} ${chalk_1.default.dim('(random)')}`,
|
|
613
|
+
value: suggestions[2],
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: 'Enter custom name...',
|
|
617
|
+
value: '__custom__',
|
|
618
|
+
},
|
|
619
|
+
],
|
|
603
620
|
});
|
|
604
|
-
|
|
621
|
+
if (nameChoice === '__custom__') {
|
|
622
|
+
const customName = await (0, prompts_1.input)({
|
|
623
|
+
message: 'Enter environment name:',
|
|
624
|
+
default: defaultName,
|
|
625
|
+
validate: (value) => {
|
|
626
|
+
if (!value.trim())
|
|
627
|
+
return 'Name is required';
|
|
628
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(value.trim())) {
|
|
629
|
+
return 'Name must be lowercase, start/end with letter or number, and contain only letters, numbers, and hyphens';
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
return customName.trim();
|
|
635
|
+
}
|
|
636
|
+
return nameChoice;
|
|
605
637
|
}
|
|
606
638
|
catch {
|
|
607
639
|
return null;
|
|
@@ -691,7 +723,7 @@ async function createCloudGenbox(name, options = {}) {
|
|
|
691
723
|
}
|
|
692
724
|
// Wait for genbox to be ready with progress indicator
|
|
693
725
|
const waitResult = await (0, genbox_progress_1.waitForGenboxReady)(genbox.id, name, {
|
|
694
|
-
timeout:
|
|
726
|
+
timeout: 600,
|
|
695
727
|
showProgress: true,
|
|
696
728
|
});
|
|
697
729
|
if (!waitResult.success) {
|
|
@@ -862,7 +894,8 @@ echo 'dev ALL=(ALL) NOPASSWD:ALL' | tee /etc/sudoers.d/dev
|
|
|
862
894
|
chmod 440 /etc/sudoers.d/dev
|
|
863
895
|
mkdir -p /home/dev/.ssh
|
|
864
896
|
echo '${publicKey}' > /home/dev/.ssh/authorized_keys
|
|
865
|
-
chown -R dev:dev /home/dev/.ssh
|
|
897
|
+
chown -R dev:dev /home/dev/.ssh || true
|
|
898
|
+
chown dev:dev /home/dev || true
|
|
866
899
|
chmod 700 /home/dev/.ssh
|
|
867
900
|
chmod 600 /home/dev/.ssh/authorized_keys
|
|
868
901
|
mkdir -p /run/sshd
|
|
@@ -930,6 +963,50 @@ mkdir -p /run/sshd
|
|
|
930
963
|
console.log(chalk_1.default.red('\nFailed to start SSH in container.'));
|
|
931
964
|
return { success: false, error: 'SSH startup timeout' };
|
|
932
965
|
}
|
|
966
|
+
// Install Node.js and provider CLI via SSH
|
|
967
|
+
showProgress('Installing Node.js', 0, 70);
|
|
968
|
+
const keyPath = (0, ssh_keys_1.getPrivateSshKeyPath)();
|
|
969
|
+
try {
|
|
970
|
+
// Determine CLI package based on provider
|
|
971
|
+
const cliPackage = provider === 'gemini' ? '@google/gemini-cli' : '@anthropic-ai/claude-code';
|
|
972
|
+
const cliCommand = provider === 'gemini' ? 'gemini' : 'claude';
|
|
973
|
+
// Install NVM, Node.js, and CLI
|
|
974
|
+
const installScript = `
|
|
975
|
+
# Install NVM
|
|
976
|
+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
|
977
|
+
export NVM_DIR="$HOME/.nvm"
|
|
978
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
|
979
|
+
|
|
980
|
+
# Install Node.js 20
|
|
981
|
+
nvm install 20
|
|
982
|
+
nvm use 20
|
|
983
|
+
nvm alias default 20
|
|
984
|
+
|
|
985
|
+
# Add NVM to bashrc for future sessions
|
|
986
|
+
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc
|
|
987
|
+
echo '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"' >> ~/.bashrc
|
|
988
|
+
|
|
989
|
+
# Install CLI
|
|
990
|
+
npm install -g ${cliPackage}
|
|
991
|
+
|
|
992
|
+
echo "Installed: $(node -v), ${cliCommand}"
|
|
993
|
+
`;
|
|
994
|
+
showProgress(`Installing ${cliCommand} CLI`, 0, 85);
|
|
995
|
+
(0, child_process_1.execFileSync)('ssh', [
|
|
996
|
+
'-p', String(sshPort),
|
|
997
|
+
'-i', keyPath,
|
|
998
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
999
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
1000
|
+
'-o', 'LogLevel=ERROR',
|
|
1001
|
+
'dev@localhost',
|
|
1002
|
+
installScript,
|
|
1003
|
+
], { timeout: 300000, stdio: 'pipe' }); // 5 minute timeout
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
// Non-fatal - CLI can be installed manually
|
|
1007
|
+
console.log(chalk_1.default.yellow(`\n Warning: Failed to install CLI automatically.`));
|
|
1008
|
+
console.log(chalk_1.default.dim(` You can install it manually after connecting.`));
|
|
1009
|
+
}
|
|
933
1010
|
showProgress('Creating session', 0, 95);
|
|
934
1011
|
// Create session record
|
|
935
1012
|
const session = await manager.createSession({
|
|
@@ -1040,32 +1117,21 @@ async function createLocalVmGenbox(name, provider = 'claude') {
|
|
|
1040
1117
|
console.log(chalk_1.default.green(`✓ Gemini credentials detected (${geminiCreds.source})`));
|
|
1041
1118
|
const isJsonCredentials = geminiCreds.credentials.trim().startsWith('{');
|
|
1042
1119
|
if (isJsonCredentials) {
|
|
1043
|
-
//
|
|
1044
|
-
const
|
|
1120
|
+
// Base64 encode the credentials to avoid YAML parsing issues
|
|
1121
|
+
const credsBase64 = Buffer.from(geminiCreds.credentials).toString('base64');
|
|
1122
|
+
const settingsJson = JSON.stringify({
|
|
1123
|
+
autoAccept: true,
|
|
1124
|
+
security: { auth: { selectedType: "oauth-personal" }, disableYoloMode: false },
|
|
1125
|
+
tools: { sandbox: false, autoAccept: true }
|
|
1126
|
+
});
|
|
1127
|
+
const settingsBase64 = Buffer.from(settingsJson).toString('base64');
|
|
1045
1128
|
credentialsScript = `
|
|
1046
|
-
# Setup Gemini credentials
|
|
1129
|
+
# Setup Gemini credentials (base64 encoded to avoid YAML issues)
|
|
1047
1130
|
echo "=== Setting up Gemini CLI Authentication ==="
|
|
1048
1131
|
mkdir -p /home/dev/.gemini
|
|
1049
|
-
|
|
1050
|
-
${geminiCreds.credentials}
|
|
1051
|
-
GEMINI_CREDS_EOF
|
|
1132
|
+
echo "${credsBase64}" | base64 -d > /home/dev/.gemini/oauth_creds.json
|
|
1052
1133
|
chmod 600 /home/dev/.gemini/oauth_creds.json
|
|
1053
|
-
|
|
1054
|
-
cat > /home/dev/.gemini/settings.json << 'GEMINI_SETTINGS_EOF'
|
|
1055
|
-
{
|
|
1056
|
-
"autoAccept": true,
|
|
1057
|
-
"security": {
|
|
1058
|
-
"auth": {
|
|
1059
|
-
"selectedType": "oauth-personal"
|
|
1060
|
-
},
|
|
1061
|
-
"disableYoloMode": false
|
|
1062
|
-
},
|
|
1063
|
-
"tools": {
|
|
1064
|
-
"sandbox": false,
|
|
1065
|
-
"autoAccept": true
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
GEMINI_SETTINGS_EOF
|
|
1134
|
+
echo "${settingsBase64}" | base64 -d > /home/dev/.gemini/settings.json
|
|
1069
1135
|
chmod 644 /home/dev/.gemini/settings.json
|
|
1070
1136
|
chown -R dev:dev /home/dev/.gemini
|
|
1071
1137
|
echo "Gemini CLI authentication configured."
|
|
@@ -1096,30 +1162,27 @@ GEMINI_SETTINGS_EOF
|
|
|
1096
1162
|
console.log(chalk_1.default.green(`✓ Claude credentials detected (${claudeCreds.source})`));
|
|
1097
1163
|
const isJsonCredentials = claudeCreds.credentials.trim().startsWith('{');
|
|
1098
1164
|
if (isJsonCredentials) {
|
|
1165
|
+
// Base64 encode the credentials to avoid YAML parsing issues
|
|
1166
|
+
const credsBase64 = Buffer.from(claudeCreds.credentials).toString('base64');
|
|
1167
|
+
const settingsJson = JSON.stringify({
|
|
1168
|
+
hasCompletedOnboarding: true,
|
|
1169
|
+
theme: "dark",
|
|
1170
|
+
preferredNotifChannel: "terminal"
|
|
1171
|
+
});
|
|
1172
|
+
const settingsBase64 = Buffer.from(settingsJson).toString('base64');
|
|
1173
|
+
const claudeJsonBase64 = Buffer.from(JSON.stringify({
|
|
1174
|
+
hasCompletedOnboarding: true,
|
|
1175
|
+
theme: "dark"
|
|
1176
|
+
})).toString('base64');
|
|
1099
1177
|
credentialsScript = `
|
|
1100
|
-
# Setup Claude credentials
|
|
1178
|
+
# Setup Claude credentials (base64 encoded to avoid YAML issues)
|
|
1101
1179
|
echo "=== Setting up Claude Code Authentication ==="
|
|
1102
1180
|
mkdir -p /home/dev/.claude
|
|
1103
|
-
|
|
1104
|
-
${claudeCreds.credentials}
|
|
1105
|
-
CLAUDE_CREDS_EOF
|
|
1181
|
+
echo "${credsBase64}" | base64 -d > /home/dev/.claude/.credentials.json
|
|
1106
1182
|
chmod 600 /home/dev/.claude/.credentials.json
|
|
1107
|
-
|
|
1108
|
-
cat > /home/dev/.claude/settings.json << 'CLAUDE_SETTINGS_EOF'
|
|
1109
|
-
{
|
|
1110
|
-
"hasCompletedOnboarding": true,
|
|
1111
|
-
"theme": "dark",
|
|
1112
|
-
"preferredNotifChannel": "terminal"
|
|
1113
|
-
}
|
|
1114
|
-
CLAUDE_SETTINGS_EOF
|
|
1183
|
+
echo "${settingsBase64}" | base64 -d > /home/dev/.claude/settings.json
|
|
1115
1184
|
chmod 644 /home/dev/.claude/settings.json
|
|
1116
|
-
|
|
1117
|
-
cat > /home/dev/.claude.json << 'CLAUDE_JSON_EOF'
|
|
1118
|
-
{
|
|
1119
|
-
"hasCompletedOnboarding": true,
|
|
1120
|
-
"theme": "dark"
|
|
1121
|
-
}
|
|
1122
|
-
CLAUDE_JSON_EOF
|
|
1185
|
+
echo "${claudeJsonBase64}" | base64 -d > /home/dev/.claude.json
|
|
1123
1186
|
chmod 644 /home/dev/.claude.json
|
|
1124
1187
|
chown -R dev:dev /home/dev/.claude /home/dev/.claude.json
|
|
1125
1188
|
echo "Claude Code authentication configured."
|
|
@@ -1234,6 +1297,11 @@ ${credentialsScript}`;
|
|
|
1234
1297
|
const stageProgress = (elapsed % 30) / 30 * 10;
|
|
1235
1298
|
updateProgress(currentStage, Math.floor(stageProgress));
|
|
1236
1299
|
}, 500);
|
|
1300
|
+
// Capture stderr for error messages
|
|
1301
|
+
let vmStderr = '';
|
|
1302
|
+
vmProcess.stderr.on('data', (data) => {
|
|
1303
|
+
vmStderr += data.toString();
|
|
1304
|
+
});
|
|
1237
1305
|
// Wait for VM creation
|
|
1238
1306
|
await new Promise((resolve, reject) => {
|
|
1239
1307
|
vmProcess.on('close', (code) => {
|
|
@@ -1242,7 +1310,8 @@ ${credentialsScript}`;
|
|
|
1242
1310
|
resolve();
|
|
1243
1311
|
}
|
|
1244
1312
|
else {
|
|
1245
|
-
|
|
1313
|
+
const errorMsg = vmStderr.trim() || `VM creation failed with code ${code}`;
|
|
1314
|
+
reject(new Error(errorMsg));
|
|
1246
1315
|
}
|
|
1247
1316
|
});
|
|
1248
1317
|
vmProcess.on('error', (err) => {
|
|
@@ -1353,12 +1422,303 @@ ${credentialsScript}`;
|
|
|
1353
1422
|
return { success: false, error: error.message };
|
|
1354
1423
|
}
|
|
1355
1424
|
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Create a local Docker genbox from a project with genbox.yaml
|
|
1427
|
+
*/
|
|
1428
|
+
async function createLocalDockerFromProject(config, options) {
|
|
1429
|
+
console.log('');
|
|
1430
|
+
console.log(chalk_1.default.blue('Creating Docker genbox from project...'));
|
|
1431
|
+
console.log(chalk_1.default.dim(`Project: ${config.project?.name || 'unknown'}`));
|
|
1432
|
+
console.log('');
|
|
1433
|
+
// Profile selection if available
|
|
1434
|
+
let selectedProfile;
|
|
1435
|
+
if (config.profiles && Object.keys(config.profiles).length > 0 && !options.skipPrompts) {
|
|
1436
|
+
const profileNames = Object.keys(config.profiles);
|
|
1437
|
+
const choices = profileNames.map(name => {
|
|
1438
|
+
const profile = config.profiles[name];
|
|
1439
|
+
const apps = profile.apps ? profile.apps.join(', ') : 'default apps';
|
|
1440
|
+
const description = profile.description || `Apps: ${apps}`;
|
|
1441
|
+
return {
|
|
1442
|
+
name: `${name} - ${chalk_1.default.dim(description)}`,
|
|
1443
|
+
value: name,
|
|
1444
|
+
};
|
|
1445
|
+
});
|
|
1446
|
+
choices.push({
|
|
1447
|
+
name: `${chalk_1.default.cyan('Custom')} - ${chalk_1.default.dim('Use default configuration')}`,
|
|
1448
|
+
value: '__custom__',
|
|
1449
|
+
});
|
|
1450
|
+
selectedProfile = await (0, select_1.default)({
|
|
1451
|
+
message: 'Select a profile:',
|
|
1452
|
+
choices,
|
|
1453
|
+
});
|
|
1454
|
+
if (selectedProfile === '__custom__') {
|
|
1455
|
+
selectedProfile = undefined;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
// Get name
|
|
1459
|
+
const defaultName = options.provider
|
|
1460
|
+
? `${options.provider}-${Date.now().toString(36)}`
|
|
1461
|
+
: `genbox-${Date.now().toString(36)}`;
|
|
1462
|
+
const name = options.name || await promptForName(defaultName);
|
|
1463
|
+
if (!name) {
|
|
1464
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1465
|
+
return { success: false, error: 'Cancelled' };
|
|
1466
|
+
}
|
|
1467
|
+
console.log('');
|
|
1468
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1469
|
+
console.log(` ${chalk_1.default.bold('Project:')} ${config.project?.name || 'unknown'}`);
|
|
1470
|
+
if (selectedProfile) {
|
|
1471
|
+
console.log(` ${chalk_1.default.bold('Profile:')} ${selectedProfile}`);
|
|
1472
|
+
}
|
|
1473
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1474
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1475
|
+
console.log('');
|
|
1476
|
+
return await createLocalDockerGenbox(name, options.provider || 'claude');
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Scaffold a project from a starter template
|
|
1480
|
+
* Returns the path to the scaffolded project
|
|
1481
|
+
*/
|
|
1482
|
+
async function scaffoldTemplate(template, projectName) {
|
|
1483
|
+
const spinner = (0, ora_1.default)('Setting up project from template...').start();
|
|
1484
|
+
try {
|
|
1485
|
+
let createCmd;
|
|
1486
|
+
if (template.createCommand) {
|
|
1487
|
+
// Use the template's create command (e.g., npx create-next-app)
|
|
1488
|
+
createCmd = template.createCommand.replace(/\{\{name\}\}/g, projectName);
|
|
1489
|
+
}
|
|
1490
|
+
else if (template.repoUrl) {
|
|
1491
|
+
// Clone from repository
|
|
1492
|
+
createCmd = `git clone https://${template.repoUrl} ${projectName}`;
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
spinner.fail('Template does not support local creation');
|
|
1496
|
+
return { success: false, error: 'Template does not support local creation' };
|
|
1497
|
+
}
|
|
1498
|
+
spinner.text = `Running: ${createCmd.substring(0, 50)}...`;
|
|
1499
|
+
// Run the create command
|
|
1500
|
+
(0, child_process_1.execSync)(createCmd, {
|
|
1501
|
+
stdio: 'pipe',
|
|
1502
|
+
cwd: process.cwd(),
|
|
1503
|
+
timeout: 300000, // 5 minute timeout
|
|
1504
|
+
});
|
|
1505
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
1506
|
+
// Run setup commands if any
|
|
1507
|
+
if (template.setupCommands && template.setupCommands.length > 0) {
|
|
1508
|
+
for (const cmd of template.setupCommands) {
|
|
1509
|
+
const setupCmd = cmd.replace(/\{\{name\}\}/g, projectName);
|
|
1510
|
+
spinner.text = `Running: ${setupCmd}`;
|
|
1511
|
+
try {
|
|
1512
|
+
(0, child_process_1.execSync)(setupCmd, {
|
|
1513
|
+
stdio: 'pipe',
|
|
1514
|
+
cwd: projectPath,
|
|
1515
|
+
timeout: 300000,
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
catch {
|
|
1519
|
+
// Some setup commands may fail, continue
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
spinner.succeed(chalk_1.default.green(`Project scaffolded: ${projectName}`));
|
|
1524
|
+
return { success: true, projectPath };
|
|
1525
|
+
}
|
|
1526
|
+
catch (error) {
|
|
1527
|
+
spinner.fail(chalk_1.default.red(`Failed to scaffold project: ${error.message}`));
|
|
1528
|
+
if (error.message?.includes('command not found')) {
|
|
1529
|
+
console.log(chalk_1.default.yellow('\nMake sure the required tools are installed:'));
|
|
1530
|
+
if (template.createCommand?.includes('npx')) {
|
|
1531
|
+
console.log(chalk_1.default.dim(' - Node.js and npx'));
|
|
1532
|
+
}
|
|
1533
|
+
if (template.createCommand?.includes('bunx')) {
|
|
1534
|
+
console.log(chalk_1.default.dim(' - Bun (https://bun.sh)'));
|
|
1535
|
+
}
|
|
1536
|
+
if (template.repoUrl) {
|
|
1537
|
+
console.log(chalk_1.default.dim(' - Git'));
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return { success: false, error: error.message };
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Create a local Docker genbox from a starter template
|
|
1545
|
+
*/
|
|
1546
|
+
async function createLocalDockerFromTemplate(options) {
|
|
1547
|
+
console.log('');
|
|
1548
|
+
console.log(chalk_1.default.blue('No genbox.yaml found - creating Docker genbox from template...'));
|
|
1549
|
+
console.log('');
|
|
1550
|
+
// Fetch available templates
|
|
1551
|
+
let templates = [];
|
|
1552
|
+
try {
|
|
1553
|
+
templates = await (0, api_1.fetchApi)('/genboxes/starter-templates');
|
|
1554
|
+
}
|
|
1555
|
+
catch (error) {
|
|
1556
|
+
console.log(chalk_1.default.yellow('Could not fetch templates, using default setup.'));
|
|
1557
|
+
// Continue without template selection
|
|
1558
|
+
}
|
|
1559
|
+
// Template selection if templates available
|
|
1560
|
+
let template;
|
|
1561
|
+
if (templates.length > 0 && !options.skipPrompts) {
|
|
1562
|
+
const templateChoices = templates.map(t => ({
|
|
1563
|
+
name: `${t.icon} ${t.displayName} - ${chalk_1.default.dim(t.description)}`,
|
|
1564
|
+
value: t.name,
|
|
1565
|
+
}));
|
|
1566
|
+
const selectedTemplate = await (0, select_1.default)({
|
|
1567
|
+
message: 'Select a template:',
|
|
1568
|
+
choices: templateChoices,
|
|
1569
|
+
pageSize: 15,
|
|
1570
|
+
});
|
|
1571
|
+
template = templates.find(t => t.name === selectedTemplate);
|
|
1572
|
+
}
|
|
1573
|
+
// Get name
|
|
1574
|
+
const defaultName = template
|
|
1575
|
+
? `my-${template.name}-app`
|
|
1576
|
+
: (options.provider ? `${options.provider}-${Date.now().toString(36)}` : `genbox-${Date.now().toString(36)}`);
|
|
1577
|
+
const name = options.name || await promptForName(defaultName);
|
|
1578
|
+
if (!name) {
|
|
1579
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1580
|
+
return { success: false, error: 'Cancelled' };
|
|
1581
|
+
}
|
|
1582
|
+
if (template) {
|
|
1583
|
+
console.log('');
|
|
1584
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1585
|
+
console.log(` ${chalk_1.default.bold('Template:')} ${template.icon} ${template.displayName}`);
|
|
1586
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1587
|
+
console.log(` ${chalk_1.default.bold('Features:')} ${template.features.join(', ')}`);
|
|
1588
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1589
|
+
console.log('');
|
|
1590
|
+
// Scaffold the project from template
|
|
1591
|
+
const scaffoldResult = await scaffoldTemplate(template, name);
|
|
1592
|
+
if (!scaffoldResult.success) {
|
|
1593
|
+
return { success: false, error: scaffoldResult.error };
|
|
1594
|
+
}
|
|
1595
|
+
// Change to the scaffolded project directory before creating genbox
|
|
1596
|
+
process.chdir(scaffoldResult.projectPath);
|
|
1597
|
+
console.log(chalk_1.default.dim(`Working directory: ${scaffoldResult.projectPath}`));
|
|
1598
|
+
}
|
|
1599
|
+
return await createLocalDockerGenbox(name, options.provider || 'claude');
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Create a local VM genbox from a project with genbox.yaml
|
|
1603
|
+
*/
|
|
1604
|
+
async function createLocalVmFromProject(config, options) {
|
|
1605
|
+
console.log('');
|
|
1606
|
+
console.log(chalk_1.default.blue('Creating VM genbox from project...'));
|
|
1607
|
+
console.log(chalk_1.default.dim(`Project: ${config.project?.name || 'unknown'}`));
|
|
1608
|
+
console.log('');
|
|
1609
|
+
// Profile selection if available
|
|
1610
|
+
let selectedProfile;
|
|
1611
|
+
if (config.profiles && Object.keys(config.profiles).length > 0 && !options.skipPrompts) {
|
|
1612
|
+
const profileNames = Object.keys(config.profiles);
|
|
1613
|
+
const choices = profileNames.map(name => {
|
|
1614
|
+
const profile = config.profiles[name];
|
|
1615
|
+
const apps = profile.apps ? profile.apps.join(', ') : 'default apps';
|
|
1616
|
+
const description = profile.description || `Apps: ${apps}`;
|
|
1617
|
+
return {
|
|
1618
|
+
name: `${name} - ${chalk_1.default.dim(description)}`,
|
|
1619
|
+
value: name,
|
|
1620
|
+
};
|
|
1621
|
+
});
|
|
1622
|
+
choices.push({
|
|
1623
|
+
name: `${chalk_1.default.cyan('Custom')} - ${chalk_1.default.dim('Use default configuration')}`,
|
|
1624
|
+
value: '__custom__',
|
|
1625
|
+
});
|
|
1626
|
+
selectedProfile = await (0, select_1.default)({
|
|
1627
|
+
message: 'Select a profile:',
|
|
1628
|
+
choices,
|
|
1629
|
+
});
|
|
1630
|
+
if (selectedProfile === '__custom__') {
|
|
1631
|
+
selectedProfile = undefined;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
// Get name
|
|
1635
|
+
const defaultName = options.provider
|
|
1636
|
+
? `${options.provider}-${Date.now().toString(36)}`
|
|
1637
|
+
: `genbox-${Date.now().toString(36)}`;
|
|
1638
|
+
const name = options.name || await promptForName(defaultName);
|
|
1639
|
+
if (!name) {
|
|
1640
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1641
|
+
return { success: false, error: 'Cancelled' };
|
|
1642
|
+
}
|
|
1643
|
+
console.log('');
|
|
1644
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1645
|
+
console.log(` ${chalk_1.default.bold('Project:')} ${config.project?.name || 'unknown'}`);
|
|
1646
|
+
if (selectedProfile) {
|
|
1647
|
+
console.log(` ${chalk_1.default.bold('Profile:')} ${selectedProfile}`);
|
|
1648
|
+
}
|
|
1649
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1650
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1651
|
+
console.log('');
|
|
1652
|
+
return await createLocalVmGenbox(name, options.provider || 'claude');
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Create a local VM genbox from a starter template
|
|
1656
|
+
*/
|
|
1657
|
+
async function createLocalVmFromTemplate(options) {
|
|
1658
|
+
console.log('');
|
|
1659
|
+
console.log(chalk_1.default.blue('No genbox.yaml found - creating VM genbox from template...'));
|
|
1660
|
+
console.log('');
|
|
1661
|
+
// Fetch available templates
|
|
1662
|
+
let templates = [];
|
|
1663
|
+
try {
|
|
1664
|
+
templates = await (0, api_1.fetchApi)('/genboxes/starter-templates');
|
|
1665
|
+
}
|
|
1666
|
+
catch (error) {
|
|
1667
|
+
console.log(chalk_1.default.yellow('Could not fetch templates, using default setup.'));
|
|
1668
|
+
// Continue without template selection
|
|
1669
|
+
}
|
|
1670
|
+
// Template selection if templates available
|
|
1671
|
+
let template;
|
|
1672
|
+
if (templates.length > 0 && !options.skipPrompts) {
|
|
1673
|
+
const templateChoices = templates.map(t => ({
|
|
1674
|
+
name: `${t.icon} ${t.displayName} - ${chalk_1.default.dim(t.description)}`,
|
|
1675
|
+
value: t.name,
|
|
1676
|
+
}));
|
|
1677
|
+
const selectedTemplate = await (0, select_1.default)({
|
|
1678
|
+
message: 'Select a template:',
|
|
1679
|
+
choices: templateChoices,
|
|
1680
|
+
pageSize: 15,
|
|
1681
|
+
});
|
|
1682
|
+
template = templates.find(t => t.name === selectedTemplate);
|
|
1683
|
+
}
|
|
1684
|
+
// Get name
|
|
1685
|
+
const defaultName = template
|
|
1686
|
+
? `my-${template.name}-app`
|
|
1687
|
+
: (options.provider ? `${options.provider}-${Date.now().toString(36)}` : `genbox-${Date.now().toString(36)}`);
|
|
1688
|
+
const name = options.name || await promptForName(defaultName);
|
|
1689
|
+
if (!name) {
|
|
1690
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1691
|
+
return { success: false, error: 'Cancelled' };
|
|
1692
|
+
}
|
|
1693
|
+
if (template) {
|
|
1694
|
+
console.log('');
|
|
1695
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1696
|
+
console.log(` ${chalk_1.default.bold('Template:')} ${template.icon} ${template.displayName}`);
|
|
1697
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1698
|
+
console.log(` ${chalk_1.default.bold('Features:')} ${template.features.join(', ')}`);
|
|
1699
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1700
|
+
console.log('');
|
|
1701
|
+
// Scaffold the project from template
|
|
1702
|
+
const scaffoldResult = await scaffoldTemplate(template, name);
|
|
1703
|
+
if (!scaffoldResult.success) {
|
|
1704
|
+
return { success: false, error: scaffoldResult.error };
|
|
1705
|
+
}
|
|
1706
|
+
// Change to the scaffolded project directory before creating genbox
|
|
1707
|
+
process.chdir(scaffoldResult.projectPath);
|
|
1708
|
+
console.log(chalk_1.default.dim(`Working directory: ${scaffoldResult.projectPath}`));
|
|
1709
|
+
}
|
|
1710
|
+
return await createLocalVmGenbox(name, options.provider || 'claude');
|
|
1711
|
+
}
|
|
1356
1712
|
/**
|
|
1357
1713
|
* Run the full creation wizard
|
|
1358
1714
|
*
|
|
1359
1715
|
* Used by both `gb create` and provider commands for consistent UX.
|
|
1360
1716
|
*/
|
|
1361
1717
|
async function runCreateWizard(options = {}) {
|
|
1718
|
+
// Step 0: Check if we're in a genbox project (has genbox.yaml)
|
|
1719
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
1720
|
+
const loadResult = await configLoader.load();
|
|
1721
|
+
const hasGenboxYaml = !!loadResult.config;
|
|
1362
1722
|
// Step 1: Determine location (cloud vs local)
|
|
1363
1723
|
let location = options.location;
|
|
1364
1724
|
if (!location && !options.skipPrompts) {
|
|
@@ -1383,51 +1743,227 @@ async function runCreateWizard(options = {}) {
|
|
|
1383
1743
|
}
|
|
1384
1744
|
// Step 2b: Handle local docker mode
|
|
1385
1745
|
if (location === 'local-docker') {
|
|
1386
|
-
//
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1393
|
-
return { success: false, error: 'Cancelled' };
|
|
1746
|
+
// Check for project or template mode
|
|
1747
|
+
if (hasGenboxYaml) {
|
|
1748
|
+
return await createLocalDockerFromProject(loadResult.config, options);
|
|
1749
|
+
}
|
|
1750
|
+
else {
|
|
1751
|
+
return await createLocalDockerFromTemplate(options);
|
|
1394
1752
|
}
|
|
1395
|
-
return await createLocalDockerGenbox(name, options.provider || 'claude');
|
|
1396
1753
|
}
|
|
1397
1754
|
// Step 2c: Handle local VM (multipass) mode
|
|
1398
1755
|
if (location === 'local-vm') {
|
|
1399
|
-
//
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
: `genbox-${Date.now().toString(36)}`;
|
|
1403
|
-
const name = options.name || await promptForName(defaultName);
|
|
1404
|
-
if (!name) {
|
|
1405
|
-
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1406
|
-
return { success: false, error: 'Cancelled' };
|
|
1756
|
+
// Check for project or template mode
|
|
1757
|
+
if (hasGenboxYaml) {
|
|
1758
|
+
return await createLocalVmFromProject(loadResult.config, options);
|
|
1407
1759
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
// Step 3: Cloud mode - get name
|
|
1411
|
-
let name = options.name;
|
|
1412
|
-
if (!name && !options.skipPrompts) {
|
|
1413
|
-
const defaultName = options.provider
|
|
1414
|
-
? `${options.provider}-${Date.now().toString(36)}`
|
|
1415
|
-
: `genbox-${Date.now().toString(36)}`;
|
|
1416
|
-
const inputName = await promptForName(defaultName);
|
|
1417
|
-
if (!inputName) {
|
|
1418
|
-
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1419
|
-
return { success: false, error: 'Cancelled' };
|
|
1760
|
+
else {
|
|
1761
|
+
return await createLocalVmFromTemplate(options);
|
|
1420
1762
|
}
|
|
1421
|
-
name = inputName;
|
|
1422
1763
|
}
|
|
1423
|
-
//
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1764
|
+
// Step 3: Cloud mode - determine project type
|
|
1765
|
+
if (hasGenboxYaml) {
|
|
1766
|
+
// Project mode: use profile selection (like gb create)
|
|
1767
|
+
return await createCloudGenboxFromProject(loadResult.config, configLoader, options);
|
|
1768
|
+
}
|
|
1769
|
+
else {
|
|
1770
|
+
// Template mode: use template selection (like gb new)
|
|
1771
|
+
return await createCloudGenboxFromTemplate(options);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Create a cloud genbox from a project with genbox.yaml
|
|
1776
|
+
* Uses profile selection and ProfileResolver similar to `gb create`
|
|
1777
|
+
*/
|
|
1778
|
+
async function createCloudGenboxFromProject(config, configLoader, options) {
|
|
1779
|
+
console.log('');
|
|
1780
|
+
console.log(chalk_1.default.blue('Creating genbox from project...'));
|
|
1781
|
+
console.log(chalk_1.default.dim(`Project: ${config.project?.name || 'unknown'}`));
|
|
1782
|
+
console.log('');
|
|
1783
|
+
// Profile selection if available
|
|
1784
|
+
let selectedProfile;
|
|
1785
|
+
const profiles = config.profiles || {};
|
|
1786
|
+
if (Object.keys(profiles).length > 0 && !options.skipPrompts) {
|
|
1787
|
+
const profileNames = Object.keys(profiles);
|
|
1788
|
+
const choices = profileNames.map(name => {
|
|
1789
|
+
const profile = profiles[name];
|
|
1790
|
+
const apps = profile.apps ? profile.apps.join(', ') : 'default apps';
|
|
1791
|
+
const description = profile.description || `Apps: ${apps}`;
|
|
1792
|
+
return {
|
|
1793
|
+
name: `${name} - ${chalk_1.default.dim(description)}`,
|
|
1794
|
+
value: name,
|
|
1795
|
+
};
|
|
1796
|
+
});
|
|
1797
|
+
choices.push({
|
|
1798
|
+
name: `${chalk_1.default.cyan('Custom')} - ${chalk_1.default.dim('Use default configuration')}`,
|
|
1799
|
+
value: '__custom__',
|
|
1800
|
+
});
|
|
1801
|
+
selectedProfile = await (0, select_1.default)({
|
|
1802
|
+
message: 'Select a profile:',
|
|
1803
|
+
choices,
|
|
1804
|
+
});
|
|
1805
|
+
if (selectedProfile === '__custom__') {
|
|
1806
|
+
selectedProfile = undefined;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
// Get name
|
|
1810
|
+
const name = options.name || await promptForName();
|
|
1811
|
+
if (!name) {
|
|
1812
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1813
|
+
return { success: false, error: 'Cancelled' };
|
|
1814
|
+
}
|
|
1815
|
+
// Use ProfileResolver to resolve full configuration (including repos)
|
|
1816
|
+
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
1817
|
+
const createOptions = {
|
|
1818
|
+
name,
|
|
1819
|
+
profile: selectedProfile,
|
|
1820
|
+
yes: options.skipPrompts,
|
|
1821
|
+
};
|
|
1822
|
+
const resolved = await profileResolver.resolve(config, createOptions);
|
|
1823
|
+
// Display resolved configuration
|
|
1824
|
+
console.log('');
|
|
1825
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1826
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1827
|
+
console.log(` ${chalk_1.default.bold('Project:')} ${resolved.project.name}`);
|
|
1828
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${resolved.size}`);
|
|
1829
|
+
if (selectedProfile) {
|
|
1830
|
+
console.log(` ${chalk_1.default.bold('Profile:')} ${selectedProfile}`);
|
|
1831
|
+
}
|
|
1832
|
+
if (resolved.apps.length > 0) {
|
|
1833
|
+
console.log(` ${chalk_1.default.bold('Apps:')} ${resolved.apps.map(a => a.name).join(', ')}`);
|
|
1834
|
+
}
|
|
1835
|
+
if (resolved.repos.length > 0) {
|
|
1836
|
+
console.log(` ${chalk_1.default.bold('Repos:')} ${resolved.repos.map(r => r.name).join(', ')}`);
|
|
1837
|
+
}
|
|
1838
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1839
|
+
console.log('');
|
|
1840
|
+
// Build repos payload
|
|
1841
|
+
const repos = {};
|
|
1842
|
+
for (const repo of resolved.repos) {
|
|
1843
|
+
repos[repo.name] = {
|
|
1844
|
+
url: repo.url,
|
|
1845
|
+
path: repo.path,
|
|
1846
|
+
branch: repo.branch || config.defaults?.branch || 'main',
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
// Get git config for commits
|
|
1850
|
+
const gitConfig = (0, utils_1.getGitConfig)();
|
|
1851
|
+
// Load env vars for GIT_TOKEN
|
|
1852
|
+
const envVars = configLoader.loadEnvVars(process.cwd());
|
|
1853
|
+
// Build full payload
|
|
1854
|
+
const payload = {
|
|
1855
|
+
profile: selectedProfile,
|
|
1856
|
+
workspace: resolved.project.name,
|
|
1857
|
+
size: resolved.size,
|
|
1858
|
+
repos,
|
|
1859
|
+
apps: resolved.apps.map(a => a.name),
|
|
1860
|
+
appConfigs: resolved.apps.map(a => ({
|
|
1861
|
+
name: a.name,
|
|
1862
|
+
path: a.path.startsWith('/') ? a.path : `${resolved.repos[0]?.path || '/home/dev'}/${a.path}`,
|
|
1863
|
+
type: a.type,
|
|
1864
|
+
port: a.port,
|
|
1865
|
+
framework: a.framework,
|
|
1866
|
+
runner: a.runner,
|
|
1867
|
+
docker: a.docker,
|
|
1868
|
+
healthcheck: a.healthcheck,
|
|
1869
|
+
dependsOn: a.dependsOn,
|
|
1870
|
+
commands: a.commands,
|
|
1871
|
+
})),
|
|
1872
|
+
infrastructure: resolved.infrastructure.map(i => ({
|
|
1873
|
+
name: i.name,
|
|
1874
|
+
type: i.type,
|
|
1875
|
+
mode: i.mode,
|
|
1876
|
+
})),
|
|
1877
|
+
database: resolved.database,
|
|
1878
|
+
gitToken: envVars.GIT_TOKEN,
|
|
1879
|
+
gitUserName: gitConfig.userName,
|
|
1880
|
+
gitUserEmail: gitConfig.userEmail,
|
|
1881
|
+
};
|
|
1882
|
+
// Add provider-specific CLI installation
|
|
1883
|
+
if (options.provider === 'claude') {
|
|
1884
|
+
payload.installClaudeCode = true;
|
|
1885
|
+
}
|
|
1886
|
+
else if (options.provider === 'gemini') {
|
|
1887
|
+
payload.installGeminiCli = true;
|
|
1888
|
+
}
|
|
1889
|
+
return await createCloudGenbox(name, {
|
|
1890
|
+
detach: options.detach,
|
|
1891
|
+
payload: { ...options.payload, ...payload },
|
|
1892
|
+
provider: options.provider,
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Create a cloud genbox from a starter template
|
|
1897
|
+
* Uses template selection similar to `gb new`
|
|
1898
|
+
*/
|
|
1899
|
+
async function createCloudGenboxFromTemplate(options) {
|
|
1900
|
+
console.log('');
|
|
1901
|
+
console.log(chalk_1.default.blue('No genbox.yaml found - creating from template...'));
|
|
1902
|
+
console.log('');
|
|
1903
|
+
// Fetch available templates
|
|
1904
|
+
let templates = [];
|
|
1905
|
+
try {
|
|
1906
|
+
templates = await (0, api_1.fetchApi)('/genboxes/starter-templates');
|
|
1907
|
+
}
|
|
1908
|
+
catch (error) {
|
|
1909
|
+
console.log(chalk_1.default.red('Failed to fetch templates:', error.message));
|
|
1910
|
+
return { success: false, error: 'Failed to fetch templates' };
|
|
1911
|
+
}
|
|
1912
|
+
if (templates.length === 0) {
|
|
1913
|
+
console.log(chalk_1.default.red('No templates available.'));
|
|
1914
|
+
return { success: false, error: 'No templates available' };
|
|
1915
|
+
}
|
|
1916
|
+
// Template selection
|
|
1917
|
+
let template;
|
|
1918
|
+
if (!options.skipPrompts) {
|
|
1919
|
+
const templateChoices = templates.map(t => ({
|
|
1920
|
+
name: `${t.icon} ${t.displayName} - ${chalk_1.default.dim(t.description)}`,
|
|
1921
|
+
value: t.name,
|
|
1922
|
+
}));
|
|
1923
|
+
const selectedTemplate = await (0, select_1.default)({
|
|
1924
|
+
message: 'Select a template:',
|
|
1925
|
+
choices: templateChoices,
|
|
1926
|
+
pageSize: 15,
|
|
1927
|
+
});
|
|
1928
|
+
template = templates.find(t => t.name === selectedTemplate);
|
|
1929
|
+
}
|
|
1930
|
+
else {
|
|
1931
|
+
// Default to nextjs in non-interactive mode
|
|
1932
|
+
template = templates.find(t => t.name === 'nextjs') || templates[0];
|
|
1933
|
+
console.log(chalk_1.default.dim(`Using template: ${template?.displayName}`));
|
|
1934
|
+
}
|
|
1935
|
+
if (!template) {
|
|
1936
|
+
console.log(chalk_1.default.red('No template selected.'));
|
|
1937
|
+
return { success: false, error: 'No template selected' };
|
|
1938
|
+
}
|
|
1939
|
+
// Get name
|
|
1940
|
+
const defaultName = `my-${template.name}-app`;
|
|
1941
|
+
const name = options.name || await promptForName(defaultName);
|
|
1942
|
+
if (!name) {
|
|
1943
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
1944
|
+
return { success: false, error: 'Cancelled' };
|
|
1945
|
+
}
|
|
1946
|
+
// Build payload with template
|
|
1947
|
+
const payload = {
|
|
1948
|
+
template: template.name,
|
|
1949
|
+
};
|
|
1950
|
+
// Add provider-specific CLI installation
|
|
1951
|
+
if (options.provider === 'claude') {
|
|
1952
|
+
payload.installClaudeCode = true;
|
|
1953
|
+
}
|
|
1954
|
+
else if (options.provider === 'gemini') {
|
|
1955
|
+
payload.installGeminiCli = true;
|
|
1956
|
+
}
|
|
1957
|
+
console.log('');
|
|
1958
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1959
|
+
console.log(` ${chalk_1.default.bold('Template:')} ${template.icon} ${template.displayName}`);
|
|
1960
|
+
console.log(` ${chalk_1.default.bold('Name:')} ${name}`);
|
|
1961
|
+
console.log(` ${chalk_1.default.bold('Features:')} ${template.features.join(', ')}`);
|
|
1962
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
1963
|
+
console.log('');
|
|
1428
1964
|
return await createCloudGenbox(name, {
|
|
1429
1965
|
detach: options.detach,
|
|
1430
|
-
payload: options.payload,
|
|
1966
|
+
payload: { ...options.payload, ...payload },
|
|
1431
1967
|
provider: options.provider,
|
|
1432
1968
|
});
|
|
1433
1969
|
}
|