gitarsenal-cli 1.4.6 → 1.4.8
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
package/python/gitarsenal.py
CHANGED
@@ -126,6 +126,7 @@ def main():
|
|
126
126
|
ssh_parser.add_argument("--timeout", type=int, default=60, help="Container timeout in minutes (default: 60)")
|
127
127
|
ssh_parser.add_argument("--use-proxy", action="store_true", help="Use Modal proxy service instead of direct Modal API")
|
128
128
|
ssh_parser.add_argument("--wait", action="store_true", help="Wait for container to be ready")
|
129
|
+
ssh_parser.add_argument("--interactive", action="store_true", help="Run in interactive mode with prompts")
|
129
130
|
|
130
131
|
# Sandbox command
|
131
132
|
sandbox_parser = subparsers.add_parser("sandbox", help="Create a Modal sandbox")
|
@@ -195,7 +196,8 @@ def main():
|
|
195
196
|
repo_url=args.repo_url,
|
196
197
|
repo_name=args.repo_name,
|
197
198
|
volume_name=args.volume_name,
|
198
|
-
timeout_minutes=args.timeout
|
199
|
+
timeout_minutes=args.timeout,
|
200
|
+
interactive=args.interactive
|
199
201
|
)
|
200
202
|
if result:
|
201
203
|
print("✅ SSH container created successfully")
|
@@ -13,7 +13,7 @@ import argparse
|
|
13
13
|
from pathlib import Path
|
14
14
|
|
15
15
|
# Parse command-line arguments
|
16
|
-
parser = argparse.ArgumentParser(
|
16
|
+
parser = argparse.ArgumentParser()
|
17
17
|
parser.add_argument('--proxy-url', help='URL of the proxy server')
|
18
18
|
parser.add_argument('--proxy-api-key', help='API key for the proxy server')
|
19
19
|
parser.add_argument('--gpu', default='A10G', help='GPU type to use')
|
@@ -2583,9 +2583,39 @@ def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_co
|
|
2583
2583
|
# Now modify the create_modal_ssh_container function to use the standalone ssh_container_function
|
2584
2584
|
|
2585
2585
|
def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
|
2586
|
-
volume_name=None, timeout_minutes=60, ssh_password=None):
|
2586
|
+
volume_name=None, timeout_minutes=60, ssh_password=None, interactive=False):
|
2587
2587
|
"""Create a Modal SSH container with GPU support and tunneling"""
|
2588
2588
|
|
2589
|
+
# Use interactive mode if specified
|
2590
|
+
if interactive:
|
2591
|
+
# If GPU type is not specified, prompt for it
|
2592
|
+
if not gpu_type:
|
2593
|
+
gpu_type = prompt_for_gpu()
|
2594
|
+
|
2595
|
+
# If repo URL is not specified, prompt for it
|
2596
|
+
if not repo_url:
|
2597
|
+
try:
|
2598
|
+
repo_url = input("? Enter GitHub repository URL: ").strip()
|
2599
|
+
if not repo_url:
|
2600
|
+
print("❌ Repository URL is required.")
|
2601
|
+
return None
|
2602
|
+
except KeyboardInterrupt:
|
2603
|
+
print("\n🛑 Setup cancelled.")
|
2604
|
+
return None
|
2605
|
+
|
2606
|
+
# If volume name is not specified, ask about persistent volume
|
2607
|
+
if not volume_name:
|
2608
|
+
try:
|
2609
|
+
use_volume = input("? Use persistent volume for faster installs? (Y/n): ").strip().lower()
|
2610
|
+
if use_volume in ('', 'y', 'yes'):
|
2611
|
+
volume_name = input("? Enter volume name: ").strip()
|
2612
|
+
if not volume_name:
|
2613
|
+
volume_name = "gitarsenal-volume"
|
2614
|
+
print(f"Using default volume name: {volume_name}")
|
2615
|
+
except KeyboardInterrupt:
|
2616
|
+
print("\n🛑 Setup cancelled.")
|
2617
|
+
return None
|
2618
|
+
|
2589
2619
|
# Check if Modal is authenticated
|
2590
2620
|
try:
|
2591
2621
|
# Print all environment variables for debugging
|
@@ -4064,13 +4094,134 @@ def get_setup_commands_from_gitingest(repo_url):
|
|
4064
4094
|
print("❌ All API endpoints failed")
|
4065
4095
|
return generate_fallback_commands(gitingest_data)
|
4066
4096
|
|
4097
|
+
def prompt_for_gpu():
|
4098
|
+
"""
|
4099
|
+
Prompt the user to select a GPU type from available options using arrow keys.
|
4100
|
+
Returns the selected GPU type.
|
4101
|
+
"""
|
4102
|
+
import sys
|
4103
|
+
import tty
|
4104
|
+
import termios
|
4105
|
+
|
4106
|
+
# Define available GPU types and their specifications
|
4107
|
+
gpu_specs = {
|
4108
|
+
'T4': {'gpu': 'T4', 'memory': '16GB'},
|
4109
|
+
'L4': {'gpu': 'L4', 'memory': '24GB'},
|
4110
|
+
'A10G': {'gpu': 'A10G', 'memory': '24GB'},
|
4111
|
+
'A100-40GB': {'gpu': 'A100-SXM4-40GB', 'memory': '40GB'},
|
4112
|
+
'A100-80GB': {'gpu': 'A100-80GB', 'memory': '80GB'},
|
4113
|
+
'L40S': {'gpu': 'L40S', 'memory': '48GB'},
|
4114
|
+
'H100': {'gpu': 'H100', 'memory': '80GB'},
|
4115
|
+
'H200': {'gpu': 'H200', 'memory': '141GB'},
|
4116
|
+
'B200': {'gpu': 'B200', 'memory': '141GB'}
|
4117
|
+
}
|
4118
|
+
|
4119
|
+
# Create a list of options
|
4120
|
+
options = list(gpu_specs.keys())
|
4121
|
+
selected_index = 2 # Default to A10G (index 2)
|
4122
|
+
|
4123
|
+
def get_key():
|
4124
|
+
"""Get a single keypress from the user."""
|
4125
|
+
fd = sys.stdin.fileno()
|
4126
|
+
old_settings = termios.tcgetattr(fd)
|
4127
|
+
try:
|
4128
|
+
tty.setraw(sys.stdin.fileno())
|
4129
|
+
ch = sys.stdin.read(1)
|
4130
|
+
if ch == '\x1b': # Escape sequence
|
4131
|
+
ch2 = sys.stdin.read(1)
|
4132
|
+
if ch2 == '[':
|
4133
|
+
ch3 = sys.stdin.read(1)
|
4134
|
+
if ch3 == 'A':
|
4135
|
+
return 'UP'
|
4136
|
+
elif ch3 == 'B':
|
4137
|
+
return 'DOWN'
|
4138
|
+
elif ch == '\r' or ch == '\n':
|
4139
|
+
return 'ENTER'
|
4140
|
+
elif ch == '\x03': # Ctrl+C
|
4141
|
+
return 'CTRL_C'
|
4142
|
+
return ch
|
4143
|
+
finally:
|
4144
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
4145
|
+
|
4146
|
+
def display_menu():
|
4147
|
+
"""Display the GPU selection menu with current selection highlighted."""
|
4148
|
+
print("\n📊 Available GPU Options:")
|
4149
|
+
print("┌─────────────┬──────────┐")
|
4150
|
+
print("│ GPU Type │ Memory │")
|
4151
|
+
print("├─────────────┼──────────┤")
|
4152
|
+
|
4153
|
+
for i, gpu_type in enumerate(options):
|
4154
|
+
specs = gpu_specs[gpu_type]
|
4155
|
+
# Calculate proper spacing for alignment
|
4156
|
+
number_part = f"{i+1}."
|
4157
|
+
if i == selected_index:
|
4158
|
+
prefix = "> "
|
4159
|
+
suffix = " ←"
|
4160
|
+
else:
|
4161
|
+
prefix = " "
|
4162
|
+
suffix = ""
|
4163
|
+
|
4164
|
+
# Ensure consistent width for GPU type column
|
4165
|
+
gpu_display = f"{prefix}{number_part} {gpu_type}"
|
4166
|
+
gpu_padded = f"{gpu_display:<12}" # Fixed width for GPU column
|
4167
|
+
|
4168
|
+
print(f"│ {gpu_padded} │ {specs['memory']:<7} │{suffix}")
|
4169
|
+
|
4170
|
+
print("└─────────────┴──────────┘")
|
4171
|
+
print("Use ↑/↓ arrows to select, Enter to confirm, Ctrl+C to cancel")
|
4172
|
+
|
4173
|
+
# Clear screen and show initial menu
|
4174
|
+
print("\033[2J\033[H", end="") # Clear screen and move cursor to top
|
4175
|
+
display_menu()
|
4176
|
+
|
4177
|
+
while True:
|
4178
|
+
try:
|
4179
|
+
key = get_key()
|
4180
|
+
|
4181
|
+
if key == 'UP':
|
4182
|
+
selected_index = (selected_index - 1) % len(options)
|
4183
|
+
print("\033[2J\033[H", end="") # Clear screen
|
4184
|
+
display_menu()
|
4185
|
+
elif key == 'DOWN':
|
4186
|
+
selected_index = (selected_index + 1) % len(options)
|
4187
|
+
print("\033[2J\033[H", end="") # Clear screen
|
4188
|
+
display_menu()
|
4189
|
+
elif key == 'ENTER':
|
4190
|
+
selected_gpu = options[selected_index]
|
4191
|
+
print(f"\n✅ Selected GPU: {selected_gpu}")
|
4192
|
+
return selected_gpu
|
4193
|
+
elif key == 'CTRL_C':
|
4194
|
+
print("\n🛑 Selection cancelled.")
|
4195
|
+
sys.exit(1)
|
4196
|
+
|
4197
|
+
except KeyboardInterrupt:
|
4198
|
+
print("\n🛑 Selection cancelled.")
|
4199
|
+
sys.exit(1)
|
4200
|
+
except Exception as e:
|
4201
|
+
print(f"\n❌ Error: {e}")
|
4202
|
+
# Fall back to simple input method
|
4203
|
+
try:
|
4204
|
+
choice = input("\n🔍 Select GPU type (number or name, default is A10G): ").strip()
|
4205
|
+
if not choice:
|
4206
|
+
return "A10G"
|
4207
|
+
if choice.isdigit():
|
4208
|
+
index = int(choice) - 1
|
4209
|
+
if 0 <= index < len(options):
|
4210
|
+
return options[index]
|
4211
|
+
elif choice in options:
|
4212
|
+
return choice
|
4213
|
+
return "A10G"
|
4214
|
+
except:
|
4215
|
+
return "A10G"
|
4216
|
+
|
4217
|
+
# Replace the existing GPU argument parsing in the main section
|
4067
4218
|
if __name__ == "__main__":
|
4068
4219
|
# Parse command line arguments when script is run directly
|
4069
4220
|
import argparse
|
4070
4221
|
import sys
|
4071
4222
|
|
4072
|
-
parser = argparse.ArgumentParser(
|
4073
|
-
parser.add_argument('--gpu', type=str,
|
4223
|
+
parser = argparse.ArgumentParser()
|
4224
|
+
parser.add_argument('--gpu', type=str, help='GPU type (e.g., A10G, T4, A100-80GB)')
|
4074
4225
|
parser.add_argument('--repo-url', type=str, help='Repository URL to clone')
|
4075
4226
|
parser.add_argument('--repo-name', type=str, help='Repository name override')
|
4076
4227
|
parser.add_argument('--setup-commands', type=str, nargs='+', help='Setup commands to run (deprecated)')
|
@@ -4085,20 +4236,105 @@ if __name__ == "__main__":
|
|
4085
4236
|
parser.add_argument('--use-gitingest', action='store_true', default=True, help='Use gitingest approach to fetch setup commands (default)')
|
4086
4237
|
parser.add_argument('--no-gitingest', action='store_true', help='Disable gitingest approach for setup commands')
|
4087
4238
|
parser.add_argument('--show-examples', action='store_true', help='Show usage examples')
|
4239
|
+
parser.add_argument('--list-gpus', action='store_true', help='List available GPU types with their specifications')
|
4240
|
+
parser.add_argument('--interactive', action='store_true', help='Run in interactive mode with prompts')
|
4088
4241
|
|
4089
4242
|
args = parser.parse_args()
|
4090
4243
|
|
4244
|
+
# If --list-gpus is specified, just show GPU options and exit
|
4245
|
+
if args.list_gpus:
|
4246
|
+
prompt_for_gpu()
|
4247
|
+
sys.exit(0)
|
4248
|
+
|
4091
4249
|
# If no arguments or only --show-examples is provided, show usage examples
|
4092
4250
|
if len(sys.argv) == 1 or args.show_examples:
|
4093
4251
|
show_usage_examples()
|
4094
4252
|
sys.exit(0)
|
4095
4253
|
|
4254
|
+
# Check for dependencies
|
4255
|
+
print("⠏ Checking dependencies...")
|
4256
|
+
print("--- Dependency Check ---")
|
4257
|
+
|
4258
|
+
# Check Python version
|
4259
|
+
python_version = sys.version.split()[0]
|
4260
|
+
print(f"✓ Python {python_version} found")
|
4261
|
+
|
4262
|
+
# Check Modal CLI
|
4263
|
+
try:
|
4264
|
+
subprocess.run(["modal", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
4265
|
+
print("✓ Modal CLI found")
|
4266
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
4267
|
+
print("❌ Modal CLI not found. Please install with: pip install modal")
|
4268
|
+
|
4269
|
+
# Check Gitingest CLI
|
4270
|
+
try:
|
4271
|
+
subprocess.run(["gitingest", "--help"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
4272
|
+
print("✓ Gitingest CLI found")
|
4273
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
4274
|
+
print("⚠️ Gitingest CLI not found (optional)")
|
4275
|
+
|
4276
|
+
# Check Git
|
4277
|
+
try:
|
4278
|
+
subprocess.run(["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
4279
|
+
print("✓ Git found")
|
4280
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
4281
|
+
print("❌ Git not found. Please install Git.")
|
4282
|
+
|
4283
|
+
print("------------------------")
|
4284
|
+
print("\n✔ Dependencies checked")
|
4285
|
+
|
4286
|
+
# Always prompt for GPU selection regardless of interactive mode
|
4287
|
+
gpu_type = prompt_for_gpu()
|
4288
|
+
args.gpu = gpu_type
|
4289
|
+
|
4290
|
+
# Interactive mode or missing required arguments
|
4291
|
+
if args.interactive or not args.repo_url or not args.volume_name:
|
4292
|
+
# Get repository URL if not provided
|
4293
|
+
repo_url = args.repo_url
|
4294
|
+
if not repo_url:
|
4295
|
+
try:
|
4296
|
+
repo_url = input("? Enter GitHub repository URL: ").strip()
|
4297
|
+
if not repo_url:
|
4298
|
+
print("❌ Repository URL is required.")
|
4299
|
+
sys.exit(1)
|
4300
|
+
except KeyboardInterrupt:
|
4301
|
+
print("\n🛑 Setup cancelled.")
|
4302
|
+
sys.exit(1)
|
4303
|
+
|
4304
|
+
# Ask about persistent volume
|
4305
|
+
volume_name = args.volume_name
|
4306
|
+
if not volume_name:
|
4307
|
+
try:
|
4308
|
+
use_volume = input("? Use persistent volume for faster installs? (Y/n): ").strip().lower()
|
4309
|
+
if use_volume in ('', 'y', 'yes'):
|
4310
|
+
volume_name = input("? Enter volume name: ").strip()
|
4311
|
+
if not volume_name:
|
4312
|
+
volume_name = "gitarsenal-volume"
|
4313
|
+
print(f"Using default volume name: {volume_name}")
|
4314
|
+
except KeyboardInterrupt:
|
4315
|
+
print("\n🛑 Setup cancelled.")
|
4316
|
+
sys.exit(1)
|
4317
|
+
|
4318
|
+
# Ask about setup commands
|
4319
|
+
use_gitingest = args.use_gitingest and not args.no_gitingest
|
4320
|
+
if not args.use_api and not args.setup_commands and not args.setup_commands_json:
|
4321
|
+
try:
|
4322
|
+
auto_detect = input("? Automatically detect setup commands for this repository? (Y/n): ").strip().lower()
|
4323
|
+
if auto_detect in ('n', 'no'):
|
4324
|
+
use_gitingest = False
|
4325
|
+
except KeyboardInterrupt:
|
4326
|
+
print("\n🛑 Setup cancelled.")
|
4327
|
+
sys.exit(1)
|
4328
|
+
|
4329
|
+
# Update args with interactive values
|
4330
|
+
args.repo_url = repo_url
|
4331
|
+
args.volume_name = volume_name
|
4332
|
+
args.use_gitingest = use_gitingest
|
4333
|
+
|
4096
4334
|
try:
|
4097
4335
|
# Get setup commands from file if specified
|
4098
4336
|
setup_commands = args.setup_commands or []
|
4099
4337
|
|
4100
|
-
# If --use-api flag is set and repo_url is provided, fetch setup commands from API
|
4101
|
-
|
4102
4338
|
# Use gitingest by default unless --no-gitingest is set
|
4103
4339
|
if args.repo_url and (args.use_gitingest and not args.no_gitingest):
|
4104
4340
|
print("🔄 Using gitingest approach to fetch setup commands (default)")
|
@@ -4215,7 +4451,8 @@ if __name__ == "__main__":
|
|
4215
4451
|
setup_commands=setup_commands,
|
4216
4452
|
volume_name=args.volume_name,
|
4217
4453
|
timeout_minutes=args.timeout,
|
4218
|
-
ssh_password=ssh_password
|
4454
|
+
ssh_password=ssh_password,
|
4455
|
+
interactive=args.interactive
|
4219
4456
|
)
|
4220
4457
|
except KeyboardInterrupt:
|
4221
4458
|
# print("\n\n🛑 Execution interrupted")
|