gitarsenal-cli 1.8.4 → 1.8.5

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.
@@ -22,6 +22,13 @@ except ImportError:
22
22
  except Exception as e:
23
23
  print(f"⚠️ Warning: Error setting up Modal token: {e}")
24
24
 
25
+ # Import authentication manager
26
+ try:
27
+ from auth_manager import AuthManager
28
+ except ImportError:
29
+ print("⚠️ Warning: auth_manager module not found. Authentication will be disabled.")
30
+ AuthManager = None
31
+
25
32
  def check_modal_auth():
26
33
  """Check if Modal is authenticated and set up the built-in token if needed"""
27
34
  try:
@@ -112,11 +119,176 @@ def check_proxy_config():
112
119
  print(f"⚠️ Error checking proxy configuration: {e}")
113
120
  return False
114
121
 
122
+ def _check_authentication(auth_manager):
123
+ """Check if user is authenticated, prompt for login if not"""
124
+ if auth_manager.is_authenticated():
125
+ user = auth_manager.get_current_user()
126
+ print(f"✅ Authenticated as: {user['username']}")
127
+ return True
128
+
129
+ print("\n🔐 Authentication required")
130
+ return auth_manager.interactive_auth_flow()
131
+
132
+ def _handle_auth_commands(auth_manager, args):
133
+ """Handle authentication-related commands"""
134
+ import getpass
135
+
136
+ if args.login:
137
+ print("\n🔐 LOGIN")
138
+ username = input("Username: ").strip()
139
+ password = getpass.getpass("Password: ").strip()
140
+ if auth_manager.login_user(username, password):
141
+ print("✅ Login successful!")
142
+ else:
143
+ print("❌ Login failed.")
144
+
145
+ elif args.register:
146
+ print("\n🔐 REGISTRATION")
147
+ username = input("Username (min 3 characters): ").strip()
148
+ email = input("Email: ").strip()
149
+ password = getpass.getpass("Password (min 8 characters): ").strip()
150
+ confirm_password = getpass.getpass("Confirm password: ").strip()
151
+
152
+ if password != confirm_password:
153
+ print("❌ Passwords do not match.")
154
+ return
155
+
156
+ if auth_manager.register_user(username, email, password):
157
+ print("✅ Registration successful!")
158
+ # Auto-login after registration
159
+ if auth_manager.login_user(username, password):
160
+ print("✅ Auto-login successful!")
161
+ else:
162
+ print("❌ Registration failed.")
163
+
164
+ elif args.logout:
165
+ auth_manager.logout_user()
166
+
167
+ elif args.user_info:
168
+ auth_manager.show_user_info()
169
+
170
+ elif args.change_password:
171
+ if not auth_manager.is_authenticated():
172
+ print("❌ Not logged in. Please login first.")
173
+ return
174
+
175
+ current_password = getpass.getpass("Current password: ").strip()
176
+ new_password = getpass.getpass("New password (min 8 characters): ").strip()
177
+ confirm_password = getpass.getpass("Confirm new password: ").strip()
178
+
179
+ if new_password != confirm_password:
180
+ print("❌ New passwords do not match.")
181
+ return
182
+
183
+ if auth_manager.change_password(current_password, new_password):
184
+ print("✅ Password changed successfully!")
185
+ else:
186
+ print("❌ Failed to change password.")
187
+
188
+ elif args.delete_account:
189
+ if not auth_manager.is_authenticated():
190
+ print("❌ Not logged in. Please login first.")
191
+ return
192
+
193
+ password = getpass.getpass("Enter your password to confirm deletion: ").strip()
194
+ if auth_manager.delete_account(password):
195
+ print("✅ Account deleted successfully!")
196
+ else:
197
+ print("❌ Failed to delete account.")
198
+
199
+ elif args.store_api_key:
200
+ if not auth_manager.is_authenticated():
201
+ print("❌ Not logged in. Please login first.")
202
+ return
203
+
204
+ service = args.store_api_key
205
+ api_key = getpass.getpass(f"Enter {service} API key: ").strip()
206
+
207
+ if auth_manager.store_api_key(service, api_key):
208
+ print(f"✅ {service} API key stored successfully!")
209
+ else:
210
+ print(f"❌ Failed to store {service} API key.")
211
+
212
+ elif args.auth:
213
+ # Interactive authentication management
214
+ while True:
215
+ print("\n" + "="*60)
216
+ print("🔐 AUTHENTICATION MANAGEMENT")
217
+ print("="*60)
218
+ print("1. Login")
219
+ print("2. Register")
220
+ print("3. Show user info")
221
+ print("4. Change password")
222
+ print("5. Store API key")
223
+ print("6. Delete account")
224
+ print("7. Logout")
225
+ print("8. Exit")
226
+
227
+ choice = input("\nSelect an option (1-8): ").strip()
228
+
229
+ if choice == "1":
230
+ username = input("Username: ").strip()
231
+ password = getpass.getpass("Password: ").strip()
232
+ auth_manager.login_user(username, password)
233
+ elif choice == "2":
234
+ username = input("Username (min 3 characters): ").strip()
235
+ email = input("Email: ").strip()
236
+ password = getpass.getpass("Password (min 8 characters): ").strip()
237
+ confirm_password = getpass.getpass("Confirm password: ").strip()
238
+ if password == confirm_password:
239
+ auth_manager.register_user(username, email, password)
240
+ else:
241
+ print("❌ Passwords do not match.")
242
+ elif choice == "3":
243
+ auth_manager.show_user_info()
244
+ elif choice == "4":
245
+ if auth_manager.is_authenticated():
246
+ current_password = getpass.getpass("Current password: ").strip()
247
+ new_password = getpass.getpass("New password (min 8 characters): ").strip()
248
+ confirm_password = getpass.getpass("Confirm new password: ").strip()
249
+ if new_password == confirm_password:
250
+ auth_manager.change_password(current_password, new_password)
251
+ else:
252
+ print("❌ New passwords do not match.")
253
+ else:
254
+ print("❌ Not logged in.")
255
+ elif choice == "5":
256
+ if auth_manager.is_authenticated():
257
+ service = input("Service name (e.g., openai, modal): ").strip()
258
+ api_key = getpass.getpass(f"Enter {service} API key: ").strip()
259
+ auth_manager.store_api_key(service, api_key)
260
+ else:
261
+ print("❌ Not logged in.")
262
+ elif choice == "6":
263
+ if auth_manager.is_authenticated():
264
+ password = getpass.getpass("Enter your password to confirm deletion: ").strip()
265
+ auth_manager.delete_account(password)
266
+ else:
267
+ print("❌ Not logged in.")
268
+ elif choice == "7":
269
+ auth_manager.logout_user()
270
+ elif choice == "8":
271
+ print("👋 Goodbye!")
272
+ break
273
+ else:
274
+ print("❌ Invalid option. Please try again.")
275
+
115
276
  def main():
116
277
  """Main entry point for the GitArsenal CLI"""
117
278
  parser = argparse.ArgumentParser(description="GitArsenal CLI - Tools for AI development")
118
279
  subparsers = parser.add_subparsers(dest="command", help="Command to run")
119
280
 
281
+ # Authentication arguments (global)
282
+ parser.add_argument('--skip-auth', action='store_true', help='Skip authentication check (for development)')
283
+ parser.add_argument('--login', action='store_true', help='Login to GitArsenal')
284
+ parser.add_argument('--register', action='store_true', help='Register new account')
285
+ parser.add_argument('--logout', action='store_true', help='Logout from GitArsenal')
286
+ parser.add_argument('--user-info', action='store_true', help='Show current user information')
287
+ parser.add_argument('--change-password', action='store_true', help='Change password')
288
+ parser.add_argument('--delete-account', action='store_true', help='Delete account')
289
+ parser.add_argument('--store-api-key', type=str, help='Store API key for a service (e.g., openai, modal)')
290
+ parser.add_argument('--auth', action='store_true', help='Manage authentication (login, register, logout)')
291
+
120
292
  # SSH command
121
293
  ssh_parser = subparsers.add_parser("ssh", help="Create a Modal SSH container")
122
294
  ssh_parser.add_argument("--gpu", type=str, default="A10G", help="GPU type (default: A10G)")
@@ -159,6 +331,22 @@ def main():
159
331
  # Parse arguments
160
332
  args = parser.parse_args()
161
333
 
334
+ # Handle authentication commands first
335
+ if AuthManager and (args.login or args.register or args.logout or args.user_info or
336
+ args.change_password or args.delete_account or args.store_api_key or args.auth):
337
+ auth_manager = AuthManager()
338
+ _handle_auth_commands(auth_manager, args)
339
+ return
340
+
341
+ # Check authentication for main commands (unless skipped)
342
+ if AuthManager and not args.skip_auth and args.command:
343
+ auth_manager = AuthManager()
344
+ if not _check_authentication(auth_manager):
345
+ print("\n❌ Authentication required. Please login or register first.")
346
+ print("Use --login to login or --register to create an account.")
347
+ print("Use --skip-auth to bypass authentication for development.")
348
+ return
349
+
162
350
  if args.command == "ssh":
163
351
  if args.use_proxy:
164
352
  # Use proxy service for SSH container
@@ -14,7 +14,7 @@ from credentials_manager import CredentialsManager
14
14
  def main():
15
15
  parser = argparse.ArgumentParser(description='GitArsenal Keys Management')
16
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)')
17
+ parser.add_argument('--service', help='Service name (openai_api_key, WANDB_API_KEY, HUGGINGFACE_TOKEN, modal_token)')
18
18
  parser.add_argument('--key', help='API key (for add command)')
19
19
 
20
20
  args = parser.parse_args()
@@ -38,23 +38,30 @@ def handle_add(credentials_manager, args):
38
38
 
39
39
  if not service:
40
40
  print("❌ Service name is required. Use --service option.")
41
+ print("💡 Available service names:")
42
+ print(" openai_api_key")
43
+ print(" WANDB_API_KEY")
44
+ print(" HUGGINGFACE_TOKEN")
45
+ print(" modal_token")
46
+ print(" gitarsenal_openai_api_key")
47
+ print("\n💡 Example: gitarsenal keys add --service openai_api_key")
41
48
  sys.exit(1)
42
49
 
43
50
  if not key:
44
51
  # Prompt for the API key interactively
45
- print(f"\n🔑 {service.upper()} API KEY REQUIRED")
52
+ print(f"\n🔑 {service.upper()} REQUIRED")
46
53
  print("=" * 50)
47
54
 
48
55
  # Get appropriate prompt based on service
49
56
  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/"
57
+ 'openai_api_key': "Please enter your OpenAI API key:\nYou can get your API key from: https://platform.openai.com/api-keys",
58
+ 'WANDB_API_KEY': "Please enter your Weights & Biases API key:\nYou can get your API key from: https://wandb.ai/authorize",
59
+ 'HUGGINGFACE_TOKEN': "Please enter your Hugging Face token:\nYou can get your token from: https://huggingface.co/settings/tokens",
60
+ 'gitarsenal_openai_api_key': "Please enter GitArsenal's OpenAI API key for debugging:",
61
+ 'modal_token': "Please enter your Modal token:\nYou can get your token from: https://modal.com/account/tokens"
55
62
  }
56
63
 
57
- prompt = prompts.get(service, f"Please enter your {service} API key:")
64
+ prompt = prompts.get(service, f"Please enter your {service}:")
58
65
  print(prompt)
59
66
  print("-" * 50)
60
67
 
@@ -72,20 +79,8 @@ def handle_add(credentials_manager, args):
72
79
  print(f"❌ Error getting API key: {e}")
73
80
  sys.exit(1)
74
81
 
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)
82
+ # Use the service name directly as the credential key
83
+ credential_key = service
89
84
 
90
85
  # Save the credential
91
86
  credentials = credentials_manager.load_credentials()
@@ -113,10 +108,11 @@ def handle_list(credentials_manager):
113
108
  # Map credential keys to display names (for known services)
114
109
  key_mapping = {
115
110
  'openai_api_key': 'OpenAI',
116
- 'wandb_api_key': 'Weights & Biases',
117
- 'huggingface_token': 'Hugging Face',
111
+ 'WANDB_API_KEY': 'Weights & Biases',
112
+ 'HUGGINGFACE_TOKEN': 'Hugging Face',
118
113
  'gitarsenal_openai_api_key': 'GitArsenal OpenAI',
119
- 'claude_api_key': 'Claude'
114
+ 'claude_api_key': 'Claude',
115
+ 'modal_token': 'Modal'
120
116
  }
121
117
 
122
118
  for key, value in credentials.items():
@@ -124,14 +120,16 @@ def handle_list(credentials_manager):
124
120
  if key in key_mapping:
125
121
  display_name = key_mapping[key]
126
122
  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()
123
+ # Use the key name directly for unknown services
124
+ display_name = key
130
125
 
131
126
  masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
132
127
  print(f" {display_name}: {masked_value}")
133
128
 
134
129
  print(f"\n📁 Storage location: {credentials_manager.credentials_file}")
130
+ print("\n💡 To delete a key, use the exact service name:")
131
+ for key in credentials.keys():
132
+ print(f" gitarsenal keys delete --service {key}")
135
133
 
136
134
  def handle_view(credentials_manager, args):
137
135
  """Handle viewing a specific API key (masked)"""
@@ -139,31 +137,26 @@ def handle_view(credentials_manager, args):
139
137
 
140
138
  if not service:
141
139
  print("❌ Service name is required. Use --service option.")
140
+ print("💡 Available service names:")
141
+ print(" openai_api_key")
142
+ print(" WANDB_API_KEY")
143
+ print(" HUGGINGFACE_TOKEN")
144
+ print(" modal_token")
142
145
  sys.exit(1)
143
146
 
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)
147
+ # Use the service name directly as the credential key
148
+ credential_key = service
157
149
  credentials = credentials_manager.load_credentials()
158
150
 
159
151
  if credential_key not in credentials:
160
152
  print(f"❌ No API key found for {service}")
153
+ print(f"💡 Available keys: {list(credentials.keys())}")
161
154
  sys.exit(1)
162
155
 
163
156
  value = credentials[credential_key]
164
157
  masked_value = value[:8] + "*" * (len(value) - 12) + value[-4:] if len(value) > 12 else "*" * len(value)
165
158
 
166
- print(f"🔑 {service.upper()} API Key:")
159
+ print(f"🔑 {service}:")
167
160
  print(f" {masked_value}")
168
161
  print(f"📁 Stored in: {credentials_manager.credentials_file}")
169
162
 
@@ -173,27 +166,22 @@ def handle_delete(credentials_manager, args):
173
166
 
174
167
  if not service:
175
168
  print("❌ Service name is required. Use --service option.")
169
+ print("💡 Available service names:")
170
+ print(" openai_api_key")
171
+ print(" WANDB_API_KEY")
172
+ print(" HUGGINGFACE_TOKEN")
173
+ print(" modal_token")
176
174
  sys.exit(1)
177
175
 
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)
176
+ # Use the service name directly as the credential key
177
+ credential_key = service
191
178
  success = credentials_manager.clear_credential(credential_key)
192
179
 
193
180
  if success:
194
181
  print(f"✅ API key for {service} deleted successfully")
195
182
  else:
196
183
  print(f"❌ No API key found for {service}")
184
+ print("💡 Use 'gitarsenal keys list' to see available keys")
197
185
 
198
186
  if __name__ == "__main__":
199
187
  main()
@@ -16,6 +16,13 @@ import signal
16
16
  from pathlib import Path
17
17
  import modal
18
18
 
19
+ # Import authentication manager
20
+ try:
21
+ from auth_manager import AuthManager
22
+ except ImportError:
23
+ print("❌ Authentication module not found. Please ensure auth_manager.py is in the same directory.")
24
+ sys.exit(1)
25
+
19
26
  # Parse command-line arguments
20
27
  parser = argparse.ArgumentParser()
21
28
  parser.add_argument('--proxy-url', help='URL of the proxy server')
@@ -3325,6 +3332,18 @@ def show_usage_examples():
3325
3332
  """Display usage examples for the script."""
3326
3333
  print("Usage Examples\n")
3327
3334
 
3335
+ print("🔐 Authentication Commands")
3336
+ print("┌────────────────────────────────────────────────────────────────────────────────────┐")
3337
+ print("│ gitarsenal --register # Register new account │")
3338
+ print("│ gitarsenal --login # Login to existing account │")
3339
+ print("│ gitarsenal --logout # Logout from account │")
3340
+ print("│ gitarsenal --user-info # Show current user information │")
3341
+ print("│ gitarsenal --change-password # Change password │")
3342
+ print("│ gitarsenal --delete-account # Delete account │")
3343
+ print("│ gitarsenal --store-api-key openai # Store OpenAI API key │")
3344
+ print("│ gitarsenal --auth # Interactive auth management │")
3345
+ print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
3346
+
3328
3347
  print("Basic Container Creation")
3329
3348
  print("┌────────────────────────────────────────────────────────────────────────┐")
3330
3349
  print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git │")
@@ -3359,19 +3378,32 @@ def show_usage_examples():
3359
3378
  print("│ --use-api │")
3360
3379
  print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
3361
3380
 
3381
+ print("Development Mode (Skip Authentication)")
3382
+ print("┌────────────────────────────────────────────────────────────────────────────────────┐")
3383
+ print("│ gitarsenal --skip-auth --gpu A10G --repo-url https://github.com/username/repo.git │")
3384
+ print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
3385
+
3362
3386
  print("Available GPU Options:")
3363
3387
  print(" T4, L4, A10G, A100-40GB, A100-80GB, L40S, H100, H200, B200")
3364
3388
  print()
3389
+ print("Authentication Behavior:")
3390
+ print(" • First time: Interactive registration/login required")
3391
+ print(" • Subsequent runs: Automatic login with stored session")
3392
+ print(" • Use --skip-auth for development (bypasses auth)")
3393
+ print()
3365
3394
  print("GPU Selection Behavior:")
3366
3395
  print(" • With --gpu: Uses specified GPU without prompting")
3367
3396
  print(" • Without --gpu: Shows interactive GPU selection menu")
3368
3397
  print()
3369
3398
  print("Examples:")
3370
- print(" # Uses A10G without prompting:")
3399
+ print(" # First time setup (will prompt for registration):")
3371
3400
  print(" gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git")
3372
3401
  print()
3373
- print(" # Shows interactive GPU selection menu:")
3402
+ print(" # Subsequent runs (automatic login):")
3374
3403
  print(" gitarsenal --repo-url https://github.com/username/repo.git")
3404
+ print()
3405
+ print(" # Development mode (skip authentication):")
3406
+ print(" gitarsenal --skip-auth --repo-url https://github.com/username/repo.git")
3375
3407
 
3376
3408
  def make_api_request_with_retry(url, payload, max_retries=2, timeout=180):
3377
3409
  """Make an API request with retry mechanism."""
@@ -4005,6 +4037,158 @@ def fallback_preprocess_commands(setup_commands, stored_credentials):
4005
4037
  print(f"🔧 Fallback preprocessing completed: {len(processed_commands)} commands")
4006
4038
  return processed_commands
4007
4039
 
4040
+ def _check_authentication(auth_manager):
4041
+ """Check if user is authenticated, prompt for login if not"""
4042
+ if auth_manager.is_authenticated():
4043
+ user = auth_manager.get_current_user()
4044
+ print(f"✅ Authenticated as: {user['username']}")
4045
+ return True
4046
+
4047
+ print("\n🔐 Authentication required")
4048
+ return auth_manager.interactive_auth_flow()
4049
+
4050
+ def _handle_auth_commands(auth_manager, args):
4051
+ """Handle authentication-related commands"""
4052
+ if args.login:
4053
+ print("\n🔐 LOGIN")
4054
+ username = input("Username: ").strip()
4055
+ password = getpass.getpass("Password: ").strip()
4056
+ if auth_manager.login_user(username, password):
4057
+ print("✅ Login successful!")
4058
+ else:
4059
+ print("❌ Login failed.")
4060
+
4061
+ elif args.register:
4062
+ print("\n🔐 REGISTRATION")
4063
+ username = input("Username (min 3 characters): ").strip()
4064
+ email = input("Email: ").strip()
4065
+ password = getpass.getpass("Password (min 8 characters): ").strip()
4066
+ confirm_password = getpass.getpass("Confirm password: ").strip()
4067
+
4068
+ if password != confirm_password:
4069
+ print("❌ Passwords do not match.")
4070
+ return
4071
+
4072
+ if auth_manager.register_user(username, email, password):
4073
+ print("✅ Registration successful!")
4074
+ # Auto-login after registration
4075
+ if auth_manager.login_user(username, password):
4076
+ print("✅ Auto-login successful!")
4077
+ else:
4078
+ print("❌ Registration failed.")
4079
+
4080
+ elif args.logout:
4081
+ auth_manager.logout_user()
4082
+
4083
+ elif args.user_info:
4084
+ auth_manager.show_user_info()
4085
+
4086
+ elif args.change_password:
4087
+ if not auth_manager.is_authenticated():
4088
+ print("❌ Not logged in. Please login first.")
4089
+ return
4090
+
4091
+ current_password = getpass.getpass("Current password: ").strip()
4092
+ new_password = getpass.getpass("New password (min 8 characters): ").strip()
4093
+ confirm_password = getpass.getpass("Confirm new password: ").strip()
4094
+
4095
+ if new_password != confirm_password:
4096
+ print("❌ New passwords do not match.")
4097
+ return
4098
+
4099
+ if auth_manager.change_password(current_password, new_password):
4100
+ print("✅ Password changed successfully!")
4101
+ else:
4102
+ print("❌ Failed to change password.")
4103
+
4104
+ elif args.delete_account:
4105
+ if not auth_manager.is_authenticated():
4106
+ print("❌ Not logged in. Please login first.")
4107
+ return
4108
+
4109
+ password = getpass.getpass("Enter your password to confirm deletion: ").strip()
4110
+ if auth_manager.delete_account(password):
4111
+ print("✅ Account deleted successfully!")
4112
+ else:
4113
+ print("❌ Failed to delete account.")
4114
+
4115
+ elif args.store_api_key:
4116
+ if not auth_manager.is_authenticated():
4117
+ print("❌ Not logged in. Please login first.")
4118
+ return
4119
+
4120
+ service = args.store_api_key
4121
+ api_key = getpass.getpass(f"Enter {service} API key: ").strip()
4122
+
4123
+ if auth_manager.store_api_key(service, api_key):
4124
+ print(f"✅ {service} API key stored successfully!")
4125
+ else:
4126
+ print(f"❌ Failed to store {service} API key.")
4127
+
4128
+ elif args.auth:
4129
+ # Interactive authentication management
4130
+ while True:
4131
+ print("\n" + "="*60)
4132
+ print("🔐 AUTHENTICATION MANAGEMENT")
4133
+ print("="*60)
4134
+ print("1. Login")
4135
+ print("2. Register")
4136
+ print("3. Show user info")
4137
+ print("4. Change password")
4138
+ print("5. Store API key")
4139
+ print("6. Delete account")
4140
+ print("7. Logout")
4141
+ print("8. Exit")
4142
+
4143
+ choice = input("\nSelect an option (1-8): ").strip()
4144
+
4145
+ if choice == "1":
4146
+ username = input("Username: ").strip()
4147
+ password = getpass.getpass("Password: ").strip()
4148
+ auth_manager.login_user(username, password)
4149
+ elif choice == "2":
4150
+ username = input("Username (min 3 characters): ").strip()
4151
+ email = input("Email: ").strip()
4152
+ password = getpass.getpass("Password (min 8 characters): ").strip()
4153
+ confirm_password = getpass.getpass("Confirm password: ").strip()
4154
+ if password == confirm_password:
4155
+ auth_manager.register_user(username, email, password)
4156
+ else:
4157
+ print("❌ Passwords do not match.")
4158
+ elif choice == "3":
4159
+ auth_manager.show_user_info()
4160
+ elif choice == "4":
4161
+ if auth_manager.is_authenticated():
4162
+ current_password = getpass.getpass("Current password: ").strip()
4163
+ new_password = getpass.getpass("New password (min 8 characters): ").strip()
4164
+ confirm_password = getpass.getpass("Confirm new password: ").strip()
4165
+ if new_password == confirm_password:
4166
+ auth_manager.change_password(current_password, new_password)
4167
+ else:
4168
+ print("❌ New passwords do not match.")
4169
+ else:
4170
+ print("❌ Not logged in.")
4171
+ elif choice == "5":
4172
+ if auth_manager.is_authenticated():
4173
+ service = input("Service name (e.g., openai, modal): ").strip()
4174
+ api_key = getpass.getpass(f"Enter {service} API key: ").strip()
4175
+ auth_manager.store_api_key(service, api_key)
4176
+ else:
4177
+ print("❌ Not logged in.")
4178
+ elif choice == "6":
4179
+ if auth_manager.is_authenticated():
4180
+ password = getpass.getpass("Enter your password to confirm deletion: ").strip()
4181
+ auth_manager.delete_account(password)
4182
+ else:
4183
+ print("❌ Not logged in.")
4184
+ elif choice == "7":
4185
+ auth_manager.logout_user()
4186
+ elif choice == "8":
4187
+ print("👋 Goodbye!")
4188
+ break
4189
+ else:
4190
+ print("❌ Invalid option. Please try again.")
4191
+
4008
4192
  # Replace the existing GPU argument parsing in the main section
4009
4193
  if __name__ == "__main__":
4010
4194
  # Parse command line arguments when script is run directly
@@ -4030,8 +4214,27 @@ if __name__ == "__main__":
4030
4214
  parser.add_argument('--list-gpus', action='store_true', help='List available GPU types with their specifications')
4031
4215
  parser.add_argument('--interactive', action='store_true', help='Run in interactive mode with prompts')
4032
4216
 
4217
+ # Authentication-related arguments
4218
+ parser.add_argument('--auth', action='store_true', help='Manage authentication (login, register, logout)')
4219
+ parser.add_argument('--login', action='store_true', help='Login to GitArsenal')
4220
+ parser.add_argument('--register', action='store_true', help='Register new account')
4221
+ parser.add_argument('--logout', action='store_true', help='Logout from GitArsenal')
4222
+ parser.add_argument('--user-info', action='store_true', help='Show current user information')
4223
+ parser.add_argument('--change-password', action='store_true', help='Change password')
4224
+ parser.add_argument('--delete-account', action='store_true', help='Delete account')
4225
+ parser.add_argument('--store-api-key', type=str, help='Store API key for a service (e.g., openai, modal)')
4226
+ parser.add_argument('--skip-auth', action='store_true', help='Skip authentication check (for development)')
4227
+
4033
4228
  args = parser.parse_args()
4034
4229
 
4230
+ # Initialize authentication manager
4231
+ auth_manager = AuthManager()
4232
+
4233
+ # Handle authentication-related commands
4234
+ if args.auth or args.login or args.register or args.logout or args.user_info or args.change_password or args.delete_account or args.store_api_key:
4235
+ _handle_auth_commands(auth_manager, args)
4236
+ sys.exit(0)
4237
+
4035
4238
  # If --list-gpus is specified, just show GPU options and exit
4036
4239
  if args.list_gpus:
4037
4240
  prompt_for_gpu()
@@ -4042,6 +4245,13 @@ if __name__ == "__main__":
4042
4245
  show_usage_examples()
4043
4246
  sys.exit(0)
4044
4247
 
4248
+ # Check authentication (unless skipped for development)
4249
+ if not args.skip_auth:
4250
+ if not _check_authentication(auth_manager):
4251
+ print("\n❌ Authentication required. Please login or register first.")
4252
+ print("Use --login to login or --register to create an account.")
4253
+ sys.exit(1)
4254
+
4045
4255
  # Check for dependencies
4046
4256
  print("⠏ Checking dependencies...")
4047
4257
  print("--- Dependency Check ---")