gitarsenal-cli 1.6.9 → 1.6.11
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 +14 -11
- package/package.json +1 -1
- package/python/__pycache__/credentials_manager.cpython-313.pyc +0 -0
- package/python/credentials_manager.py +60 -6
- package/python/gitarsenal_keys.py +166 -0
- package/python/test_modalSandboxScript.py +15 -35
- package/test_modalSandboxScript.py +2 -1
package/bin/gitarsenal.js
CHANGED
@@ -283,7 +283,8 @@ async function handleKeysAdd(options) {
|
|
283
283
|
choices: [
|
284
284
|
{ name: 'OpenAI', value: 'openai' },
|
285
285
|
{ name: 'Weights & Biases', value: 'wandb' },
|
286
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
286
|
+
{ name: 'Hugging Face', value: 'huggingface' },
|
287
|
+
{ name: 'GitArsenal OpenAI (for debugging)', value: 'gitarsenal-openai' }
|
287
288
|
]
|
288
289
|
}
|
289
290
|
]);
|
@@ -305,11 +306,11 @@ async function handleKeysAdd(options) {
|
|
305
306
|
|
306
307
|
// Call Python script to add the key
|
307
308
|
const { spawn } = require('child_process');
|
308
|
-
const
|
309
|
+
const path = require('path');
|
310
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
309
311
|
|
310
312
|
const pythonProcess = spawn('python', [
|
311
313
|
scriptPath,
|
312
|
-
'keys',
|
313
314
|
'add',
|
314
315
|
'--service', service,
|
315
316
|
'--key', key
|
@@ -344,11 +345,11 @@ async function handleKeysList() {
|
|
344
345
|
|
345
346
|
// Call Python script to list keys
|
346
347
|
const { spawn } = require('child_process');
|
347
|
-
const
|
348
|
+
const path = require('path');
|
349
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
348
350
|
|
349
351
|
const pythonProcess = spawn('python', [
|
350
352
|
scriptPath,
|
351
|
-
'keys',
|
352
353
|
'list'
|
353
354
|
], { stdio: 'inherit' });
|
354
355
|
|
@@ -382,7 +383,8 @@ async function handleKeysView(options) {
|
|
382
383
|
choices: [
|
383
384
|
{ name: 'OpenAI', value: 'openai' },
|
384
385
|
{ name: 'Weights & Biases', value: 'wandb' },
|
385
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
386
|
+
{ name: 'Hugging Face', value: 'huggingface' },
|
387
|
+
{ name: 'GitArsenal OpenAI (for debugging)', value: 'gitarsenal-openai' }
|
386
388
|
]
|
387
389
|
}
|
388
390
|
]);
|
@@ -391,11 +393,11 @@ async function handleKeysView(options) {
|
|
391
393
|
|
392
394
|
// Call Python script to view the key
|
393
395
|
const { spawn } = require('child_process');
|
394
|
-
const
|
396
|
+
const path = require('path');
|
397
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
395
398
|
|
396
399
|
const pythonProcess = spawn('python', [
|
397
400
|
scriptPath,
|
398
|
-
'keys',
|
399
401
|
'view',
|
400
402
|
'--service', service
|
401
403
|
], { stdio: 'inherit' });
|
@@ -430,7 +432,8 @@ async function handleKeysDelete(options) {
|
|
430
432
|
choices: [
|
431
433
|
{ name: 'OpenAI', value: 'openai' },
|
432
434
|
{ name: 'Weights & Biases', value: 'wandb' },
|
433
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
435
|
+
{ name: 'Hugging Face', value: 'huggingface' },
|
436
|
+
{ name: 'GitArsenal OpenAI (for debugging)', value: 'gitarsenal-openai' }
|
434
437
|
]
|
435
438
|
}
|
436
439
|
]);
|
@@ -457,11 +460,11 @@ async function handleKeysDelete(options) {
|
|
457
460
|
|
458
461
|
// Call Python script to delete the key
|
459
462
|
const { spawn } = require('child_process');
|
460
|
-
const
|
463
|
+
const path = require('path');
|
464
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
461
465
|
|
462
466
|
const pythonProcess = spawn('python', [
|
463
467
|
scriptPath,
|
464
|
-
'keys',
|
465
468
|
'delete',
|
466
469
|
'--service', service
|
467
470
|
], { stdio: 'pipe' });
|
package/package.json
CHANGED
Binary file
|
@@ -115,17 +115,34 @@ class CredentialsManager:
|
|
115
115
|
return None
|
116
116
|
|
117
117
|
def get_openai_api_key(self):
|
118
|
-
"""Get OpenAI API key with validation"""
|
118
|
+
"""Get OpenAI API key with validation - for user's repository execution"""
|
119
119
|
def validate_openai_key(key):
|
120
120
|
# Basic validation - OpenAI keys usually start with "sk-" and are 51 chars
|
121
121
|
return key.startswith("sk-") and len(key) > 40
|
122
122
|
|
123
|
-
# First check
|
123
|
+
# First check stored credentials (user's key)
|
124
|
+
credentials = self.load_credentials()
|
125
|
+
if "openai_api_key" in credentials:
|
126
|
+
stored_key = credentials["openai_api_key"]
|
127
|
+
if validate_openai_key(stored_key):
|
128
|
+
return stored_key
|
129
|
+
|
130
|
+
# Then check environment variable
|
124
131
|
env_key = os.environ.get("OPENAI_API_KEY")
|
125
132
|
if env_key and validate_openai_key(env_key):
|
126
133
|
return env_key
|
127
134
|
|
128
|
-
#
|
135
|
+
# For user's repository execution, prompt if no key found
|
136
|
+
prompt = "An OpenAI API key is needed to run this repository.\nYou can get your API key from: https://platform.openai.com/api-keys"
|
137
|
+
return self.get_credential("openai_api_key", prompt, is_password=True, validate_func=validate_openai_key)
|
138
|
+
|
139
|
+
def get_gitarsenal_openai_api_key(self):
|
140
|
+
"""Get GitArsenal's OpenAI API key for debugging - never prompts user"""
|
141
|
+
def validate_openai_key(key):
|
142
|
+
# Basic validation - OpenAI keys usually start with "sk-" and are 51 chars
|
143
|
+
return key.startswith("sk-") and len(key) > 40
|
144
|
+
|
145
|
+
# First try to fetch from server using fetch_modal_tokens (GitArsenal's key)
|
129
146
|
try:
|
130
147
|
from fetch_modal_tokens import get_tokens
|
131
148
|
_, _, api_key = get_tokens()
|
@@ -136,10 +153,23 @@ class CredentialsManager:
|
|
136
153
|
except ImportError:
|
137
154
|
pass
|
138
155
|
except Exception as e:
|
139
|
-
print(f"⚠️ Error fetching API key from server: {e}")
|
156
|
+
print(f"⚠️ Error fetching GitArsenal API key from server: {e}")
|
157
|
+
|
158
|
+
# Then check environment variable (for development/testing)
|
159
|
+
env_key = os.environ.get("GITARSENAL_OPENAI_API_KEY")
|
160
|
+
if env_key and validate_openai_key(env_key):
|
161
|
+
return env_key
|
140
162
|
|
141
|
-
|
142
|
-
|
163
|
+
# Check for GitArsenal's key in credentials (for development)
|
164
|
+
credentials = self.load_credentials()
|
165
|
+
if "gitarsenal_openai_api_key" in credentials:
|
166
|
+
stored_key = credentials["gitarsenal_openai_api_key"]
|
167
|
+
if validate_openai_key(stored_key):
|
168
|
+
return stored_key
|
169
|
+
|
170
|
+
# If no GitArsenal key found, return None (don't prompt user)
|
171
|
+
print("⚠️ GitArsenal's OpenAI API key not available for debugging")
|
172
|
+
return None
|
143
173
|
|
144
174
|
def get_modal_token(self):
|
145
175
|
"""Get Modal token with basic validation"""
|
@@ -163,6 +193,13 @@ class CredentialsManager:
|
|
163
193
|
def validate_hf_token(token):
|
164
194
|
# HF tokens are typically non-empty strings
|
165
195
|
return bool(token) and len(token) > 8
|
196
|
+
|
197
|
+
# First check stored credentials
|
198
|
+
credentials = self.load_credentials()
|
199
|
+
if "huggingface_token" in credentials:
|
200
|
+
stored_token = credentials["huggingface_token"]
|
201
|
+
if validate_hf_token(stored_token):
|
202
|
+
return stored_token
|
166
203
|
|
167
204
|
prompt = "A Hugging Face token is required.\nYou can get your token from: https://huggingface.co/settings/tokens"
|
168
205
|
return self.get_credential("huggingface_token", prompt, is_password=True, validate_func=validate_hf_token)
|
@@ -172,6 +209,13 @@ class CredentialsManager:
|
|
172
209
|
def validate_wandb_key(key):
|
173
210
|
# W&B API keys are typically 40 characters
|
174
211
|
return len(key) == 40
|
212
|
+
|
213
|
+
# First check stored credentials
|
214
|
+
credentials = self.load_credentials()
|
215
|
+
if "wandb_api_key" in credentials:
|
216
|
+
stored_key = credentials["wandb_api_key"]
|
217
|
+
if validate_wandb_key(stored_key):
|
218
|
+
return stored_key
|
175
219
|
|
176
220
|
prompt = "A Weights & Biases API key is required.\nYou can get your API key from: https://wandb.ai/authorize"
|
177
221
|
return self.get_credential("wandb_api_key", prompt, is_password=True, validate_func=validate_wandb_key)
|
@@ -189,6 +233,16 @@ class CredentialsManager:
|
|
189
233
|
"""Clear all saved credentials"""
|
190
234
|
return self.save_credentials({})
|
191
235
|
|
236
|
+
def has_credential(self, key):
|
237
|
+
"""Check if a credential exists without prompting"""
|
238
|
+
credentials = self.load_credentials()
|
239
|
+
return key in credentials and credentials[key]
|
240
|
+
|
241
|
+
def get_credential_silently(self, key):
|
242
|
+
"""Get a credential without prompting if it exists"""
|
243
|
+
credentials = self.load_credentials()
|
244
|
+
return credentials.get(key)
|
245
|
+
|
192
246
|
def cleanup_security_files(self):
|
193
247
|
"""Clean up all security-related files including legacy files"""
|
194
248
|
try:
|
@@ -0,0 +1,166 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
GitArsenal Keys Management Script
|
4
|
+
Handles API key storage and retrieval for GitArsenal CLI
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
import sys
|
9
|
+
import json
|
10
|
+
import argparse
|
11
|
+
from pathlib import Path
|
12
|
+
from credentials_manager import CredentialsManager
|
13
|
+
|
14
|
+
def main():
|
15
|
+
parser = argparse.ArgumentParser(description='GitArsenal Keys Management')
|
16
|
+
parser.add_argument('command', choices=['add', 'list', 'view', 'delete'], help='Command to execute')
|
17
|
+
parser.add_argument('--service', help='Service name (openai, wandb, huggingface)')
|
18
|
+
parser.add_argument('--key', help='API key (for add command)')
|
19
|
+
|
20
|
+
args = parser.parse_args()
|
21
|
+
|
22
|
+
# Initialize credentials manager
|
23
|
+
credentials_manager = CredentialsManager()
|
24
|
+
|
25
|
+
if args.command == 'add':
|
26
|
+
handle_add(credentials_manager, args)
|
27
|
+
elif args.command == 'list':
|
28
|
+
handle_list(credentials_manager)
|
29
|
+
elif args.command == 'view':
|
30
|
+
handle_view(credentials_manager, args)
|
31
|
+
elif args.command == 'delete':
|
32
|
+
handle_delete(credentials_manager, args)
|
33
|
+
|
34
|
+
def handle_add(credentials_manager, args):
|
35
|
+
"""Handle adding a new API key"""
|
36
|
+
service = args.service
|
37
|
+
key = args.key
|
38
|
+
|
39
|
+
if not service:
|
40
|
+
print("❌ Service name is required. Use --service option.")
|
41
|
+
sys.exit(1)
|
42
|
+
|
43
|
+
if not key:
|
44
|
+
print("❌ API key is required. Use --key option.")
|
45
|
+
sys.exit(1)
|
46
|
+
|
47
|
+
# Map service names to credential keys
|
48
|
+
service_mapping = {
|
49
|
+
'openai': 'openai_api_key',
|
50
|
+
'wandb': 'wandb_api_key',
|
51
|
+
'huggingface': 'huggingface_token',
|
52
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
53
|
+
}
|
54
|
+
|
55
|
+
if service not in service_mapping:
|
56
|
+
print(f"❌ Unsupported service: {service}")
|
57
|
+
print("Supported services: openai, wandb, huggingface, gitarsenal-openai")
|
58
|
+
sys.exit(1)
|
59
|
+
|
60
|
+
credential_key = service_mapping[service]
|
61
|
+
|
62
|
+
# Save the credential
|
63
|
+
credentials = credentials_manager.load_credentials()
|
64
|
+
credentials[credential_key] = key
|
65
|
+
success = credentials_manager.save_credentials(credentials)
|
66
|
+
|
67
|
+
if success:
|
68
|
+
print(f"✅ API key for {service} added successfully")
|
69
|
+
print(f"📁 Stored in: {credentials_manager.credentials_file}")
|
70
|
+
else:
|
71
|
+
print("❌ Failed to save API key")
|
72
|
+
sys.exit(1)
|
73
|
+
|
74
|
+
def handle_list(credentials_manager):
|
75
|
+
"""Handle listing all stored API keys"""
|
76
|
+
credentials = credentials_manager.load_credentials()
|
77
|
+
|
78
|
+
if not credentials:
|
79
|
+
print("📭 No API keys stored")
|
80
|
+
return
|
81
|
+
|
82
|
+
print("🔑 Stored API Keys:")
|
83
|
+
print("=" * 50)
|
84
|
+
|
85
|
+
# Map credential keys to display names
|
86
|
+
key_mapping = {
|
87
|
+
'openai_api_key': 'OpenAI',
|
88
|
+
'wandb_api_key': 'Weights & Biases',
|
89
|
+
'huggingface_token': 'Hugging Face',
|
90
|
+
'gitarsenal_openai_api_key': 'GitArsenal OpenAI'
|
91
|
+
}
|
92
|
+
|
93
|
+
for key, value in credentials.items():
|
94
|
+
if key in key_mapping:
|
95
|
+
display_name = key_mapping[key]
|
96
|
+
masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
|
97
|
+
print(f" {display_name}: {masked_value}")
|
98
|
+
|
99
|
+
print(f"\n📁 Storage location: {credentials_manager.credentials_file}")
|
100
|
+
|
101
|
+
def handle_view(credentials_manager, args):
|
102
|
+
"""Handle viewing a specific API key (masked)"""
|
103
|
+
service = args.service
|
104
|
+
|
105
|
+
if not service:
|
106
|
+
print("❌ Service name is required. Use --service option.")
|
107
|
+
sys.exit(1)
|
108
|
+
|
109
|
+
# Map service names to credential keys
|
110
|
+
service_mapping = {
|
111
|
+
'openai': 'openai_api_key',
|
112
|
+
'wandb': 'wandb_api_key',
|
113
|
+
'huggingface': 'huggingface_token',
|
114
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
115
|
+
}
|
116
|
+
|
117
|
+
if service not in service_mapping:
|
118
|
+
print(f"❌ Unsupported service: {service}")
|
119
|
+
print("Supported services: openai, wandb, huggingface, gitarsenal-openai")
|
120
|
+
sys.exit(1)
|
121
|
+
|
122
|
+
credential_key = service_mapping[service]
|
123
|
+
credentials = credentials_manager.load_credentials()
|
124
|
+
|
125
|
+
if credential_key not in credentials:
|
126
|
+
print(f"❌ No API key found for {service}")
|
127
|
+
sys.exit(1)
|
128
|
+
|
129
|
+
value = credentials[credential_key]
|
130
|
+
masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
|
131
|
+
|
132
|
+
print(f"🔑 {service.upper()} API Key:")
|
133
|
+
print(f" {masked_value}")
|
134
|
+
print(f"📁 Stored in: {credentials_manager.credentials_file}")
|
135
|
+
|
136
|
+
def handle_delete(credentials_manager, args):
|
137
|
+
"""Handle deleting an API key"""
|
138
|
+
service = args.service
|
139
|
+
|
140
|
+
if not service:
|
141
|
+
print("❌ Service name is required. Use --service option.")
|
142
|
+
sys.exit(1)
|
143
|
+
|
144
|
+
# Map service names to credential keys
|
145
|
+
service_mapping = {
|
146
|
+
'openai': 'openai_api_key',
|
147
|
+
'wandb': 'wandb_api_key',
|
148
|
+
'huggingface': 'huggingface_token',
|
149
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
150
|
+
}
|
151
|
+
|
152
|
+
if service not in service_mapping:
|
153
|
+
print(f"❌ Unsupported service: {service}")
|
154
|
+
print("Supported services: openai, wandb, huggingface, gitarsenal-openai")
|
155
|
+
sys.exit(1)
|
156
|
+
|
157
|
+
credential_key = service_mapping[service]
|
158
|
+
success = credentials_manager.clear_credential(credential_key)
|
159
|
+
|
160
|
+
if success:
|
161
|
+
print(f"✅ API key for {service} deleted successfully")
|
162
|
+
else:
|
163
|
+
print(f"❌ No API key found for {service}")
|
164
|
+
|
165
|
+
if __name__ == "__main__":
|
166
|
+
main()
|
@@ -418,59 +418,38 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
|
|
418
418
|
except Exception as e:
|
419
419
|
print(f"⚠️ Could not load saved API key: {e}")
|
420
420
|
|
421
|
-
# Then try credentials manager
|
421
|
+
# Then try credentials manager for GitArsenal's debugging key
|
422
422
|
if not api_key:
|
423
|
-
print("🔍 DEBUG: Trying credentials manager...")
|
423
|
+
print("🔍 DEBUG: Trying credentials manager for GitArsenal's debugging key...")
|
424
424
|
try:
|
425
425
|
from credentials_manager import CredentialsManager
|
426
426
|
credentials_manager = CredentialsManager()
|
427
|
-
api_key = credentials_manager.
|
427
|
+
api_key = credentials_manager.get_gitarsenal_openai_api_key()
|
428
428
|
if api_key:
|
429
|
-
print(f"🔍 DEBUG: API key from credentials manager: Found")
|
430
|
-
print(f"🔍 DEBUG:
|
429
|
+
print(f"🔍 DEBUG: GitArsenal API key from credentials manager: Found")
|
430
|
+
print(f"🔍 DEBUG: GitArsenal API key value: {api_key}")
|
431
431
|
# Set in environment for this session
|
432
432
|
os.environ["OPENAI_API_KEY"] = api_key
|
433
433
|
else:
|
434
|
-
print(f"🔍 DEBUG: API key from credentials manager: Not found")
|
434
|
+
print(f"🔍 DEBUG: GitArsenal API key from credentials manager: Not found")
|
435
435
|
except ImportError as e:
|
436
436
|
print(f"🔍 DEBUG: Credentials manager not available: {e}")
|
437
437
|
# Fall back to direct input if credentials_manager is not available
|
438
438
|
pass
|
439
439
|
|
440
|
-
#
|
440
|
+
# If still no GitArsenal API key found, skip debugging
|
441
441
|
if not api_key:
|
442
|
-
print("🔍 DEBUG: No API key found
|
443
|
-
print("
|
444
|
-
|
445
|
-
print("="*60)
|
446
|
-
print("To debug failed commands, an OpenAI API key is needed.")
|
447
|
-
print("📝 Please paste your OpenAI API key below:")
|
448
|
-
print(" (Your input will be hidden for security)")
|
449
|
-
print("-" * 60)
|
450
|
-
|
451
|
-
try:
|
452
|
-
api_key = getpass.getpass("OpenAI API Key: ").strip()
|
453
|
-
if not api_key:
|
454
|
-
print("❌ No API key provided. Skipping debugging.")
|
455
|
-
return None
|
456
|
-
print("✅ API key received successfully!")
|
457
|
-
print(f"🔍 DEBUG: User-provided API key: {api_key}")
|
458
|
-
# Save the API key to environment for future use in this session
|
459
|
-
os.environ["OPENAI_API_KEY"] = api_key
|
460
|
-
except KeyboardInterrupt:
|
461
|
-
print("\n❌ API key input cancelled by user.")
|
462
|
-
return None
|
463
|
-
except Exception as e:
|
464
|
-
print(f"❌ Error getting API key: {e}")
|
465
|
-
return None
|
442
|
+
print("🔍 DEBUG: No GitArsenal API key found for debugging")
|
443
|
+
print("⚠️ Skipping LLM debugging - GitArsenal's API key not available")
|
444
|
+
return None
|
466
445
|
|
467
446
|
# If we still don't have an API key, we can't proceed
|
468
447
|
if not api_key:
|
469
|
-
print("❌ No OpenAI API key available. Cannot perform LLM debugging.")
|
470
|
-
print("💡
|
448
|
+
print("❌ No GitArsenal OpenAI API key available. Cannot perform LLM debugging.")
|
449
|
+
print("💡 GitArsenal's debugging API key is not available")
|
471
450
|
return None
|
472
451
|
|
473
|
-
print(f"✅ OpenAI API key available (length: {len(api_key)})")
|
452
|
+
print(f"✅ GitArsenal OpenAI API key available for debugging (length: {len(api_key)})")
|
474
453
|
|
475
454
|
# Gather additional context to help with debugging
|
476
455
|
directory_context = ""
|
@@ -1186,7 +1165,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1186
1165
|
|
1187
1166
|
# Use a more stable CUDA base image and avoid problematic packages
|
1188
1167
|
ssh_image = (
|
1189
|
-
modal.Image.from_registry("nvidia/cuda:12.4.0-runtime-ubuntu22.04", add_python="3.11")
|
1168
|
+
# modal.Image.from_registry("nvidia/cuda:12.4.0-runtime-ubuntu22.04", add_python="3.11")
|
1169
|
+
modal.Image.debian_slim()
|
1190
1170
|
.apt_install(
|
1191
1171
|
"openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
|
1192
1172
|
"python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
|
@@ -1186,7 +1186,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1186
1186
|
|
1187
1187
|
# Use a more stable CUDA base image and avoid problematic packages
|
1188
1188
|
ssh_image = (
|
1189
|
-
modal.Image.from_registry("nvidia/cuda:12.4.0-runtime-ubuntu22.04", add_python="3.11")
|
1189
|
+
# modal.Image.from_registry("nvidia/cuda:12.4.0-runtime-ubuntu22.04", add_python="3.11")
|
1190
|
+
modal.Image.debian_slim()
|
1190
1191
|
.apt_install(
|
1191
1192
|
"openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
|
1192
1193
|
"python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
|