gitarsenal-cli 1.6.10 → 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 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 scriptPath = require('../lib/sandbox').getPythonScriptPath();
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 scriptPath = require('../lib/sandbox').getPythonScriptPath();
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 scriptPath = require('../lib/sandbox').getPythonScriptPath();
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 scriptPath = require('../lib/sandbox').getPythonScriptPath();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.6.10",
3
+ "version": "1.6.11",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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 environment variable
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
- # Then try to fetch from server using fetch_modal_tokens if available
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
- prompt = "To debug failed commands, an OpenAI API key is needed.\nYou can get your API key from: https://platform.openai.com/api-keys"
142
- return self.get_credential("openai_api_key", prompt, is_password=True, validate_func=validate_openai_key)
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.get_openai_api_key()
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: Credentials manager API key value: {api_key}")
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
- # Finally, prompt the user if still no API key
440
+ # If still no GitArsenal API key found, skip debugging
441
441
  if not api_key:
442
- print("🔍 DEBUG: No API key found in any source, prompting user...")
443
- print("\n" + "="*60)
444
- print("🔑 OPENAI API KEY REQUIRED FOR DEBUGGING")
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("💡 To enable LLM debugging, set the OPENAI_API_KEY environment variable")
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",