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/python/gitarsenal.py
CHANGED
@@ -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 (
|
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()}
|
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
|
-
'
|
51
|
-
'
|
52
|
-
'
|
53
|
-
'
|
54
|
-
'
|
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}
|
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
|
-
#
|
76
|
-
|
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
|
-
'
|
117
|
-
'
|
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
|
-
#
|
128
|
-
|
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
|
-
#
|
145
|
-
credential_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
|
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
|
-
#
|
179
|
-
credential_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(" #
|
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(" #
|
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 ---")
|