gitarsenal-cli 1.4.9 → 1.4.11

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/bin/gitarsenal.js CHANGED
@@ -225,24 +225,7 @@ async function runContainerCommand(options) {
225
225
  }
226
226
  }
227
227
 
228
- // Show configuration summary
229
- console.log(chalk.bold('\nšŸ“‹ Container Configuration:'));
230
- console.log(chalk.cyan('Repository URL: ') + repoUrl);
231
- console.log(chalk.cyan('GPU Type: ') + gpuType);
232
- console.log(chalk.cyan('Volume: ') + (volumeName || 'None'));
233
-
234
- if (useApi) {
235
- console.log(chalk.cyan('Setup Commands: ') + 'Auto-detect from repository');
236
- } else if (setupCommands.length > 0) {
237
- console.log(chalk.cyan('Setup Commands:'));
238
- setupCommands.forEach((cmd, i) => {
239
- console.log(` ${i + 1}. ${cmd}`);
240
- });
241
- } else {
242
- console.log(chalk.cyan('Setup Commands: ') + 'None');
243
- }
244
-
245
- // Confirm settings
228
+ // Confirm settings (configuration will be shown by Python script after GPU selection)
246
229
  if (!skipConfirmation) {
247
230
  const confirmAnswers = await inquirer.prompt([
248
231
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.4.9",
3
+ "version": "1.4.11",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fix commands for Modal SSH container
4
+ This script fixes common issues with commands in the Modal SSH container
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import subprocess
10
+
11
+ def fix_command(cmd):
12
+ """Fix common issues with commands"""
13
+ # Replace 'source' with '.' for sh shell
14
+ if 'source' in cmd:
15
+ cmd = cmd.replace('source', '.')
16
+ print(f"āœ… Fixed: Replaced 'source' with '.' for sh shell")
17
+
18
+ # Fix uv venv and activation in one command
19
+ if 'uv venv' in cmd and '&&' in cmd:
20
+ # The command is already trying to create and activate a venv
21
+ # Just need to make sure 'source' is replaced with '.'
22
+ pass
23
+
24
+ # Fix uv pip install without active venv
25
+ if 'uv pip install' in cmd and 'venv' not in cmd:
26
+ # Create and activate a venv first
27
+ cmd = f"uv venv .venv && . .venv/bin/activate && {cmd}"
28
+ print(f"āœ… Fixed: Added venv creation and activation before pip install")
29
+
30
+ return cmd
31
+
32
+ def run_fixed_command(cmd):
33
+ """Run the fixed command"""
34
+ fixed_cmd = fix_command(cmd)
35
+ print(f"\nšŸ”§ Running fixed command: {fixed_cmd}")
36
+
37
+ # Run the command
38
+ result = subprocess.run(fixed_cmd, shell=True, text=True, capture_output=True)
39
+
40
+ # Print the output
41
+ if result.stdout:
42
+ print(f"\n--- Output ---\n{result.stdout}")
43
+ if result.stderr:
44
+ print(f"\n--- Error ---\n{result.stderr}")
45
+
46
+ # Return the result
47
+ return result.returncode == 0
48
+
49
+ def main():
50
+ """Main function"""
51
+ if len(sys.argv) < 2:
52
+ print("Usage: python fix_commands.py 'command to fix'")
53
+ return 1
54
+
55
+ # Get the command from command line
56
+ cmd = ' '.join(sys.argv[1:])
57
+ print(f"šŸ” Original command: {cmd}")
58
+
59
+ # Run the fixed command
60
+ success = run_fixed_command(cmd)
61
+
62
+ # Print the result
63
+ if success:
64
+ print("\nāœ… Command executed successfully")
65
+ else:
66
+ print("\nāŒ Command failed")
67
+
68
+ return 0 if success else 1
69
+
70
+ if __name__ == "__main__":
71
+ sys.exit(main())
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fix setup commands for open-r1 repository
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import subprocess
9
+
10
+ # Original problematic commands
11
+ original_commands = [
12
+ "git clone https://github.com/huggingface/open-r1.git",
13
+ "cd open-r1",
14
+ "uv venv openr1 --python 3.11 && source openr1/bin/activate && uv pip install --upgrade pip",
15
+ "uv pip install vllm==0.8.5.post1",
16
+ "uv pip install setuptools && uv pip install flash-attn --no-build-isolation"
17
+ ]
18
+
19
+ # Fixed commands
20
+ fixed_commands = [
21
+ "git clone https://github.com/huggingface/open-r1.git",
22
+ "cd open-r1",
23
+ "uv venv openr1 --python 3.11 && . openr1/bin/activate && uv pip install --upgrade pip",
24
+ ". openr1/bin/activate && uv pip install vllm==0.8.5.post1",
25
+ ". openr1/bin/activate && uv pip install setuptools && uv pip install flash-attn --no-build-isolation"
26
+ ]
27
+
28
+ def run_command(cmd, cwd=None):
29
+ """Run a command and return the result"""
30
+ print(f"\nā–¶ {cmd}")
31
+
32
+ try:
33
+ result = subprocess.run(
34
+ cmd,
35
+ shell=True,
36
+ text=True,
37
+ capture_output=True,
38
+ cwd=cwd
39
+ )
40
+
41
+ # Print output
42
+ if result.stdout:
43
+ print(result.stdout)
44
+
45
+ # Print error
46
+ if result.stderr:
47
+ print(f"Error: {result.stderr}")
48
+
49
+ # Return success/failure
50
+ return result.returncode == 0, result.stdout, result.stderr
51
+ except Exception as e:
52
+ print(f"Error executing command: {e}")
53
+ return False, "", str(e)
54
+
55
+ def main():
56
+ """Main function"""
57
+ print("šŸ”§ Fixing setup commands for open-r1 repository")
58
+
59
+ # Check if we're in the right directory
60
+ cwd = os.getcwd()
61
+ print(f"šŸ“‚ Current directory: {cwd}")
62
+
63
+ # Check if we need to change directory
64
+ if not cwd.endswith('open-r1'):
65
+ parent_dir = cwd
66
+ # Check if open-r1 exists in the current directory
67
+ if os.path.exists(os.path.join(cwd, 'open-r1')):
68
+ print(f"šŸ“‚ Found open-r1 directory, changing to it")
69
+ os.chdir(os.path.join(cwd, 'open-r1'))
70
+ cwd = os.getcwd()
71
+ print(f"šŸ“‚ New current directory: {cwd}")
72
+
73
+ # Run the fixed commands
74
+ for i, cmd in enumerate(fixed_commands):
75
+ print(f"\nšŸ“‹ Running command {i+1}/{len(fixed_commands)}: {cmd}")
76
+
77
+ # Skip git clone if the directory already exists
78
+ if cmd.startswith("git clone") and os.path.exists("open-r1"):
79
+ print("āœ… Repository already cloned, skipping")
80
+ continue
81
+
82
+ # Skip cd if we're already in the right directory
83
+ if cmd.startswith("cd "):
84
+ target_dir = cmd.split(" ", 1)[1]
85
+ if cwd.endswith(target_dir):
86
+ print(f"āœ… Already in {target_dir}, skipping")
87
+ continue
88
+
89
+ # Run the command
90
+ success, stdout, stderr = run_command(cmd)
91
+
92
+ # Check if the command succeeded
93
+ if not success:
94
+ print(f"āŒ Command failed: {cmd}")
95
+ print(f"āŒ Error: {stderr}")
96
+
97
+ # If this is a cd command, try to continue
98
+ if cmd.startswith("cd "):
99
+ print("āš ļø Directory change failed, but continuing with next command")
100
+ continue
101
+
102
+ # For other commands, ask if the user wants to continue
103
+ try:
104
+ choice = input("Continue with next command? (y/n): ").strip().lower()
105
+ if choice != 'y':
106
+ print("šŸ›‘ Stopping execution")
107
+ return 1
108
+ except:
109
+ print("šŸ›‘ Stopping execution due to error")
110
+ return 1
111
+
112
+ print("\nāœ… All commands executed")
113
+ return 0
114
+
115
+ if __name__ == "__main__":
116
+ sys.exit(main())
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Patch the test_modalSandboxScript.py file to fix issues with LLM debugging
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import re
9
+
10
+ def read_file(filename):
11
+ """Read a file and return its contents"""
12
+ with open(filename, 'r') as f:
13
+ return f.read()
14
+
15
+ def write_file(filename, content):
16
+ """Write content to a file"""
17
+ with open(filename, 'w') as f:
18
+ f.write(content)
19
+
20
+ def patch_file(filename):
21
+ """Patch the file to fix issues with LLM debugging"""
22
+ print(f"šŸ”§ Patching {filename}...")
23
+
24
+ # Read the file
25
+ content = read_file(filename)
26
+
27
+ # Make a backup
28
+ backup_filename = f"{filename}.bak"
29
+ write_file(backup_filename, content)
30
+ print(f"āœ… Created backup: {backup_filename}")
31
+
32
+ # Fix 1: Replace 'source' with '.' in commands
33
+ pattern1 = r'(uv venv.*?)source(.*?activate)'
34
+ replacement1 = r'\1.\2'
35
+ content = re.sub(pattern1, replacement1, content)
36
+ print("āœ… Fixed 'source' commands")
37
+
38
+ # Fix 2: Add environment activation before uv pip commands
39
+ pattern2 = r'(uv pip install.*?)(?!\. .*?activate)'
40
+ replacement2 = r'. openr1/bin/activate && \1'
41
+ content = re.sub(pattern2, replacement2, content)
42
+ print("āœ… Added environment activation before pip commands")
43
+
44
+ # Write the patched file
45
+ write_file(filename, content)
46
+ print(f"āœ… Patched file written: {filename}")
47
+
48
+ return True
49
+
50
+ def main():
51
+ """Main function"""
52
+ # Get the file to patch
53
+ if len(sys.argv) > 1:
54
+ filename = sys.argv[1]
55
+ else:
56
+ # Default to the test_modalSandboxScript.py in the current directory
57
+ filename = "test_modalSandboxScript.py"
58
+
59
+ # Check if the file exists
60
+ if not os.path.exists(filename):
61
+ print(f"āŒ File not found: {filename}")
62
+ return 1
63
+
64
+ # Patch the file
65
+ success = patch_file(filename)
66
+
67
+ if success:
68
+ print("\nāœ… Patching completed successfully")
69
+ return 0
70
+ else:
71
+ print("\nāŒ Patching failed")
72
+ return 1
73
+
74
+ if __name__ == "__main__":
75
+ sys.exit(main())
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify LLM debugging functionality
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import requests
9
+ import getpass
10
+
11
+ def test_openai_connection():
12
+ """Test if we can connect to OpenAI API"""
13
+ print("šŸ” Testing OpenAI API connection...")
14
+
15
+ # Try to get API key
16
+ api_key = os.environ.get("OPENAI_API_KEY")
17
+ if not api_key:
18
+ print("āŒ No OPENAI_API_KEY environment variable found")
19
+ print("šŸ’” Please set your OpenAI API key:")
20
+ print(" export OPENAI_API_KEY='your-api-key-here'")
21
+ return False
22
+
23
+ print(f"āœ… Found API key (length: {len(api_key)})")
24
+
25
+ # Test API connection
26
+ headers = {
27
+ "Content-Type": "application/json",
28
+ "Authorization": f"Bearer {api_key}"
29
+ }
30
+
31
+ payload = {
32
+ "model": "gpt-4o-mini",
33
+ "messages": [
34
+ {"role": "user", "content": "Say 'Hello, LLM debugging is working!'"}
35
+ ],
36
+ "max_tokens": 50
37
+ }
38
+
39
+ try:
40
+ print("šŸ¤– Testing API call...")
41
+ response = requests.post(
42
+ "https://api.openai.com/v1/chat/completions",
43
+ headers=headers,
44
+ json=payload,
45
+ timeout=30
46
+ )
47
+
48
+ if response.status_code == 200:
49
+ result = response.json()
50
+ message = result["choices"][0]["message"]["content"]
51
+ print(f"āœ… API test successful: {message}")
52
+ return True
53
+ else:
54
+ print(f"āŒ API test failed with status code: {response.status_code}")
55
+ print(f"Response: {response.text}")
56
+ return False
57
+
58
+ except Exception as e:
59
+ print(f"āŒ API test failed with exception: {e}")
60
+ return False
61
+
62
+ def test_llm_debug_function():
63
+ """Test the LLM debug function from the main script"""
64
+ print("\nšŸ” Testing LLM debug function...")
65
+
66
+ # Import the function from the main script
67
+ try:
68
+ # Add the current directory to Python path
69
+ sys.path.insert(0, os.path.dirname(__file__))
70
+
71
+ # Import the function
72
+ from test_modalSandboxScript import call_openai_for_debug
73
+
74
+ # Test with a simple command failure
75
+ test_command = "ls /nonexistent/directory"
76
+ test_error = "ls: cannot access '/nonexistent/directory': No such file or directory"
77
+
78
+ print(f"🧪 Testing with command: {test_command}")
79
+ print(f"🧪 Error: {test_error}")
80
+
81
+ result = call_openai_for_debug(test_command, test_error)
82
+
83
+ if result:
84
+ print(f"āœ… LLM debug function returned: {result}")
85
+ return True
86
+ else:
87
+ print("āŒ LLM debug function returned None")
88
+ return False
89
+
90
+ except Exception as e:
91
+ print(f"āŒ Error testing LLM debug function: {e}")
92
+ return False
93
+
94
+ if __name__ == "__main__":
95
+ print("🧪 Testing LLM debugging functionality...")
96
+ print("=" * 60)
97
+
98
+ # Test 1: OpenAI API connection
99
+ api_ok = test_openai_connection()
100
+
101
+ # Test 2: LLM debug function
102
+ if api_ok:
103
+ debug_ok = test_llm_debug_function()
104
+ else:
105
+ debug_ok = False
106
+
107
+ print("\n" + "=" * 60)
108
+ print("šŸ“Š Test Results:")
109
+ print(f" OpenAI API Connection: {'āœ… PASS' if api_ok else 'āŒ FAIL'}")
110
+ print(f" LLM Debug Function: {'āœ… PASS' if debug_ok else 'āŒ FAIL'}")
111
+
112
+ if api_ok and debug_ok:
113
+ print("\nšŸŽ‰ All tests passed! LLM debugging should work.")
114
+ else:
115
+ print("\nāš ļø Some tests failed. LLM debugging may not work properly.")
116
+ if not api_ok:
117
+ print("šŸ’” To fix OpenAI API issues:")
118
+ print(" 1. Get an API key from https://platform.openai.com/api-keys")
119
+ print(" 2. Set it as environment variable: export OPENAI_API_KEY='your-key'")
120
+ print(" 3. Run this test again")
@@ -331,6 +331,12 @@ def handle_interactive_command(cmd, sandbox, current_dir):
331
331
 
332
332
  def call_openai_for_debug(command, error_output, api_key=None, current_dir=None, sandbox=None):
333
333
  """Call OpenAI to debug a failed command and suggest a fix"""
334
+ print("\nšŸ” DEBUG: Starting OpenAI LLM debugging...")
335
+ print(f"šŸ” DEBUG: Command: {command}")
336
+ print(f"šŸ” DEBUG: Error output length: {len(error_output) if error_output else 0}")
337
+ print(f"šŸ” DEBUG: Current directory: {current_dir}")
338
+ print(f"šŸ” DEBUG: Sandbox available: {sandbox is not None}")
339
+
334
340
  # Define _to_str function locally to avoid NameError
335
341
  def _to_str(maybe_bytes):
336
342
  try:
@@ -359,8 +365,11 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
359
365
 
360
366
  # Try to get API key from multiple sources
361
367
  if not api_key:
368
+ print("šŸ” DEBUG: No API key provided, searching for one...")
369
+
362
370
  # First try environment variable
363
371
  api_key = os.environ.get("OPENAI_API_KEY")
372
+ print(f"šŸ” DEBUG: API key from environment: {'Found' if api_key else 'Not found'}")
364
373
 
365
374
  # Store the API key in a persistent file if found
366
375
  if api_key:
@@ -376,28 +385,38 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
376
385
  if not api_key:
377
386
  try:
378
387
  key_file = os.path.expanduser("~/.gitarsenal/openai_key")
388
+ print(f"šŸ” DEBUG: Checking for saved API key at: {key_file}")
379
389
  if os.path.exists(key_file):
380
390
  with open(key_file, "r") as f:
381
391
  api_key = f.read().strip()
382
392
  if api_key:
383
393
  print("āœ… Loaded OpenAI API key from saved file")
394
+ print(f"šŸ” DEBUG: API key length: {len(api_key)}")
384
395
  # Also set in environment for this session
385
396
  os.environ["OPENAI_API_KEY"] = api_key
397
+ else:
398
+ print("šŸ” DEBUG: Saved file exists but is empty")
399
+ else:
400
+ print("šŸ” DEBUG: No saved API key file found")
386
401
  except Exception as e:
387
402
  print(f"āš ļø Could not load saved API key: {e}")
388
403
 
389
404
  # Then try credentials manager
390
405
  if not api_key:
406
+ print("šŸ” DEBUG: Trying credentials manager...")
391
407
  try:
392
408
  from credentials_manager import CredentialsManager
393
409
  credentials_manager = CredentialsManager()
394
410
  api_key = credentials_manager.get_openai_api_key()
395
- except ImportError:
411
+ print(f"šŸ” DEBUG: API key from credentials manager: {'Found' if api_key else 'Not found'}")
412
+ except ImportError as e:
413
+ print(f"šŸ” DEBUG: Credentials manager not available: {e}")
396
414
  # Fall back to direct input if credentials_manager is not available
397
415
  pass
398
416
 
399
417
  # Finally, prompt the user if still no API key
400
418
  if not api_key:
419
+ print("šŸ” DEBUG: No API key found in any source, prompting user...")
401
420
  print("\n" + "="*60)
402
421
  print("šŸ”‘ OPENAI API KEY REQUIRED FOR DEBUGGING")
403
422
  print("="*60)
@@ -421,6 +440,14 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
421
440
  print(f"āŒ Error getting API key: {e}")
422
441
  return None
423
442
 
443
+ # If we still don't have an API key, we can't proceed
444
+ if not api_key:
445
+ print("āŒ No OpenAI API key available. Cannot perform LLM debugging.")
446
+ print("šŸ’” To enable LLM debugging, set the OPENAI_API_KEY environment variable")
447
+ return None
448
+
449
+ print(f"āœ… OpenAI API key available (length: {len(api_key)})")
450
+
424
451
  # Gather additional context to help with debugging
425
452
  directory_context = ""
426
453
  system_info = ""
@@ -606,6 +633,8 @@ Do not provide any explanations, just the exact command to run.
606
633
  """
607
634
 
608
635
  # Prepare the API request payload
636
+ print("šŸ” DEBUG: Preparing API request...")
637
+
609
638
  # Try to use GPT-4 first, but fall back to other models if needed
610
639
  models_to_try = [
611
640
  "gpt-4o-mini", # First choice: GPT-4o (most widely available)
@@ -620,9 +649,16 @@ Do not provide any explanations, just the exact command to run.
620
649
 
621
650
  # Remove duplicates while preserving order
622
651
  models_to_try = list(dict.fromkeys(models_to_try))
652
+ print(f"šŸ” DEBUG: Models to try: {models_to_try}")
623
653
 
624
654
  # Function to make the API call with a specific model
625
655
  def try_api_call(model_name, retries=2, backoff_factor=1.5):
656
+ print(f"šŸ” DEBUG: Attempting API call with model: {model_name}")
657
+ print(f"šŸ” DEBUG: API key available: {'Yes' if api_key else 'No'}")
658
+ if api_key:
659
+ print(f"šŸ” DEBUG: API key length: {len(api_key)}")
660
+ print(f"šŸ” DEBUG: API key starts with: {api_key[:10]}...")
661
+
626
662
  payload = {
627
663
  "model": model_name,
628
664
  "messages": [
@@ -633,6 +669,8 @@ Do not provide any explanations, just the exact command to run.
633
669
  "max_tokens": 300
634
670
  }
635
671
 
672
+ print(f"šŸ” DEBUG: Payload prepared, prompt length: {len(prompt)}")
673
+
636
674
  # Add specific handling for common errors
637
675
  last_error = None
638
676
  for attempt in range(retries + 1):
@@ -644,6 +682,7 @@ Do not provide any explanations, just the exact command to run.
644
682
  time.sleep(wait_time)
645
683
 
646
684
  print(f"šŸ¤– Calling OpenAI with {model_name} model to debug the failed command...")
685
+ print(f"šŸ” DEBUG: Making POST request to OpenAI API...")
647
686
  response = requests.post(
648
687
  "https://api.openai.com/v1/chat/completions",
649
688
  headers=headers,
@@ -651,29 +690,36 @@ Do not provide any explanations, just the exact command to run.
651
690
  timeout=45 # Increased timeout for reliability
652
691
  )
653
692
 
693
+ print(f"šŸ” DEBUG: Response received, status code: {response.status_code}")
694
+
654
695
  # Handle specific status codes
655
696
  if response.status_code == 200:
697
+ print(f"šŸ” DEBUG: Success! Response length: {len(response.text)}")
656
698
  return response.json(), None
657
699
  elif response.status_code == 401:
658
700
  error_msg = "Authentication error: Invalid API key"
659
701
  print(f"āŒ {error_msg}")
702
+ print(f"šŸ” DEBUG: Response text: {response.text}")
660
703
  # Don't retry auth errors
661
704
  return None, error_msg
662
705
  elif response.status_code == 429:
663
706
  error_msg = "Rate limit exceeded or quota reached"
664
707
  print(f"āš ļø {error_msg}")
708
+ print(f"šŸ” DEBUG: Response text: {response.text}")
665
709
  # Always retry rate limit errors with increasing backoff
666
710
  last_error = error_msg
667
711
  continue
668
712
  elif response.status_code == 500:
669
713
  error_msg = "OpenAI server error"
670
714
  print(f"āš ļø {error_msg}")
715
+ print(f"šŸ” DEBUG: Response text: {response.text}")
671
716
  # Retry server errors
672
717
  last_error = error_msg
673
718
  continue
674
719
  else:
675
720
  error_msg = f"Status code: {response.status_code}, Response: {response.text}"
676
721
  print(f"āš ļø OpenAI API error: {error_msg}")
722
+ print(f"šŸ” DEBUG: Full response text: {response.text}")
677
723
  last_error = error_msg
678
724
  # Only retry if we have attempts left
679
725
  if attempt < retries:
@@ -682,18 +728,22 @@ Do not provide any explanations, just the exact command to run.
682
728
  except requests.exceptions.Timeout:
683
729
  error_msg = "Request timed out"
684
730
  print(f"āš ļø {error_msg}")
731
+ print(f"šŸ” DEBUG: Timeout after 45 seconds")
685
732
  last_error = error_msg
686
733
  # Always retry timeouts
687
734
  continue
688
735
  except requests.exceptions.ConnectionError:
689
736
  error_msg = "Connection error"
690
737
  print(f"āš ļø {error_msg}")
738
+ print(f"šŸ” DEBUG: Connection failed to api.openai.com")
691
739
  last_error = error_msg
692
740
  # Always retry connection errors
693
741
  continue
694
742
  except Exception as e:
695
743
  error_msg = str(e)
696
744
  print(f"āš ļø Unexpected error: {error_msg}")
745
+ print(f"šŸ” DEBUG: Exception type: {type(e).__name__}")
746
+ print(f"šŸ” DEBUG: Exception details: {str(e)}")
697
747
  last_error = error_msg
698
748
  # Only retry if we have attempts left
699
749
  if attempt < retries:
@@ -722,7 +772,12 @@ Do not provide any explanations, just the exact command to run.
722
772
 
723
773
  # Process the response
724
774
  try:
775
+ print(f"šŸ” DEBUG: Processing OpenAI response...")
776
+ print(f"šŸ” DEBUG: Response structure: {list(result.keys())}")
777
+ print(f"šŸ” DEBUG: Choices count: {len(result.get('choices', []))}")
778
+
725
779
  fix_command = result["choices"][0]["message"]["content"].strip()
780
+ print(f"šŸ” DEBUG: Raw response content: {fix_command}")
726
781
 
727
782
  # Save the original response for debugging
728
783
  original_response = fix_command
@@ -798,9 +853,12 @@ Do not provide any explanations, just the exact command to run.
798
853
  print("āš ļø Using best guess for command")
799
854
 
800
855
  print(f"šŸ”§ Suggested fix: {fix_command}")
856
+ print(f"šŸ” DEBUG: Returning fix command: {fix_command}")
801
857
  return fix_command
802
858
  except Exception as e:
803
859
  print(f"āŒ Error processing OpenAI response: {e}")
860
+ print(f"šŸ” DEBUG: Exception type: {type(e).__name__}")
861
+ print(f"šŸ” DEBUG: Exception details: {str(e)}")
804
862
  return None
805
863
 
806
864
  def prompt_for_hf_token():
@@ -1646,6 +1704,11 @@ cd "{current_dir}"
1646
1704
  # If command failed and we're debugging with LLM
1647
1705
  if debug_with_llm:
1648
1706
  print("šŸ” Attempting to debug the failed command with OpenAI...")
1707
+ print(f"šŸ” DEBUG: Command that failed: {cmd_to_execute}")
1708
+ print(f"šŸ” DEBUG: Exit code: {exit_code}")
1709
+ print(f"šŸ” DEBUG: stderr length: {len(stderr_buffer)}")
1710
+ print(f"šŸ” DEBUG: stdout length: {len(stdout_buffer)}")
1711
+
1649
1712
  # Ensure we have a non-empty error message to debug
1650
1713
  if not stderr_buffer.strip() and stdout_buffer.strip():
1651
1714
  print("āš ļø stderr is empty but stdout contains content, using stdout for debugging")
@@ -1848,8 +1911,17 @@ cd "{current_dir}"
1848
1911
  print(debug_output if debug_output else "[EMPTY]")
1849
1912
  print("="*60)
1850
1913
 
1914
+ print(f"šŸ” DEBUG: About to call call_openai_for_debug...")
1915
+ print(f"šŸ” DEBUG: Command: {cmd_to_execute}")
1916
+ print(f"šŸ” DEBUG: Debug output length: {len(debug_output)}")
1917
+ print(f"šŸ” DEBUG: Current directory: {current_dir}")
1918
+ print(f"šŸ” DEBUG: Sandbox available: {sandbox is not None}")
1919
+ print(f"šŸ” DEBUG: Debug output preview: {debug_output[:200]}...")
1920
+
1851
1921
  fix_command = call_openai_for_debug(cmd_to_execute, debug_output, current_dir=current_dir, sandbox=sandbox)
1852
1922
 
1923
+ print(f"šŸ” DEBUG: call_openai_for_debug returned: {fix_command}")
1924
+
1853
1925
  if fix_command:
1854
1926
  print(f"šŸ”§ OpenAI suggested fix command: {fix_command}")
1855
1927
 
@@ -2487,7 +2559,7 @@ ssh_app = modal.App("ssh-container-app")
2487
2559
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2488
2560
  "gpg", "ca-certificates", "software-properties-common"
2489
2561
  )
2490
- .pip_install("uv", "modal") # Fast Python package installer and Modal
2562
+ .pip_install("uv", "modal", "requests", "openai") # Install required packages for LLM debugging
2491
2563
  .run_commands(
2492
2564
  # Create SSH directory
2493
2565
  "mkdir -p /var/run/sshd",
@@ -2553,17 +2625,172 @@ def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_co
2553
2625
  # Run setup commands if provided
2554
2626
  if setup_commands:
2555
2627
  print(f"āš™ļø Running {len(setup_commands)} setup commands...")
2556
- for i, cmd in enumerate(setup_commands, 1):
2557
- print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
2628
+
2629
+ # First, let's check the current directory structure
2630
+ print("šŸ” Checking current directory structure before running setup commands...")
2631
+ try:
2632
+ result = subprocess.run("pwd && ls -la", shell=True, check=True,
2633
+ capture_output=True, text=True)
2634
+ print(f"šŸ“‚ Current directory: {result.stdout}")
2635
+ except subprocess.CalledProcessError as e:
2636
+ print(f"āš ļø Could not check directory structure: {e}")
2637
+
2638
+ # Define a simple run_command function for SSH container
2639
+ def run_command_with_llm_debug(cmd, show_output=True, retry_count=0, max_retries=3):
2640
+ """Execute a command with LLM debugging enabled"""
2641
+ print(f"šŸ”§ Executing: {cmd}")
2558
2642
  try:
2559
- result = subprocess.run(cmd, shell=True, check=True,
2560
- capture_output=True, text=True)
2561
- if result.stdout:
2643
+ # Handle special case for source command which doesn't work with subprocess.run
2644
+ if cmd.strip().startswith("source ") or " source " in cmd:
2645
+ print("āš ļø Detected 'source' command which doesn't work with subprocess.run")
2646
+ print("šŸ”„ Converting to bash -c with dot (.) instead of source")
2647
+ # Replace source with . (dot) which is the same as source but works in sh
2648
+ modified_cmd = cmd.replace("source ", ". ")
2649
+ # Wrap in bash -c to ensure it runs in bash
2650
+ bash_cmd = f"bash -c '{modified_cmd}'"
2651
+ print(f"šŸ”„ Modified command: {bash_cmd}")
2652
+ result = subprocess.run(bash_cmd, shell=True, check=True,
2653
+ capture_output=True, text=True)
2654
+ else:
2655
+ result = subprocess.run(cmd, shell=True, check=True,
2656
+ capture_output=True, text=True)
2657
+
2658
+ if result.stdout and show_output:
2562
2659
  print(f"āœ… Output: {result.stdout}")
2660
+ return True, result.stdout, ""
2563
2661
  except subprocess.CalledProcessError as e:
2662
+ error_output = e.stderr if e.stderr else str(e)
2564
2663
  print(f"āŒ Command failed: {e}")
2565
- if e.stderr:
2566
- print(f"āŒ Error: {e.stderr}")
2664
+ print(f"āŒ Error: {error_output}")
2665
+
2666
+ # Check for common errors that we can fix automatically
2667
+ common_errors = [
2668
+ # Source command not found
2669
+ {
2670
+ "pattern": "source: not found",
2671
+ "fix": lambda cmd: cmd.replace("source ", ". ")
2672
+ },
2673
+ # Conda not found
2674
+ {
2675
+ "pattern": "conda: not found",
2676
+ "fix": lambda cmd: "apt-get update && apt-get install -y wget bzip2 && wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && export PATH=/opt/conda/bin:$PATH && echo 'export PATH=/opt/conda/bin:$PATH' >> ~/.bashrc && source ~/.bashrc && " + cmd
2677
+ },
2678
+ # File not found for chmod
2679
+ {
2680
+ "pattern": "chmod: cannot access",
2681
+ "fix": lambda cmd: "pwd && ls -la && echo 'File not found, checking current directory and contents'"
2682
+ },
2683
+ # No such file or directory
2684
+ {
2685
+ "pattern": "No such file or directory",
2686
+ "fix": lambda cmd: "pwd && ls -la && echo 'Checking current directory and contents'"
2687
+ },
2688
+ # Directory navigation issues
2689
+ {
2690
+ "pattern": "cd: no such file or directory",
2691
+ "fix": lambda cmd: "pwd && ls -la && echo 'Directory not found, checking current location'"
2692
+ },
2693
+ # File not found for execution
2694
+ {
2695
+ "pattern": "not found",
2696
+ "fix": lambda cmd: "pwd && ls -la && echo 'File not found, checking current location'"
2697
+ }
2698
+ ]
2699
+
2700
+ # Check if any of the common errors match and apply automatic fix
2701
+ for error_info in common_errors:
2702
+ if error_info["pattern"] in error_output:
2703
+ print(f"šŸ” Detected common error: {error_info['pattern']}")
2704
+ fix_func = error_info["fix"]
2705
+ fixed_cmd = fix_func(cmd)
2706
+ print(f"šŸ”§ Applying automatic fix: {fixed_cmd}")
2707
+
2708
+ # Run the fixed command
2709
+ try:
2710
+ fix_result = subprocess.run(fixed_cmd, shell=True, check=True,
2711
+ capture_output=True, text=True)
2712
+ if fix_result.stdout:
2713
+ print(f"āœ… Automatic fix output: {fix_result.stdout}")
2714
+
2715
+ # Retry the original command
2716
+ print(f"šŸ”„ Retrying original command: {cmd}")
2717
+ return run_command_with_llm_debug(cmd, show_output, retry_count + 1, max_retries)
2718
+ except subprocess.CalledProcessError as fix_e:
2719
+ print(f"āŒ Automatic fix failed: {fix_e}")
2720
+ # Continue with LLM debugging
2721
+
2722
+ # Call OpenAI for debugging
2723
+ print("šŸ” Attempting to debug the failed command with OpenAI...")
2724
+ try:
2725
+ # Get the current directory for context
2726
+ current_dir = os.getcwd()
2727
+
2728
+ # Call OpenAI for debugging
2729
+ print(f"šŸ” DEBUG: About to call call_openai_for_debug...")
2730
+ print(f"šŸ” DEBUG: Command: {cmd}")
2731
+ print(f"šŸ” DEBUG: Error output length: {len(error_output)}")
2732
+ print(f"šŸ” DEBUG: Current directory: {current_dir}")
2733
+
2734
+ fix_command = call_openai_for_debug(cmd, error_output, current_dir=current_dir)
2735
+
2736
+ print(f"šŸ” DEBUG: call_openai_for_debug returned: {fix_command}")
2737
+
2738
+ if fix_command:
2739
+ print(f"šŸ”§ OpenAI suggested fix command: {fix_command}")
2740
+
2741
+ # Run the fix command
2742
+ print(f"šŸ”„ Running suggested fix command: {fix_command}")
2743
+ try:
2744
+ fix_result = subprocess.run(fix_command, shell=True, check=True,
2745
+ capture_output=True, text=True)
2746
+ if fix_result.stdout:
2747
+ print(f"āœ… Fix command output: {fix_result.stdout}")
2748
+
2749
+ # Retry the original command
2750
+ print(f"šŸ”„ Retrying original command: {cmd}")
2751
+ return run_command_with_llm_debug(cmd, show_output, retry_count + 1, max_retries)
2752
+ except subprocess.CalledProcessError as fix_e:
2753
+ print(f"āŒ Fix command also failed: {fix_e}")
2754
+ return False, "", error_output
2755
+ else:
2756
+ print("āŒ No fix suggested by OpenAI")
2757
+ return False, "", error_output
2758
+
2759
+ except Exception as debug_e:
2760
+ print(f"āŒ LLM debugging failed: {debug_e}")
2761
+ return False, "", error_output
2762
+
2763
+ for i, cmd in enumerate(setup_commands, 1):
2764
+ print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
2765
+
2766
+ # Check if this is a cd command and if the directory exists
2767
+ if cmd.strip().startswith("cd "):
2768
+ cd_parts = cmd.split(None, 1)
2769
+ if len(cd_parts) >= 2:
2770
+ target_dir = cd_parts[1].strip('"\'')
2771
+ print(f"šŸ” Checking if directory exists: {target_dir}")
2772
+ try:
2773
+ check_result = subprocess.run(f"test -d '{target_dir}'", shell=True,
2774
+ capture_output=True, text=True)
2775
+ if check_result.returncode != 0:
2776
+ print(f"āš ļø Directory does not exist: {target_dir}")
2777
+ print(f"šŸ” Current directory contents:")
2778
+ subprocess.run("pwd && ls -la", shell=True, check=False)
2779
+
2780
+ # Try to find similar directories
2781
+ print(f"šŸ” Looking for similar directories...")
2782
+ subprocess.run("find . -type d -name '*llama*' -o -name '*nano*' 2>/dev/null | head -10", shell=True, check=False)
2783
+ except Exception as e:
2784
+ print(f"āš ļø Could not check directory: {e}")
2785
+
2786
+ success, stdout, stderr = run_command_with_llm_debug(cmd, show_output=True)
2787
+ if not success:
2788
+ print(f"āš ļø Command {i} failed, but continuing with remaining commands...")
2789
+
2790
+ # If this was a cd command that failed, try to understand the directory structure
2791
+ if cmd.strip().startswith("cd ") and "No such file or directory" in stderr:
2792
+ print(f"šŸ” Analyzing directory structure after failed cd command...")
2793
+ subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
2567
2794
 
2568
2795
  # Get container info
2569
2796
  print("šŸ” Container started successfully!")
@@ -2798,7 +3025,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2798
3025
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2799
3026
  "gpg", "ca-certificates", "software-properties-common"
2800
3027
  )
2801
- .pip_install("uv", "modal") # Fast Python package installer and Modal
3028
+ .pip_install("uv", "modal", "requests") # Fast Python package installer, Modal, and requests for API calls
2802
3029
  .run_commands(
2803
3030
  # Create SSH directory
2804
3031
  "mkdir -p /var/run/sshd",
@@ -2831,7 +3058,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2831
3058
  if volume:
2832
3059
  volumes_config[volume_mount_path] = volume
2833
3060
 
2834
- # Define the SSH container function
3061
+ # Define the SSH container function with all necessary imports
2835
3062
  @app.function(
2836
3063
  image=ssh_image,
2837
3064
  timeout=timeout_minutes * 60, # Convert to seconds
@@ -2840,6 +3067,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2840
3067
  memory=8192,
2841
3068
  serialized=True,
2842
3069
  volumes=volumes_config if volumes_config else None,
3070
+ # Include all required modules in container
3071
+ mounts=[modal.Mount.from_local_python_packages("requests", "openai")]
2843
3072
  )
2844
3073
  def ssh_container_function():
2845
3074
  """Start SSH container with password authentication and optional setup."""
@@ -2874,17 +3103,164 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2874
3103
  # Run setup commands if provided
2875
3104
  if setup_commands:
2876
3105
  print(f"āš™ļø Running {len(setup_commands)} setup commands...")
2877
- for i, cmd in enumerate(setup_commands, 1):
2878
- print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
3106
+
3107
+ # Define a helper function for running commands with LLM debugging
3108
+ def run_command_with_basic_error_handling(cmd, show_output=True, retry_count=0, max_retries=3):
3109
+ """Execute a command with LLM debugging enabled"""
3110
+ print(f"šŸ”§ Executing: {cmd}")
2879
3111
  try:
2880
- result = subprocess.run(cmd, shell=True, check=True,
2881
- capture_output=True, text=True)
2882
- if result.stdout:
3112
+ # Handle special case for source command which doesn't work with subprocess.run
3113
+ if cmd.strip().startswith("source ") or " source " in cmd:
3114
+ print("āš ļø Detected 'source' command which doesn't work with subprocess.run")
3115
+ print("šŸ”„ Converting to bash -c with dot (.) instead of source")
3116
+ # Replace source with . (dot) which is the same as source but works in sh
3117
+ modified_cmd = cmd.replace("source ", ". ")
3118
+ # Wrap in bash -c to ensure it runs in bash
3119
+ bash_cmd = f"bash -c '{modified_cmd}'"
3120
+ print(f"šŸ”„ Modified command: {bash_cmd}")
3121
+ result = subprocess.run(bash_cmd, shell=True, check=True,
3122
+ capture_output=True, text=True)
3123
+ else:
3124
+ result = subprocess.run(cmd, shell=True, check=True,
3125
+ capture_output=True, text=True)
3126
+
3127
+ if result.stdout and show_output:
2883
3128
  print(f"āœ… Output: {result.stdout}")
3129
+ return True, result.stdout, ""
2884
3130
  except subprocess.CalledProcessError as e:
3131
+ error_output = e.stderr if e.stderr else str(e)
2885
3132
  print(f"āŒ Command failed: {e}")
2886
- if e.stderr:
2887
- print(f"āŒ Error: {e.stderr}")
3133
+ print(f"āŒ Error: {error_output}")
3134
+
3135
+ # Check for common errors that we can fix automatically
3136
+ common_errors = [
3137
+ # Source command not found
3138
+ {
3139
+ "pattern": "source: not found",
3140
+ "fix": lambda cmd: cmd.replace("source ", ". ")
3141
+ },
3142
+ # Conda not found
3143
+ {
3144
+ "pattern": "conda: not found",
3145
+ "fix": lambda cmd: "apt-get update && apt-get install -y wget bzip2 && wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && export PATH=/opt/conda/bin:$PATH && echo 'export PATH=/opt/conda/bin:$PATH' >> ~/.bashrc && source ~/.bashrc && " + cmd
3146
+ },
3147
+ # File not found for chmod
3148
+ {
3149
+ "pattern": "chmod: cannot access",
3150
+ "fix": lambda cmd: "pwd && ls -la && echo 'File not found, checking current directory and contents'"
3151
+ },
3152
+ # No such file or directory
3153
+ {
3154
+ "pattern": "No such file or directory",
3155
+ "fix": lambda cmd: "pwd && ls -la && echo 'Checking current directory and contents'"
3156
+ },
3157
+ # Directory navigation issues
3158
+ {
3159
+ "pattern": "cd: no such file or directory",
3160
+ "fix": lambda cmd: "pwd && ls -la && echo 'Directory not found, checking current location'"
3161
+ },
3162
+ # File not found for execution
3163
+ {
3164
+ "pattern": "not found",
3165
+ "fix": lambda cmd: "pwd && ls -la && echo 'File not found, checking current location'"
3166
+ }
3167
+ ]
3168
+
3169
+ # Check if any of the common errors match and apply automatic fix
3170
+ for error_info in common_errors:
3171
+ if error_info["pattern"] in error_output:
3172
+ print(f"šŸ” Detected common error: {error_info['pattern']}")
3173
+ fix_func = error_info["fix"]
3174
+ fixed_cmd = fix_func(cmd)
3175
+ print(f"šŸ”§ Applying automatic fix: {fixed_cmd}")
3176
+
3177
+ # Run the fixed command
3178
+ try:
3179
+ fix_result = subprocess.run(fixed_cmd, shell=True, check=True,
3180
+ capture_output=True, text=True)
3181
+ if fix_result.stdout:
3182
+ print(f"āœ… Automatic fix output: {fix_result.stdout}")
3183
+
3184
+ # Retry the original command
3185
+ print(f"šŸ”„ Retrying original command: {cmd}")
3186
+ return run_command_with_basic_error_handling(cmd, show_output, retry_count + 1, max_retries)
3187
+ except subprocess.CalledProcessError as fix_e:
3188
+ print(f"āŒ Automatic fix failed: {fix_e}")
3189
+ # Continue with LLM debugging
3190
+
3191
+ # Call OpenAI for debugging
3192
+ print("šŸ” Attempting to debug the failed command with OpenAI...")
3193
+ try:
3194
+ # Get the current directory for context
3195
+ current_dir = os.getcwd()
3196
+
3197
+ # Call OpenAI for debugging
3198
+ print(f"šŸ” DEBUG: About to call call_openai_for_debug...")
3199
+ print(f"šŸ” DEBUG: Command: {cmd}")
3200
+ print(f"šŸ” DEBUG: Error output length: {len(error_output)}")
3201
+ print(f"šŸ” DEBUG: Current directory: {current_dir}")
3202
+
3203
+ fix_command = call_openai_for_debug(cmd, error_output, current_dir=current_dir)
3204
+
3205
+ print(f"šŸ” DEBUG: call_openai_for_debug returned: {fix_command}")
3206
+
3207
+ if fix_command:
3208
+ print(f"šŸ”§ OpenAI suggested fix command: {fix_command}")
3209
+
3210
+ # Run the fix command
3211
+ print(f"šŸ”„ Running suggested fix command: {fix_command}")
3212
+ try:
3213
+ fix_result = subprocess.run(fix_command, shell=True, check=True,
3214
+ capture_output=True, text=True)
3215
+ if fix_result.stdout:
3216
+ print(f"āœ… Fix command output: {fix_result.stdout}")
3217
+
3218
+ # Retry the original command
3219
+ print(f"šŸ”„ Retrying original command: {cmd}")
3220
+ return run_command_with_basic_error_handling(cmd, show_output, retry_count + 1, max_retries)
3221
+ except subprocess.CalledProcessError as fix_e:
3222
+ print(f"āŒ Fix command also failed: {fix_e}")
3223
+ return False, "", error_output
3224
+ else:
3225
+ print("āŒ No fix suggested by OpenAI")
3226
+ return False, "", error_output
3227
+
3228
+ except Exception as debug_e:
3229
+ print(f"āŒ LLM debugging failed: {debug_e}")
3230
+ return False, "", error_output
3231
+
3232
+ # Run each setup command
3233
+ for i, cmd in enumerate(setup_commands, 1):
3234
+ print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
3235
+
3236
+ # Check if this is a cd command and if the directory exists
3237
+ if cmd.strip().startswith("cd "):
3238
+ cd_parts = cmd.split(None, 1)
3239
+ if len(cd_parts) >= 2:
3240
+ target_dir = cd_parts[1].strip('"\'')
3241
+ print(f"šŸ” Checking if directory exists: {target_dir}")
3242
+ try:
3243
+ check_result = subprocess.run(f"test -d '{target_dir}'", shell=True,
3244
+ capture_output=True, text=True)
3245
+ if check_result.returncode != 0:
3246
+ print(f"āš ļø Directory does not exist: {target_dir}")
3247
+ print(f"šŸ” Current directory contents:")
3248
+ subprocess.run("pwd && ls -la", shell=True, check=False)
3249
+
3250
+ # Try to find similar directories
3251
+ print(f"šŸ” Looking for similar directories...")
3252
+ subprocess.run("find . -type d -name '*llama*' -o -name '*nano*' 2>/dev/null | head -10", shell=True, check=False)
3253
+ except Exception as e:
3254
+ print(f"āš ļø Could not check directory: {e}")
3255
+
3256
+ success, stdout, stderr = run_command_with_basic_error_handling(cmd, show_output=True)
3257
+ if not success:
3258
+ print(f"āš ļø Command {i} failed, but continuing with remaining commands...")
3259
+
3260
+ # If this was a cd command that failed, try to understand the directory structure
3261
+ if cmd.strip().startswith("cd ") and "No such file or directory" in stderr:
3262
+ print(f"šŸ” Analyzing directory structure after failed cd command...")
3263
+ subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
2888
3264
 
2889
3265
  # Create SSH tunnel
2890
3266
  with modal.forward(22, unencrypted=True) as tunnel:
@@ -3573,7 +3949,7 @@ def create_ssh_container_function(gpu_type="a10g", timeout_minutes=60, volume=No
3573
3949
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
3574
3950
  "gpg", "ca-certificates", "software-properties-common"
3575
3951
  )
3576
- .pip_install("uv", "modal") # Fast Python package installer and Modal
3952
+ .pip_install("uv", "modal", "requests", "openai") # Install required packages for LLM debugging
3577
3953
  .run_commands(
3578
3954
  # Create SSH directory
3579
3955
  "mkdir -p /var/run/sshd",
@@ -3611,6 +3987,8 @@ def create_ssh_container_function(gpu_type="a10g", timeout_minutes=60, volume=No
3611
3987
  memory=8192,
3612
3988
  serialized=True,
3613
3989
  volumes=volumes if volumes else None,
3990
+ # Include all required modules in container
3991
+ mounts=[modal.Mount.from_local_python_packages("requests", "openai")]
3614
3992
  )
3615
3993
  def ssh_container(ssh_password, repo_url=None, repo_name=None, setup_commands=None):
3616
3994
  import subprocess
@@ -4147,7 +4525,7 @@ def prompt_for_gpu():
4147
4525
  """Display the GPU selection menu with current selection highlighted."""
4148
4526
  print("\nšŸ“Š Available GPU Options:")
4149
4527
  print("ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”")
4150
- print("│ GPU Type │ Memory │")
4528
+ print("│ GPU Type │ VRAM │")
4151
4529
  print("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤")
4152
4530
 
4153
4531
  for i, gpu_type in enumerate(options):
@@ -4299,6 +4677,16 @@ if __name__ == "__main__":
4299
4677
  else:
4300
4678
  print("Setup Commands: Auto-detect from repository")
4301
4679
 
4680
+ # Confirm settings
4681
+ try:
4682
+ proceed = input("Proceed with these settings? (Y/n): ").strip().lower()
4683
+ if proceed in ('n', 'no'):
4684
+ print("šŸ›‘ Operation cancelled by user.")
4685
+ sys.exit(0)
4686
+ except KeyboardInterrupt:
4687
+ print("\nšŸ›‘ Operation cancelled by user.")
4688
+ sys.exit(0)
4689
+
4302
4690
  # Interactive mode or missing required arguments
4303
4691
  if args.interactive or not args.repo_url or not args.volume_name:
4304
4692
  # Get repository URL if not provided