gitarsenal-cli 1.1.14 → 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 +79 -18
- package/python/setup_modal_token.py +12 -0
- package/python/test_modalSandboxScript.py +42 -19
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,17 +132,28 @@ 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:
|
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}")
|
144
|
+
|
145
|
+
# Try to use the token via CLI
|
123
146
|
import subprocess
|
124
147
|
result = subprocess.run(
|
125
|
-
["modal", "token", "
|
148
|
+
["modal", "token", "current"],
|
126
149
|
capture_output=True, text=True, check=False
|
127
150
|
)
|
151
|
+
|
128
152
|
if result.returncode == 0:
|
129
153
|
logger.info("Modal token set via CLI command")
|
130
154
|
else:
|
131
|
-
logger.warning(f"CLI token
|
155
|
+
logger.warning(f"CLI token check returned: {result.stderr}")
|
156
|
+
|
132
157
|
except Exception as cli_err:
|
133
158
|
logger.warning(f"Failed to set token via CLI: {cli_err}")
|
134
159
|
|
@@ -151,6 +176,16 @@ def setup_modal_auth():
|
|
151
176
|
@app.route('/api/health', methods=['GET'])
|
152
177
|
def health_check():
|
153
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"""
|
154
189
|
return jsonify({"status": "ok", "message": "Modal proxy service is running"})
|
155
190
|
|
156
191
|
@app.route('/api/create-api-key', methods=['POST'])
|
@@ -249,17 +284,37 @@ def create_ssh_container():
|
|
249
284
|
os.environ["MODAL_TOKEN"] = MODAL_TOKEN
|
250
285
|
logger.info(f"Set MODAL_TOKEN_ID in environment (length: {len(MODAL_TOKEN)})")
|
251
286
|
|
252
|
-
#
|
287
|
+
# Create the token file directly in the expected location
|
253
288
|
try:
|
254
|
-
import
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
)
|
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}")
|
296
|
+
|
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
|
301
|
+
|
259
302
|
if result.returncode == 0:
|
260
303
|
logger.info("Successfully set token via Modal CLI")
|
261
304
|
else:
|
262
305
|
logger.warning(f"Modal CLI token set returned: {result.stderr}")
|
306
|
+
|
307
|
+
# As a fallback, also create the token file in the expected location
|
308
|
+
try:
|
309
|
+
from pathlib import Path
|
310
|
+
modal_dir = Path.home() / ".modal"
|
311
|
+
modal_dir.mkdir(exist_ok=True)
|
312
|
+
token_file = modal_dir / "token.json"
|
313
|
+
with open(token_file, 'w') as f:
|
314
|
+
f.write(f'{{"token_id": "{MODAL_TOKEN}", "token": "{MODAL_TOKEN}"}}')
|
315
|
+
logger.info(f"Created Modal token file at {token_file}")
|
316
|
+
except Exception as file_err:
|
317
|
+
logger.warning(f"Failed to create Modal token file: {file_err}")
|
263
318
|
except Exception as e:
|
264
319
|
logger.warning(f"Failed to set token via CLI: {e}")
|
265
320
|
|
@@ -300,14 +355,20 @@ def create_ssh_container():
|
|
300
355
|
env_copy["MODAL_TOKEN_ID"] = MODAL_TOKEN
|
301
356
|
env_copy["MODAL_TOKEN"] = MODAL_TOKEN
|
302
357
|
|
303
|
-
#
|
358
|
+
# Create the token file directly in the expected location
|
304
359
|
try:
|
305
|
-
import
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
)
|
310
|
-
|
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")
|
367
|
+
|
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")
|
311
372
|
except Exception as e:
|
312
373
|
logger.warning(f"Failed to set token via CLI in thread: {e}")
|
313
374
|
|
@@ -41,6 +41,18 @@ def setup_modal_token():
|
|
41
41
|
os.environ["MODAL_TOKEN_ID"] = BUILT_IN_MODAL_TOKEN
|
42
42
|
os.environ["MODAL_TOKEN"] = BUILT_IN_MODAL_TOKEN
|
43
43
|
logger.info("Built-in Modal token set in environment")
|
44
|
+
|
45
|
+
# Also create the token file in the expected location
|
46
|
+
try:
|
47
|
+
modal_dir = Path.home() / ".modal"
|
48
|
+
modal_dir.mkdir(exist_ok=True)
|
49
|
+
token_file = modal_dir / "token.json"
|
50
|
+
with open(token_file, 'w') as f:
|
51
|
+
f.write(f'{{"token_id": "{BUILT_IN_MODAL_TOKEN}", "token": "{BUILT_IN_MODAL_TOKEN}"}}')
|
52
|
+
logger.info(f"Created Modal token file at {token_file}")
|
53
|
+
except Exception as e:
|
54
|
+
logger.warning(f"Failed to create Modal token file: {e}")
|
55
|
+
|
44
56
|
return True
|
45
57
|
|
46
58
|
def get_token_from_gitarsenal():
|
@@ -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
|
@@ -517,7 +540,7 @@ def create_modal_sandbox(gpu_type, repo_url=None, repo_name=None, setup_commands
|
|
517
540
|
try:
|
518
541
|
import subprocess
|
519
542
|
token_result = subprocess.run(
|
520
|
-
["modal", "token", "set",
|
543
|
+
["modal", "token", "set", "--from-env"],
|
521
544
|
capture_output=True, text=True
|
522
545
|
)
|
523
546
|
if token_result.returncode == 0:
|
@@ -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
|