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.
@@ -65,46 +65,88 @@ async function checkAndInstallUv() {
65
65
  }
66
66
  }
67
67
 
68
- // Function to install Python packages
69
- async function installPythonPackages() {
68
+ // Function to create and activate virtual environment using uv
69
+ async function createVirtualEnvironment() {
70
70
  const packages = ['modal', 'gitingest', 'requests'];
71
71
 
72
- console.log(chalk.yellow(`šŸ“¦ Installing Python packages: ${packages.join(', ')}`));
72
+ console.log(chalk.yellow(`šŸ“¦ Creating virtual environment with uv and installing packages: ${packages.join(', ')}`));
73
73
 
74
- // Try uv first, then fall back to pip commands
75
- const installCommands = [
76
- 'uv pip install',
77
- 'pip3 install',
78
- 'pip install',
79
- 'python -m pip install',
80
- 'py -m pip install'
81
- ];
82
-
83
- for (const installCommand of installCommands) {
74
+ try {
75
+ // First, ensure uv is available
76
+ let uvAvailable = false;
84
77
  try {
85
- // Check if command exists
86
- const [cmd] = installCommand.split(' ');
87
- await execAsync(`${cmd} --version`);
88
-
89
- console.log(chalk.gray(`šŸ”„ Using: ${installCommand}`));
90
-
91
- await execAsync(`${installCommand} ${packages.join(' ')}`, {
92
- env: { ...process.env, PYTHONIOENCODING: 'utf-8' }
93
- });
94
-
95
- console.log(chalk.green('āœ… Python packages installed successfully!'));
96
- return true;
78
+ await execAsync('uv --version');
79
+ uvAvailable = true;
97
80
  } catch (error) {
98
- console.log(chalk.gray(`āš ļø ${installCommand} failed, trying next...`));
81
+ console.log(chalk.yellow('āš ļø uv not found, attempting to install...'));
82
+ await checkAndInstallUv();
83
+ try {
84
+ await execAsync('uv --version');
85
+ uvAvailable = true;
86
+ } catch (installError) {
87
+ console.log(chalk.red('āŒ Failed to install uv'));
88
+ return false;
89
+ }
90
+ }
91
+
92
+ if (!uvAvailable) {
93
+ console.log(chalk.red('āŒ uv is required but not available'));
94
+ return false;
99
95
  }
96
+
97
+ console.log(chalk.gray(`šŸ”„ Creating virtual environment with uv...`));
98
+
99
+ // Create virtual environment using uv
100
+ await execAsync('uv venv', {
101
+ cwd: path.join(__dirname, '..'),
102
+ env: { ...process.env, PYTHONIOENCODING: 'utf-8' }
103
+ });
104
+
105
+ console.log(chalk.green('āœ… Virtual environment created successfully with uv!'));
106
+
107
+ console.log(chalk.gray(`šŸ”„ Installing packages in virtual environment with uv...`));
108
+
109
+ // Install packages using uv pip
110
+ await execAsync(`uv pip install ${packages.join(' ')}`, {
111
+ cwd: path.join(__dirname, '..'),
112
+ env: { ...process.env, PYTHONIOENCODING: 'utf-8' }
113
+ });
114
+
115
+ console.log(chalk.green('āœ… Python packages installed successfully in virtual environment!'));
116
+
117
+ // Create a script to activate the virtual environment
118
+ const isWindows = process.platform === 'win32';
119
+ const venvPath = path.join(__dirname, '..', '.venv');
120
+
121
+ const activateScript = isWindows ?
122
+ `@echo off
123
+ cd /d "%~dp0"
124
+ call ".venv\\Scripts\\activate.bat"
125
+ %*` :
126
+ `#!/bin/bash
127
+ cd "$(dirname "$0")"
128
+ source ".venv/bin/activate"
129
+ exec "$@"`;
130
+
131
+ const activateScriptPath = path.join(__dirname, '..', 'activate_venv' + (isWindows ? '.bat' : '.sh'));
132
+ await fs.writeFile(activateScriptPath, activateScript);
133
+
134
+ if (!isWindows) {
135
+ await fs.chmod(activateScriptPath, 0o755);
136
+ }
137
+
138
+ console.log(chalk.green(`āœ… Virtual environment activation script created: ${activateScriptPath}`));
139
+
140
+ return true;
141
+ } catch (error) {
142
+ console.log(chalk.red(`āŒ Error creating virtual environment with uv: ${error.message}`));
143
+ console.log(chalk.yellow('šŸ’” Please run manually:'));
144
+ console.log(chalk.yellow(' uv venv'));
145
+ console.log(chalk.yellow(' uv pip install modal gitingest requests'));
146
+ console.log(chalk.yellow(' source .venv/bin/activate # On Unix/macOS'));
147
+ console.log(chalk.yellow(' .venv\\Scripts\\activate.bat # On Windows'));
148
+ return false;
100
149
  }
101
-
102
- console.log(chalk.red('āŒ Failed to install Python packages'));
103
- console.log(chalk.yellow('šŸ’” Please run manually:'));
104
- console.log(chalk.yellow(' uv pip install modal gitingest requests'));
105
- console.log(chalk.yellow(' or: pip install modal gitingest requests'));
106
- console.log(chalk.yellow(' or: pip3 install modal gitingest requests'));
107
- return false;
108
150
  }
109
151
 
110
152
  // Function to check Python
@@ -161,9 +203,9 @@ async function postinstall() {
161
203
  console.log(chalk.blue('šŸ” Checking for uv package manager...'));
162
204
  await checkAndInstallUv();
163
205
 
164
- // Install Python packages
165
- console.log(chalk.blue('šŸ” Installing Python dependencies...'));
166
- await installPythonPackages();
206
+ // Install Python packages in virtual environment
207
+ console.log(chalk.blue('šŸ” Installing Python dependencies in virtual environment...'));
208
+ await createVirtualEnvironment();
167
209
 
168
210
  // Create the Python directory if it doesn't exist
169
211
  await fs.ensureDir(pythonScriptDir);
@@ -278,15 +320,21 @@ if __name__ == "__main__":
278
320
 
279
321
  What was installed:
280
322
  • GitArsenal CLI (npm package)
281
- • Modal (Python package)
282
- • GitIngest (Python package)
283
- • Requests (Python package)
323
+ • Virtual environment with Python packages:
324
+ - Modal
325
+ - GitIngest
326
+ - Requests
284
327
 
285
328
  šŸ’” Next steps:
286
329
  • Run: gitarsenal --help
287
330
  • Run: gitarsenal setup
288
331
  • Visit: https://gitarsenal.dev
289
332
 
333
+ šŸ’” To activate the virtual environment:
334
+ • Unix/macOS: source .venv/bin/activate
335
+ • Windows: .venv\\Scripts\\activate.bat
336
+ • Or use: ./activate_venv.sh (Unix/macOS) or activate_venv.bat (Windows)
337
+
290
338
  Having issues? Run: gitarsenal --debug
291
339
  `));
292
340
 
@@ -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 ---")