gitarsenal-cli 1.7.3 → 1.7.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,11 +30,9 @@ args, unknown = parser.parse_known_args()
30
30
  # Set proxy URL and API key in environment variables if provided
31
31
  if args.proxy_url:
32
32
  os.environ["MODAL_PROXY_URL"] = args.proxy_url
33
- # print(f"✅ Set MODAL_PROXY_URL from command line")
34
33
 
35
34
  if args.proxy_api_key:
36
35
  os.environ["MODAL_PROXY_API_KEY"] = args.proxy_api_key
37
- # print(f"✅ Set MODAL_PROXY_API_KEY from command line")
38
36
 
39
37
  class PersistentShell:
40
38
  """A persistent bash shell using subprocess.Popen for executing commands with state persistence."""
@@ -446,26 +444,20 @@ except Exception as e:
446
444
  # Apply the comprehensive Modal token solution as fallback
447
445
  try:
448
446
  # Import the comprehensive solution module
449
- # print("🔄 Applying comprehensive Modal token solution...")
450
447
  import modal_token_solution
451
- # print("✅ Comprehensive Modal token solution applied")
452
448
 
453
449
  # Set token variables for later use
454
450
  token = modal_token_solution.TOKEN_ID # For backward compatibility
455
451
  except Exception as e:
456
- # print(f"⚠️ Error applying comprehensive Modal token solution: {e}")
457
452
 
458
453
  # Fall back to the authentication patch
459
454
  try:
460
455
  # Import the patch module
461
- # print("🔄 Falling back to Modal authentication patch...")
462
456
  import modal_auth_patch
463
- # print("✅ Modal authentication patch applied")
464
457
 
465
458
  # Set token variables for later use
466
459
  token = modal_auth_patch.TOKEN_ID # For backward compatibility
467
460
  except Exception as e:
468
- # print(f"⚠️ Error applying Modal authentication patch: {e}")
469
461
 
470
462
  # Fall back to fix_modal_token.py
471
463
  try:
@@ -500,22 +492,14 @@ except Exception as e:
500
492
  token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
501
493
  except Exception as e:
502
494
  print(f"⚠️ Error running fix_modal_token.py: {e}")
503
-
504
- # Last resort: use hardcoded tokens
505
495
  token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
506
496
 
507
- # Print debug info
508
- # print(f"🔍 DEBUG: Checking environment variables")
509
- # print(f"🔍 Token ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
510
- # print(f"🔍 Token secret exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
511
- # print(f"🔍 Token exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
512
497
  if os.environ.get('MODAL_TOKEN_ID'):
513
498
  print(f"🔍 Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
514
499
  if os.environ.get('MODAL_TOKEN_SECRET'):
515
500
  print(f"🔍 Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
516
501
  if os.environ.get('MODAL_TOKEN'):
517
502
  print(f"🔍 Token length: {len(os.environ.get('MODAL_TOKEN'))}")
518
- # print(f"✅ Token setup completed")
519
503
 
520
504
  # Import modal after token setup
521
505
  import modal
@@ -544,168 +528,6 @@ def handle_interactive_input(prompt, is_password=False):
544
528
  print(f"❌ Error getting input: {e}")
545
529
  return None
546
530
 
547
- def handle_wandb_login(sandbox, current_dir):
548
- """Handle Weights & Biases login with proper API key input"""
549
- # Define _to_str function locally to avoid NameError
550
- def _to_str(maybe_bytes):
551
- try:
552
- return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
553
- except UnicodeDecodeError:
554
- # Handle non-UTF-8 bytes by replacing invalid characters
555
- if isinstance(maybe_bytes, (bytes, bytearray)):
556
- return maybe_bytes.decode('utf-8', errors='replace')
557
- else:
558
- return str(maybe_bytes)
559
- except Exception:
560
- # Last resort fallback
561
- return str(maybe_bytes)
562
-
563
- print("\n🔑 WEIGHTS & BIASES LOGIN")
564
- print("="*60)
565
- print("Setting up Weights & Biases credentials")
566
- print("You can get your API key from: https://wandb.ai/authorize")
567
-
568
- # Try to use credentials manager first
569
- api_key = None
570
- try:
571
- from credentials_manager import CredentialsManager
572
- credentials_manager = CredentialsManager()
573
- api_key = credentials_manager.get_wandb_api_key()
574
- except ImportError:
575
- # Fall back to direct input if credentials_manager is not available
576
- pass
577
-
578
- # If credentials manager didn't provide a key, use direct input
579
- if not api_key:
580
- # Get API key from user
581
- api_key = handle_interactive_input(
582
- "🔑 WEIGHTS & BIASES API KEY REQUIRED\n" +
583
- "Please paste your W&B API key below:\n" +
584
- "(Your API key should be 40 characters long)",
585
- is_password=True
586
- )
587
-
588
- if not api_key:
589
- print("❌ No API key provided. Cannot continue with W&B login.")
590
- return False, "", "No W&B API key provided"
591
-
592
- # Validate API key length
593
- if len(api_key) != 40:
594
- print(f"⚠️ Warning: API key should be 40 characters long, yours was {len(api_key)}")
595
- confirm = handle_interactive_input("Continue anyway? (yes/no)")
596
- if not confirm or confirm.lower() not in ["yes", "y"]:
597
- print("❌ W&B login cancelled.")
598
- return False, "", "W&B login cancelled"
599
-
600
- # Use non-interactive login
601
- cmd = f"wandb login {api_key}"
602
- print(f"🔄 Running non-interactive login command")
603
-
604
- # Execute the command
605
- result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
606
-
607
- # Collect output
608
- stdout_lines = []
609
- stderr_lines = []
610
-
611
- for line in result.stdout:
612
- line_str = _to_str(line)
613
- stdout_lines.append(line_str)
614
- sys.stdout.write(line_str)
615
- sys.stdout.flush()
616
-
617
- for line in result.stderr:
618
- line_str = _to_str(line)
619
- stderr_lines.append(line_str)
620
- sys.stderr.write(line_str)
621
- sys.stderr.flush()
622
-
623
- result.wait()
624
- exit_code = result.returncode
625
-
626
- stdout_buffer = ''.join(stdout_lines)
627
- stderr_buffer = ''.join(stderr_lines)
628
-
629
- if exit_code == 0:
630
- print("✅ Weights & Biases login successful")
631
- # Also set the environment variable for this session
632
- os.environ["WANDB_API_KEY"] = api_key
633
- print("✅ WANDB_API_KEY environment variable set")
634
- else:
635
- print(f"❌ Weights & Biases login failed with exit code {exit_code}")
636
- if stderr_buffer:
637
- print(f"Error: {stderr_buffer}")
638
-
639
- return exit_code == 0, stdout_buffer, stderr_buffer
640
-
641
- def handle_huggingface_login(sandbox, current_dir):
642
- """Handle Hugging Face login with proper token input"""
643
- # Define _to_str function locally to avoid NameError
644
- def _to_str(maybe_bytes):
645
- try:
646
- return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
647
- except UnicodeDecodeError:
648
- # Handle non-UTF-8 bytes by replacing invalid characters
649
- if isinstance(maybe_bytes, (bytes, bytearray)):
650
- return maybe_bytes.decode('utf-8', errors='replace')
651
- else:
652
- return str(maybe_bytes)
653
- except Exception:
654
- # Last resort fallback
655
- return str(maybe_bytes)
656
-
657
- print("\n🔑 HUGGING FACE LOGIN")
658
- print("="*60)
659
- print("Setting up Hugging Face credentials")
660
-
661
- # Get token from user
662
- token = prompt_for_hf_token()
663
- if not token:
664
- print("❌ No token provided. Cannot continue with Hugging Face login.")
665
- return False, "", "No Hugging Face token provided"
666
-
667
- # Use non-interactive login
668
- cmd = f"huggingface-cli login --token {token} --add-to-git-credential"
669
- print(f"🔄 Running non-interactive login command")
670
-
671
- # Execute the command
672
- result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
673
-
674
- # Collect output
675
- stdout_lines = []
676
- stderr_lines = []
677
-
678
- for line in result.stdout:
679
- line_str = _to_str(line)
680
- stdout_lines.append(line_str)
681
- sys.stdout.write(line_str)
682
- sys.stdout.flush()
683
-
684
- for line in result.stderr:
685
- line_str = _to_str(line)
686
- stderr_lines.append(line_str)
687
- sys.stderr.write(line_str)
688
- sys.stderr.flush()
689
-
690
- result.wait()
691
- exit_code = result.returncode
692
-
693
- stdout_buffer = ''.join(stdout_lines)
694
- stderr_buffer = ''.join(stderr_lines)
695
-
696
- if exit_code == 0:
697
- print("✅ Hugging Face login successful")
698
- # Also set the environment variable for this session
699
- os.environ["HF_TOKEN"] = token
700
- print("✅ HF_TOKEN environment variable set")
701
- else:
702
- print(f"❌ Hugging Face login failed with exit code {exit_code}")
703
- if stderr_buffer:
704
- print(f"Error: {stderr_buffer}")
705
-
706
- return exit_code == 0, stdout_buffer, stderr_buffer
707
-
708
-
709
531
  def get_stored_credentials():
710
532
  """Load stored credentials from ~/.gitarsenal/credentials.json"""
711
533
  import json
@@ -788,8 +610,6 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
788
610
  from fetch_modal_tokens import get_tokens
789
611
  _, _, api_key = get_tokens()
790
612
  if api_key:
791
- # print("✅ Successfully fetched OpenAI API key from server")
792
- # print(f"🔍 DEBUG: Fetched OpenAI API key value: {api_key}")
793
613
  # Set in environment for this session
794
614
  os.environ["OPENAI_API_KEY"] = api_key
795
615
  else:
@@ -930,29 +750,25 @@ System Information:
930
750
  print("🔍 Getting directory context for better debugging...")
931
751
 
932
752
  # Get current directory contents
933
- ls_result = sandbox.exec("bash", "-c", f"cd {current_dir} && ls -la")
753
+ ls_result = sandbox.exec("bash", "-c", "ls -la")
934
754
  ls_output = ""
935
755
  for line in ls_result.stdout:
936
756
  ls_output += _to_str(line)
937
757
  ls_result.wait()
938
758
 
939
- # Get parent directory contents if this isn't root
940
- parent_context = ""
941
- if current_dir != "/" and "/" in current_dir:
942
- parent_dir = os.path.dirname(current_dir)
943
- parent_result = sandbox.exec("bash", "-c", f"cd {parent_dir} && ls -la")
944
- parent_ls = ""
945
- for line in parent_result.stdout:
946
- parent_ls += _to_str(line)
947
- parent_result.wait()
948
- parent_context = f"\nParent directory ({parent_dir}) contents:\n{parent_ls}"
949
-
759
+ # Get parent directory contents
760
+ parent_result = sandbox.exec("bash", "-c", "ls -la ../")
761
+ parent_ls = ""
762
+ for line in parent_result.stdout:
763
+ parent_ls += _to_str(line)
764
+ parent_result.wait()
765
+
950
766
  directory_context = f"""
951
- Current directory: {current_dir}
952
-
953
- Directory contents:
767
+ Current directory contents:
954
768
  {ls_output}
955
- {parent_context}
769
+
770
+ Parent directory contents:
771
+ {parent_ls}
956
772
  """
957
773
  print("✅ Directory context gathered successfully")
958
774
 
@@ -966,11 +782,11 @@ Directory contents:
966
782
  file_path = file_path.strip("'\"")
967
783
  if not os.path.isabs(file_path):
968
784
  file_path = os.path.join(current_dir, file_path)
969
-
970
- # Try to get the parent directory if the file doesn't exist
971
- if '/' in file_path:
972
- parent_file_dir = os.path.dirname(file_path)
973
- relevant_files.append(parent_file_dir)
785
+
786
+ # Try to get the parent directory if the file doesn't exist
787
+ if '/' in file_path:
788
+ parent_file_dir = os.path.dirname(file_path)
789
+ relevant_files.append(parent_file_dir)
974
790
 
975
791
  # Look for package.json, requirements.txt, etc.
976
792
  common_config_files = ["package.json", "requirements.txt", "pyproject.toml", "setup.py",
@@ -1033,6 +849,12 @@ Directory contents:
1033
849
 
1034
850
  stored_credentials = get_stored_credentials()
1035
851
  auth_context = generate_auth_context(stored_credentials)
852
+
853
+
854
+ print("DEBUG: AUTH_CONTEXT SENT TO LLM:")
855
+ print("="*60)
856
+ print(auth_context)
857
+ print("="*60 + "\n")
1036
858
 
1037
859
  # Create a prompt for the LLM
1038
860
  print("\n" + "="*60)
@@ -1081,17 +903,25 @@ IMPORTANT GUIDELINES:
1081
903
 
1082
904
  4. For authentication issues:
1083
905
  - Analyze the error to determine what type of authentication is needed
1084
- - Use the actual credential values provided above (not placeholders)
906
+ - ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
907
+ - Look for the specific API key or token needed in the auth_context and use its exact value
1085
908
  - Common patterns:
1086
- * wandb errors: use wandb login with WANDB_API_KEY
1087
- * huggingface errors: use huggingface-cli login with HF_TOKEN or HUGGINGFACE_TOKEN
1088
- * github errors: configure git credentials with GITHUB_TOKEN
1089
- * kaggle errors: create ~/.kaggle/kaggle.json with KAGGLE_USERNAME and KAGGLE_KEY
1090
- * API errors: export the appropriate API key as environment variable
909
+ * wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
910
+ * huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
911
+ * github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
912
+ * kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
913
+ * API errors: export the appropriate API key as environment variable using the actual value from auth_context
1091
914
 
1092
915
  5. Environment variable exports:
1093
916
  - Use export commands for API keys that need to be in environment
1094
- - Use the actual credential values, not placeholders
917
+ - ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
918
+ - Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
919
+
920
+ 6. CRITICAL: When using any API key, token, or credential:
921
+ - Find the exact value in the AVAILABLE CREDENTIALS section
922
+ - Use that exact value in your command
923
+ - Do not use generic placeholders or dummy values
924
+ - The auth_context contains real, usable credentials
1095
925
 
1096
926
  Do not provide any explanations, just the exact command to run.
1097
927
  """
@@ -1362,31 +1192,6 @@ def prompt_for_hf_token():
1362
1192
  print(f"❌ Error getting token: {e}")
1363
1193
  return None
1364
1194
 
1365
-
1366
- def handle_interactive_input(prompt, is_password=False):
1367
- """Handle interactive input from the user with optional password masking"""
1368
- print("\n" + "="*60)
1369
- print(f"{prompt}")
1370
- print("="*60)
1371
-
1372
- try:
1373
- if is_password:
1374
- user_input = getpass.getpass("Input (hidden): ").strip()
1375
- else:
1376
- user_input = input("Input: ").strip()
1377
-
1378
- if not user_input:
1379
- print("❌ No input provided.")
1380
- return None
1381
- print("✅ Input received successfully!")
1382
- return user_input
1383
- except KeyboardInterrupt:
1384
- print("\n❌ Input cancelled by user.")
1385
- return None
1386
- except Exception as e:
1387
- print(f"❌ Error getting input: {e}")
1388
- return None
1389
-
1390
1195
  def generate_random_password(length=16):
1391
1196
  """Generate a random password for SSH access"""
1392
1197
  alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
@@ -1618,7 +1423,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1618
1423
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1619
1424
  "gpg", "ca-certificates", "software-properties-common"
1620
1425
  )
1621
- .uv_pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
1426
+ .pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
1622
1427
  .run_commands(
1623
1428
  # Create SSH directory
1624
1429
  "mkdir -p /var/run/sshd",
@@ -1668,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1668
1473
  serialized=True,
1669
1474
  volumes=volumes_config if volumes_config else None,
1670
1475
  )
1671
- def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1476
+ def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, stored_credentials=None):
1672
1477
  """Start SSH container with password authentication and optional setup."""
1673
1478
  import subprocess
1674
1479
  import time
1675
1480
  import os
1481
+ import json
1676
1482
 
1677
1483
  # Set root password
1678
1484
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
@@ -1680,37 +1486,52 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1680
1486
  # Set OpenAI API key if provided
1681
1487
  if openai_api_key:
1682
1488
  os.environ['OPENAI_API_KEY'] = openai_api_key
1683
- print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1489
+ # print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1684
1490
  else:
1685
1491
  print("⚠️ No OpenAI API key provided to container")
1686
1492
 
1687
- # Start SSH service
1688
- subprocess.run(["service", "ssh", "start"], check=True)
1689
-
1690
- # Clone repository if provided
1691
- repo_dir = "/root"
1692
- if repo_url:
1693
- repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1694
- print(f"📥 Cloning repository: {repo_url}")
1493
+ # Set up stored credentials in container environment
1494
+ if stored_credentials:
1495
+ print(f"🔐 Setting up {len(stored_credentials)} stored credentials in container...")
1496
+ for key, value in stored_credentials.items():
1497
+ # Set each credential as an environment variable
1498
+ env_var_name = key.upper().replace('-', '_').replace(' ', '_')
1499
+ os.environ[env_var_name] = value
1500
+ print(f" Set {env_var_name} in container environment")
1695
1501
 
1502
+ # Also save credentials to a file in the container for easy access
1696
1503
  try:
1697
- subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1698
- print(f"✅ Repository cloned successfully: {repo_name_from_url}")
1504
+ credentials_dir = "/root/.gitarsenal"
1505
+ os.makedirs(credentials_dir, exist_ok=True)
1506
+ credentials_file = os.path.join(credentials_dir, "credentials.json")
1507
+ with open(credentials_file, 'w') as f:
1508
+ json.dump(stored_credentials, f, indent=2)
1509
+ print(f"✅ Saved credentials to {credentials_file}")
1699
1510
 
1700
- # Change to repository directory
1701
- repo_dir = f"/root/{repo_name_from_url}"
1702
- if os.path.exists(repo_dir):
1703
- print(f"📂 Will run setup commands in repository directory: {repo_dir}")
1511
+ # Print available credentials for user reference
1512
+ print("\n🔐 AVAILABLE CREDENTIALS IN CONTAINER:")
1513
+ print("="*50)
1514
+ for key, value in stored_credentials.items():
1515
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
1516
+ env_var_name = key.upper().replace('-', '_').replace(' ', '_')
1517
+ print(f" {key} -> {env_var_name} = {masked_value}")
1518
+ print("="*50)
1519
+ print("💡 These credentials are available as environment variables and in /root/.gitarsenal/credentials.json")
1704
1520
 
1705
- except subprocess.CalledProcessError as e:
1706
- print(f" Failed to clone repository: {e}")
1521
+ except Exception as e:
1522
+ print(f"⚠️ Could not save credentials file: {e}")
1523
+ else:
1524
+ print("⚠️ No stored credentials provided to container")
1525
+
1526
+ # Start SSH service
1527
+ subprocess.run(["service", "ssh", "start"], check=True)
1707
1528
 
1708
1529
  # Run setup commands if provided using PersistentShell
1709
1530
  if setup_commands:
1710
1531
  print(f"⚙️ Running {len(setup_commands)} setup commands with persistent shell...")
1711
1532
 
1712
- # Create persistent shell instance
1713
- shell = PersistentShell(working_dir=repo_dir, timeout=120)
1533
+ # Create persistent shell instance starting in /root
1534
+ shell = PersistentShell(working_dir="/root", timeout=120)
1714
1535
 
1715
1536
  try:
1716
1537
  # Start the persistent shell
@@ -1808,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1808
1629
  with app.run():
1809
1630
  # Get the API key from environment
1810
1631
  api_key = os.environ.get("OPENAI_API_KEY")
1811
- ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key)
1632
+
1633
+ # Get stored credentials from local file
1634
+ stored_credentials = get_stored_credentials()
1635
+ if stored_credentials:
1636
+ print(f"🔐 Found {len(stored_credentials)} stored credentials to send to container")
1637
+ else:
1638
+ print("⚠️ No stored credentials found")
1639
+
1640
+ ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, stored_credentials)
1812
1641
 
1813
1642
  # Clean up Modal token after container is successfully created
1814
1643
  cleanup_modal_token()
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify that stored credentials are being passed correctly to SSH containers.
4
+ """
5
+
6
+ import os
7
+ import json
8
+ from pathlib import Path
9
+
10
+ def test_credentials_loading():
11
+ """Test that credentials can be loaded from the local file"""
12
+ print("🔍 Testing credentials loading...")
13
+
14
+ # Test get_stored_credentials function
15
+ try:
16
+ from test_modalSandboxScript import get_stored_credentials
17
+ credentials = get_stored_credentials()
18
+
19
+ if credentials:
20
+ print(f"✅ Found {len(credentials)} stored credentials:")
21
+ for key, value in credentials.items():
22
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
23
+ print(f" - {key}: {masked_value}")
24
+ else:
25
+ print("⚠️ No stored credentials found")
26
+
27
+ return credentials
28
+ except Exception as e:
29
+ print(f"❌ Error loading credentials: {e}")
30
+ return {}
31
+
32
+ def test_credentials_file():
33
+ """Test that the credentials file exists and is readable"""
34
+ print("\n🔍 Testing credentials file...")
35
+
36
+ credentials_file = Path.home() / ".gitarsenal" / "credentials.json"
37
+
38
+ if credentials_file.exists():
39
+ print(f"✅ Credentials file exists at: {credentials_file}")
40
+ try:
41
+ with open(credentials_file, 'r') as f:
42
+ credentials = json.load(f)
43
+ print(f"✅ Successfully loaded {len(credentials)} credentials from file")
44
+ return credentials
45
+ except Exception as e:
46
+ print(f"❌ Error reading credentials file: {e}")
47
+ return {}
48
+ else:
49
+ print(f"⚠️ Credentials file not found at: {credentials_file}")
50
+ print("💡 You can create it by running the credentials manager")
51
+ return {}
52
+
53
+ def test_environment_variables():
54
+ """Test that credentials are available as environment variables"""
55
+ print("\n🔍 Testing environment variables...")
56
+
57
+ credential_env_vars = [
58
+ 'OPENAI_API_KEY', 'WANDB_API_KEY', 'HF_TOKEN', 'HUGGINGFACE_TOKEN',
59
+ 'GITHUB_TOKEN', 'KAGGLE_USERNAME', 'KAGGLE_KEY'
60
+ ]
61
+
62
+ found_creds = {}
63
+ for env_var in credential_env_vars:
64
+ value = os.environ.get(env_var)
65
+ if value:
66
+ found_creds[env_var] = value
67
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
68
+ print(f"✅ {env_var}: {masked_value}")
69
+
70
+ if not found_creds:
71
+ print("⚠️ No credential environment variables found")
72
+
73
+ return found_creds
74
+
75
+ def test_credentials_integration():
76
+ """Test the full credentials integration"""
77
+ print("\n" + "="*60)
78
+ print("🔐 CREDENTIALS INTEGRATION TEST")
79
+ print("="*60)
80
+
81
+ # Test all credential sources
82
+ file_creds = test_credentials_file()
83
+ env_creds = test_environment_variables()
84
+ function_creds = test_credentials_loading()
85
+
86
+ # Combine all credentials
87
+ all_creds = {}
88
+ all_creds.update(file_creds)
89
+ all_creds.update(env_creds)
90
+ all_creds.update(function_creds)
91
+
92
+ print(f"\n📊 SUMMARY:")
93
+ print(f" - File credentials: {len(file_creds)}")
94
+ print(f" - Environment credentials: {len(env_creds)}")
95
+ print(f" - Function credentials: {len(function_creds)}")
96
+ print(f" - Total unique credentials: {len(all_creds)}")
97
+
98
+ if all_creds:
99
+ print(f"\n✅ Credentials are available for SSH container integration")
100
+ print("💡 These credentials will be automatically passed to SSH containers")
101
+ else:
102
+ print(f"\n⚠️ No credentials found")
103
+ print("💡 Consider setting up credentials using the credentials manager")
104
+
105
+ return all_creds
106
+
107
+ if __name__ == "__main__":
108
+ test_credentials_integration()
@@ -30,11 +30,9 @@ args, unknown = parser.parse_known_args()
30
30
  # Set proxy URL and API key in environment variables if provided
31
31
  if args.proxy_url:
32
32
  os.environ["MODAL_PROXY_URL"] = args.proxy_url
33
- # print(f"✅ Set MODAL_PROXY_URL from command line")
34
33
 
35
34
  if args.proxy_api_key:
36
35
  os.environ["MODAL_PROXY_API_KEY"] = args.proxy_api_key
37
- # print(f"✅ Set MODAL_PROXY_API_KEY from command line")
38
36
 
39
37
  class PersistentShell:
40
38
  """A persistent bash shell using subprocess.Popen for executing commands with state persistence."""
@@ -446,26 +444,20 @@ except Exception as e:
446
444
  # Apply the comprehensive Modal token solution as fallback
447
445
  try:
448
446
  # Import the comprehensive solution module
449
- # print("🔄 Applying comprehensive Modal token solution...")
450
447
  import modal_token_solution
451
- # print("✅ Comprehensive Modal token solution applied")
452
448
 
453
449
  # Set token variables for later use
454
450
  token = modal_token_solution.TOKEN_ID # For backward compatibility
455
451
  except Exception as e:
456
- # print(f"⚠️ Error applying comprehensive Modal token solution: {e}")
457
452
 
458
453
  # Fall back to the authentication patch
459
454
  try:
460
455
  # Import the patch module
461
- # print("🔄 Falling back to Modal authentication patch...")
462
456
  import modal_auth_patch
463
- # print("✅ Modal authentication patch applied")
464
457
 
465
458
  # Set token variables for later use
466
459
  token = modal_auth_patch.TOKEN_ID # For backward compatibility
467
460
  except Exception as e:
468
- # print(f"⚠️ Error applying Modal authentication patch: {e}")
469
461
 
470
462
  # Fall back to fix_modal_token.py
471
463
  try:
@@ -500,22 +492,14 @@ except Exception as e:
500
492
  token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
501
493
  except Exception as e:
502
494
  print(f"⚠️ Error running fix_modal_token.py: {e}")
503
-
504
- # Last resort: use hardcoded tokens
505
495
  token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
506
496
 
507
- # Print debug info
508
- # print(f"🔍 DEBUG: Checking environment variables")
509
- # print(f"🔍 Token ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
510
- # print(f"🔍 Token secret exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
511
- # print(f"🔍 Token exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
512
497
  if os.environ.get('MODAL_TOKEN_ID'):
513
498
  print(f"🔍 Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
514
499
  if os.environ.get('MODAL_TOKEN_SECRET'):
515
500
  print(f"🔍 Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
516
501
  if os.environ.get('MODAL_TOKEN'):
517
502
  print(f"🔍 Token length: {len(os.environ.get('MODAL_TOKEN'))}")
518
- # print(f"✅ Token setup completed")
519
503
 
520
504
  # Import modal after token setup
521
505
  import modal
@@ -544,168 +528,6 @@ def handle_interactive_input(prompt, is_password=False):
544
528
  print(f"❌ Error getting input: {e}")
545
529
  return None
546
530
 
547
- def handle_wandb_login(sandbox, current_dir):
548
- """Handle Weights & Biases login with proper API key input"""
549
- # Define _to_str function locally to avoid NameError
550
- def _to_str(maybe_bytes):
551
- try:
552
- return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
553
- except UnicodeDecodeError:
554
- # Handle non-UTF-8 bytes by replacing invalid characters
555
- if isinstance(maybe_bytes, (bytes, bytearray)):
556
- return maybe_bytes.decode('utf-8', errors='replace')
557
- else:
558
- return str(maybe_bytes)
559
- except Exception:
560
- # Last resort fallback
561
- return str(maybe_bytes)
562
-
563
- print("\n🔑 WEIGHTS & BIASES LOGIN")
564
- print("="*60)
565
- print("Setting up Weights & Biases credentials")
566
- print("You can get your API key from: https://wandb.ai/authorize")
567
-
568
- # Try to use credentials manager first
569
- api_key = None
570
- try:
571
- from credentials_manager import CredentialsManager
572
- credentials_manager = CredentialsManager()
573
- api_key = credentials_manager.get_wandb_api_key()
574
- except ImportError:
575
- # Fall back to direct input if credentials_manager is not available
576
- pass
577
-
578
- # If credentials manager didn't provide a key, use direct input
579
- if not api_key:
580
- # Get API key from user
581
- api_key = handle_interactive_input(
582
- "🔑 WEIGHTS & BIASES API KEY REQUIRED\n" +
583
- "Please paste your W&B API key below:\n" +
584
- "(Your API key should be 40 characters long)",
585
- is_password=True
586
- )
587
-
588
- if not api_key:
589
- print("❌ No API key provided. Cannot continue with W&B login.")
590
- return False, "", "No W&B API key provided"
591
-
592
- # Validate API key length
593
- if len(api_key) != 40:
594
- print(f"⚠️ Warning: API key should be 40 characters long, yours was {len(api_key)}")
595
- confirm = handle_interactive_input("Continue anyway? (yes/no)")
596
- if not confirm or confirm.lower() not in ["yes", "y"]:
597
- print("❌ W&B login cancelled.")
598
- return False, "", "W&B login cancelled"
599
-
600
- # Use non-interactive login
601
- cmd = f"wandb login {api_key}"
602
- print(f"🔄 Running non-interactive login command")
603
-
604
- # Execute the command
605
- result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
606
-
607
- # Collect output
608
- stdout_lines = []
609
- stderr_lines = []
610
-
611
- for line in result.stdout:
612
- line_str = _to_str(line)
613
- stdout_lines.append(line_str)
614
- sys.stdout.write(line_str)
615
- sys.stdout.flush()
616
-
617
- for line in result.stderr:
618
- line_str = _to_str(line)
619
- stderr_lines.append(line_str)
620
- sys.stderr.write(line_str)
621
- sys.stderr.flush()
622
-
623
- result.wait()
624
- exit_code = result.returncode
625
-
626
- stdout_buffer = ''.join(stdout_lines)
627
- stderr_buffer = ''.join(stderr_lines)
628
-
629
- if exit_code == 0:
630
- print("✅ Weights & Biases login successful")
631
- # Also set the environment variable for this session
632
- os.environ["WANDB_API_KEY"] = api_key
633
- print("✅ WANDB_API_KEY environment variable set")
634
- else:
635
- print(f"❌ Weights & Biases login failed with exit code {exit_code}")
636
- if stderr_buffer:
637
- print(f"Error: {stderr_buffer}")
638
-
639
- return exit_code == 0, stdout_buffer, stderr_buffer
640
-
641
- def handle_huggingface_login(sandbox, current_dir):
642
- """Handle Hugging Face login with proper token input"""
643
- # Define _to_str function locally to avoid NameError
644
- def _to_str(maybe_bytes):
645
- try:
646
- return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
647
- except UnicodeDecodeError:
648
- # Handle non-UTF-8 bytes by replacing invalid characters
649
- if isinstance(maybe_bytes, (bytes, bytearray)):
650
- return maybe_bytes.decode('utf-8', errors='replace')
651
- else:
652
- return str(maybe_bytes)
653
- except Exception:
654
- # Last resort fallback
655
- return str(maybe_bytes)
656
-
657
- print("\n🔑 HUGGING FACE LOGIN")
658
- print("="*60)
659
- print("Setting up Hugging Face credentials")
660
-
661
- # Get token from user
662
- token = prompt_for_hf_token()
663
- if not token:
664
- print("❌ No token provided. Cannot continue with Hugging Face login.")
665
- return False, "", "No Hugging Face token provided"
666
-
667
- # Use non-interactive login
668
- cmd = f"huggingface-cli login --token {token} --add-to-git-credential"
669
- print(f"🔄 Running non-interactive login command")
670
-
671
- # Execute the command
672
- result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
673
-
674
- # Collect output
675
- stdout_lines = []
676
- stderr_lines = []
677
-
678
- for line in result.stdout:
679
- line_str = _to_str(line)
680
- stdout_lines.append(line_str)
681
- sys.stdout.write(line_str)
682
- sys.stdout.flush()
683
-
684
- for line in result.stderr:
685
- line_str = _to_str(line)
686
- stderr_lines.append(line_str)
687
- sys.stderr.write(line_str)
688
- sys.stderr.flush()
689
-
690
- result.wait()
691
- exit_code = result.returncode
692
-
693
- stdout_buffer = ''.join(stdout_lines)
694
- stderr_buffer = ''.join(stderr_lines)
695
-
696
- if exit_code == 0:
697
- print("✅ Hugging Face login successful")
698
- # Also set the environment variable for this session
699
- os.environ["HF_TOKEN"] = token
700
- print("✅ HF_TOKEN environment variable set")
701
- else:
702
- print(f"❌ Hugging Face login failed with exit code {exit_code}")
703
- if stderr_buffer:
704
- print(f"Error: {stderr_buffer}")
705
-
706
- return exit_code == 0, stdout_buffer, stderr_buffer
707
-
708
-
709
531
  def get_stored_credentials():
710
532
  """Load stored credentials from ~/.gitarsenal/credentials.json"""
711
533
  import json
@@ -788,8 +610,6 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
788
610
  from fetch_modal_tokens import get_tokens
789
611
  _, _, api_key = get_tokens()
790
612
  if api_key:
791
- # print("✅ Successfully fetched OpenAI API key from server")
792
- # print(f"🔍 DEBUG: Fetched OpenAI API key value: {api_key}")
793
613
  # Set in environment for this session
794
614
  os.environ["OPENAI_API_KEY"] = api_key
795
615
  else:
@@ -930,29 +750,25 @@ System Information:
930
750
  print("🔍 Getting directory context for better debugging...")
931
751
 
932
752
  # Get current directory contents
933
- ls_result = sandbox.exec("bash", "-c", f"cd {current_dir} && ls -la")
753
+ ls_result = sandbox.exec("bash", "-c", "ls -la")
934
754
  ls_output = ""
935
755
  for line in ls_result.stdout:
936
756
  ls_output += _to_str(line)
937
757
  ls_result.wait()
938
758
 
939
- # Get parent directory contents if this isn't root
940
- parent_context = ""
941
- if current_dir != "/" and "/" in current_dir:
942
- parent_dir = os.path.dirname(current_dir)
943
- parent_result = sandbox.exec("bash", "-c", f"cd {parent_dir} && ls -la")
944
- parent_ls = ""
945
- for line in parent_result.stdout:
946
- parent_ls += _to_str(line)
947
- parent_result.wait()
948
- parent_context = f"\nParent directory ({parent_dir}) contents:\n{parent_ls}"
949
-
759
+ # Get parent directory contents
760
+ parent_result = sandbox.exec("bash", "-c", "ls -la ../")
761
+ parent_ls = ""
762
+ for line in parent_result.stdout:
763
+ parent_ls += _to_str(line)
764
+ parent_result.wait()
765
+
950
766
  directory_context = f"""
951
- Current directory: {current_dir}
952
-
953
- Directory contents:
767
+ Current directory contents:
954
768
  {ls_output}
955
- {parent_context}
769
+
770
+ Parent directory contents:
771
+ {parent_ls}
956
772
  """
957
773
  print("✅ Directory context gathered successfully")
958
774
 
@@ -966,11 +782,11 @@ Directory contents:
966
782
  file_path = file_path.strip("'\"")
967
783
  if not os.path.isabs(file_path):
968
784
  file_path = os.path.join(current_dir, file_path)
969
-
970
- # Try to get the parent directory if the file doesn't exist
971
- if '/' in file_path:
972
- parent_file_dir = os.path.dirname(file_path)
973
- relevant_files.append(parent_file_dir)
785
+
786
+ # Try to get the parent directory if the file doesn't exist
787
+ if '/' in file_path:
788
+ parent_file_dir = os.path.dirname(file_path)
789
+ relevant_files.append(parent_file_dir)
974
790
 
975
791
  # Look for package.json, requirements.txt, etc.
976
792
  common_config_files = ["package.json", "requirements.txt", "pyproject.toml", "setup.py",
@@ -1033,6 +849,12 @@ Directory contents:
1033
849
 
1034
850
  stored_credentials = get_stored_credentials()
1035
851
  auth_context = generate_auth_context(stored_credentials)
852
+
853
+
854
+ print("DEBUG: AUTH_CONTEXT SENT TO LLM:")
855
+ print("="*60)
856
+ print(auth_context)
857
+ print("="*60 + "\n")
1036
858
 
1037
859
  # Create a prompt for the LLM
1038
860
  print("\n" + "="*60)
@@ -1081,17 +903,25 @@ IMPORTANT GUIDELINES:
1081
903
 
1082
904
  4. For authentication issues:
1083
905
  - Analyze the error to determine what type of authentication is needed
1084
- - Use the actual credential values provided above (not placeholders)
906
+ - ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
907
+ - Look for the specific API key or token needed in the auth_context and use its exact value
1085
908
  - Common patterns:
1086
- * wandb errors: use wandb login with WANDB_API_KEY
1087
- * huggingface errors: use huggingface-cli login with HF_TOKEN or HUGGINGFACE_TOKEN
1088
- * github errors: configure git credentials with GITHUB_TOKEN
1089
- * kaggle errors: create ~/.kaggle/kaggle.json with KAGGLE_USERNAME and KAGGLE_KEY
1090
- * API errors: export the appropriate API key as environment variable
909
+ * wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
910
+ * huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
911
+ * github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
912
+ * kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
913
+ * API errors: export the appropriate API key as environment variable using the actual value from auth_context
1091
914
 
1092
915
  5. Environment variable exports:
1093
916
  - Use export commands for API keys that need to be in environment
1094
- - Use the actual credential values, not placeholders
917
+ - ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
918
+ - Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
919
+
920
+ 6. CRITICAL: When using any API key, token, or credential:
921
+ - Find the exact value in the AVAILABLE CREDENTIALS section
922
+ - Use that exact value in your command
923
+ - Do not use generic placeholders or dummy values
924
+ - The auth_context contains real, usable credentials
1095
925
 
1096
926
  Do not provide any explanations, just the exact command to run.
1097
927
  """
@@ -1362,31 +1192,6 @@ def prompt_for_hf_token():
1362
1192
  print(f"❌ Error getting token: {e}")
1363
1193
  return None
1364
1194
 
1365
-
1366
- def handle_interactive_input(prompt, is_password=False):
1367
- """Handle interactive input from the user with optional password masking"""
1368
- print("\n" + "="*60)
1369
- print(f"{prompt}")
1370
- print("="*60)
1371
-
1372
- try:
1373
- if is_password:
1374
- user_input = getpass.getpass("Input (hidden): ").strip()
1375
- else:
1376
- user_input = input("Input: ").strip()
1377
-
1378
- if not user_input:
1379
- print("❌ No input provided.")
1380
- return None
1381
- print("✅ Input received successfully!")
1382
- return user_input
1383
- except KeyboardInterrupt:
1384
- print("\n❌ Input cancelled by user.")
1385
- return None
1386
- except Exception as e:
1387
- print(f"❌ Error getting input: {e}")
1388
- return None
1389
-
1390
1195
  def generate_random_password(length=16):
1391
1196
  """Generate a random password for SSH access"""
1392
1197
  alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
@@ -1618,7 +1423,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1618
1423
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1619
1424
  "gpg", "ca-certificates", "software-properties-common"
1620
1425
  )
1621
- .uv_pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
1426
+ .pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
1622
1427
  .run_commands(
1623
1428
  # Create SSH directory
1624
1429
  "mkdir -p /var/run/sshd",
@@ -1668,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1668
1473
  serialized=True,
1669
1474
  volumes=volumes_config if volumes_config else None,
1670
1475
  )
1671
- def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1476
+ def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, stored_credentials=None):
1672
1477
  """Start SSH container with password authentication and optional setup."""
1673
1478
  import subprocess
1674
1479
  import time
1675
1480
  import os
1481
+ import json
1676
1482
 
1677
1483
  # Set root password
1678
1484
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
@@ -1680,37 +1486,52 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1680
1486
  # Set OpenAI API key if provided
1681
1487
  if openai_api_key:
1682
1488
  os.environ['OPENAI_API_KEY'] = openai_api_key
1683
- print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1489
+ # print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1684
1490
  else:
1685
1491
  print("⚠️ No OpenAI API key provided to container")
1686
1492
 
1687
- # Start SSH service
1688
- subprocess.run(["service", "ssh", "start"], check=True)
1689
-
1690
- # Clone repository if provided
1691
- repo_dir = "/root"
1692
- if repo_url:
1693
- repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1694
- print(f"📥 Cloning repository: {repo_url}")
1493
+ # Set up stored credentials in container environment
1494
+ if stored_credentials:
1495
+ print(f"🔐 Setting up {len(stored_credentials)} stored credentials in container...")
1496
+ for key, value in stored_credentials.items():
1497
+ # Set each credential as an environment variable
1498
+ env_var_name = key.upper().replace('-', '_').replace(' ', '_')
1499
+ os.environ[env_var_name] = value
1500
+ print(f" Set {env_var_name} in container environment")
1695
1501
 
1502
+ # Also save credentials to a file in the container for easy access
1696
1503
  try:
1697
- subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1698
- print(f"✅ Repository cloned successfully: {repo_name_from_url}")
1504
+ credentials_dir = "/root/.gitarsenal"
1505
+ os.makedirs(credentials_dir, exist_ok=True)
1506
+ credentials_file = os.path.join(credentials_dir, "credentials.json")
1507
+ with open(credentials_file, 'w') as f:
1508
+ json.dump(stored_credentials, f, indent=2)
1509
+ print(f"✅ Saved credentials to {credentials_file}")
1699
1510
 
1700
- # Change to repository directory
1701
- repo_dir = f"/root/{repo_name_from_url}"
1702
- if os.path.exists(repo_dir):
1703
- print(f"📂 Will run setup commands in repository directory: {repo_dir}")
1511
+ # Print available credentials for user reference
1512
+ print("\n🔐 AVAILABLE CREDENTIALS IN CONTAINER:")
1513
+ print("="*50)
1514
+ for key, value in stored_credentials.items():
1515
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
1516
+ env_var_name = key.upper().replace('-', '_').replace(' ', '_')
1517
+ print(f" {key} -> {env_var_name} = {masked_value}")
1518
+ print("="*50)
1519
+ print("💡 These credentials are available as environment variables and in /root/.gitarsenal/credentials.json")
1704
1520
 
1705
- except subprocess.CalledProcessError as e:
1706
- print(f" Failed to clone repository: {e}")
1521
+ except Exception as e:
1522
+ print(f"⚠️ Could not save credentials file: {e}")
1523
+ else:
1524
+ print("⚠️ No stored credentials provided to container")
1525
+
1526
+ # Start SSH service
1527
+ subprocess.run(["service", "ssh", "start"], check=True)
1707
1528
 
1708
1529
  # Run setup commands if provided using PersistentShell
1709
1530
  if setup_commands:
1710
1531
  print(f"⚙️ Running {len(setup_commands)} setup commands with persistent shell...")
1711
1532
 
1712
- # Create persistent shell instance
1713
- shell = PersistentShell(working_dir=repo_dir, timeout=120)
1533
+ # Create persistent shell instance starting in /root
1534
+ shell = PersistentShell(working_dir="/root", timeout=120)
1714
1535
 
1715
1536
  try:
1716
1537
  # Start the persistent shell
@@ -1808,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1808
1629
  with app.run():
1809
1630
  # Get the API key from environment
1810
1631
  api_key = os.environ.get("OPENAI_API_KEY")
1811
- ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key)
1632
+
1633
+ # Get stored credentials from local file
1634
+ stored_credentials = get_stored_credentials()
1635
+ if stored_credentials:
1636
+ print(f"🔐 Found {len(stored_credentials)} stored credentials to send to container")
1637
+ else:
1638
+ print("⚠️ No stored credentials found")
1639
+
1640
+ ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, stored_credentials)
1812
1641
 
1813
1642
  # Clean up Modal token after container is successfully created
1814
1643
  cleanup_modal_token()