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.
Binary file
|
package/package.json
CHANGED
@@ -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",
|
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
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
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:
|
952
|
-
|
953
|
-
Directory contents:
|
767
|
+
Current directory contents:
|
954
768
|
{ls_output}
|
955
|
-
|
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
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
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
|
-
|
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",
|
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
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
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:
|
952
|
-
|
953
|
-
Directory contents:
|
767
|
+
Current directory contents:
|
954
768
|
{ls_output}
|
955
|
-
|
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
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
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
|
-
|
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()
|