gitarsenal-cli 1.6.10 → 1.6.12
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 +18 -30
- 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 +199 -0
- package/python/test_dynamic_services.py +67 -0
- package/python/test_interactive_keys.py +34 -0
- package/python/test_modalSandboxScript.py +2 -1
- package/test_modalSandboxScript.py +2 -1
package/bin/gitarsenal.js
CHANGED
@@ -60,7 +60,7 @@ const keysCmd = program
|
|
60
60
|
keysCmd
|
61
61
|
.command('add')
|
62
62
|
.description('Add an API key')
|
63
|
-
.option('-s, --service <service>', 'Service name (
|
63
|
+
.option('-s, --service <service>', 'Service name (any service supported)')
|
64
64
|
.option('-k, --key <key>', 'API key (if not provided, will prompt)')
|
65
65
|
.action(async (options) => {
|
66
66
|
await handleKeysAdd(options);
|
@@ -277,14 +277,10 @@ async function handleKeysAdd(options) {
|
|
277
277
|
spinner.stop();
|
278
278
|
const serviceAnswer = await inquirer.prompt([
|
279
279
|
{
|
280
|
-
type: '
|
280
|
+
type: 'input',
|
281
281
|
name: 'service',
|
282
|
-
message: '
|
283
|
-
|
284
|
-
{ name: 'OpenAI', value: 'openai' },
|
285
|
-
{ name: 'Weights & Biases', value: 'wandb' },
|
286
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
287
|
-
]
|
282
|
+
message: 'Enter service name:',
|
283
|
+
validate: (input) => input.trim() !== '' ? true : 'Service name is required'
|
288
284
|
}
|
289
285
|
]);
|
290
286
|
service = serviceAnswer.service;
|
@@ -305,11 +301,11 @@ async function handleKeysAdd(options) {
|
|
305
301
|
|
306
302
|
// Call Python script to add the key
|
307
303
|
const { spawn } = require('child_process');
|
308
|
-
const
|
304
|
+
const path = require('path');
|
305
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
309
306
|
|
310
307
|
const pythonProcess = spawn('python', [
|
311
308
|
scriptPath,
|
312
|
-
'keys',
|
313
309
|
'add',
|
314
310
|
'--service', service,
|
315
311
|
'--key', key
|
@@ -344,11 +340,11 @@ async function handleKeysList() {
|
|
344
340
|
|
345
341
|
// Call Python script to list keys
|
346
342
|
const { spawn } = require('child_process');
|
347
|
-
const
|
343
|
+
const path = require('path');
|
344
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
348
345
|
|
349
346
|
const pythonProcess = spawn('python', [
|
350
347
|
scriptPath,
|
351
|
-
'keys',
|
352
348
|
'list'
|
353
349
|
], { stdio: 'inherit' });
|
354
350
|
|
@@ -376,14 +372,10 @@ async function handleKeysView(options) {
|
|
376
372
|
spinner.stop();
|
377
373
|
const serviceAnswer = await inquirer.prompt([
|
378
374
|
{
|
379
|
-
type: '
|
375
|
+
type: 'input',
|
380
376
|
name: 'service',
|
381
|
-
message: '
|
382
|
-
|
383
|
-
{ name: 'OpenAI', value: 'openai' },
|
384
|
-
{ name: 'Weights & Biases', value: 'wandb' },
|
385
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
386
|
-
]
|
377
|
+
message: 'Enter service name:',
|
378
|
+
validate: (input) => input.trim() !== '' ? true : 'Service name is required'
|
387
379
|
}
|
388
380
|
]);
|
389
381
|
service = serviceAnswer.service;
|
@@ -391,11 +383,11 @@ async function handleKeysView(options) {
|
|
391
383
|
|
392
384
|
// Call Python script to view the key
|
393
385
|
const { spawn } = require('child_process');
|
394
|
-
const
|
386
|
+
const path = require('path');
|
387
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
395
388
|
|
396
389
|
const pythonProcess = spawn('python', [
|
397
390
|
scriptPath,
|
398
|
-
'keys',
|
399
391
|
'view',
|
400
392
|
'--service', service
|
401
393
|
], { stdio: 'inherit' });
|
@@ -424,14 +416,10 @@ async function handleKeysDelete(options) {
|
|
424
416
|
spinner.stop();
|
425
417
|
const serviceAnswer = await inquirer.prompt([
|
426
418
|
{
|
427
|
-
type: '
|
419
|
+
type: 'input',
|
428
420
|
name: 'service',
|
429
|
-
message: '
|
430
|
-
|
431
|
-
{ name: 'OpenAI', value: 'openai' },
|
432
|
-
{ name: 'Weights & Biases', value: 'wandb' },
|
433
|
-
{ name: 'Hugging Face', value: 'huggingface' }
|
434
|
-
]
|
421
|
+
message: 'Enter service name:',
|
422
|
+
validate: (input) => input.trim() !== '' ? true : 'Service name is required'
|
435
423
|
}
|
436
424
|
]);
|
437
425
|
service = serviceAnswer.service;
|
@@ -457,11 +445,11 @@ async function handleKeysDelete(options) {
|
|
457
445
|
|
458
446
|
// Call Python script to delete the key
|
459
447
|
const { spawn } = require('child_process');
|
460
|
-
const
|
448
|
+
const path = require('path');
|
449
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
461
450
|
|
462
451
|
const pythonProcess = spawn('python', [
|
463
452
|
scriptPath,
|
464
|
-
'keys',
|
465
453
|
'delete',
|
466
454
|
'--service', service
|
467
455
|
], { 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,199 @@
|
|
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
|
+
# Prompt for the API key interactively
|
45
|
+
print(f"\n🔑 {service.upper()} API KEY REQUIRED")
|
46
|
+
print("=" * 50)
|
47
|
+
|
48
|
+
# Get appropriate prompt based on service
|
49
|
+
prompts = {
|
50
|
+
'openai': "Please enter your OpenAI API key:\nYou can get your API key from: https://platform.openai.com/api-keys",
|
51
|
+
'wandb': "Please enter your Weights & Biases API key:\nYou can get your API key from: https://wandb.ai/authorize",
|
52
|
+
'huggingface': "Please enter your Hugging Face token:\nYou can get your token from: https://huggingface.co/settings/tokens",
|
53
|
+
'gitarsenal-openai': "Please enter GitArsenal's OpenAI API key for debugging:",
|
54
|
+
'claude': "Please enter your Claude API key:\nYou can get your API key from: https://console.anthropic.com/"
|
55
|
+
}
|
56
|
+
|
57
|
+
prompt = prompts.get(service, f"Please enter your {service} API key:")
|
58
|
+
print(prompt)
|
59
|
+
print("-" * 50)
|
60
|
+
|
61
|
+
try:
|
62
|
+
import getpass
|
63
|
+
key = getpass.getpass("API Key (hidden): ").strip()
|
64
|
+
if not key:
|
65
|
+
print("❌ No API key provided.")
|
66
|
+
sys.exit(1)
|
67
|
+
print("✅ API key received successfully!")
|
68
|
+
except KeyboardInterrupt:
|
69
|
+
print("\n❌ API key input cancelled by user.")
|
70
|
+
sys.exit(1)
|
71
|
+
except Exception as e:
|
72
|
+
print(f"❌ Error getting API key: {e}")
|
73
|
+
sys.exit(1)
|
74
|
+
|
75
|
+
# Generate credential key from service name
|
76
|
+
# Convert service name to a standardized key format
|
77
|
+
credential_key = f"{service.replace('-', '_')}_api_key"
|
78
|
+
|
79
|
+
# Special mappings for backward compatibility
|
80
|
+
special_mappings = {
|
81
|
+
'openai': 'openai_api_key',
|
82
|
+
'wandb': 'wandb_api_key',
|
83
|
+
'huggingface': 'huggingface_token',
|
84
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
85
|
+
}
|
86
|
+
|
87
|
+
# Use special mapping if it exists, otherwise use generated key
|
88
|
+
credential_key = special_mappings.get(service, credential_key)
|
89
|
+
|
90
|
+
# Save the credential
|
91
|
+
credentials = credentials_manager.load_credentials()
|
92
|
+
credentials[credential_key] = key
|
93
|
+
success = credentials_manager.save_credentials(credentials)
|
94
|
+
|
95
|
+
if success:
|
96
|
+
print(f"✅ API key for {service} added successfully")
|
97
|
+
print(f"📁 Stored in: {credentials_manager.credentials_file}")
|
98
|
+
else:
|
99
|
+
print("❌ Failed to save API key")
|
100
|
+
sys.exit(1)
|
101
|
+
|
102
|
+
def handle_list(credentials_manager):
|
103
|
+
"""Handle listing all stored API keys"""
|
104
|
+
credentials = credentials_manager.load_credentials()
|
105
|
+
|
106
|
+
if not credentials:
|
107
|
+
print("📭 No API keys stored")
|
108
|
+
return
|
109
|
+
|
110
|
+
print("🔑 Stored API Keys:")
|
111
|
+
print("=" * 50)
|
112
|
+
|
113
|
+
# Map credential keys to display names (for known services)
|
114
|
+
key_mapping = {
|
115
|
+
'openai_api_key': 'OpenAI',
|
116
|
+
'wandb_api_key': 'Weights & Biases',
|
117
|
+
'huggingface_token': 'Hugging Face',
|
118
|
+
'gitarsenal_openai_api_key': 'GitArsenal OpenAI',
|
119
|
+
'claude_api_key': 'Claude'
|
120
|
+
}
|
121
|
+
|
122
|
+
for key, value in credentials.items():
|
123
|
+
# Generate display name from key if not in mapping
|
124
|
+
if key in key_mapping:
|
125
|
+
display_name = key_mapping[key]
|
126
|
+
else:
|
127
|
+
# Convert key back to service name for display
|
128
|
+
service_name = key.replace('_api_key', '').replace('_token', '').replace('_', '-')
|
129
|
+
display_name = service_name.title()
|
130
|
+
|
131
|
+
masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
|
132
|
+
print(f" {display_name}: {masked_value}")
|
133
|
+
|
134
|
+
print(f"\n📁 Storage location: {credentials_manager.credentials_file}")
|
135
|
+
|
136
|
+
def handle_view(credentials_manager, args):
|
137
|
+
"""Handle viewing a specific API key (masked)"""
|
138
|
+
service = args.service
|
139
|
+
|
140
|
+
if not service:
|
141
|
+
print("❌ Service name is required. Use --service option.")
|
142
|
+
sys.exit(1)
|
143
|
+
|
144
|
+
# Generate credential key from service name
|
145
|
+
credential_key = f"{service.replace('-', '_')}_api_key"
|
146
|
+
|
147
|
+
# Special mappings for backward compatibility
|
148
|
+
special_mappings = {
|
149
|
+
'openai': 'openai_api_key',
|
150
|
+
'wandb': 'wandb_api_key',
|
151
|
+
'huggingface': 'huggingface_token',
|
152
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
153
|
+
}
|
154
|
+
|
155
|
+
# Use special mapping if it exists, otherwise use generated key
|
156
|
+
credential_key = special_mappings.get(service, credential_key)
|
157
|
+
credentials = credentials_manager.load_credentials()
|
158
|
+
|
159
|
+
if credential_key not in credentials:
|
160
|
+
print(f"❌ No API key found for {service}")
|
161
|
+
sys.exit(1)
|
162
|
+
|
163
|
+
value = credentials[credential_key]
|
164
|
+
masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
|
165
|
+
|
166
|
+
print(f"🔑 {service.upper()} API Key:")
|
167
|
+
print(f" {masked_value}")
|
168
|
+
print(f"📁 Stored in: {credentials_manager.credentials_file}")
|
169
|
+
|
170
|
+
def handle_delete(credentials_manager, args):
|
171
|
+
"""Handle deleting an API key"""
|
172
|
+
service = args.service
|
173
|
+
|
174
|
+
if not service:
|
175
|
+
print("❌ Service name is required. Use --service option.")
|
176
|
+
sys.exit(1)
|
177
|
+
|
178
|
+
# Generate credential key from service name
|
179
|
+
credential_key = f"{service.replace('-', '_')}_api_key"
|
180
|
+
|
181
|
+
# Special mappings for backward compatibility
|
182
|
+
special_mappings = {
|
183
|
+
'openai': 'openai_api_key',
|
184
|
+
'wandb': 'wandb_api_key',
|
185
|
+
'huggingface': 'huggingface_token',
|
186
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
187
|
+
}
|
188
|
+
|
189
|
+
# Use special mapping if it exists, otherwise use generated key
|
190
|
+
credential_key = special_mappings.get(service, credential_key)
|
191
|
+
success = credentials_manager.clear_credential(credential_key)
|
192
|
+
|
193
|
+
if success:
|
194
|
+
print(f"✅ API key for {service} deleted successfully")
|
195
|
+
else:
|
196
|
+
print(f"❌ No API key found for {service}")
|
197
|
+
|
198
|
+
if __name__ == "__main__":
|
199
|
+
main()
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Test script to verify dynamic service support
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import os
|
8
|
+
from gitarsenal_keys import main
|
9
|
+
|
10
|
+
def test_dynamic_services():
|
11
|
+
print("🧪 Testing GitArsenal Dynamic Service Support")
|
12
|
+
print("=" * 60)
|
13
|
+
|
14
|
+
# Test adding a new service (claude)
|
15
|
+
print("\n1. Testing Claude service addition...")
|
16
|
+
sys.argv = ['gitarsenal_keys.py', 'add', '--service', 'claude', '--key', 'sk-test-claude-key']
|
17
|
+
|
18
|
+
try:
|
19
|
+
main()
|
20
|
+
print("✅ Claude service test completed")
|
21
|
+
except SystemExit as e:
|
22
|
+
if e.code == 0:
|
23
|
+
print("✅ Claude service test completed successfully")
|
24
|
+
else:
|
25
|
+
print(f"❌ Claude service test failed with exit code {e.code}")
|
26
|
+
|
27
|
+
# Test adding another new service (custom-service)
|
28
|
+
print("\n2. Testing custom service addition...")
|
29
|
+
sys.argv = ['gitarsenal_keys.py', 'add', '--service', 'custom-service', '--key', 'custom-key-123']
|
30
|
+
|
31
|
+
try:
|
32
|
+
main()
|
33
|
+
print("✅ Custom service test completed")
|
34
|
+
except SystemExit as e:
|
35
|
+
if e.code == 0:
|
36
|
+
print("✅ Custom service test completed successfully")
|
37
|
+
else:
|
38
|
+
print(f"❌ Custom service test failed with exit code {e.code}")
|
39
|
+
|
40
|
+
# Test listing to see all services
|
41
|
+
print("\n3. Testing service listing...")
|
42
|
+
sys.argv = ['gitarsenal_keys.py', 'list']
|
43
|
+
|
44
|
+
try:
|
45
|
+
main()
|
46
|
+
print("✅ Service listing test completed")
|
47
|
+
except SystemExit as e:
|
48
|
+
if e.code == 0:
|
49
|
+
print("✅ Service listing test completed successfully")
|
50
|
+
else:
|
51
|
+
print(f"❌ Service listing test failed with exit code {e.code}")
|
52
|
+
|
53
|
+
# Test viewing a specific service
|
54
|
+
print("\n4. Testing service viewing...")
|
55
|
+
sys.argv = ['gitarsenal_keys.py', 'view', '--service', 'claude']
|
56
|
+
|
57
|
+
try:
|
58
|
+
main()
|
59
|
+
print("✅ Service viewing test completed")
|
60
|
+
except SystemExit as e:
|
61
|
+
if e.code == 0:
|
62
|
+
print("✅ Service viewing test completed successfully")
|
63
|
+
else:
|
64
|
+
print(f"❌ Service viewing test failed with exit code {e.code}")
|
65
|
+
|
66
|
+
if __name__ == "__main__":
|
67
|
+
test_dynamic_services()
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Test script to verify interactive key prompting functionality
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import os
|
8
|
+
from gitarsenal_keys import main
|
9
|
+
|
10
|
+
def test_interactive_prompting():
|
11
|
+
print("🧪 Testing GitArsenal Interactive Key Prompting")
|
12
|
+
print("=" * 60)
|
13
|
+
|
14
|
+
# Test that the script can be called with just service
|
15
|
+
print("\n1. Testing interactive prompting...")
|
16
|
+
print(" This should prompt for the API key when only service is provided")
|
17
|
+
|
18
|
+
# Simulate command line arguments
|
19
|
+
sys.argv = ['gitarsenal_keys.py', 'add', '--service', 'huggingface']
|
20
|
+
|
21
|
+
try:
|
22
|
+
# This will prompt for the key interactively
|
23
|
+
main()
|
24
|
+
print("✅ Interactive prompting test completed")
|
25
|
+
except SystemExit as e:
|
26
|
+
if e.code == 0:
|
27
|
+
print("✅ Interactive prompting test completed successfully")
|
28
|
+
else:
|
29
|
+
print(f"❌ Interactive prompting test failed with exit code {e.code}")
|
30
|
+
except Exception as e:
|
31
|
+
print(f"❌ Interactive prompting test failed with error: {e}")
|
32
|
+
|
33
|
+
if __name__ == "__main__":
|
34
|
+
test_interactive_prompting()
|
@@ -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",
|
@@ -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",
|