gitarsenal-cli 1.9.6 → 1.9.8

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/.venv_status.json CHANGED
@@ -1 +1 @@
1
- {"created":"2025-08-03T17:42:46.952Z","packages":["modal","gitingest","requests"],"uv_version":"uv 0.8.4 (e176e1714 2025-07-30)"}
1
+ {"created":"2025-08-04T06:53:34.568Z","packages":["modal","gitingest","requests"],"uv_version":"uv 0.8.4 (e176e1714 2025-07-30)"}
package/bin/gitarsenal.js CHANGED
@@ -13,6 +13,8 @@ const pkg = require('../package.json');
13
13
  const boxen = require('boxen');
14
14
  const { spawn } = require('child_process');
15
15
  const fs = require('fs');
16
+ const https = require('https');
17
+ const http = require('http');
16
18
 
17
19
  // Function to activate virtual environment
18
20
  function activateVirtualEnvironment() {
@@ -102,6 +104,162 @@ function activateVirtualEnvironment() {
102
104
  return true;
103
105
  }
104
106
 
107
+ // Function to send user data to web application
108
+ async function sendUserData(userId, userName) {
109
+ try {
110
+ console.log(chalk.blue(`šŸ”— Attempting to register user: ${userName} (${userId})`));
111
+
112
+ const userData = {
113
+ id: userId,
114
+ name: userName
115
+ };
116
+
117
+ const data = JSON.stringify(userData);
118
+
119
+ // Get webhook URL from config or use default
120
+ let webhookUrl = 'https://www.gitarsenal.dev/api/users';
121
+ const configPath = path.join(__dirname, '..', 'config.json');
122
+ if (fs.existsSync(configPath)) {
123
+ try {
124
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
125
+ if (config.webhookUrl) {
126
+ webhookUrl = config.webhookUrl;
127
+ }
128
+ } catch (error) {
129
+ console.log(chalk.yellow('āš ļø Could not read config file, using default URL'));
130
+ }
131
+ }
132
+
133
+ console.log(chalk.gray(`šŸ“” Sending to: ${webhookUrl}`));
134
+ console.log(chalk.gray(`šŸ“¦ Data: ${data}`));
135
+
136
+ const urlObj = new URL(webhookUrl);
137
+ const options = {
138
+ hostname: urlObj.hostname,
139
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
140
+ path: urlObj.pathname,
141
+ method: 'POST',
142
+ headers: {
143
+ 'Content-Type': 'application/json',
144
+ 'Content-Length': Buffer.byteLength(data),
145
+ 'User-Agent': 'GitArsenal-CLI/1.0'
146
+ }
147
+ };
148
+
149
+ return new Promise((resolve, reject) => {
150
+ const client = urlObj.protocol === 'https:' ? https : http;
151
+ const req = client.request(options, (res) => {
152
+ let responseData = '';
153
+ res.on('data', (chunk) => {
154
+ responseData += chunk;
155
+ });
156
+ res.on('end', () => {
157
+ console.log(chalk.gray(`šŸ“Š Response status: ${res.statusCode}`));
158
+ console.log(chalk.gray(`šŸ“„ Response: ${responseData}`));
159
+
160
+ if (res.statusCode >= 200 && res.statusCode < 300) {
161
+ console.log(chalk.green('āœ… User registered on GitArsenal dashboard'));
162
+ resolve(responseData);
163
+ } else if (res.statusCode === 409) {
164
+ console.log(chalk.green('āœ… User already exists on GitArsenal dashboard'));
165
+ resolve(responseData);
166
+ } else {
167
+ console.log(chalk.yellow(`āš ļø Failed to register user (status: ${res.statusCode})`));
168
+ console.log(chalk.gray(`Response: ${responseData}`));
169
+ resolve(responseData);
170
+ }
171
+ });
172
+ });
173
+
174
+ req.on('error', (err) => {
175
+ console.log(chalk.red(`āŒ Could not connect to GitArsenal dashboard: ${err.message}`));
176
+ resolve();
177
+ });
178
+
179
+ req.setTimeout(10000, () => {
180
+ console.log(chalk.red('āŒ Request timeout - could not connect to dashboard'));
181
+ req.destroy();
182
+ resolve();
183
+ });
184
+
185
+ req.write(data);
186
+ req.end();
187
+ });
188
+ } catch (error) {
189
+ console.log(chalk.red(`āŒ Error registering user: ${error.message}`));
190
+ }
191
+ }
192
+
193
+ // Function to collect user credentials
194
+ async function collectUserCredentials(options) {
195
+ let userId = options.userId;
196
+ let userName = options.userName;
197
+
198
+ // Check for config file first
199
+ const configPath = path.join(__dirname, '..', 'config.json');
200
+ if (fs.existsSync(configPath)) {
201
+ try {
202
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
203
+ if (config.userId && config.userName) {
204
+ userId = config.userId;
205
+ userName = config.userName;
206
+ }
207
+ } catch (error) {
208
+ console.log(chalk.yellow('āš ļø Could not read config file'));
209
+ }
210
+ }
211
+
212
+ // If not provided via CLI or config, prompt for them
213
+ if (!userId || !userName) {
214
+ console.log(chalk.blue('\nšŸ” GitArsenal User Identification'));
215
+ console.log(chalk.gray('Help us track your usage and improve GitArsenal!'));
216
+ console.log(chalk.gray('Your credentials will be saved locally for future use.'));
217
+
218
+ const credentials = await inquirer.prompt([
219
+ {
220
+ type: 'input',
221
+ name: 'userId',
222
+ message: 'Enter your user ID (or email):',
223
+ default: userId || 'anonymous',
224
+ validate: (input) => input.trim() !== '' ? true : 'User ID is required'
225
+ },
226
+ {
227
+ type: 'input',
228
+ name: 'userName',
229
+ message: 'Enter your name:',
230
+ default: userName || 'Anonymous User',
231
+ validate: (input) => input.trim() !== '' ? true : 'Name is required'
232
+ },
233
+ {
234
+ type: 'confirm',
235
+ name: 'saveConfig',
236
+ message: 'Save these credentials for future use?',
237
+ default: true
238
+ }
239
+ ]);
240
+
241
+ userId = credentials.userId;
242
+ userName = credentials.userName;
243
+
244
+ // Save to config file if requested
245
+ if (credentials.saveConfig) {
246
+ try {
247
+ const config = {
248
+ userId,
249
+ userName,
250
+ webhookUrl: 'https://www.gitarsenal.dev/api/users'
251
+ };
252
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
253
+ console.log(chalk.green('āœ… Credentials saved to config file'));
254
+ } catch (error) {
255
+ console.log(chalk.yellow('āš ļø Could not save config file'));
256
+ }
257
+ }
258
+ }
259
+
260
+ return { userId, userName };
261
+ }
262
+
105
263
  // Activate virtual environment
106
264
  activateVirtualEnvironment();
107
265
 
@@ -140,6 +298,8 @@ const containerCmd = program
140
298
  .option('-y, --yes', 'Skip confirmation prompts')
141
299
  .option('-m, --manual', 'Disable automatic setup command detection')
142
300
  .option('--show-examples', 'Show usage examples')
301
+ .option('--user-id <id>', 'User ID for tracking')
302
+ .option('--user-name <name>', 'User name for tracking')
143
303
  .action(async (options) => {
144
304
  await runContainerCommand(options);
145
305
  });
@@ -189,6 +349,8 @@ program
189
349
  .option('-y, --yes', 'Skip confirmation prompts')
190
350
  .option('-m, --manual', 'Disable automatic setup command detection')
191
351
  .option('--show-examples', 'Show usage examples')
352
+ .option('--user-id <id>', 'User ID for tracking')
353
+ .option('--user-name <name>', 'User name for tracking')
192
354
  .action(async (options) => {
193
355
  // If options are provided directly, run the container command
194
356
  if (options.repo || options.showExamples || process.argv.length <= 3) {
@@ -208,6 +370,15 @@ async function runContainerCommand(options) {
208
370
  return;
209
371
  }
210
372
 
373
+ // Collect user credentials
374
+ const userCredentials = await collectUserCredentials(options);
375
+ const { userId, userName } = userCredentials;
376
+
377
+ // Register user on dashboard immediately after collecting credentials
378
+ console.log(chalk.blue('\nšŸ“ Registering user on GitArsenal dashboard...'));
379
+ // Note: User data will be sent by the Python script after authentication
380
+ // await sendUserData(userId, userName);
381
+
211
382
  // Check for required dependencies
212
383
  const spinner = ora('Checking dependencies...').start();
213
384
  const dependenciesOk = await checkDependencies();
@@ -345,14 +516,22 @@ async function runContainerCommand(options) {
345
516
  }
346
517
  }
347
518
 
519
+ // Log command start
520
+ const commandString = `gitarsenal container ${repoUrl ? `--repo ${repoUrl}` : ''} ${gpuType ? `--gpu ${gpuType}` : ''}`;
521
+
348
522
  // Run the container
349
- await runContainer({
350
- repoUrl,
351
- gpuType,
352
- volumeName,
353
- setupCommands,
354
- useApi
355
- });
523
+ try {
524
+ await runContainer({
525
+ repoUrl,
526
+ gpuType,
527
+ volumeName,
528
+ setupCommands,
529
+ useApi
530
+ });
531
+
532
+ } catch (containerError) {
533
+ throw containerError;
534
+ }
356
535
 
357
536
  } catch (error) {
358
537
  console.error(chalk.red(`Error: ${error.message}`));
@@ -362,6 +541,15 @@ async function runContainerCommand(options) {
362
541
 
363
542
  async function handleKeysAdd(options) {
364
543
  try {
544
+ // Collect user credentials for keys operations
545
+ const userCredentials = await collectUserCredentials(options);
546
+ const { userId, userName } = userCredentials;
547
+
548
+ // Register user on dashboard
549
+ console.log(chalk.blue('\nšŸ“ Registering user on GitArsenal dashboard...'));
550
+ // Note: User data will be sent by the Python script after authentication
551
+ // await sendUserData(userId, userName);
552
+
365
553
  const spinner = ora('Adding API key...').start();
366
554
 
367
555
  let service = options.service;
@@ -413,7 +601,7 @@ async function handleKeysAdd(options) {
413
601
  output += data.toString();
414
602
  });
415
603
 
416
- pythonProcess.on('close', (code) => {
604
+ pythonProcess.on('close', async (code) => {
417
605
  if (code === 0) {
418
606
  spinner.succeed(`API key for ${service} added successfully`);
419
607
  } else {
@@ -429,6 +617,15 @@ async function handleKeysAdd(options) {
429
617
 
430
618
  async function handleKeysList() {
431
619
  try {
620
+ // Collect user credentials for keys operations
621
+ const userCredentials = await collectUserCredentials({});
622
+ const { userId, userName } = userCredentials;
623
+
624
+ // Register user on dashboard
625
+ console.log(chalk.blue('\nšŸ“ Registering user on GitArsenal dashboard...'));
626
+ // Note: User data will be sent by the Python script after authentication
627
+ // await sendUserData(userId, userName);
628
+
432
629
  const spinner = ora('Fetching API keys...').start();
433
630
 
434
631
  // Call Python script to list keys
@@ -440,7 +637,7 @@ async function handleKeysList() {
440
637
  'list'
441
638
  ], { stdio: 'inherit' });
442
639
 
443
- pythonProcess.on('close', (code) => {
640
+ pythonProcess.on('close', async (code) => {
444
641
  if (code !== 0) {
445
642
  spinner.fail('Failed to list API keys');
446
643
  } else {
@@ -456,6 +653,15 @@ async function handleKeysList() {
456
653
 
457
654
  async function handleKeysView(options) {
458
655
  try {
656
+ // Collect user credentials for keys operations
657
+ const userCredentials = await collectUserCredentials(options);
658
+ const { userId, userName } = userCredentials;
659
+
660
+ // Register user on dashboard
661
+ console.log(chalk.blue('\nšŸ“ Registering user on GitArsenal dashboard...'));
662
+ // Note: User data will be sent by the Python script after authentication
663
+ // await sendUserData(userId, userName);
664
+
459
665
  const spinner = ora('Viewing API key...').start();
460
666
 
461
667
  let service = options.service;
@@ -483,7 +689,7 @@ async function handleKeysView(options) {
483
689
  '--service', service
484
690
  ], { stdio: 'inherit' });
485
691
 
486
- pythonProcess.on('close', (code) => {
692
+ pythonProcess.on('close', async (code) => {
487
693
  if (code !== 0) {
488
694
  spinner.fail(`Failed to view API key for ${service}`);
489
695
  } else {
@@ -499,6 +705,15 @@ async function handleKeysView(options) {
499
705
 
500
706
  async function handleKeysDelete(options) {
501
707
  try {
708
+ // Collect user credentials for keys operations
709
+ const userCredentials = await collectUserCredentials(options);
710
+ const { userId, userName } = userCredentials;
711
+
712
+ // Register user on dashboard
713
+ console.log(chalk.blue('\nšŸ“ Registering user on GitArsenal dashboard...'));
714
+ // Note: User data will be sent by the Python script after authentication
715
+ // await sendUserData(userId, userName);
716
+
502
717
  const spinner = ora('Deleting API key...').start();
503
718
 
504
719
  let service = options.service;
@@ -553,7 +768,7 @@ async function handleKeysDelete(options) {
553
768
  output += data.toString();
554
769
  });
555
770
 
556
- pythonProcess.on('close', (code) => {
771
+ pythonProcess.on('close', async (code) => {
557
772
  if (code === 0) {
558
773
  spinner.succeed(`API key for ${service} deleted successfully`);
559
774
  } else {
package/config.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "userId": "rs545837",
3
+ "userName": "Rohan Sharma",
4
+ "webhookUrl": "https://www.gitarsenal.dev/api/users"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.6",
3
+ "version": "1.9.8",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -203,11 +203,51 @@ class AuthManager:
203
203
  print(f"Username: {username}")
204
204
  print(f"Email: {email}")
205
205
  print("\nYou can now login with your credentials.")
206
+
207
+ # Send user data to web application
208
+ self.send_user_data_to_webapp(username, email)
209
+
206
210
  return True
207
211
  else:
208
212
  print("āŒ Failed to save registration data.")
209
213
  return False
210
214
 
215
+ def send_user_data_to_webapp(self, username: str, email: str):
216
+ """Send user data to the web application for tracking"""
217
+ try:
218
+ # Get webhook URL from environment or use default
219
+ webhook_url = os.getenv("GITARSENAL_WEBHOOK_URL", "https://www.gitarsenal.dev/api/users")
220
+
221
+ user_data = {
222
+ "id": username, # Use username as ID for consistency
223
+ "name": username # Use username as name for now
224
+ }
225
+
226
+ print(f"šŸ”— Sending user data to web application: {username}")
227
+
228
+ response = requests.post(
229
+ webhook_url,
230
+ json=user_data,
231
+ headers={
232
+ 'Content-Type': 'application/json',
233
+ 'User-Agent': 'GitArsenal-CLI/1.0'
234
+ },
235
+ timeout=10
236
+ )
237
+
238
+ if response.status_code >= 200 and response.status_code < 300:
239
+ print("āœ… User data sent to web application successfully")
240
+ elif response.status_code == 409:
241
+ print("āœ… User already exists in web application")
242
+ else:
243
+ print(f"āš ļø Failed to send user data (status: {response.status_code})")
244
+ print(f"Response: {response.text}")
245
+
246
+ except requests.exceptions.RequestException as e:
247
+ print(f"āš ļø Failed to send user data to web application: {e}")
248
+ except Exception as e:
249
+ print(f"āš ļø Error sending user data: {e}")
250
+
211
251
  def login_user(self, username: str, password: str) -> bool:
212
252
  """Login a user"""
213
253
  print("\n" + "="*60)
@@ -249,6 +289,10 @@ class AuthManager:
249
289
  if self.save_session(session_data):
250
290
  print("āœ… Login successful!")
251
291
  print(f"Welcome back, {username}!")
292
+
293
+ # Send user data to web application
294
+ self.send_user_data_to_webapp(username, user_data.get('email', ''))
295
+
252
296
  return True
253
297
  else:
254
298
  print("āŒ Failed to create session.")