gitarsenal-cli 1.7.4 → 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.4",
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,7 +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)
1036
856
  print(auth_context)
857
+ print("="*60 + "\n")
1037
858
 
1038
859
  # Create a prompt for the LLM
1039
860
  print("\n" + "="*60)
@@ -1371,31 +1192,6 @@ def prompt_for_hf_token():
1371
1192
  print(f"❌ Error getting token: {e}")
1372
1193
  return None
1373
1194
 
1374
-
1375
- def handle_interactive_input(prompt, is_password=False):
1376
- """Handle interactive input from the user with optional password masking"""
1377
- print("\n" + "="*60)
1378
- print(f"{prompt}")
1379
- print("="*60)
1380
-
1381
- try:
1382
- if is_password:
1383
- user_input = getpass.getpass("Input (hidden): ").strip()
1384
- else:
1385
- user_input = input("Input: ").strip()
1386
-
1387
- if not user_input:
1388
- print("❌ No input provided.")
1389
- return None
1390
- print("✅ Input received successfully!")
1391
- return user_input
1392
- except KeyboardInterrupt:
1393
- print("\n❌ Input cancelled by user.")
1394
- return None
1395
- except Exception as e:
1396
- print(f"❌ Error getting input: {e}")
1397
- return None
1398
-
1399
1195
  def generate_random_password(length=16):
1400
1196
  """Generate a random password for SSH access"""
1401
1197
  alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
@@ -1677,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1677
1473
  serialized=True,
1678
1474
  volumes=volumes_config if volumes_config else None,
1679
1475
  )
1680
- 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):
1681
1477
  """Start SSH container with password authentication and optional setup."""
1682
1478
  import subprocess
1683
1479
  import time
1684
1480
  import os
1481
+ import json
1685
1482
 
1686
1483
  # Set root password
1687
1484
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
@@ -1693,6 +1490,39 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1693
1490
  else:
1694
1491
  print("⚠️ No OpenAI API key provided to container")
1695
1492
 
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")
1501
+
1502
+ # Also save credentials to a file in the container for easy access
1503
+ try:
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}")
1510
+
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")
1520
+
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
+
1696
1526
  # Start SSH service
1697
1527
  subprocess.run(["service", "ssh", "start"], check=True)
1698
1528
 
@@ -1799,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1799
1629
  with app.run():
1800
1630
  # Get the API key from environment
1801
1631
  api_key = os.environ.get("OPENAI_API_KEY")
1802
- 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)
1803
1641
 
1804
1642
  # Clean up Modal token after container is successfully created
1805
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,7 +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)
1036
856
  print(auth_context)
857
+ print("="*60 + "\n")
1037
858
 
1038
859
  # Create a prompt for the LLM
1039
860
  print("\n" + "="*60)
@@ -1371,31 +1192,6 @@ def prompt_for_hf_token():
1371
1192
  print(f"❌ Error getting token: {e}")
1372
1193
  return None
1373
1194
 
1374
-
1375
- def handle_interactive_input(prompt, is_password=False):
1376
- """Handle interactive input from the user with optional password masking"""
1377
- print("\n" + "="*60)
1378
- print(f"{prompt}")
1379
- print("="*60)
1380
-
1381
- try:
1382
- if is_password:
1383
- user_input = getpass.getpass("Input (hidden): ").strip()
1384
- else:
1385
- user_input = input("Input: ").strip()
1386
-
1387
- if not user_input:
1388
- print("❌ No input provided.")
1389
- return None
1390
- print("✅ Input received successfully!")
1391
- return user_input
1392
- except KeyboardInterrupt:
1393
- print("\n❌ Input cancelled by user.")
1394
- return None
1395
- except Exception as e:
1396
- print(f"❌ Error getting input: {e}")
1397
- return None
1398
-
1399
1195
  def generate_random_password(length=16):
1400
1196
  """Generate a random password for SSH access"""
1401
1197
  alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
@@ -1677,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1677
1473
  serialized=True,
1678
1474
  volumes=volumes_config if volumes_config else None,
1679
1475
  )
1680
- 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):
1681
1477
  """Start SSH container with password authentication and optional setup."""
1682
1478
  import subprocess
1683
1479
  import time
1684
1480
  import os
1481
+ import json
1685
1482
 
1686
1483
  # Set root password
1687
1484
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
@@ -1693,6 +1490,39 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1693
1490
  else:
1694
1491
  print("⚠️ No OpenAI API key provided to container")
1695
1492
 
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")
1501
+
1502
+ # Also save credentials to a file in the container for easy access
1503
+ try:
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}")
1510
+
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")
1520
+
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
+
1696
1526
  # Start SSH service
1697
1527
  subprocess.run(["service", "ssh", "start"], check=True)
1698
1528
 
@@ -1799,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1799
1629
  with app.run():
1800
1630
  # Get the API key from environment
1801
1631
  api_key = os.environ.get("OPENAI_API_KEY")
1802
- 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)
1803
1641
 
1804
1642
  # Clean up Modal token after container is successfully created
1805
1643
  cleanup_modal_token()