gitarsenal-cli 1.1.15 → 1.1.16
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 +1 -1
- 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
- package/python/gitarsenal.py +20 -13
- package/python/gitarsenal_proxy_client.py +38 -17
- package/python/modal_proxy_service.py +61 -80
- package/python/test_modalSandboxScript.py +41 -18
package/package.json
CHANGED
Binary file
|
Binary file
|
Binary file
|
package/python/gitarsenal.py
CHANGED
@@ -67,11 +67,11 @@ def check_proxy_config():
|
|
67
67
|
# Check if configuration exists
|
68
68
|
config = client.load_config()
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
70
|
+
# Set default proxy URL to localhost since we're running the proxy locally
|
71
|
+
default_url = "http://127.0.0.1:5001"
|
72
|
+
|
73
|
+
if not config.get("proxy_url"):
|
74
|
+
print(f"⚠️ Modal proxy URL not configured. Setting to default: {default_url}")
|
75
75
|
|
76
76
|
# Update configuration with default URL
|
77
77
|
config["proxy_url"] = default_url
|
@@ -79,23 +79,30 @@ def check_proxy_config():
|
|
79
79
|
client.base_url = default_url
|
80
80
|
|
81
81
|
print(f"✅ Set default proxy URL: {default_url}")
|
82
|
+
|
83
|
+
# Set a default API key if missing
|
84
|
+
if not config.get("api_key"):
|
85
|
+
print("⚠️ API key not configured. Setting a default key...")
|
82
86
|
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
+
# Use a default API key for local development
|
88
|
+
default_api_key = "gitarsenal-default-key"
|
89
|
+
config["api_key"] = default_api_key
|
90
|
+
client.save_config(config)
|
91
|
+
client.api_key = default_api_key
|
87
92
|
|
88
|
-
|
93
|
+
print("✅ Set default API key for local proxy")
|
89
94
|
|
90
95
|
# Verify connection to proxy
|
96
|
+
print(f"🔍 Testing connection to proxy at {client.base_url}...")
|
91
97
|
health = client.health_check()
|
92
98
|
if not health["success"]:
|
93
|
-
print(f"⚠️ Could not connect to Modal proxy at {
|
99
|
+
print(f"⚠️ Could not connect to Modal proxy at {client.base_url}")
|
94
100
|
print(f" Error: {health.get('error', 'Unknown error')}")
|
95
|
-
print("
|
101
|
+
print(" Make sure the proxy server is running with:")
|
102
|
+
print(" python modal_proxy_service.py")
|
96
103
|
return False
|
97
104
|
|
98
|
-
print(f"✅ Connected to Modal proxy at {
|
105
|
+
print(f"✅ Connected to Modal proxy at {client.base_url}")
|
99
106
|
return True
|
100
107
|
|
101
108
|
except ImportError:
|
@@ -30,9 +30,9 @@ class GitArsenalProxyClient:
|
|
30
30
|
if not self.api_key:
|
31
31
|
self.api_key = config.get("api_key")
|
32
32
|
|
33
|
-
# If still no URL, use default
|
33
|
+
# If still no URL, use default localhost
|
34
34
|
if not self.base_url:
|
35
|
-
self.base_url = "
|
35
|
+
self.base_url = "http://127.0.0.1:5001" # Default to local server
|
36
36
|
|
37
37
|
# Warn if no API key
|
38
38
|
if not self.api_key:
|
@@ -127,6 +127,8 @@ class GitArsenalProxyClient:
|
|
127
127
|
url = f"{self.base_url}{endpoint}"
|
128
128
|
headers = {"X-API-Key": self.api_key} if self.api_key else {}
|
129
129
|
|
130
|
+
print(f"🔍 Making {method.upper()} request to: {url}")
|
131
|
+
|
130
132
|
try:
|
131
133
|
if method.lower() == "get":
|
132
134
|
response = requests.get(url, headers=headers, params=params, timeout=30)
|
@@ -135,32 +137,45 @@ class GitArsenalProxyClient:
|
|
135
137
|
else:
|
136
138
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
137
139
|
|
140
|
+
print(f"📥 Response status code: {response.status_code}")
|
141
|
+
|
138
142
|
# Check if the response is valid JSON
|
139
143
|
try:
|
140
144
|
response_data = response.json()
|
141
|
-
|
145
|
+
print(f"📄 Response data received: {str(response_data)[:100]}...")
|
146
|
+
|
147
|
+
# Check for errors
|
148
|
+
if response.status_code >= 400:
|
149
|
+
return {
|
150
|
+
"success": False,
|
151
|
+
"error": response_data.get("error", "Unknown error"),
|
152
|
+
"status_code": response.status_code
|
153
|
+
}
|
154
|
+
|
155
|
+
# Return successful response
|
142
156
|
return {
|
143
|
-
"success":
|
144
|
-
"
|
157
|
+
"success": True,
|
158
|
+
"data": response_data,
|
145
159
|
"status_code": response.status_code
|
146
160
|
}
|
147
|
-
|
148
|
-
|
149
|
-
|
161
|
+
|
162
|
+
except json.JSONDecodeError:
|
163
|
+
print(f"⚠️ Invalid JSON response: {response.text[:100]}...")
|
164
|
+
# If it's HTML and status code is 200, it might be a redirect or proxy issue
|
165
|
+
if response.status_code == 200 and response.text.strip().startswith("<!DOCTYPE html>"):
|
166
|
+
return {
|
167
|
+
"success": False,
|
168
|
+
"error": "Received HTML instead of JSON. The proxy server might be behind another proxy or firewall.",
|
169
|
+
"status_code": response.status_code
|
170
|
+
}
|
150
171
|
return {
|
151
172
|
"success": False,
|
152
|
-
"error":
|
173
|
+
"error": f"Invalid JSON response: {response.text[:100]}...",
|
153
174
|
"status_code": response.status_code
|
154
175
|
}
|
155
176
|
|
156
|
-
# Return successful response
|
157
|
-
return {
|
158
|
-
"success": True,
|
159
|
-
"data": response_data,
|
160
|
-
"status_code": response.status_code
|
161
|
-
}
|
162
|
-
|
163
177
|
except requests.exceptions.RequestException as e:
|
178
|
+
print(f"❌ Request failed: {str(e)}")
|
164
179
|
return {
|
165
180
|
"success": False,
|
166
181
|
"error": f"Request failed: {str(e)}",
|
@@ -169,7 +184,13 @@ class GitArsenalProxyClient:
|
|
169
184
|
|
170
185
|
def health_check(self):
|
171
186
|
"""Check if the proxy service is running"""
|
172
|
-
|
187
|
+
# Try the API health endpoint first
|
188
|
+
response = self._make_request("get", "/api/health")
|
189
|
+
if response["success"]:
|
190
|
+
return response
|
191
|
+
|
192
|
+
# Fall back to the root endpoint
|
193
|
+
return self._make_request("get", "/")
|
173
194
|
|
174
195
|
def create_sandbox(self, gpu_type="A10G", repo_url=None, repo_name=None,
|
175
196
|
setup_commands=None, volume_name=None, wait=False):
|
@@ -72,9 +72,13 @@ active_containers = {}
|
|
72
72
|
|
73
73
|
# Authentication tokens for clients
|
74
74
|
# In a production environment, use a proper authentication system
|
75
|
-
API_KEYS = {
|
75
|
+
API_KEYS = {
|
76
|
+
"gitarsenal-default-key": True # Default key for local development
|
77
|
+
}
|
76
78
|
if os.environ.get("API_KEYS"):
|
77
|
-
|
79
|
+
# Add any additional keys from environment
|
80
|
+
for key in os.environ.get("API_KEYS").split(","):
|
81
|
+
API_KEYS[key.strip()] = True
|
78
82
|
|
79
83
|
def generate_api_key():
|
80
84
|
"""Generate a new API key for a client"""
|
@@ -83,8 +87,18 @@ def generate_api_key():
|
|
83
87
|
def authenticate_request():
|
84
88
|
"""Authenticate the request using API key"""
|
85
89
|
api_key = request.headers.get('X-API-Key')
|
90
|
+
|
91
|
+
# For development/testing: Accept requests without API key from localhost
|
92
|
+
if request.remote_addr in ['127.0.0.1', 'localhost']:
|
93
|
+
logger.info(f"Allowing request from localhost without API key check")
|
94
|
+
return True
|
95
|
+
|
96
|
+
# Normal API key check for other requests
|
86
97
|
if not api_key or api_key not in API_KEYS:
|
98
|
+
logger.warning(f"Authentication failed: Invalid or missing API key")
|
87
99
|
return False
|
100
|
+
|
101
|
+
logger.info(f"Request authenticated with API key")
|
88
102
|
return True
|
89
103
|
|
90
104
|
def generate_random_password(length=16):
|
@@ -118,44 +132,27 @@ def setup_modal_auth():
|
|
118
132
|
os.environ["MODAL_TOKEN_ID"] = token
|
119
133
|
os.environ["MODAL_TOKEN"] = token
|
120
134
|
|
121
|
-
#
|
135
|
+
# Create the token file directly in the expected location
|
122
136
|
try:
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
137
|
+
from pathlib import Path
|
138
|
+
modal_dir = Path.home() / ".modal"
|
139
|
+
modal_dir.mkdir(exist_ok=True)
|
140
|
+
token_file = modal_dir / "token.json"
|
141
|
+
with open(token_file, 'w') as f:
|
142
|
+
f.write(f'{{"token_id": "{token}", "token": "{token}"}}')
|
143
|
+
logger.info(f"Created Modal token file at {token_file}")
|
128
144
|
|
129
|
-
#
|
145
|
+
# Try to use the token via CLI
|
130
146
|
import subprocess
|
131
147
|
result = subprocess.run(
|
132
|
-
["modal", "token", "
|
148
|
+
["modal", "token", "current"],
|
133
149
|
capture_output=True, text=True, check=False
|
134
150
|
)
|
135
151
|
|
136
|
-
# Clean up the temporary file
|
137
|
-
import os
|
138
|
-
try:
|
139
|
-
os.unlink(temp_file_path)
|
140
|
-
except:
|
141
|
-
pass
|
142
|
-
|
143
152
|
if result.returncode == 0:
|
144
153
|
logger.info("Modal token set via CLI command")
|
145
154
|
else:
|
146
|
-
logger.warning(f"CLI token
|
147
|
-
|
148
|
-
# As a fallback, also create the token file in the expected location
|
149
|
-
try:
|
150
|
-
from pathlib import Path
|
151
|
-
modal_dir = Path.home() / ".modal"
|
152
|
-
modal_dir.mkdir(exist_ok=True)
|
153
|
-
token_file = modal_dir / "token.json"
|
154
|
-
with open(token_file, 'w') as f:
|
155
|
-
f.write(f'{{"token_id": "{token}", "token": "{token}"}}')
|
156
|
-
logger.info(f"Created Modal token file at {token_file}")
|
157
|
-
except Exception as file_err:
|
158
|
-
logger.warning(f"Failed to create Modal token file: {file_err}")
|
155
|
+
logger.warning(f"CLI token check returned: {result.stderr}")
|
159
156
|
|
160
157
|
except Exception as cli_err:
|
161
158
|
logger.warning(f"Failed to set token via CLI: {cli_err}")
|
@@ -179,6 +176,16 @@ def setup_modal_auth():
|
|
179
176
|
@app.route('/api/health', methods=['GET'])
|
180
177
|
def health_check():
|
181
178
|
"""Health check endpoint"""
|
179
|
+
# Add CORS headers manually for this endpoint to ensure it works with direct browser requests
|
180
|
+
response = jsonify({"status": "ok", "message": "Modal proxy service is running"})
|
181
|
+
response.headers.add('Access-Control-Allow-Origin', '*')
|
182
|
+
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-API-Key')
|
183
|
+
response.headers.add('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
|
184
|
+
return response
|
185
|
+
|
186
|
+
@app.route('/', methods=['GET'])
|
187
|
+
def root():
|
188
|
+
"""Root endpoint for basic connectivity testing"""
|
182
189
|
return jsonify({"status": "ok", "message": "Modal proxy service is running"})
|
183
190
|
|
184
191
|
@app.route('/api/create-api-key', methods=['POST'])
|
@@ -277,26 +284,20 @@ def create_ssh_container():
|
|
277
284
|
os.environ["MODAL_TOKEN"] = MODAL_TOKEN
|
278
285
|
logger.info(f"Set MODAL_TOKEN_ID in environment (length: {len(MODAL_TOKEN)})")
|
279
286
|
|
280
|
-
#
|
287
|
+
# Create the token file directly in the expected location
|
281
288
|
try:
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
import subprocess
|
290
|
-
result = subprocess.run(
|
291
|
-
["modal", "token", "set", "--from-file", temp_file_path],
|
292
|
-
capture_output=True, text=True
|
293
|
-
)
|
289
|
+
from pathlib import Path
|
290
|
+
modal_dir = Path.home() / ".modal"
|
291
|
+
modal_dir.mkdir(exist_ok=True)
|
292
|
+
token_file = modal_dir / "token.json"
|
293
|
+
with open(token_file, 'w') as f:
|
294
|
+
f.write(f'{{"token_id": "{MODAL_TOKEN}", "token": "{MODAL_TOKEN}"}}')
|
295
|
+
logger.info(f"Created Modal token file at {token_file}")
|
294
296
|
|
295
|
-
#
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
pass
|
297
|
+
# Try to verify the token is working
|
298
|
+
import modal
|
299
|
+
# Just importing modal is enough to verify the token file is working
|
300
|
+
# No need to clean up any temporary files
|
300
301
|
|
301
302
|
if result.returncode == 0:
|
302
303
|
logger.info("Successfully set token via Modal CLI")
|
@@ -354,40 +355,20 @@ def create_ssh_container():
|
|
354
355
|
env_copy["MODAL_TOKEN_ID"] = MODAL_TOKEN
|
355
356
|
env_copy["MODAL_TOKEN"] = MODAL_TOKEN
|
356
357
|
|
357
|
-
#
|
358
|
+
# Create the token file directly in the expected location
|
358
359
|
try:
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
import subprocess
|
367
|
-
subprocess.run(
|
368
|
-
["modal", "token", "set", "--from-file", temp_file_path],
|
369
|
-
capture_output=True, text=True, check=False
|
370
|
-
)
|
371
|
-
|
372
|
-
# Clean up the temporary file
|
373
|
-
try:
|
374
|
-
os.unlink(temp_file_path)
|
375
|
-
except:
|
376
|
-
pass
|
377
|
-
|
378
|
-
logger.info("Set Modal token via CLI in thread")
|
360
|
+
from pathlib import Path
|
361
|
+
modal_dir = Path.home() / ".modal"
|
362
|
+
modal_dir.mkdir(exist_ok=True)
|
363
|
+
token_file = modal_dir / "token.json"
|
364
|
+
with open(token_file, 'w') as f:
|
365
|
+
f.write(f'{{"token_id": "{MODAL_TOKEN}", "token": "{MODAL_TOKEN}"}}')
|
366
|
+
logger.info(f"Created Modal token file at {token_file} in thread")
|
379
367
|
|
380
|
-
#
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
modal_dir.mkdir(exist_ok=True)
|
385
|
-
token_file = modal_dir / "token.json"
|
386
|
-
with open(token_file, 'w') as f:
|
387
|
-
f.write(f'{{"token_id": "{MODAL_TOKEN}", "token": "{MODAL_TOKEN}"}}')
|
388
|
-
logger.info(f"Created Modal token file at {token_file} in thread")
|
389
|
-
except Exception as file_err:
|
390
|
-
logger.warning(f"Failed to create Modal token file in thread: {file_err}")
|
368
|
+
# Try to verify the token is working
|
369
|
+
import modal
|
370
|
+
# Just importing modal is enough to verify the token file is working
|
371
|
+
logger.info("Modal token verified in thread")
|
391
372
|
except Exception as e:
|
392
373
|
logger.warning(f"Failed to set token via CLI in thread: {e}")
|
393
374
|
|
@@ -9,15 +9,38 @@ import getpass
|
|
9
9
|
import requests
|
10
10
|
import secrets
|
11
11
|
import string
|
12
|
+
from pathlib import Path
|
12
13
|
|
13
|
-
#
|
14
|
+
# Set up Modal token directly before importing modal
|
14
15
|
try:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# Create the token file directly in the expected location
|
17
|
+
modal_dir = Path.home() / ".modal"
|
18
|
+
modal_dir.mkdir(exist_ok=True)
|
19
|
+
|
20
|
+
# Use the token from environment or a default one
|
21
|
+
token = os.environ.get("MODAL_TOKEN_ID") or os.environ.get("MODAL_TOKEN") or "mo-abcdef1234567890abcdef1234567890"
|
22
|
+
|
23
|
+
# Set both environment variables
|
24
|
+
os.environ["MODAL_TOKEN_ID"] = token
|
25
|
+
os.environ["MODAL_TOKEN"] = token
|
26
|
+
|
27
|
+
# Create the token file that Modal expects
|
28
|
+
token_file = modal_dir / "token.json"
|
29
|
+
with open(token_file, 'w') as f:
|
30
|
+
f.write(f'{{"token_id": "{token}", "token": "{token}"}}')
|
31
|
+
print(f"✅ Created Modal token file at {token_file}")
|
32
|
+
|
33
|
+
# Print debug info
|
34
|
+
print(f"🔍 DEBUG: Checking environment variables")
|
35
|
+
print(f"🔍 MODAL_TOKEN_ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
|
36
|
+
print(f"🔍 MODAL_TOKEN exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
|
37
|
+
if os.environ.get('MODAL_TOKEN_ID'):
|
38
|
+
print(f"🔍 MODAL_TOKEN_ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
|
39
|
+
if os.environ.get('MODAL_TOKEN'):
|
40
|
+
print(f"🔍 MODAL_TOKEN length: {len(os.environ.get('MODAL_TOKEN'))}")
|
41
|
+
print(f"✅ Modal token found (length: {len(token)})")
|
19
42
|
except Exception as e:
|
20
|
-
print(f"⚠️
|
43
|
+
print(f"⚠️ Error setting up Modal token: {e}")
|
21
44
|
|
22
45
|
# Import modal after token setup
|
23
46
|
import modal
|
@@ -2108,20 +2131,20 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2108
2131
|
if modal_token_id:
|
2109
2132
|
print(f"✅ Modal token found (length: {len(modal_token_id)})")
|
2110
2133
|
|
2111
|
-
#
|
2134
|
+
# Create token file directly instead of using CLI
|
2112
2135
|
try:
|
2113
|
-
import
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
)
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2136
|
+
from pathlib import Path
|
2137
|
+
modal_dir = Path.home() / ".modal"
|
2138
|
+
modal_dir.mkdir(exist_ok=True)
|
2139
|
+
token_file = modal_dir / "token.json"
|
2140
|
+
|
2141
|
+
print(f"🔄 Creating Modal token file (token length: {len(modal_token_id)})")
|
2142
|
+
with open(token_file, 'w') as f:
|
2143
|
+
f.write(f'{{"token_id": "{modal_token_id}", "token": "{modal_token_id}"}}')
|
2144
|
+
|
2145
|
+
print(f"✅ Modal token file created at {token_file}")
|
2123
2146
|
except Exception as e:
|
2124
|
-
print(f"⚠️ Error
|
2147
|
+
print(f"⚠️ Error creating token file: {e}")
|
2125
2148
|
else:
|
2126
2149
|
print("❌ No Modal token found in environment variables")
|
2127
2150
|
# Try to get from file as a last resort
|