gitarsenal-cli 1.2.7 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gitarsenal.js +301 -20
- package/lib/sandbox.js +8 -8
- package/package.json +2 -2
- package/python/emoji_fix_patch.py +79 -0
- package/python/modal_logs_patch.py +86 -0
- package/python/test_modalSandboxScript.py +113 -143
- package/python/test_modalSandboxScript.py.bak +2742 -0
- package/python/test_modalSandboxScript.py.emoji_backup +2742 -0
- package/scripts/postinstall.js +1 -1
- package/python/__pycache__/credentials_manager.cpython-313.pyc +0 -0
- package/python/__pycache__/gitarsenal_proxy_client.cpython-313.pyc +0 -0
- package/python/__pycache__/setup_modal_token.cpython-313.pyc +0 -0
- package/python/__pycache__/test_modalSandboxScript.cpython-313.pyc +0 -0
@@ -687,11 +687,15 @@ def create_modal_sandbox(gpu_type, repo_url=None, repo_name=None, setup_commands
|
|
687
687
|
app_name = f"sandbox-{timestamp}"
|
688
688
|
|
689
689
|
gpu_configs = {
|
690
|
+
'T4': {'gpu': 'T4', 'memory': 16},
|
691
|
+
'L4': {'gpu': 'L4', 'memory': 24},
|
690
692
|
'A10G': {'gpu': 'A10G', 'memory': 24},
|
691
|
-
'A100': {'gpu': 'A100-SXM4-40GB', 'memory': 40},
|
693
|
+
'A100-40GB': {'gpu': 'A100-SXM4-40GB', 'memory': 40},
|
694
|
+
'A100-80GB': {'gpu': 'A100-80GB', 'memory': 80},
|
695
|
+
'L40S': {'gpu': 'L40S', 'memory': 48},
|
692
696
|
'H100': {'gpu': 'H100', 'memory': 80},
|
693
|
-
'
|
694
|
-
'
|
697
|
+
'H200': {'gpu': 'H200', 'memory': 141},
|
698
|
+
'B200': {'gpu': 'B200', 'memory': 96}
|
695
699
|
}
|
696
700
|
|
697
701
|
if gpu_type not in gpu_configs:
|
@@ -2163,7 +2167,7 @@ ssh_app = modal.App("ssh-container-app")
|
|
2163
2167
|
"echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
|
2164
2168
|
),
|
2165
2169
|
timeout=3600, # Default 1 hour timeout
|
2166
|
-
gpu="a10g", # Default GPU
|
2170
|
+
gpu="a10g", # Default GPU - this will be overridden when called
|
2167
2171
|
cpu=2,
|
2168
2172
|
memory=8192,
|
2169
2173
|
serialized=True,
|
@@ -2344,11 +2348,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2344
2348
|
app_name = f"ssh-container-{timestamp}"
|
2345
2349
|
|
2346
2350
|
gpu_configs = {
|
2351
|
+
'T4': {'gpu': 't4', 'memory': 16},
|
2352
|
+
'L4': {'gpu': 'l4', 'memory': 24},
|
2347
2353
|
'A10G': {'gpu': 'a10g', 'memory': 24},
|
2348
|
-
'A100': {'gpu': 'a100', 'memory': 40},
|
2354
|
+
'A100-40GB': {'gpu': 'a100', 'memory': 40},
|
2355
|
+
'A100-80GB': {'gpu': 'a100-80gb', 'memory': 80},
|
2356
|
+
'L40S': {'gpu': 'l40s', 'memory': 48},
|
2349
2357
|
'H100': {'gpu': 'h100', 'memory': 80},
|
2350
|
-
'
|
2351
|
-
'
|
2358
|
+
'H200': {'gpu': 'h200', 'memory': 141},
|
2359
|
+
'B200': {'gpu': 'b200', 'memory': 96}
|
2352
2360
|
}
|
2353
2361
|
|
2354
2362
|
if gpu_type not in gpu_configs:
|
@@ -3322,9 +3330,40 @@ def cleanup_modal_token():
|
|
3322
3330
|
except Exception as e:
|
3323
3331
|
print(f"❌ Error during Modal token cleanup: {e}")
|
3324
3332
|
|
3333
|
+
def show_usage_examples():
|
3334
|
+
"""Display usage examples for the command-line interface."""
|
3335
|
+
print("\033[92mUsage Examples\033[0m")
|
3336
|
+
print("")
|
3337
|
+
print("\033[92mBasic Container Creation\033[0m")
|
3338
|
+
print("\033[90m┌────────────────────────────────────────────────────────────────────────┐\033[0m")
|
3339
|
+
print("\033[90m│\033[0m \033[92mgitarsenal --gpu A10G --repo-url https://github.com/username/repo.git\033[0m \033[90m│\033[0m")
|
3340
|
+
print("\033[90m└────────────────────────────────────────────────────────────────────────┘\033[0m")
|
3341
|
+
print("")
|
3342
|
+
print("\033[92mWith Setup Commands\033[0m")
|
3343
|
+
print("\033[90m┌────────────────────────────────────────────────────────────────────────────────────────────────────┐\033[0m")
|
3344
|
+
print("\033[90m│\033[0m \033[92mgitarsenal --gpu A100 --repo-url https://github.com/username/repo.git \\\033[0m \033[90m│\033[0m")
|
3345
|
+
print("\033[90m│\033[0m \033[92m --setup-commands \"pip install -r requirements.txt\" \"python setup.py install\"\033[0m \033[90m│\033[0m")
|
3346
|
+
print("\033[90m└────────────────────────────────────────────────────────────────────────────────────────────────────┘\033[0m")
|
3347
|
+
print("")
|
3348
|
+
print("\033[92mWith Persistent Storage\033[0m")
|
3349
|
+
print("\033[90m┌────────────────────────────────────────────────────────────────────────────────────┐\033[0m")
|
3350
|
+
print("\033[90m│\033[0m \033[92mgitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\\033[0m \033[90m│\033[0m")
|
3351
|
+
print("\033[90m│\033[0m \033[92m --volume-name my-persistent-volume\033[0m \033[90m│\033[0m")
|
3352
|
+
print("\033[90m└────────────────────────────────────────────────────────────────────────────────────┘\033[0m")
|
3353
|
+
print("")
|
3354
|
+
print("\033[92mInteractive Mode\033[0m")
|
3355
|
+
print("\033[90m┌────────────────────────────┐\033[0m")
|
3356
|
+
print("\033[90m│\033[0m \033[92mgitarsenal --interactive\033[0m \033[90m│\033[0m")
|
3357
|
+
print("\033[90m└────────────────────────────┘\033[0m")
|
3358
|
+
print("")
|
3359
|
+
print("\033[92mAvailable GPU Options:\033[0m")
|
3360
|
+
print(" T4, L4, A10G, A100-40GB, A100-80GB, L40S, H100, H200, B200")
|
3361
|
+
print("")
|
3362
|
+
|
3325
3363
|
if __name__ == "__main__":
|
3326
3364
|
# Parse command line arguments when script is run directly
|
3327
3365
|
import argparse
|
3366
|
+
import sys
|
3328
3367
|
|
3329
3368
|
parser = argparse.ArgumentParser(description='Create a Modal SSH container with GPU')
|
3330
3369
|
parser.add_argument('--gpu', type=str, default='A10G', help='GPU type (default: A10G)')
|
@@ -3339,12 +3378,79 @@ if __name__ == "__main__":
|
|
3339
3378
|
parser.add_argument('--timeout', type=int, default=60, help='Container timeout in minutes (default: 60)')
|
3340
3379
|
parser.add_argument('--ssh-password', type=str, help='SSH password (random if not provided)')
|
3341
3380
|
parser.add_argument('--use-api', action='store_true', help='Fetch setup commands from API')
|
3381
|
+
parser.add_argument('--interactive', action='store_true', help='Run in interactive mode with prompts')
|
3382
|
+
parser.add_argument('--show-examples', action='store_true', help='Show usage examples')
|
3342
3383
|
|
3343
3384
|
args = parser.parse_args()
|
3344
3385
|
|
3386
|
+
# If no arguments or only --show-examples is provided, show usage examples
|
3387
|
+
if len(sys.argv) == 1 or args.show_examples:
|
3388
|
+
show_usage_examples()
|
3389
|
+
sys.exit(0)
|
3390
|
+
|
3345
3391
|
# Get setup commands from file if specified
|
3346
3392
|
setup_commands = args.setup_commands or []
|
3347
3393
|
|
3394
|
+
# If interactive mode is enabled, prompt for options
|
3395
|
+
if args.interactive:
|
3396
|
+
# If repo URL wasn't provided via command line, ask for it
|
3397
|
+
if not args.repo_url:
|
3398
|
+
args.repo_url = input("✔ Dependencies checked\n? Enter GitHub repository URL: ").strip()
|
3399
|
+
if not args.repo_url:
|
3400
|
+
print("❌ No repository URL provided. Exiting.")
|
3401
|
+
sys.exit(1)
|
3402
|
+
|
3403
|
+
# Ask about persistent volume
|
3404
|
+
use_volume = input("? Use persistent volume for faster installs? (Yes/No) [Yes]: ").strip().lower()
|
3405
|
+
if not use_volume or use_volume.startswith('y'):
|
3406
|
+
if not args.volume_name:
|
3407
|
+
args.volume_name = input("? Enter volume name [gitarsenal-volume]: ").strip()
|
3408
|
+
if not args.volume_name:
|
3409
|
+
args.volume_name = "gitarsenal-volume"
|
3410
|
+
else:
|
3411
|
+
args.volume_name = None
|
3412
|
+
|
3413
|
+
# Ask about auto-detecting setup commands
|
3414
|
+
use_api = input("? Automatically detect setup commands for this repository? (Yes/No) [Yes]: ").strip().lower()
|
3415
|
+
if not use_api or use_api.startswith('y'):
|
3416
|
+
args.use_api = True
|
3417
|
+
else:
|
3418
|
+
args.use_api = False
|
3419
|
+
|
3420
|
+
# GPU selection
|
3421
|
+
gpu_options = ['T4', 'L4', 'A10G', 'A100-40GB', 'A100-80GB', 'L40S', 'H100', 'H200', 'B200']
|
3422
|
+
print("\n? Select GPU type:")
|
3423
|
+
for i, gpu in enumerate(gpu_options, 1):
|
3424
|
+
print(f" {i}. {gpu}")
|
3425
|
+
|
3426
|
+
gpu_choice = input(f"Enter choice (1-{len(gpu_options)}) [default: 3 for A10G]: ").strip()
|
3427
|
+
if not gpu_choice:
|
3428
|
+
args.gpu = 'A10G' # Default
|
3429
|
+
else:
|
3430
|
+
try:
|
3431
|
+
gpu_index = int(gpu_choice) - 1
|
3432
|
+
if 0 <= gpu_index < len(gpu_options):
|
3433
|
+
args.gpu = gpu_options[gpu_index]
|
3434
|
+
else:
|
3435
|
+
print("⚠️ Invalid choice. Using default: A10G")
|
3436
|
+
args.gpu = 'A10G'
|
3437
|
+
except ValueError:
|
3438
|
+
print("⚠️ Invalid input. Using default: A10G")
|
3439
|
+
args.gpu = 'A10G'
|
3440
|
+
|
3441
|
+
# Show configuration summary
|
3442
|
+
print("\n📋 Container Configuration:")
|
3443
|
+
print(f"Repository URL: {args.repo_url}")
|
3444
|
+
print(f"GPU Type: {args.gpu}")
|
3445
|
+
print(f"Volume: {args.volume_name if args.volume_name else 'None'}")
|
3446
|
+
print(f"Setup Commands: {'Auto-detect from repository' if args.use_api else 'None'}")
|
3447
|
+
|
3448
|
+
# Confirm settings
|
3449
|
+
confirm = input("? Proceed with these settings? (Yes/No) [Yes]: ").strip().lower()
|
3450
|
+
if confirm and not confirm.startswith('y'):
|
3451
|
+
print("❌ Setup cancelled by user.")
|
3452
|
+
sys.exit(0)
|
3453
|
+
|
3348
3454
|
# If --use-api flag is set and repo_url is provided, fetch setup commands from API
|
3349
3455
|
if args.use_api and args.repo_url:
|
3350
3456
|
print("🔄 Using API to fetch setup commands")
|
@@ -3408,70 +3514,7 @@ if __name__ == "__main__":
|
|
3408
3514
|
|
3409
3515
|
except Exception as e:
|
3410
3516
|
print(f"⚠️ Error reading commands file: {e}")
|
3411
|
-
|
3412
|
-
# Execute setup script if provided
|
3413
|
-
if args.setup_script:
|
3414
|
-
print(f"📜 Setup script path: {args.setup_script}")
|
3415
|
-
|
3416
|
-
# Verify script exists
|
3417
|
-
if os.path.exists(args.setup_script):
|
3418
|
-
print(f"✅ Script exists at: {args.setup_script}")
|
3419
|
-
|
3420
|
-
# Check if script is executable
|
3421
|
-
if not os.access(args.setup_script, os.X_OK):
|
3422
|
-
print(f"⚠️ Script is not executable, setting permissions...")
|
3423
|
-
try:
|
3424
|
-
os.chmod(args.setup_script, 0o755)
|
3425
|
-
print(f"✅ Set executable permissions on script")
|
3426
|
-
except Exception as e:
|
3427
|
-
print(f"❌ Failed to set permissions: {e}")
|
3428
|
-
|
3429
|
-
working_dir = args.working_dir or os.getcwd()
|
3430
|
-
print(f"📂 Using working directory: {working_dir}")
|
3431
|
-
|
3432
|
-
# Execute the script directly instead of through container
|
3433
|
-
try:
|
3434
|
-
print(f"🔄 Executing script directly: bash {args.setup_script} {working_dir}")
|
3435
|
-
result = subprocess.run(['bash', args.setup_script, working_dir],
|
3436
|
-
capture_output=True, text=True)
|
3437
|
-
|
3438
|
-
print(f"📋 Script output:")
|
3439
|
-
print(result.stdout)
|
3440
|
-
|
3441
|
-
if result.returncode != 0:
|
3442
|
-
print(f"❌ Script execution failed with error code {result.returncode}")
|
3443
|
-
print(f"Error output: {result.stderr}")
|
3444
|
-
else:
|
3445
|
-
print(f"✅ Script executed successfully")
|
3446
|
-
|
3447
|
-
# Skip the regular setup commands since we executed the script directly
|
3448
|
-
setup_commands = []
|
3449
|
-
except Exception as e:
|
3450
|
-
print(f"❌ Failed to execute script: {e}")
|
3451
|
-
# Fall back to running the script through container
|
3452
|
-
setup_commands = [f"bash {args.setup_script} {working_dir}"]
|
3453
|
-
print("🔄 Falling back to running script through container")
|
3454
|
-
else:
|
3455
|
-
print(f"❌ Script not found at: {args.setup_script}")
|
3456
|
-
# Try to find the script in common locations
|
3457
|
-
possible_paths = [
|
3458
|
-
os.path.join(os.path.expanduser('~'), os.path.basename(args.setup_script)),
|
3459
|
-
os.path.join('/tmp', os.path.basename(args.setup_script)),
|
3460
|
-
os.path.join('/var/tmp', os.path.basename(args.setup_script))
|
3461
|
-
]
|
3462
3517
|
|
3463
|
-
found = False
|
3464
|
-
for test_path in possible_paths:
|
3465
|
-
if os.path.exists(test_path):
|
3466
|
-
print(f"🔍 Found script at alternative location: {test_path}")
|
3467
|
-
setup_commands = [f"bash {test_path} {args.working_dir or os.getcwd()}"]
|
3468
|
-
found = True
|
3469
|
-
break
|
3470
|
-
|
3471
|
-
if not found:
|
3472
|
-
print("❌ Could not find script in any location")
|
3473
|
-
setup_commands = []
|
3474
|
-
|
3475
3518
|
try:
|
3476
3519
|
result = create_modal_ssh_container(
|
3477
3520
|
args.gpu,
|
@@ -3490,79 +3533,6 @@ if __name__ == "__main__":
|
|
3490
3533
|
print(".", end="", flush=True)
|
3491
3534
|
except KeyboardInterrupt:
|
3492
3535
|
print("\n👋 Script exited. The SSH container will continue running.")
|
3493
|
-
if 'result' in locals() and result:
|
3494
|
-
container_id = None
|
3495
|
-
ssh_password = None
|
3496
|
-
|
3497
|
-
# Try to get container ID and SSH password from the result dictionary
|
3498
|
-
if isinstance(result, dict):
|
3499
|
-
container_id = result.get('container_id')
|
3500
|
-
ssh_password = result.get('ssh_password')
|
3501
|
-
elif hasattr(result, 'container_id'):
|
3502
|
-
container_id = result.container_id
|
3503
|
-
ssh_password = getattr(result, 'ssh_password', None)
|
3504
|
-
|
3505
|
-
# If we still don't have the container ID, try to read it from the file
|
3506
|
-
if not container_id:
|
3507
|
-
try:
|
3508
|
-
with open(os.path.expanduser("~/.modal_last_container_id"), "r") as f:
|
3509
|
-
container_id = f.read().strip()
|
3510
|
-
print(f"📋 Retrieved container ID from file: {container_id}")
|
3511
|
-
except Exception as e:
|
3512
|
-
print(f"⚠️ Could not read container ID from file: {e}")
|
3513
|
-
|
3514
|
-
if container_id:
|
3515
|
-
print(f"🚀 SSH connection information:")
|
3516
|
-
print(f" ssh root@{container_id}.modal.run")
|
3517
|
-
if ssh_password:
|
3518
|
-
print(f" Password: {ssh_password}")
|
3519
|
-
|
3520
|
-
# Try to open a new terminal window with SSH connection
|
3521
|
-
try:
|
3522
|
-
terminal_script = f'''
|
3523
|
-
tell application "Terminal"
|
3524
|
-
do script "echo 'Connecting to Modal SSH container...'; echo 'Password: {ssh_password or 'unknown'}'; ssh root@{container_id}.modal.run"
|
3525
|
-
activate
|
3526
|
-
end tell
|
3527
|
-
'''
|
3528
|
-
|
3529
|
-
subprocess.run(['osascript', '-e', terminal_script],
|
3530
|
-
capture_output=True, text=True, timeout=30)
|
3531
|
-
print("✅ New terminal window opened with SSH connection")
|
3532
|
-
|
3533
|
-
except Exception as e:
|
3534
|
-
print(f"⚠️ Failed to open terminal window: {e}")
|
3535
|
-
|
3536
|
-
# Try alternative approach with iTerm2
|
3537
|
-
try:
|
3538
|
-
iterm_script = f'''
|
3539
|
-
tell application "iTerm"
|
3540
|
-
create window with default profile
|
3541
|
-
tell current session of current window
|
3542
|
-
write text "echo 'Connecting to Modal SSH container...'; echo 'Password: {ssh_password or 'unknown'}'; ssh root@{container_id}.modal.run"
|
3543
|
-
end tell
|
3544
|
-
end tell
|
3545
|
-
'''
|
3546
|
-
|
3547
|
-
subprocess.run(['osascript', '-e', iterm_script],
|
3548
|
-
capture_output=True, text=True, timeout=30)
|
3549
|
-
print("✅ New iTerm2 window opened with SSH connection")
|
3550
|
-
|
3551
|
-
except Exception as e2:
|
3552
|
-
print(f"⚠️ Failed to open iTerm2 window: {e2}")
|
3553
|
-
print("📝 You can manually connect using:")
|
3554
|
-
print(f" ssh root@{container_id}.modal.run")
|
3555
|
-
if ssh_password:
|
3556
|
-
print(f" Password: {ssh_password}")
|
3557
|
-
print(" Or use Modal exec:")
|
3558
|
-
print(f" modal container exec --pty {container_id} bash")
|
3559
|
-
else:
|
3560
|
-
print("⚠️ Could not determine container ID")
|
3561
|
-
print("📝 You can manually connect using:")
|
3562
|
-
print(" modal container list")
|
3563
|
-
print(" modal container exec --pty <CONTAINER_ID> bash")
|
3564
|
-
|
3565
|
-
# Exit cleanly
|
3566
3536
|
sys.exit(0)
|
3567
3537
|
|
3568
3538
|
except KeyboardInterrupt:
|