gitarsenal-cli 1.8.4 → 1.8.6
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/activate_venv.sh +4 -0
- package/package.json +1 -1
- package/python/__pycache__/auth_manager.cpython-313.pyc +0 -0
- package/python/auth_manager.py +485 -0
- package/python/debug_delete.py +167 -0
- package/python/gitarsenal.py +188 -0
- package/python/gitarsenal_keys.py +43 -55
- package/python/test_modalSandboxScript.py +212 -2
- package/scripts/postinstall.js +87 -39
- package/test_modalSandboxScript.py +212 -2
package/activate_venv.sh
ADDED
package/package.json
CHANGED
Binary file
|
@@ -0,0 +1,485 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
GitArsenal Authentication Manager
|
4
|
+
|
5
|
+
Handles user registration, login, and session management for the CLI.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import json
|
10
|
+
import hashlib
|
11
|
+
import secrets
|
12
|
+
import getpass
|
13
|
+
import time
|
14
|
+
import requests
|
15
|
+
from pathlib import Path
|
16
|
+
from datetime import datetime, timedelta
|
17
|
+
from typing import Optional, Dict, Any
|
18
|
+
|
19
|
+
class AuthManager:
|
20
|
+
"""
|
21
|
+
Manages user authentication for GitArsenal CLI.
|
22
|
+
Handles registration, login, session management, and API key storage.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, config_dir=None):
|
26
|
+
"""Initialize the authentication manager"""
|
27
|
+
if config_dir:
|
28
|
+
self.config_dir = Path(config_dir)
|
29
|
+
else:
|
30
|
+
self.config_dir = Path.home() / ".gitarsenal"
|
31
|
+
|
32
|
+
self.auth_file = self.config_dir / "auth.json"
|
33
|
+
self.session_file = self.config_dir / "session.json"
|
34
|
+
self.ensure_config_dir()
|
35
|
+
|
36
|
+
# API endpoints for authentication (can be configured)
|
37
|
+
self.auth_api_url = os.getenv("GITARSENAL_AUTH_API", "https://api.gitarsenal.com/auth")
|
38
|
+
|
39
|
+
def ensure_config_dir(self):
|
40
|
+
"""Ensure the configuration directory exists with proper permissions"""
|
41
|
+
if not self.config_dir.exists():
|
42
|
+
self.config_dir.mkdir(parents=True)
|
43
|
+
# Set restrictive permissions on Unix-like systems
|
44
|
+
if os.name == 'posix':
|
45
|
+
self.config_dir.chmod(0o700) # Only owner can read/write/execute
|
46
|
+
|
47
|
+
def hash_password(self, password: str) -> str:
|
48
|
+
"""Hash a password using SHA-256 with salt"""
|
49
|
+
salt = secrets.token_hex(16)
|
50
|
+
hash_obj = hashlib.sha256()
|
51
|
+
hash_obj.update((password + salt).encode('utf-8'))
|
52
|
+
return f"{salt}${hash_obj.hexdigest()}"
|
53
|
+
|
54
|
+
def verify_password(self, password: str, hashed_password: str) -> bool:
|
55
|
+
"""Verify a password against its hash"""
|
56
|
+
try:
|
57
|
+
salt, hash_value = hashed_password.split('$', 1)
|
58
|
+
hash_obj = hashlib.sha256()
|
59
|
+
hash_obj.update((password + salt).encode('utf-8'))
|
60
|
+
return hash_obj.hexdigest() == hash_value
|
61
|
+
except ValueError:
|
62
|
+
return False
|
63
|
+
|
64
|
+
def generate_session_token(self) -> str:
|
65
|
+
"""Generate a secure session token"""
|
66
|
+
return secrets.token_urlsafe(32)
|
67
|
+
|
68
|
+
def load_auth_data(self) -> Dict[str, Any]:
|
69
|
+
"""Load authentication data from file"""
|
70
|
+
if not self.auth_file.exists():
|
71
|
+
return {}
|
72
|
+
|
73
|
+
try:
|
74
|
+
with open(self.auth_file, 'r') as f:
|
75
|
+
return json.load(f)
|
76
|
+
except (json.JSONDecodeError, IOError):
|
77
|
+
print("⚠️ Error reading auth file. Starting fresh.")
|
78
|
+
return {}
|
79
|
+
|
80
|
+
def save_auth_data(self, data: Dict[str, Any]) -> bool:
|
81
|
+
"""Save authentication data to file"""
|
82
|
+
try:
|
83
|
+
with open(self.auth_file, 'w') as f:
|
84
|
+
json.dump(data, f, indent=2)
|
85
|
+
|
86
|
+
# Set restrictive permissions on Unix-like systems
|
87
|
+
if os.name == 'posix':
|
88
|
+
self.auth_file.chmod(0o600) # Only owner can read/write
|
89
|
+
|
90
|
+
return True
|
91
|
+
except IOError as e:
|
92
|
+
print(f"❌ Error saving auth data: {e}")
|
93
|
+
return False
|
94
|
+
|
95
|
+
def load_session(self) -> Dict[str, Any]:
|
96
|
+
"""Load session data from file"""
|
97
|
+
if not self.session_file.exists():
|
98
|
+
return {}
|
99
|
+
|
100
|
+
try:
|
101
|
+
with open(self.session_file, 'r') as f:
|
102
|
+
session_data = json.load(f)
|
103
|
+
|
104
|
+
# Check if session is still valid
|
105
|
+
if session_data.get('expires_at'):
|
106
|
+
expires_at = datetime.fromisoformat(session_data['expires_at'])
|
107
|
+
if datetime.now() > expires_at:
|
108
|
+
print("⚠️ Session expired. Please login again.")
|
109
|
+
return {}
|
110
|
+
|
111
|
+
return session_data
|
112
|
+
except (json.JSONDecodeError, IOError):
|
113
|
+
return {}
|
114
|
+
|
115
|
+
def save_session(self, session_data: Dict[str, Any]) -> bool:
|
116
|
+
"""Save session data to file"""
|
117
|
+
try:
|
118
|
+
with open(self.session_file, 'w') as f:
|
119
|
+
json.dump(session_data, f, indent=2)
|
120
|
+
|
121
|
+
# Set restrictive permissions on Unix-like systems
|
122
|
+
if os.name == 'posix':
|
123
|
+
self.session_file.chmod(0o600) # Only owner can read/write
|
124
|
+
|
125
|
+
return True
|
126
|
+
except IOError as e:
|
127
|
+
print(f"❌ Error saving session: {e}")
|
128
|
+
return False
|
129
|
+
|
130
|
+
def clear_session(self):
|
131
|
+
"""Clear the current session"""
|
132
|
+
if self.session_file.exists():
|
133
|
+
self.session_file.unlink()
|
134
|
+
|
135
|
+
def is_authenticated(self) -> bool:
|
136
|
+
"""Check if user is currently authenticated"""
|
137
|
+
session = self.load_session()
|
138
|
+
return bool(session.get('user_id') and session.get('token'))
|
139
|
+
|
140
|
+
def get_current_user(self) -> Optional[Dict[str, Any]]:
|
141
|
+
"""Get current user information"""
|
142
|
+
session = self.load_session()
|
143
|
+
if not session.get('user_id'):
|
144
|
+
return None
|
145
|
+
|
146
|
+
auth_data = self.load_auth_data()
|
147
|
+
return auth_data.get('users', {}).get(session['user_id'])
|
148
|
+
|
149
|
+
def register_user(self, username: str, email: str, password: str) -> bool:
|
150
|
+
"""Register a new user"""
|
151
|
+
print("\n" + "="*60)
|
152
|
+
print("🔐 USER REGISTRATION")
|
153
|
+
print("="*60)
|
154
|
+
|
155
|
+
# Validate input
|
156
|
+
if not username or len(username) < 3:
|
157
|
+
print("❌ Username must be at least 3 characters long.")
|
158
|
+
return False
|
159
|
+
|
160
|
+
if not email or '@' not in email:
|
161
|
+
print("❌ Please provide a valid email address.")
|
162
|
+
return False
|
163
|
+
|
164
|
+
if not password or len(password) < 8:
|
165
|
+
print("❌ Password must be at least 8 characters long.")
|
166
|
+
return False
|
167
|
+
|
168
|
+
auth_data = self.load_auth_data()
|
169
|
+
users = auth_data.get('users', {})
|
170
|
+
|
171
|
+
# Check if username already exists
|
172
|
+
for user_id, user_data in users.items():
|
173
|
+
if user_data.get('username') == username:
|
174
|
+
print("❌ Username already exists. Please choose a different username.")
|
175
|
+
return False
|
176
|
+
if user_data.get('email') == email:
|
177
|
+
print("❌ Email already registered. Please use a different email.")
|
178
|
+
return False
|
179
|
+
|
180
|
+
# Create new user
|
181
|
+
user_id = secrets.token_hex(16)
|
182
|
+
hashed_password = self.hash_password(password)
|
183
|
+
|
184
|
+
new_user = {
|
185
|
+
'user_id': user_id,
|
186
|
+
'username': username,
|
187
|
+
'email': email,
|
188
|
+
'password_hash': hashed_password,
|
189
|
+
'created_at': datetime.now().isoformat(),
|
190
|
+
'api_keys': {},
|
191
|
+
'settings': {
|
192
|
+
'default_gpu': 'A10G',
|
193
|
+
'timeout_minutes': 60,
|
194
|
+
'interactive_mode': False
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
users[user_id] = new_user
|
199
|
+
auth_data['users'] = users
|
200
|
+
|
201
|
+
if self.save_auth_data(auth_data):
|
202
|
+
print("✅ Registration successful!")
|
203
|
+
print(f"Username: {username}")
|
204
|
+
print(f"Email: {email}")
|
205
|
+
print("\nYou can now login with your credentials.")
|
206
|
+
return True
|
207
|
+
else:
|
208
|
+
print("❌ Failed to save registration data.")
|
209
|
+
return False
|
210
|
+
|
211
|
+
def login_user(self, username: str, password: str) -> bool:
|
212
|
+
"""Login a user"""
|
213
|
+
print("\n" + "="*60)
|
214
|
+
print("🔐 USER LOGIN")
|
215
|
+
print("="*60)
|
216
|
+
|
217
|
+
auth_data = self.load_auth_data()
|
218
|
+
users = auth_data.get('users', {})
|
219
|
+
|
220
|
+
# Find user by username
|
221
|
+
user_id = None
|
222
|
+
user_data = None
|
223
|
+
|
224
|
+
for uid, user in users.items():
|
225
|
+
if user.get('username') == username:
|
226
|
+
user_id = uid
|
227
|
+
user_data = user
|
228
|
+
break
|
229
|
+
|
230
|
+
if not user_data:
|
231
|
+
print("❌ Username not found. Please register first.")
|
232
|
+
return False
|
233
|
+
|
234
|
+
# Verify password
|
235
|
+
if not self.verify_password(password, user_data['password_hash']):
|
236
|
+
print("❌ Invalid password.")
|
237
|
+
return False
|
238
|
+
|
239
|
+
# Create session
|
240
|
+
session_token = self.generate_session_token()
|
241
|
+
session_data = {
|
242
|
+
'user_id': user_id,
|
243
|
+
'token': session_token,
|
244
|
+
'username': username,
|
245
|
+
'created_at': datetime.now().isoformat(),
|
246
|
+
'expires_at': (datetime.now() + timedelta(days=30)).isoformat()
|
247
|
+
}
|
248
|
+
|
249
|
+
if self.save_session(session_data):
|
250
|
+
print("✅ Login successful!")
|
251
|
+
print(f"Welcome back, {username}!")
|
252
|
+
return True
|
253
|
+
else:
|
254
|
+
print("❌ Failed to create session.")
|
255
|
+
return False
|
256
|
+
|
257
|
+
def logout_user(self):
|
258
|
+
"""Logout the current user"""
|
259
|
+
self.clear_session()
|
260
|
+
print("✅ Logged out successfully.")
|
261
|
+
|
262
|
+
def change_password(self, current_password: str, new_password: str) -> bool:
|
263
|
+
"""Change user password"""
|
264
|
+
user = self.get_current_user()
|
265
|
+
if not user:
|
266
|
+
print("❌ Not logged in. Please login first.")
|
267
|
+
return False
|
268
|
+
|
269
|
+
# Verify current password
|
270
|
+
if not self.verify_password(current_password, user['password_hash']):
|
271
|
+
print("❌ Current password is incorrect.")
|
272
|
+
return False
|
273
|
+
|
274
|
+
# Validate new password
|
275
|
+
if not new_password or len(new_password) < 8:
|
276
|
+
print("❌ New password must be at least 8 characters long.")
|
277
|
+
return False
|
278
|
+
|
279
|
+
# Update password
|
280
|
+
auth_data = self.load_auth_data()
|
281
|
+
user_id = user['user_id']
|
282
|
+
auth_data['users'][user_id]['password_hash'] = self.hash_password(new_password)
|
283
|
+
|
284
|
+
if self.save_auth_data(auth_data):
|
285
|
+
print("✅ Password changed successfully!")
|
286
|
+
return True
|
287
|
+
else:
|
288
|
+
print("❌ Failed to update password.")
|
289
|
+
return False
|
290
|
+
|
291
|
+
def store_api_key(self, service: str, api_key: str) -> bool:
|
292
|
+
"""Store an API key for the current user"""
|
293
|
+
user = self.get_current_user()
|
294
|
+
if not user:
|
295
|
+
print("❌ Not logged in. Please login first.")
|
296
|
+
return False
|
297
|
+
|
298
|
+
auth_data = self.load_auth_data()
|
299
|
+
user_id = user['user_id']
|
300
|
+
|
301
|
+
if 'api_keys' not in auth_data['users'][user_id]:
|
302
|
+
auth_data['users'][user_id]['api_keys'] = {}
|
303
|
+
|
304
|
+
auth_data['users'][user_id]['api_keys'][service] = api_key
|
305
|
+
|
306
|
+
if self.save_auth_data(auth_data):
|
307
|
+
print(f"✅ {service} API key stored successfully!")
|
308
|
+
return True
|
309
|
+
else:
|
310
|
+
print(f"❌ Failed to store {service} API key.")
|
311
|
+
return False
|
312
|
+
|
313
|
+
def get_api_key(self, service: str) -> Optional[str]:
|
314
|
+
"""Get an API key for the current user"""
|
315
|
+
user = self.get_current_user()
|
316
|
+
if not user:
|
317
|
+
return None
|
318
|
+
|
319
|
+
return user.get('api_keys', {}).get(service)
|
320
|
+
|
321
|
+
def update_user_settings(self, settings: Dict[str, Any]) -> bool:
|
322
|
+
"""Update user settings"""
|
323
|
+
user = self.get_current_user()
|
324
|
+
if not user:
|
325
|
+
print("❌ Not logged in. Please login first.")
|
326
|
+
return False
|
327
|
+
|
328
|
+
auth_data = self.load_auth_data()
|
329
|
+
user_id = user['user_id']
|
330
|
+
|
331
|
+
if 'settings' not in auth_data['users'][user_id]:
|
332
|
+
auth_data['users'][user_id]['settings'] = {}
|
333
|
+
|
334
|
+
auth_data['users'][user_id]['settings'].update(settings)
|
335
|
+
|
336
|
+
if self.save_auth_data(auth_data):
|
337
|
+
print("✅ Settings updated successfully!")
|
338
|
+
return True
|
339
|
+
else:
|
340
|
+
print("❌ Failed to update settings.")
|
341
|
+
return False
|
342
|
+
|
343
|
+
def get_user_settings(self) -> Dict[str, Any]:
|
344
|
+
"""Get current user settings"""
|
345
|
+
user = self.get_current_user()
|
346
|
+
if not user:
|
347
|
+
return {}
|
348
|
+
|
349
|
+
return user.get('settings', {})
|
350
|
+
|
351
|
+
def delete_account(self, password: str) -> bool:
|
352
|
+
"""Delete user account"""
|
353
|
+
user = self.get_current_user()
|
354
|
+
if not user:
|
355
|
+
print("❌ Not logged in. Please login first.")
|
356
|
+
return False
|
357
|
+
|
358
|
+
# Verify password
|
359
|
+
if not self.verify_password(password, user['password_hash']):
|
360
|
+
print("❌ Password is incorrect.")
|
361
|
+
return False
|
362
|
+
|
363
|
+
# Confirm deletion
|
364
|
+
print("\n⚠️ WARNING: This action cannot be undone!")
|
365
|
+
print("All your data, including API keys and settings, will be permanently deleted.")
|
366
|
+
confirm = input("Type 'DELETE' to confirm: ").strip()
|
367
|
+
|
368
|
+
if confirm != 'DELETE':
|
369
|
+
print("❌ Account deletion cancelled.")
|
370
|
+
return False
|
371
|
+
|
372
|
+
# Delete user data
|
373
|
+
auth_data = self.load_auth_data()
|
374
|
+
user_id = user['user_id']
|
375
|
+
|
376
|
+
if user_id in auth_data.get('users', {}):
|
377
|
+
del auth_data['users'][user_id]
|
378
|
+
|
379
|
+
if self.save_auth_data(auth_data):
|
380
|
+
self.clear_session()
|
381
|
+
print("✅ Account deleted successfully.")
|
382
|
+
return True
|
383
|
+
else:
|
384
|
+
print("❌ Failed to delete account.")
|
385
|
+
return False
|
386
|
+
else:
|
387
|
+
print("❌ User data not found.")
|
388
|
+
return False
|
389
|
+
|
390
|
+
def show_user_info(self):
|
391
|
+
"""Display current user information"""
|
392
|
+
user = self.get_current_user()
|
393
|
+
if not user:
|
394
|
+
print("❌ Not logged in. Please login first.")
|
395
|
+
return
|
396
|
+
|
397
|
+
print("\n" + "="*60)
|
398
|
+
print("👤 USER INFORMATION")
|
399
|
+
print("="*60)
|
400
|
+
print(f"Username: {user['username']}")
|
401
|
+
print(f"Email: {user['email']}")
|
402
|
+
print(f"Member since: {user['created_at'][:10]}")
|
403
|
+
|
404
|
+
# Show API keys (masked)
|
405
|
+
api_keys = user.get('api_keys', {})
|
406
|
+
if api_keys:
|
407
|
+
print("\n🔑 API Keys:")
|
408
|
+
for service, key in api_keys.items():
|
409
|
+
masked_key = key[:8] + "..." + key[-4:] if len(key) > 12 else "***"
|
410
|
+
print(f" {service}: {masked_key}")
|
411
|
+
else:
|
412
|
+
print("\n🔑 API Keys: None configured")
|
413
|
+
|
414
|
+
# Show settings
|
415
|
+
settings = user.get('settings', {})
|
416
|
+
if settings:
|
417
|
+
print("\n⚙️ Settings:")
|
418
|
+
for key, value in settings.items():
|
419
|
+
print(f" {key}: {value}")
|
420
|
+
|
421
|
+
print("="*60)
|
422
|
+
|
423
|
+
def interactive_auth_flow(self) -> bool:
|
424
|
+
"""Interactive authentication flow"""
|
425
|
+
if self.is_authenticated():
|
426
|
+
user = self.get_current_user()
|
427
|
+
print(f"✅ Already logged in as: {user['username']}")
|
428
|
+
return True
|
429
|
+
|
430
|
+
print("\n" + "="*60)
|
431
|
+
print("🔐 GITARSENAL AUTHENTICATION")
|
432
|
+
print("="*60)
|
433
|
+
print("Welcome to GitArsenal CLI!")
|
434
|
+
print("You need to create an account or login to continue.")
|
435
|
+
print("="*60)
|
436
|
+
|
437
|
+
while True:
|
438
|
+
print("\nOptions:")
|
439
|
+
print("1. Login")
|
440
|
+
print("2. Register")
|
441
|
+
print("3. Exit")
|
442
|
+
|
443
|
+
choice = input("\nSelect an option (1-3): ").strip()
|
444
|
+
|
445
|
+
if choice == "1":
|
446
|
+
return self._login_flow()
|
447
|
+
elif choice == "2":
|
448
|
+
return self._register_flow()
|
449
|
+
elif choice == "3":
|
450
|
+
print("👋 Goodbye!")
|
451
|
+
return False
|
452
|
+
else:
|
453
|
+
print("❌ Invalid option. Please try again.")
|
454
|
+
|
455
|
+
def _login_flow(self) -> bool:
|
456
|
+
"""Interactive login flow"""
|
457
|
+
print("\n--- LOGIN ---")
|
458
|
+
username = input("Username: ").strip()
|
459
|
+
password = getpass.getpass("Password: ").strip()
|
460
|
+
|
461
|
+
if self.login_user(username, password):
|
462
|
+
return True
|
463
|
+
else:
|
464
|
+
retry = input("\nTry again? (y/n): ").strip().lower()
|
465
|
+
return retry == 'y' and self._login_flow()
|
466
|
+
|
467
|
+
def _register_flow(self) -> bool:
|
468
|
+
"""Interactive registration flow"""
|
469
|
+
print("\n--- REGISTRATION ---")
|
470
|
+
username = input("Username (min 3 characters): ").strip()
|
471
|
+
email = input("Email: ").strip()
|
472
|
+
password = getpass.getpass("Password (min 8 characters): ").strip()
|
473
|
+
confirm_password = getpass.getpass("Confirm password: ").strip()
|
474
|
+
|
475
|
+
if password != confirm_password:
|
476
|
+
print("❌ Passwords do not match.")
|
477
|
+
retry = input("\nTry again? (y/n): ").strip().lower()
|
478
|
+
return retry == 'y' and self._register_flow()
|
479
|
+
|
480
|
+
if self.register_user(username, email, password):
|
481
|
+
# Auto-login after registration
|
482
|
+
return self.login_user(username, password)
|
483
|
+
else:
|
484
|
+
retry = input("\nTry again? (y/n): ").strip().lower()
|
485
|
+
return retry == 'y' and self._register_flow()
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Debug script for GitArsenal Keys delete functionality
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
|
10
|
+
# Add the current directory to the path
|
11
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
12
|
+
|
13
|
+
from credentials_manager import CredentialsManager
|
14
|
+
from gitarsenal_keys import handle_delete
|
15
|
+
|
16
|
+
def test_delete_functionality():
|
17
|
+
"""Test the delete functionality step by step"""
|
18
|
+
print("🔍 Debugging GitArsenal Keys Delete Functionality")
|
19
|
+
print("=" * 60)
|
20
|
+
|
21
|
+
# Initialize credentials manager
|
22
|
+
credentials_manager = CredentialsManager()
|
23
|
+
|
24
|
+
print(f"📁 Credentials file: {credentials_manager.credentials_file}")
|
25
|
+
print(f"📁 Config directory: {credentials_manager.config_dir}")
|
26
|
+
|
27
|
+
# Check if credentials file exists
|
28
|
+
if credentials_manager.credentials_file.exists():
|
29
|
+
print("✅ Credentials file exists")
|
30
|
+
else:
|
31
|
+
print("❌ Credentials file does not exist")
|
32
|
+
return
|
33
|
+
|
34
|
+
# Load current credentials
|
35
|
+
credentials = credentials_manager.load_credentials()
|
36
|
+
print(f"📋 Current credentials: {list(credentials.keys())}")
|
37
|
+
|
38
|
+
# Test different service mappings
|
39
|
+
test_services = ['openai', 'wandb', 'huggingface', 'gitarsenal-openai', 'claude']
|
40
|
+
|
41
|
+
for service in test_services:
|
42
|
+
print(f"\n🔍 Testing service: {service}")
|
43
|
+
|
44
|
+
# Generate credential key from service name
|
45
|
+
credential_key = f"{service.replace('-', '_')}_api_key"
|
46
|
+
|
47
|
+
# Special mappings for backward compatibility
|
48
|
+
special_mappings = {
|
49
|
+
'openai': 'openai_api_key',
|
50
|
+
'wandb': 'wandb_api_key',
|
51
|
+
'huggingface': 'huggingface_token',
|
52
|
+
'gitarsenal-openai': 'gitarsenal_openai_api_key'
|
53
|
+
}
|
54
|
+
|
55
|
+
# Use special mapping if it exists, otherwise use generated key
|
56
|
+
credential_key = special_mappings.get(service, credential_key)
|
57
|
+
|
58
|
+
print(f" Service: {service}")
|
59
|
+
print(f" Generated key: {service.replace('-', '_')}_api_key")
|
60
|
+
print(f" Final key: {credential_key}")
|
61
|
+
print(f" Exists in credentials: {credential_key in credentials}")
|
62
|
+
|
63
|
+
if credential_key in credentials:
|
64
|
+
print(f" Current value: {credentials[credential_key][:8]}...")
|
65
|
+
|
66
|
+
# Test the clear_credential method directly
|
67
|
+
print(f"\n🧪 Testing clear_credential method directly")
|
68
|
+
|
69
|
+
# Try to delete a key that exists
|
70
|
+
existing_keys = list(credentials.keys())
|
71
|
+
if existing_keys:
|
72
|
+
test_key = existing_keys[0]
|
73
|
+
print(f" Testing deletion of: {test_key}")
|
74
|
+
|
75
|
+
# Check if key exists before deletion
|
76
|
+
before_delete = test_key in credentials
|
77
|
+
print(f" Key exists before deletion: {before_delete}")
|
78
|
+
|
79
|
+
# Delete the key
|
80
|
+
success = credentials_manager.clear_credential(test_key)
|
81
|
+
print(f" Deletion success: {success}")
|
82
|
+
|
83
|
+
# Check if key exists after deletion
|
84
|
+
credentials_after = credentials_manager.load_credentials()
|
85
|
+
after_delete = test_key in credentials_after
|
86
|
+
print(f" Key exists after deletion: {after_delete}")
|
87
|
+
|
88
|
+
# Restore the key for testing
|
89
|
+
if not after_delete and before_delete:
|
90
|
+
credentials_after[test_key] = credentials[test_key]
|
91
|
+
credentials_manager.save_credentials(credentials_after)
|
92
|
+
print(f" ✅ Key restored for further testing")
|
93
|
+
|
94
|
+
# Test with a non-existent key
|
95
|
+
print(f"\n🧪 Testing deletion of non-existent key")
|
96
|
+
fake_key = "fake_api_key_12345"
|
97
|
+
success = credentials_manager.clear_credential(fake_key)
|
98
|
+
print(f" Deletion of non-existent key success: {success}")
|
99
|
+
|
100
|
+
print(f"\n✅ Debug test completed!")
|
101
|
+
|
102
|
+
def test_delete_command():
|
103
|
+
"""Test the delete command with mock arguments"""
|
104
|
+
print(f"\n🔧 Testing delete command with mock arguments")
|
105
|
+
print("=" * 60)
|
106
|
+
|
107
|
+
from gitarsenal_keys import handle_delete
|
108
|
+
import argparse
|
109
|
+
|
110
|
+
# Create mock arguments
|
111
|
+
class MockArgs:
|
112
|
+
def __init__(self, service):
|
113
|
+
self.service = service
|
114
|
+
|
115
|
+
# Test with different services
|
116
|
+
test_services = ['openai', 'wandb', 'huggingface']
|
117
|
+
|
118
|
+
for service in test_services:
|
119
|
+
print(f"\n🔍 Testing delete command for: {service}")
|
120
|
+
|
121
|
+
try:
|
122
|
+
args = MockArgs(service)
|
123
|
+
handle_delete(None, args) # This will fail because we need a real credentials manager
|
124
|
+
except Exception as e:
|
125
|
+
print(f" Error: {e}")
|
126
|
+
|
127
|
+
print(f"\n✅ Delete command test completed!")
|
128
|
+
|
129
|
+
def show_usage_examples():
|
130
|
+
"""Show how to use the delete command"""
|
131
|
+
print(f"\n📋 Usage Examples")
|
132
|
+
print("=" * 60)
|
133
|
+
|
134
|
+
print("To delete an API key:")
|
135
|
+
print(" python gitarsenal_keys.py delete --service openai")
|
136
|
+
print(" python gitarsenal_keys.py delete --service wandb")
|
137
|
+
print(" python gitarsenal_keys.py delete --service huggingface")
|
138
|
+
print(" python gitarsenal_keys.py delete --service gitarsenal-openai")
|
139
|
+
|
140
|
+
print("\nTo list all stored keys:")
|
141
|
+
print(" python gitarsenal_keys.py list")
|
142
|
+
|
143
|
+
print("\nTo view a specific key (masked):")
|
144
|
+
print(" python gitarsenal_keys.py view --service openai")
|
145
|
+
|
146
|
+
print("\nTo add a new key:")
|
147
|
+
print(" python gitarsenal_keys.py add --service openai --key sk-...")
|
148
|
+
print(" python gitarsenal_keys.py add --service openai # Interactive mode")
|
149
|
+
|
150
|
+
if __name__ == "__main__":
|
151
|
+
print("GitArsenal Keys Delete Debug Tool")
|
152
|
+
print("=" * 60)
|
153
|
+
|
154
|
+
try:
|
155
|
+
test_delete_functionality()
|
156
|
+
test_delete_command()
|
157
|
+
show_usage_examples()
|
158
|
+
|
159
|
+
print(f"\n🎉 Debug completed!")
|
160
|
+
print(f"\nTo test the actual delete command:")
|
161
|
+
print(f" python gitarsenal_keys.py delete --service openai")
|
162
|
+
|
163
|
+
except Exception as e:
|
164
|
+
print(f"\n❌ Debug failed with error: {e}")
|
165
|
+
import traceback
|
166
|
+
traceback.print_exc()
|
167
|
+
sys.exit(1)
|