gitarsenal-cli 1.9.95 → 1.9.97

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.
Files changed (37) hide show
  1. package/.venv_status.json +1 -1
  2. package/README.md +29 -0
  3. package/bin/gitarsenal.js +220 -127
  4. package/kill_claude/requirements.txt +1 -1
  5. package/lib/dependencies.js +130 -4
  6. package/lib/e2b-sandbox.js +158 -0
  7. package/lib/execAsync.js +12 -0
  8. package/lib/sandbox.js +97 -113
  9. package/package.json +2 -1
  10. package/python/__pycache__/credentials_manager.cpython-312.pyc +0 -0
  11. package/python/__pycache__/e2b_sandbox_agent.cpython-313.pyc +0 -0
  12. package/python/__pycache__/fetch_modal_tokens.cpython-312.pyc +0 -0
  13. package/python/credentials_manager.py +2 -1
  14. package/python/e2b_sandbox_agent.py +787 -0
  15. package/python/fetch_modal_tokens.py +47 -25
  16. package/python/requirements.txt +2 -1
  17. package/python/test_enhanced_sandbox_script.py +1429 -0
  18. package/python/test_modalSandboxScript.py +42 -6
  19. package/scripts/setup_e2b.js +162 -0
  20. package/kill_claude/.claude/settings.local.json +0 -9
  21. package/kill_claude/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
  22. package/kill_claude/__pycache__/bash_tool.cpython-313.pyc +0 -0
  23. package/kill_claude/__pycache__/claude_code_agent.cpython-313.pyc +0 -0
  24. package/kill_claude/__pycache__/edit_tool.cpython-313.pyc +0 -0
  25. package/kill_claude/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
  26. package/kill_claude/__pycache__/glob_tool.cpython-313.pyc +0 -0
  27. package/kill_claude/__pycache__/grep_tool.cpython-313.pyc +0 -0
  28. package/kill_claude/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
  29. package/kill_claude/__pycache__/ls_tool.cpython-313.pyc +0 -0
  30. package/kill_claude/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
  31. package/kill_claude/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
  32. package/kill_claude/__pycache__/read_tool.cpython-313.pyc +0 -0
  33. package/kill_claude/__pycache__/task_tool.cpython-313.pyc +0 -0
  34. package/kill_claude/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
  35. package/kill_claude/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
  36. package/kill_claude/__pycache__/web_search_tool.cpython-313.pyc +0 -0
  37. package/kill_claude/__pycache__/write_tool.cpython-313.pyc +0 -0
package/.venv_status.json CHANGED
@@ -1 +1 @@
1
- {"created":"2025-09-03T03:52:07.558Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
1
+ {"created":"2025-09-13T16:50:38.652Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
package/README.md CHANGED
@@ -25,6 +25,9 @@ gitarsenal --gpu A10G --repo https://github.com/username/awesome-project.git
25
25
 
26
26
  # With custom setup commands
27
27
  gitarsenal --gpu A100 --repo https://github.com/username/awesome-project.git --setup-commands "pip install -r requirements.txt" "python setup.py install"
28
+
29
+ # Using E2B sandbox provider (faster startup, no GPU)
30
+ gitarsenal --sandbox-provider e2b --repo https://github.com/username/awesome-project.git
28
31
  ```
29
32
 
30
33
  ### Examples
@@ -65,6 +68,32 @@ gitarsenal keys delete --service openai
65
68
 
66
69
  ## Features
67
70
 
71
+ ### Sandbox Providers
72
+
73
+ GitArsenal supports multiple sandbox providers to fit your needs:
74
+
75
+ #### Modal Sandbox (Default)
76
+ - Full GPU support (A10G, A100, H100, etc.)
77
+ - Persistent storage with volumes
78
+ - SSH access
79
+ - Longer startup time
80
+
81
+ ```bash
82
+ # Explicitly use Modal sandbox
83
+ gitarsenal --sandbox-provider modal --repo https://github.com/username/project.git
84
+ ```
85
+
86
+ #### E2B Sandbox
87
+ - Faster startup time
88
+ - Lightweight environment
89
+ - No GPU support
90
+ - Ideal for quick testing and development
91
+
92
+ ```bash
93
+ # Use E2B sandbox for faster startup
94
+ gitarsenal --sandbox-provider e2b --repo https://github.com/username/project.git
95
+ ```
96
+
68
97
  ### Automatic Environment Setup
69
98
  The CLI automatically:
70
99
  - Clones your repository
package/bin/gitarsenal.js CHANGED
@@ -6,7 +6,7 @@ const inquirer = require('inquirer');
6
6
  const ora = require('ora');
7
7
  const path = require('path');
8
8
  const { version } = require('../package.json');
9
- const { checkDependencies } = require('../lib/dependencies');
9
+ const { checkDependencies, checkDependenciesExceptE2B } = require('../lib/dependencies');
10
10
  const { runContainer } = require('../lib/sandbox');
11
11
  const updateNotifier = require('update-notifier');
12
12
  const pkg = require('../package.json');
@@ -16,6 +16,7 @@ const fs = require('fs');
16
16
  const https = require('https');
17
17
  const http = require('http');
18
18
  const { fetchGitIngestData } = require('../gitingest-integration');
19
+ const { execAsync } = require('../lib/execAsync');
19
20
 
20
21
  // Function to activate virtual environment
21
22
  function activateVirtualEnvironment() {
@@ -1042,7 +1043,27 @@ program
1042
1043
  .version(version)
1043
1044
  .description('GitArsenal CLI - Create GPU-accelerated development environments');
1044
1045
 
1045
-
1046
+ // Add E2B setup command
1047
+ program
1048
+ .command('setup-e2b')
1049
+ .description('Set up E2B sandbox provider')
1050
+ .action(async () => {
1051
+ const setupPath = path.join(__dirname, '..', 'scripts', 'setup_e2b.js');
1052
+ if (fs.existsSync(setupPath)) {
1053
+ const child = spawn('node', [setupPath], {
1054
+ stdio: 'inherit'
1055
+ });
1056
+ child.on('exit', (code) => {
1057
+ if (code !== 0) {
1058
+ console.log(chalk.red('E2B setup failed.'));
1059
+ process.exit(code);
1060
+ }
1061
+ });
1062
+ } else {
1063
+ console.log(chalk.red(`Setup script not found at ${setupPath}`));
1064
+ process.exit(1);
1065
+ }
1066
+ });
1046
1067
 
1047
1068
  // Keys management command
1048
1069
  const keysCmd = program
@@ -1096,6 +1117,7 @@ program
1096
1117
  .option('--show-examples', 'Show usage examples')
1097
1118
  .option('--user-id <id>', 'User ID for tracking')
1098
1119
  .option('--user-name <name>', 'User name for tracking')
1120
+ .option('--sandbox-provider <provider>', 'Sandbox provider (modal or e2b)', 'modal')
1099
1121
  .action(async (options) => {
1100
1122
  // If options are provided directly, run the container command
1101
1123
  if (options.repo || options.showExamples || process.argv.length <= 3) {
@@ -1125,15 +1147,37 @@ async function runContainerCommand(options) {
1125
1147
  // Send user data immediately so the dashboard records users
1126
1148
  await sendUserData(userId, userName, userEmail);
1127
1149
 
1128
- // Check for required dependencies
1129
- const spinner = ora('Checking dependencies...').start();
1130
- const dependenciesOk = await checkDependencies();
1131
-
1132
- if (!dependenciesOk) {
1133
- spinner.fail('Missing dependencies. Please install them and try again.');
1150
+ // Initialize sandbox provider from options or default to modal
1151
+ let sandboxProvider = options.sandboxProvider || 'modal';
1152
+
1153
+ // Prompt for sandbox provider if not specified
1154
+ if (!options.sandboxProvider) {
1155
+ const providerAnswers = await inquirer.prompt([
1156
+ {
1157
+ type: 'list',
1158
+ name: 'sandboxProvider',
1159
+ message: 'Select sandbox provider:',
1160
+ choices: [
1161
+ { name: 'Modal (GPU support, persistent volumes)', value: 'modal' },
1162
+ { name: 'E2B (Faster startup, no GPU)', value: 'e2b' }
1163
+ ],
1164
+ default: 'modal'
1165
+ }
1166
+ ]);
1167
+ sandboxProvider = providerAnswers.sandboxProvider;
1168
+ }
1169
+
1170
+ // Check dependencies based on selected sandbox provider
1171
+ console.log(chalk.blue('⠋ Checking dependencies...'));
1172
+ const dependenciesMet = sandboxProvider === 'e2b'
1173
+ ? await checkDependenciesExceptE2B()
1174
+ : await checkDependencies();
1175
+
1176
+ if (!dependenciesMet) {
1177
+ console.log(chalk.red('✖ Missing dependencies. Please install them and try again.'));
1134
1178
  process.exit(1);
1135
1179
  }
1136
- spinner.succeed('Dependencies checked');
1180
+ console.log(chalk.green('Dependencies checked'));
1137
1181
 
1138
1182
  // If repo URL not provided, prompt for it
1139
1183
  let repoUrl = options.repoUrl || options.repo;
@@ -1142,6 +1186,14 @@ async function runContainerCommand(options) {
1142
1186
  let volumeName = options.volumeName || options.volume;
1143
1187
  let skipConfirmation = options.yes;
1144
1188
  let setupCommands = options.setupCommands || [];
1189
+ let analysisData = null;
1190
+ let collectedApiKeys = {};
1191
+
1192
+ // Validate sandbox provider
1193
+ if (sandboxProvider !== 'modal' && sandboxProvider !== 'e2b') {
1194
+ console.log(chalk.yellow(`⚠️ Invalid sandbox provider: ${sandboxProvider}. Using 'modal' as default.`));
1195
+ sandboxProvider = 'modal';
1196
+ }
1145
1197
 
1146
1198
  if (!repoUrl) {
1147
1199
  const answers = await inquirer.prompt([
@@ -1155,135 +1207,149 @@ async function runContainerCommand(options) {
1155
1207
  repoUrl = answers.repoUrl;
1156
1208
  }
1157
1209
 
1158
- // Analyze repository for GPU recommendations and API key requirements
1159
- let analysisData = null;
1160
- let collectedApiKeys = {};
1161
-
1162
- if (repoUrl) {
1163
- // Load stored API keys first
1164
- console.log(chalk.blue('🔍 Loading stored API keys...'));
1165
- const storedKeys = await loadStoredApiKeys();
1166
-
1167
- // Start a main spinner that will show overall progress
1168
- const mainSpinner = ora('Analyzing repository...').start();
1169
-
1170
- try {
1171
- // Start preview immediately so we get early feedback; suppress summary here to avoid duplicates.
1172
- // Provide an AbortController so we can stop the preview spinner as soon as full fetch returns.
1173
- const previewAbort = new AbortController();
1174
- mainSpinner.text = 'Analyzing repository for GPU/Torch/CUDA recommendations...';
1175
- const previewPromise = previewRecommendations(repoUrl, { showSummary: false, abortSignal: previewAbort.signal, hideSpinner: true }).catch(() => null);
1176
-
1177
- // Run full fetch in parallel with stored credentials; prefer its results if available.
1178
- mainSpinner.text = 'Finding the best machine for your code and detecting API requirements...';
1179
- const fullData = await fetchFullSetupAndRecs(repoUrl, storedKeys).catch(() => null);
1210
+ // Always prompt for sandbox provider, even if specified via command line
1211
+ const providerAnswers = await inquirer.prompt([
1212
+ {
1213
+ type: 'list',
1214
+ name: 'sandboxProvider',
1215
+ message: 'Select sandbox provider:',
1216
+ choices: [
1217
+ { name: 'Modal (GPU support, persistent volumes)', value: 'modal' },
1218
+ { name: 'E2B (Faster startup, no GPU)', value: 'e2b' }
1219
+ ],
1220
+ default: sandboxProvider // Use command line value as default if provided
1221
+ }
1222
+ ]);
1223
+ sandboxProvider = providerAnswers.sandboxProvider;
1224
+
1225
+ // Load stored API keys for both providers
1226
+ console.log(chalk.blue('🔍 Loading stored API keys...'));
1227
+ const storedKeys = await loadStoredApiKeys();
1228
+ collectedApiKeys = storedKeys;
1229
+
1230
+ // Only perform repository analysis and GPU selection for Modal provider
1231
+ if (sandboxProvider === 'modal') {
1232
+ // Analyze repository for GPU recommendations and API key requirements
1233
+ if (repoUrl) {
1234
+ // Start a main spinner that will show overall progress
1235
+ const mainSpinner = ora('Analyzing repository...').start();
1180
1236
 
1181
- if (fullData) {
1182
- // Stop preview spinner immediately since we have a response
1183
- previewAbort.abort();
1184
- mainSpinner.succeed('Analysis complete!');
1185
- printGpuTorchCudaSummary(fullData);
1186
- analysisData = fullData;
1237
+ try {
1238
+ // Start preview immediately so we get early feedback; suppress summary here to avoid duplicates.
1239
+ // Provide an AbortController so we can stop the preview spinner as soon as full fetch returns.
1240
+ const previewAbort = new AbortController();
1241
+ mainSpinner.text = 'Analyzing repository for GPU/Torch/CUDA recommendations...';
1242
+ const previewPromise = previewRecommendations(repoUrl, { showSummary: false, abortSignal: previewAbort.signal, hideSpinner: true }).catch(() => null);
1243
+
1244
+ // Run full fetch in parallel with stored credentials; prefer its results if available.
1245
+ mainSpinner.text = 'Finding the best machine for your code and detecting API requirements...';
1246
+ const fullData = await fetchFullSetupAndRecs(repoUrl, storedKeys).catch(() => null);
1187
1247
 
1188
- // Handle API key requirements
1189
- if (fullData.requiredApiKeys && Array.isArray(fullData.requiredApiKeys) && fullData.requiredApiKeys.length > 0) {
1190
- const missingKeys = await promptForMissingApiKeys(fullData.requiredApiKeys, storedKeys);
1191
- collectedApiKeys = { ...storedKeys, ...missingKeys };
1192
- } else {
1193
- collectedApiKeys = storedKeys;
1194
- }
1195
- } else {
1196
- // Full fetch failed, wait for preview and show its results
1197
- mainSpinner.text = 'Waiting for preview analysis to complete...';
1198
- const previewData = await previewPromise;
1199
- if (previewData) {
1200
- mainSpinner.succeed('Preview analysis complete!');
1201
- printGpuTorchCudaSummary(previewData);
1202
- analysisData = previewData;
1248
+ if (fullData) {
1249
+ // Stop preview spinner immediately since we have a response
1250
+ previewAbort.abort();
1251
+ mainSpinner.succeed('Analysis complete!');
1252
+ printGpuTorchCudaSummary(fullData);
1253
+ analysisData = fullData;
1254
+
1255
+ // Handle API key requirements
1256
+ if (fullData.requiredApiKeys && Array.isArray(fullData.requiredApiKeys) && fullData.requiredApiKeys.length > 0) {
1257
+ const missingKeys = await promptForMissingApiKeys(fullData.requiredApiKeys, storedKeys);
1258
+ collectedApiKeys = { ...storedKeys, ...missingKeys };
1259
+ } else {
1260
+ collectedApiKeys = storedKeys;
1261
+ }
1203
1262
  } else {
1204
- mainSpinner.fail('Analysis failed - both preview and full analysis timed out or failed');
1205
- console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
1206
- console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
1263
+ // Full fetch failed, wait for preview and show its results
1264
+ mainSpinner.text = 'Waiting for preview analysis to complete...';
1265
+ const previewData = await previewPromise;
1266
+ if (previewData) {
1267
+ mainSpinner.succeed('Preview analysis complete!');
1268
+ printGpuTorchCudaSummary(previewData);
1269
+ analysisData = previewData;
1270
+ } else {
1271
+ mainSpinner.fail('Analysis failed - both preview and full analysis timed out or failed');
1272
+ console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
1273
+ console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
1274
+ }
1207
1275
  }
1208
- collectedApiKeys = storedKeys;
1276
+ } catch (error) {
1277
+ mainSpinner.fail(`Analysis failed: ${error.message}`);
1278
+ console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
1279
+ console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
1209
1280
  }
1210
- } catch (error) {
1211
- mainSpinner.fail(`Analysis failed: ${error.message}`);
1212
- console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
1213
- console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
1214
- collectedApiKeys = await loadStoredApiKeys();
1215
1281
  }
1216
- }
1217
1282
 
1218
- // Prompt for GPU type if not specified
1219
- if (!gpuType) {
1220
- const gpuAnswers = await inquirer.prompt([
1221
- {
1222
- type: 'list',
1223
- name: 'gpuType',
1224
- message: 'Select GPU type:',
1225
- choices: [
1226
- { name: 'T4 (16GB VRAM)', value: 'T4' },
1227
- { name: 'L4 (24GB VRAM)', value: 'L4' },
1228
- { name: 'A10G (24GB VRAM)', value: 'A10G' },
1229
- { name: 'A100-40 (40GB VRAM)', value: 'A100-40GB' },
1230
- { name: 'A100-80 (80GB VRAM)', value: 'A100-80GB' },
1231
- { name: 'L40S (48GB VRAM)', value: 'L40S' },
1232
- { name: 'H100 (80GB VRAM)', value: 'H100' },
1233
- { name: 'H200 (141GB VRAM)', value: 'H200' },
1234
- { name: 'B200 (141GB VRAM)', value: 'B200' }
1235
- ],
1236
- default: 'A10G'
1237
- }
1238
- ]);
1239
- gpuType = gpuAnswers.gpuType;
1240
- }
1283
+ // Prompt for GPU type if not specified
1284
+ if (!gpuType) {
1285
+ const gpuAnswers = await inquirer.prompt([
1286
+ {
1287
+ type: 'list',
1288
+ name: 'gpuType',
1289
+ message: 'Select GPU type:',
1290
+ choices: [
1291
+ { name: 'T4 (16GB VRAM)', value: 'T4' },
1292
+ { name: 'L4 (24GB VRAM)', value: 'L4' },
1293
+ { name: 'A10G (24GB VRAM)', value: 'A10G' },
1294
+ { name: 'A100-40 (40GB VRAM)', value: 'A100-40GB' },
1295
+ { name: 'A100-80 (80GB VRAM)', value: 'A100-80GB' },
1296
+ { name: 'L40S (48GB VRAM)', value: 'L40S' },
1297
+ { name: 'H100 (80GB VRAM)', value: 'H100' },
1298
+ { name: 'H200 (141GB VRAM)', value: 'H200' },
1299
+ { name: 'B200 (141GB VRAM)', value: 'B200' }
1300
+ ],
1301
+ default: 'A10G'
1302
+ }
1303
+ ]);
1304
+ gpuType = gpuAnswers.gpuType;
1305
+ }
1241
1306
 
1242
- // Prompt for GPU count if not already specified via CLI
1243
- if (!options.gpuCount) {
1244
- const gpuCountAnswers = await inquirer.prompt([
1245
- {
1246
- type: 'list',
1247
- name: 'gpuCount',
1248
- message: 'How many GPUs do you need?',
1249
- choices: [
1250
- { name: '1 GPU', value: 1 },
1251
- { name: '2 GPUs', value: 2 },
1252
- { name: '3 GPUs', value: 3 },
1253
- { name: '4 GPUs', value: 4 },
1254
- { name: '6 GPUs', value: 6 },
1255
- { name: '8 GPUs', value: 8 }
1256
- ],
1257
- default: gpuCount
1258
- }
1259
- ]);
1260
- gpuCount = gpuCountAnswers.gpuCount;
1261
- }
1307
+ // Prompt for GPU count if not already specified via CLI
1308
+ if (!options.gpuCount) {
1309
+ const gpuCountAnswers = await inquirer.prompt([
1310
+ {
1311
+ type: 'list',
1312
+ name: 'gpuCount',
1313
+ message: 'How many GPUs do you need?',
1314
+ choices: [
1315
+ { name: '1 GPU', value: 1 },
1316
+ { name: '2 GPUs', value: 2 },
1317
+ { name: '3 GPUs', value: 3 },
1318
+ { name: '4 GPUs', value: 4 },
1319
+ { name: '6 GPUs', value: 6 },
1320
+ { name: '8 GPUs', value: 8 }
1321
+ ],
1322
+ default: gpuCount
1323
+ }
1324
+ ]);
1325
+ gpuCount = gpuCountAnswers.gpuCount;
1326
+ }
1262
1327
 
1263
- // Prompt for persistent volume
1264
- if (!volumeName && !skipConfirmation) {
1265
- const volumeAnswers = await inquirer.prompt([
1266
- {
1267
- type: 'confirm',
1268
- name: 'useVolume',
1269
- message: 'Use persistent volume for faster installs?',
1270
- default: true
1271
- },
1272
- {
1273
- type: 'input',
1274
- name: 'volumeName',
1275
- message: 'Enter volume name:',
1276
- default: getDefaultVolumeName(repoUrl),
1277
- when: (answers) => answers.useVolume
1328
+ // Prompt for persistent volume
1329
+ if (!volumeName && !skipConfirmation) {
1330
+ const volumeAnswers = await inquirer.prompt([
1331
+ {
1332
+ type: 'confirm',
1333
+ name: 'useVolume',
1334
+ message: 'Use persistent volume for faster installs?',
1335
+ default: true
1336
+ },
1337
+ {
1338
+ type: 'input',
1339
+ name: 'volumeName',
1340
+ message: 'Enter volume name:',
1341
+ default: getDefaultVolumeName(repoUrl),
1342
+ when: (answers) => answers.useVolume
1343
+ }
1344
+ ]);
1345
+
1346
+ if (volumeAnswers.useVolume) {
1347
+ volumeName = volumeAnswers.volumeName || getDefaultVolumeName(repoUrl);
1278
1348
  }
1279
- ]);
1280
-
1281
- if (volumeAnswers.useVolume) {
1349
+ } else if (!volumeName && skipConfirmation) {
1350
+ // If --yes flag is used and no volume specified, use default
1282
1351
  volumeName = getDefaultVolumeName(repoUrl);
1283
1352
  }
1284
- } else if (!volumeName && skipConfirmation) {
1285
- // If --yes flag is used and no volume specified, use default
1286
- volumeName = getDefaultVolumeName(repoUrl);
1287
1353
  }
1288
1354
 
1289
1355
  // Prompt for custom setup commands only if no repo URL provided and no commands specified
@@ -1317,6 +1383,32 @@ async function runContainerCommand(options) {
1317
1383
 
1318
1384
  // Confirm settings (configuration will be shown by Python script after GPU selection)
1319
1385
  if (!skipConfirmation) {
1386
+ // Display configuration summary
1387
+ console.log(chalk.cyan('\n📋 Configuration Summary:'));
1388
+ console.log(chalk.dim('─────────────────────────────────────'));
1389
+ console.log(`${chalk.dim('Repository URL:')} ${chalk.white(repoUrl || 'Not specified')}`);
1390
+ console.log(`${chalk.dim('Sandbox Provider:')} ${chalk.white(sandboxProvider)}`);
1391
+
1392
+ if (sandboxProvider === 'modal') {
1393
+ if (gpuCount > 1) {
1394
+ console.log(`${chalk.dim('GPU Type:')} ${chalk.white(`${gpuCount}x ${gpuType}`)}`);
1395
+ } else {
1396
+ console.log(`${chalk.dim('GPU Type:')} ${chalk.white(gpuType)}`);
1397
+ }
1398
+ console.log(`${chalk.dim('Volume:')} ${chalk.white(volumeName || 'None')}`);
1399
+ } else {
1400
+ console.log(`${chalk.dim('Note:')} ${chalk.yellow('E2B sandboxes do not support GPU selection or volumes')}`);
1401
+ }
1402
+
1403
+ if (setupCommands.length > 0) {
1404
+ console.log(`${chalk.dim('Setup Commands:')} ${chalk.white(setupCommands.length)} custom commands`);
1405
+ } else if (repoUrl) {
1406
+ console.log(`${chalk.dim('Setup:')} ${chalk.white('Intelligent repository setup')}`);
1407
+ } else {
1408
+ console.log(`${chalk.dim('Setup Commands:')} ${chalk.white('None')}`);
1409
+ }
1410
+ console.log(chalk.dim('─────────────────────────────────────'));
1411
+
1320
1412
  const confirmAnswers = await inquirer.prompt([
1321
1413
  {
1322
1414
  type: 'confirm',
@@ -1348,7 +1440,8 @@ async function runContainerCommand(options) {
1348
1440
  userName,
1349
1441
  userEmail,
1350
1442
  apiKeys: collectedApiKeys,
1351
- analysisData
1443
+ analysisData,
1444
+ sandboxProvider
1352
1445
  });
1353
1446
 
1354
1447
  } catch (containerError) {
@@ -1,3 +1,3 @@
1
1
  anthropic>=0.25.0
2
2
  typer>=0.12.0
3
- rich>=13.0.0
3
+ rich>=13.0.0
@@ -2,9 +2,43 @@ const { promisify } = require('util');
2
2
  const { exec } = require('child_process');
3
3
  const which = require('which');
4
4
  const chalk = require('chalk');
5
+ const path = require('path');
6
+ const fs = require('fs');
5
7
 
6
8
  const execAsync = promisify(exec);
7
9
 
10
+ /**
11
+ * Get the Python executable path from the virtual environment
12
+ * @returns {string} - Path to Python executable or 'python3'
13
+ */
14
+ function getPythonExecutable() {
15
+ // Check if PYTHON_EXECUTABLE is set by the virtual environment activation
16
+ if (process.env.PYTHON_EXECUTABLE && fs.existsSync(process.env.PYTHON_EXECUTABLE)) {
17
+ return process.env.PYTHON_EXECUTABLE;
18
+ }
19
+
20
+ // Try to find the virtual environment Python
21
+ const venvPath = path.join(__dirname, '..', '.venv');
22
+ const isWindows = process.platform === 'win32';
23
+
24
+ // Check for uv-style virtual environment first
25
+ const uvPythonPath = path.join(venvPath, 'bin', 'python');
26
+
27
+ // Check for traditional venv structure
28
+ const traditionalPythonPath = isWindows ?
29
+ path.join(venvPath, 'Scripts', 'python.exe') :
30
+ path.join(venvPath, 'bin', 'python');
31
+
32
+ if (fs.existsSync(uvPythonPath)) {
33
+ return uvPythonPath;
34
+ } else if (fs.existsSync(traditionalPythonPath)) {
35
+ return traditionalPythonPath;
36
+ }
37
+
38
+ // Fall back to system Python
39
+ return isWindows ? 'python' : 'python3';
40
+ }
41
+
8
42
  /**
9
43
  * Check if a command is available in the system
10
44
  * @param {string} command - Command to check
@@ -24,8 +58,10 @@ async function commandExists(command) {
24
58
  * @returns {Promise<{exists: boolean, version: string|null}>}
25
59
  */
26
60
  async function checkPython() {
61
+ const pythonExecutable = getPythonExecutable();
62
+
27
63
  try {
28
- const { stdout } = await execAsync('python --version');
64
+ const { stdout } = await execAsync(`${pythonExecutable} --version`);
29
65
  const version = stdout.trim().split(' ')[1];
30
66
  return { exists: true, version };
31
67
  } catch (error) {
@@ -44,9 +80,34 @@ async function checkPython() {
44
80
  * @returns {Promise<boolean>}
45
81
  */
46
82
  async function checkModal() {
83
+ const pythonExecutable = getPythonExecutable();
84
+
47
85
  try {
48
- await execAsync('modal --version');
86
+ // Check if modal is importable in the Python environment
87
+ await execAsync(`${pythonExecutable} -c "import modal"`);
49
88
  return true;
89
+ } catch (error) {
90
+ try {
91
+ // Fall back to checking the modal command
92
+ await execAsync('modal --version');
93
+ return true;
94
+ } catch (error) {
95
+ return false;
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Check if E2B code interpreter is installed
102
+ * @returns {Promise<boolean>}
103
+ */
104
+ async function checkE2B() {
105
+ const pythonExecutable = getPythonExecutable();
106
+
107
+ try {
108
+ // Use a more reliable method to check if the module is importable
109
+ const { stdout } = await execAsync(`${pythonExecutable} -c "import importlib.util; print(importlib.util.find_spec('e2b_code_interpreter') is not None)"`);
110
+ return stdout.trim() === 'True';
50
111
  } catch (error) {
51
112
  return false;
52
113
  }
@@ -66,11 +127,20 @@ async function checkGit() {
66
127
  }
67
128
 
68
129
  async function checkGitingest() {
130
+ const pythonExecutable = getPythonExecutable();
131
+
69
132
  try {
70
- await execAsync('gitingest --help');
133
+ // Check if gitingest is importable in the Python environment
134
+ await execAsync(`${pythonExecutable} -c "import gitingest"`);
71
135
  return true;
72
136
  } catch (error) {
73
- return false;
137
+ try {
138
+ // Fall back to checking the gitingest command
139
+ await execAsync('gitingest --help');
140
+ return true;
141
+ } catch (error) {
142
+ return false;
143
+ }
74
144
  }
75
145
  }
76
146
 
@@ -81,6 +151,7 @@ async function checkGitingest() {
81
151
  async function checkDependencies() {
82
152
  const pythonCheck = await checkPython();
83
153
  const modalExists = await checkModal();
154
+ const e2bExists = await checkE2B();
84
155
  const gitExists = await checkGit();
85
156
  const gitingestExists = await checkGitingest();
86
157
 
@@ -102,6 +173,59 @@ async function checkDependencies() {
102
173
  allDependenciesMet = false;
103
174
  }
104
175
 
176
+ if (e2bExists) {
177
+ console.log(`${chalk.green('✓')} E2B Code Interpreter found`);
178
+ } else {
179
+ console.log(`${chalk.red('✗')} E2B Code Interpreter not found. Please install it with: pip install e2b-code-interpreter`);
180
+ allDependenciesMet = false;
181
+ }
182
+
183
+ if (gitingestExists) {
184
+ console.log(`${chalk.green('✓')} Gitingest CLI found`);
185
+ } else {
186
+ console.log(`${chalk.red('✗')} Gitingest CLI not found. Please install it with: pip install gitingest`);
187
+ allDependenciesMet = false;
188
+ }
189
+
190
+ if (gitExists) {
191
+ console.log(`${chalk.green('✓')} Git found`);
192
+ } else {
193
+ console.log(`${chalk.red('✗')} Git not found. Please install Git.`);
194
+ allDependenciesMet = false;
195
+ }
196
+
197
+ console.log('------------------------\n');
198
+
199
+ return allDependenciesMet;
200
+ }
201
+
202
+ /**
203
+ * Check all required dependencies except E2B
204
+ * @returns {Promise<boolean>} - True if all dependencies are met
205
+ */
206
+ async function checkDependenciesExceptE2B() {
207
+ const pythonCheck = await checkPython();
208
+ const modalExists = await checkModal();
209
+ const gitExists = await checkGit();
210
+ const gitingestExists = await checkGitingest();
211
+
212
+ let allDependenciesMet = true;
213
+
214
+ console.log('\n--- Dependency Check ---');
215
+
216
+ if (pythonCheck.exists) {
217
+ console.log(`${chalk.green('✓')} Python ${pythonCheck.version} found`);
218
+ } else {
219
+ console.log(`${chalk.red('✗')} Python not found. Please install Python 3.8 or newer.`);
220
+ allDependenciesMet = false;
221
+ }
222
+
223
+ if (modalExists) {
224
+ console.log(`${chalk.green('✓')} Modal CLI found`);
225
+ } else {
226
+ console.log(`${chalk.yellow('!')} Modal CLI not found. Not required for E2B sandbox.`);
227
+ }
228
+
105
229
  if (gitingestExists) {
106
230
  console.log(`${chalk.green('✓')} Gitingest CLI found`);
107
231
  } else {
@@ -123,8 +247,10 @@ async function checkDependencies() {
123
247
 
124
248
  module.exports = {
125
249
  checkDependencies,
250
+ checkDependenciesExceptE2B,
126
251
  commandExists,
127
252
  checkPython,
128
253
  checkModal,
254
+ checkE2B,
129
255
  checkGit
130
256
  };