gitarsenal-cli 1.9.96 → 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 +41 -5
  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
@@ -0,0 +1,158 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const { spawn } = require('child_process');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const { promisify } = require('util');
7
+ const { exec } = require('child_process');
8
+ const os = require('os');
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ /**
13
+ * Get the Python executable path from the virtual environment
14
+ * @returns {string} - Path to Python executable or 'python3'
15
+ */
16
+ function getPythonExecutable() {
17
+ // Check if PYTHON_EXECUTABLE is set by the virtual environment activation
18
+ if (process.env.PYTHON_EXECUTABLE && fs.existsSync(process.env.PYTHON_EXECUTABLE)) {
19
+ return process.env.PYTHON_EXECUTABLE;
20
+ }
21
+
22
+ // Try to find the virtual environment Python
23
+ const venvPath = path.join(__dirname, '..', '.venv');
24
+ const isWindows = process.platform === 'win32';
25
+
26
+ // Check for uv-style virtual environment first
27
+ const uvPythonPath = path.join(venvPath, 'bin', 'python');
28
+
29
+ // Check for traditional venv structure
30
+ const traditionalPythonPath = isWindows ?
31
+ path.join(venvPath, 'Scripts', 'python.exe') :
32
+ path.join(venvPath, 'bin', 'python');
33
+
34
+ if (fs.existsSync(uvPythonPath)) {
35
+ return uvPythonPath;
36
+ } else if (fs.existsSync(traditionalPythonPath)) {
37
+ return traditionalPythonPath;
38
+ }
39
+
40
+ // Fall back to system Python
41
+ return isWindows ? 'python' : 'python3';
42
+ }
43
+
44
+ /**
45
+ * Run an E2B sandbox with the given options
46
+ * @param {Object} options - Sandbox options
47
+ * @param {string} options.repoUrl - GitHub repository URL
48
+ * @param {Array<string>} options.setupCommands - Setup commands
49
+ * @returns {Promise<void>}
50
+ */
51
+ async function runE2BSandbox(options) {
52
+ const {
53
+ repoUrl,
54
+ setupCommands = [],
55
+ userId,
56
+ userName,
57
+ userEmail,
58
+ apiKeys = {}
59
+ } = options;
60
+
61
+ console.log(chalk.blue('🚀 Starting E2B sandbox...'));
62
+
63
+ // Check if E2B_API_KEY is already set in environment variables
64
+ let e2bApiKey = process.env.E2B_API_KEY;
65
+
66
+ // If not, try to get it from apiKeys
67
+ if (!e2bApiKey && apiKeys.E2B_API_KEY) {
68
+ e2bApiKey = apiKeys.E2B_API_KEY;
69
+ console.log(chalk.green('✅ Using E2B API key from apiKeys'));
70
+ }
71
+
72
+ // If still not found, try to get it from modal_tokens.json
73
+ if (!e2bApiKey) {
74
+ try {
75
+ const modalTokensPath = path.join(__dirname, '..', 'python', 'modal_tokens.json');
76
+ if (fs.existsSync(modalTokensPath)) {
77
+ const modalTokens = JSON.parse(fs.readFileSync(modalTokensPath, 'utf8'));
78
+ if (modalTokens.e2b_api_key) {
79
+ e2bApiKey = modalTokens.e2b_api_key;
80
+ console.log(chalk.green('✅ Using E2B API key from modal_tokens.json'));
81
+ }
82
+ }
83
+ } catch (error) {
84
+ console.error(chalk.yellow(`⚠️ Error reading modal_tokens.json: ${error.message}`));
85
+ }
86
+ }
87
+
88
+ // Check if we have a valid E2B API key
89
+ if (!e2bApiKey) {
90
+ console.error(chalk.red('❌ E2B API key not found'));
91
+ console.error(chalk.yellow('Please set the E2B_API_KEY environment variable or add it to your API keys'));
92
+ return { success: false, error: 'E2B API key not found' };
93
+ }
94
+
95
+ // Log a masked version of the API key for debugging
96
+ console.log(chalk.green(`✅ E2B API key found (${e2bApiKey.substring(0, 5)}...)`));
97
+
98
+ // Set up the environment for the Python process
99
+ const pythonExecutable = getPythonExecutable();
100
+ if (!pythonExecutable) {
101
+ console.error(chalk.red('❌ Python executable not found'));
102
+ return { success: false, error: 'Python executable not found' };
103
+ }
104
+
105
+ // Get OpenAI and Anthropic API keys from environment or apiKeys
106
+ const openaiApiKey = apiKeys.OPENAI_API_KEY || process.env.OPENAI_API_KEY;
107
+ const anthropicApiKey = apiKeys.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY;
108
+
109
+ // Run the e2b_sandbox_agent.py script with the repository URL
110
+ const scriptPath = path.join(__dirname, '..', 'python', 'e2b_sandbox_agent.py');
111
+
112
+ // Prepare command line arguments
113
+ const args = [
114
+ scriptPath,
115
+ '--repo', repoUrl
116
+ ];
117
+
118
+ // Add API keys if available
119
+ if (openaiApiKey) {
120
+ args.push('--openai-api-key', openaiApiKey);
121
+ }
122
+
123
+ if (anthropicApiKey) {
124
+ args.push('--anthropic-api-key', anthropicApiKey);
125
+ }
126
+
127
+ // Run the Python script
128
+ const pythonProcess = spawn(pythonExecutable, args, {
129
+ stdio: 'inherit',
130
+ env: {
131
+ ...process.env,
132
+ E2B_API_KEY: e2bApiKey
133
+ }
134
+ });
135
+
136
+ return new Promise((resolve, reject) => {
137
+ pythonProcess.on('close', (code) => {
138
+ if (code === 0 || code === 130) { // 130 is the exit code for SIGINT (Ctrl+C)
139
+ console.log(chalk.green('✅ E2B sandbox session completed'));
140
+ resolve({ success: true });
141
+ } else {
142
+ console.error(chalk.red(`✗ E2B sandbox session failed`));
143
+ console.error(chalk.red(` Exit code: ${code}`));
144
+ reject(new Error(`Process exited with code ${code}`));
145
+ }
146
+ });
147
+
148
+ pythonProcess.on('error', (error) => {
149
+ console.error(chalk.red(`✗ E2B sandbox session failed`));
150
+ console.error(chalk.red(` Error: ${error.message}`));
151
+ reject(error);
152
+ });
153
+ });
154
+ }
155
+
156
+ module.exports = {
157
+ runE2BSandbox
158
+ };
@@ -0,0 +1,12 @@
1
+ const { exec } = require('child_process');
2
+ const { promisify } = require('util');
3
+
4
+ /**
5
+ * Promisified version of child_process.exec
6
+ * @type {Function}
7
+ */
8
+ const execAsync = promisify(exec);
9
+
10
+ module.exports = {
11
+ execAsync
12
+ };
package/lib/sandbox.js CHANGED
@@ -6,6 +6,7 @@ const ora = require('ora');
6
6
  const { promisify } = require('util');
7
7
  const { exec } = require('child_process');
8
8
  const os = require('os');
9
+ const { runE2BSandbox } = require('./e2b-sandbox');
9
10
 
10
11
  const execAsync = promisify(exec);
11
12
 
@@ -22,62 +23,38 @@ function getPythonScriptPath() {
22
23
  }
23
24
 
24
25
  // If not found, return the path where it will be copied during installation
25
- return packageScriptPath;
26
+ return path.join(os.homedir(), '.gitarsenal', 'python', 'test_modalSandboxScript.py');
26
27
  }
27
28
 
28
29
  /**
29
- * Run the container with the given options
30
+ * Run a container with the given options
30
31
  * @param {Object} options - Container options
31
- * @param {string} options.repoUrl - GitHub repository URL
32
- * @param {string} options.gpuType - GPU type
33
- * @param {number} options.gpuCount - Number of GPUs (default: 1)
34
- * @param {string} options.volumeName - Volume name
35
- * @param {Array<string>} options.setupCommands - Setup commands
36
- * @param {boolean} options.showExamples - Whether to show usage examples
37
- * @param {Object} options.analysisData - Repository analysis data from best_gpu endpoint
38
32
  * @returns {Promise<void>}
39
33
  */
40
- async function runContainer(options) {
41
- const {
42
- repoUrl,
43
- gpuType,
44
- gpuCount = 1,
45
- volumeName,
46
- setupCommands = [],
34
+ async function runContainer(options = {}) {
35
+ const {
47
36
  showExamples = false,
37
+ repoUrl = '',
38
+ gpuType = 'A10G',
39
+ gpuCount = 1,
40
+ volumeName = '',
41
+ setupCommands = [],
48
42
  yes = false,
49
- userId,
50
- userName,
51
- userEmail,
52
- analysisData
43
+ userId = '',
44
+ userName = '',
45
+ userEmail = '',
46
+ apiKeys = {},
47
+ analysisData = null,
48
+ sandboxProvider = 'modal'
53
49
  } = options;
54
-
55
- // Get the path to the Python script
56
- const scriptPath = getPythonScriptPath();
57
-
58
- // Check if the script exists
59
- if (!fs.existsSync(scriptPath)) {
60
- throw new Error(`Python script not found at ${scriptPath}. Please reinstall the package.`);
61
- }
62
-
63
- // Prepare command arguments
64
- const args = [
65
- scriptPath
66
- ];
67
50
 
68
- // If show examples is true, only pass that flag
51
+ // Check if this is just a request to show examples
69
52
  if (showExamples) {
70
- args.push('--show-examples');
71
-
72
- // Log the command being executed
73
- // console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
74
-
75
- // Run the Python script with show examples flag
76
- const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
77
- const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
78
- stdio: 'inherit', // Inherit stdio to show real-time output
79
- env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
80
- });
53
+ const scriptPath = getPythonScriptPath();
54
+ const pythonProcess = spawn('python', [
55
+ scriptPath,
56
+ '--show-examples'
57
+ ], { stdio: 'inherit' });
81
58
 
82
59
  return new Promise((resolve, reject) => {
83
60
  pythonProcess.on('close', (code) => {
@@ -87,94 +64,101 @@ async function runContainer(options) {
87
64
  reject(new Error(`Process exited with code ${code}`));
88
65
  }
89
66
  });
90
-
91
- pythonProcess.on('error', (error) => {
92
- reject(error);
93
- });
94
67
  });
95
68
  }
69
+
70
+ // Choose sandbox provider
71
+ if (sandboxProvider === 'e2b') {
72
+ console.log(chalk.blue('🚀 Using E2B sandbox provider'));
73
+ return runE2BSandbox({
74
+ repoUrl,
75
+ setupCommands,
76
+ userId,
77
+ userName,
78
+ userEmail,
79
+ apiKeys
80
+ });
81
+ }
82
+
83
+ console.log(chalk.blue('🚀 Using Modal sandbox provider'));
84
+
85
+ // Get the path to the Python script
86
+ const scriptPath = getPythonScriptPath();
87
+
88
+ if (!fs.existsSync(scriptPath)) {
89
+ throw new Error(`Python script not found at ${scriptPath}`);
90
+ }
91
+
92
+ console.log('Launching container...');
93
+
94
+ // Convert setup commands to JSON for passing to Python
95
+ const setupCommandsJson = JSON.stringify(setupCommands);
96
+
97
+ // Convert analysis data to JSON for passing to Python
98
+ const analysisDataJson = analysisData ? JSON.stringify(analysisData) : '';
99
+
100
+ // Convert API keys to JSON for passing to Python
101
+ const apiKeysJson = JSON.stringify(apiKeys);
102
+
103
+ // Build the command arguments
104
+ const args = [
105
+ scriptPath,
106
+ '--gpu', gpuType,
107
+ ];
108
+
109
+ if (gpuCount && gpuCount > 1) {
110
+ args.push('--gpu-count', gpuCount.toString());
111
+ }
96
112
 
97
- // Add normal arguments
98
- if (gpuType) args.push('--gpu', gpuType);
99
- if (gpuCount && gpuCount > 1) args.push('--gpu-count', gpuCount.toString());
100
- if (repoUrl) args.push('--repo-url', repoUrl);
113
+ if (repoUrl) {
114
+ args.push('--repo-url', repoUrl);
115
+ }
101
116
 
102
117
  if (volumeName) {
103
118
  args.push('--volume-name', volumeName);
104
119
  }
105
120
 
106
- // Repository setup is now handled by Agent when --repo-url is provided
121
+ if (setupCommands && setupCommands.length > 0) {
122
+ args.push('--setup-commands-json', setupCommandsJson);
123
+ }
107
124
 
108
- // Add --yes flag to skip confirmation prompts
109
- if (yes) {
110
- args.push('--yes');
111
- console.log(chalk.gray(`🔍 Debug: Adding --yes flag to Python script`));
125
+ if (userId) {
126
+ args.push('--user-id', userId);
112
127
  }
113
128
 
114
- // Add user credentials if provided
115
- if (userId && userEmail && userName) {
116
- args.push('--user-id', userEmail);
117
- args.push('--user-name', userId);
118
- args.push('--display-name', userName);
129
+ if (userName) {
130
+ args.push('--user-name', userName);
119
131
  }
120
-
121
- // Add analysis data if provided
122
- if (analysisData) {
123
- args.push('--analysis-data', JSON.stringify(analysisData));
132
+
133
+ if (userEmail) {
134
+ args.push('--display-name', userEmail);
135
+ }
136
+
137
+ if (analysisDataJson) {
138
+ args.push('--analysis-data', analysisDataJson);
124
139
  }
125
140
 
126
- // Handle manual setup commands if provided
127
- if (setupCommands.length > 0) {
128
- // Create a temporary file to store setup commands
129
- const tempCommandsFile = path.join(os.tmpdir(), `gitarsenal-commands-${Date.now()}.txt`);
130
- fs.writeFileSync(tempCommandsFile, setupCommands.join('\n'));
131
- args.push('--commands-file', tempCommandsFile);
141
+ if (yes) {
142
+ args.push('--yes');
132
143
  }
133
144
 
134
- // Log the command being executed
135
- // console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
136
- // console.log(chalk.gray(`🔍 Debug: yes parameter = ${yes}`));
145
+ // Use system Python instead of virtual env Python
146
+ const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
137
147
 
138
- // Run the Python script without spinner to avoid terminal interference
139
- console.log(chalk.dim('Launching container...'));
148
+ // Run the Python script
149
+ const pythonProcess = spawn(pythonCmd, args, { stdio: 'inherit' });
140
150
 
141
- try {
142
- // Run the Python script
143
- const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
144
- const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
145
- stdio: 'inherit', // Inherit stdio to show real-time output
146
- env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
147
- });
148
-
149
- // Handle process completion
150
- return new Promise((resolve, reject) => {
151
- pythonProcess.on('close', (code) => {
152
- if (code === 0) {
153
- console.log(chalk.green('✓ Environment ready'));
154
- console.log(chalk.dim(' Your secure development environment is now active'));
155
- resolve();
156
- } else {
157
- console.log(chalk.red('✗ Launch failed'));
158
- console.log(chalk.dim(` Exit code: ${code}`));
159
- reject(new Error(`Process exited with code ${code}`));
160
- }
161
- });
162
-
163
- // Handle process errors
164
- pythonProcess.on('error', (error) => {
165
- console.log(chalk.red('✗ Process error'));
166
- console.log(chalk.dim(` ${error.message}`));
167
- reject(error);
168
- });
151
+ return new Promise((resolve, reject) => {
152
+ pythonProcess.on('close', (code) => {
153
+ if (code === 0) {
154
+ resolve();
155
+ } else {
156
+ reject(new Error(`Process exited with code ${code}`));
157
+ }
169
158
  });
170
- } catch (error) {
171
- console.log(chalk.red('✗ Launch error'));
172
- console.log(chalk.dim(` ${error.message}`));
173
- throw error;
174
- }
159
+ });
175
160
  }
176
161
 
177
162
  module.exports = {
178
- runContainer,
179
- getPythonScriptPath
163
+ runContainer
180
164
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.96",
3
+ "version": "1.9.97",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "author": "",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
+ "@e2b/code-interpreter": "^2.0.0",
24
25
  "@supabase/supabase-js": "^2.53.0",
25
26
  "boxen": "^5.1.2",
26
27
  "chalk": "^4.1.2",
@@ -159,7 +159,8 @@ class CredentialsManager:
159
159
  "MODAL_TOKEN_ID",
160
160
  "MODAL_TOKEN",
161
161
  "MODAL_TOKEN_SECRET",
162
- "GROQ_API_KEY"
162
+ "GROQ_API_KEY",
163
+ "E2B_API_KEY"
163
164
  ]
164
165
 
165
166
  for var in security_vars: