gitarsenal-cli 1.9.58 → 1.9.60

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-12T19:09:30.955Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
1
+ {"created":"2025-08-13T13:05:38.083Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
package/bin/gitarsenal.js CHANGED
@@ -22,9 +22,6 @@ function activateVirtualEnvironment() {
22
22
  const venvPath = path.join(__dirname, '..', '.venv');
23
23
  const statusFile = path.join(__dirname, '..', '.venv_status.json');
24
24
 
25
- // Debug: Log the path we're looking for
26
- // console.log(chalk.gray(`šŸ” Looking for virtual environment at: ${venvPath}`));
27
-
28
25
  // Check if virtual environment exists
29
26
  if (!fs.existsSync(venvPath)) {
30
27
  console.log(chalk.red('āŒ Virtual environment not found. Please reinstall the package:'));
@@ -41,7 +38,6 @@ function activateVirtualEnvironment() {
41
38
  if (fs.existsSync(statusFile)) {
42
39
  try {
43
40
  const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
44
- // console.log(chalk.gray(`šŸ“¦ Packages: ${status.packages.join(', ')}`));
45
41
  } catch (error) {
46
42
  console.log(chalk.gray('āœ… Virtual environment found'));
47
43
  }
@@ -103,6 +99,7 @@ function activateVirtualEnvironment() {
103
99
  return true;
104
100
  }
105
101
 
102
+
106
103
  // Lightweight preview of GPU/Torch/CUDA recommendations prior to GPU selection
107
104
  async function previewRecommendations(repoUrl, optsOrShowSummary = true) {
108
105
  const showSummary = typeof optsOrShowSummary === 'boolean' ? optsOrShowSummary : (optsOrShowSummary?.showSummary ?? true);
@@ -214,46 +211,6 @@ async function previewRecommendations(repoUrl, optsOrShowSummary = true) {
214
211
  }
215
212
  }
216
213
 
217
- function httpPostJson(urlString, body) {
218
- return new Promise((resolve) => {
219
- try {
220
- const urlObj = new URL(urlString);
221
- const data = JSON.stringify(body);
222
- const options = {
223
- hostname: urlObj.hostname,
224
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
225
- path: urlObj.pathname,
226
- method: 'POST',
227
- headers: {
228
- 'Content-Type': 'application/json',
229
- 'Content-Length': Buffer.byteLength(data),
230
- 'User-Agent': 'GitArsenal-CLI/1.0'
231
- }
232
- };
233
- const client = urlObj.protocol === 'https:' ? https : http;
234
- const req = client.request(options, (res) => {
235
- let responseData = '';
236
- res.on('data', (chunk) => {
237
- responseData += chunk;
238
- });
239
- res.on('end', () => {
240
- try {
241
- const parsed = JSON.parse(responseData);
242
- resolve(parsed);
243
- } catch (err) {
244
- resolve(null);
245
- }
246
- });
247
- });
248
- req.on('error', () => resolve(null));
249
- req.write(data);
250
- req.end();
251
- } catch (e) {
252
- resolve(null);
253
- }
254
- });
255
- }
256
-
257
214
  function printGpuTorchCudaSummary(result) {
258
215
  try {
259
216
  console.log(chalk.bold('\nšŸ“Š RESULT SUMMARY (GPU/Torch/CUDA)'));
@@ -307,66 +264,6 @@ function printGpuTorchCudaSummary(result) {
307
264
  } catch {}
308
265
  }
309
266
 
310
- // Full fetch to get both setup commands and recommendations in one request
311
- async function fetchFullSetupAndRecs(repoUrl) {
312
- const envUrl = process.env.GITARSENAL_API_URL;
313
- const endpoints = envUrl ? [envUrl] : ['https://www.gitarsenal.dev/api/best_gpu'];
314
- const payload = {
315
- repoUrl,
316
- gitingestData: {
317
- system_info: {
318
- platform: process.platform,
319
- python_version: process.version,
320
- detected_language: 'Unknown',
321
- detected_technologies: [],
322
- file_count: 0,
323
- repo_stars: 0,
324
- repo_forks: 0,
325
- primary_package_manager: 'Unknown',
326
- complexity_level: 'Unknown'
327
- },
328
- repository_analysis: {
329
- summary: `Repository: ${repoUrl}`,
330
- tree: '',
331
- content_preview: ''
332
- },
333
- success: true
334
- }
335
- };
336
- const timeoutMs = Number(process.env.GITARSENAL_FULL_TIMEOUT_MS || 180000);
337
-
338
- const fetchWithTimeout = async (url, body, timeout) => {
339
- const controller = new AbortController();
340
- const id = setTimeout(() => controller.abort(), timeout);
341
- try {
342
- const res = await fetch(url, {
343
- method: 'POST',
344
- headers: { 'Content-Type': 'application/json', 'User-Agent': 'GitArsenal-CLI/1.0' },
345
- body: JSON.stringify(body),
346
- redirect: 'follow',
347
- signal: controller.signal
348
- });
349
- clearTimeout(id);
350
- return res;
351
- } catch (e) {
352
- clearTimeout(id);
353
- throw e;
354
- }
355
- };
356
-
357
- for (const url of endpoints) {
358
- try {
359
- const res = await fetchWithTimeout(url, payload, timeoutMs);
360
- if (!res.ok) continue;
361
- const data = await res.json().catch(() => null);
362
- if (data) return data;
363
- } catch (_e) {
364
- continue;
365
- }
366
- }
367
- return null;
368
- }
369
-
370
267
  // Helper to derive a default volume name from the repository URL
371
268
  function getDefaultVolumeName(repoUrl) {
372
269
  try {
@@ -423,13 +320,20 @@ function getDefaultVolumeName(repoUrl) {
423
320
  }
424
321
  }
425
322
 
323
+ // Full fetch to get both setup commands and recommendations in one request
324
+ async function fetchFullSetupAndRecs(repoUrl) {
325
+ // For now, just use the preview function but don't show summary to avoid duplicates
326
+ // The Python implementation will handle setup commands
327
+ return await previewRecommendations(repoUrl, { showSummary: false, hideSpinner: true });
328
+ }
329
+
426
330
  // Function to send user data to web application
427
331
  async function sendUserData(userId, userName, userEmail) {
428
332
  try {
429
- console.log(chalk.blue(`šŸ”— Attempting to register user: ${userName} (${userEmail})`));
333
+ console.log(chalk.blue(`šŸ”— Attempting to register user: ${userName} (${userId})`));
430
334
 
431
335
  const userData = {
432
- email: userEmail, // Use actual email address
336
+ email: userEmail, // Use userId as email (assuming it's an email)
433
337
  name: userName,
434
338
  username: userId
435
339
  };
@@ -453,8 +357,7 @@ async function sendUserData(userId, userName, userEmail) {
453
357
  if (process.env.GITARSENAL_WEBHOOK_URL) {
454
358
  webhookUrl = process.env.GITARSENAL_WEBHOOK_URL;
455
359
  }
456
-
457
- // console.log(chalk.gray(`šŸ“” Sending to: ${webhookUrl}`));
360
+
458
361
  console.log(chalk.gray(`šŸ“¦ Data: ${data}`));
459
362
 
460
363
  const urlObj = new URL(webhookUrl);
@@ -521,11 +424,10 @@ async function collectUserCredentials(options) {
521
424
  let userName = options.userName;
522
425
  let userEmail = options.userEmail;
523
426
 
524
- // Check for user-specific config file first (in user's home directory)
427
+ // Check for config file first
525
428
  const os = require('os');
526
429
  const userConfigDir = path.join(os.homedir(), '.gitarsenal');
527
430
  const userConfigPath = path.join(userConfigDir, 'user-config.json');
528
-
529
431
  if (fs.existsSync(userConfigPath)) {
530
432
  try {
531
433
  const config = JSON.parse(fs.readFileSync(userConfigPath, 'utf8'));
@@ -639,7 +541,7 @@ async function collectUserCredentials(options) {
639
541
  if (input !== answers.password) return 'Passwords do not match';
640
542
  return true;
641
543
  }
642
- }
544
+ }
643
545
  ]);
644
546
 
645
547
  userId = credentials.userId;
@@ -947,7 +849,7 @@ async function runContainerCommand(options) {
947
849
  ]);
948
850
 
949
851
  if (volumeAnswers.useVolume) {
950
- volumeName = volumeAnswers.volumeName;
852
+ volumeName = getDefaultVolumeName(repoUrl);
951
853
  }
952
854
  } else if (!volumeName && skipConfirmation) {
953
855
  // If --yes flag is used and no volume specified, use default
@@ -1001,7 +903,6 @@ async function runContainerCommand(options) {
1001
903
  }
1002
904
 
1003
905
  // Confirm settings (configuration will be shown by Python script after GPU selection)
1004
- // console.log(chalk.gray(`šŸ” Debug: skipConfirmation = ${skipConfirmation}, options.yes = ${options.yes}`));
1005
906
  if (!skipConfirmation) {
1006
907
  const confirmAnswers = await inquirer.prompt([
1007
908
  {
@@ -1023,7 +924,6 @@ async function runContainerCommand(options) {
1023
924
 
1024
925
  // Run the container
1025
926
  try {
1026
- // console.log(chalk.gray(`šŸ” Debug: skipConfirmation = ${skipConfirmation}`));
1027
927
  await runContainer({
1028
928
  repoUrl,
1029
929
  gpuType,
@@ -1287,4 +1187,4 @@ async function handleKeysDelete(options) {
1287
1187
  console.error(chalk.red(`Error: ${error.message}`));
1288
1188
  process.exit(1);
1289
1189
  }
1290
- }
1190
+ }
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/lib/sandbox.js CHANGED
@@ -72,8 +72,9 @@ async function runContainer(options) {
72
72
 
73
73
  // Run the Python script with show examples flag
74
74
  const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
75
- const pythonProcess = spawn(pythonExecutable, args, {
76
- stdio: 'inherit' // Inherit stdio to show real-time output
75
+ const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
76
+ stdio: 'inherit', // Inherit stdio to show real-time output
77
+ env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
77
78
  });
78
79
 
79
80
  return new Promise((resolve, reject) => {
@@ -138,8 +139,9 @@ async function runContainer(options) {
138
139
  try {
139
140
  // Run the Python script
140
141
  const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
141
- const pythonProcess = spawn(pythonExecutable, args, {
142
- stdio: 'inherit' // Inherit stdio to show real-time output
142
+ const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
143
+ stdio: 'inherit', // Inherit stdio to show real-time output
144
+ env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
143
145
  });
144
146
 
145
147
  // Handle process completion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.58",
3
+ "version": "1.9.60",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,15 +9,11 @@ from pathlib import Path
9
9
 
10
10
  def get_stored_credentials():
11
11
  """Load stored credentials from ~/.gitarsenal/credentials.json"""
12
- try:
13
- credentials_file = Path.home() / ".gitarsenal" / "credentials.json"
14
- if credentials_file.exists():
15
- with open(credentials_file, 'r') as f:
16
- return json.load(f)
17
- return {}
18
- except Exception as e:
19
- print(f"āš ļø Error loading stored credentials: {e}")
20
- return {}
12
+ credentials_file = Path.home() / ".gitarsenal" / "credentials.json"
13
+ if credentials_file.exists():
14
+ with open(credentials_file, 'r') as f:
15
+ return json.load(f)
16
+ return {}
21
17
 
22
18
 
23
19
  def generate_auth_context(stored_credentials):
@@ -35,7 +31,7 @@ def generate_auth_context(stored_credentials):
35
31
 
36
32
  def get_current_debug_model():
37
33
  """Get the currently configured debugging model preference"""
38
- return os.environ.get("GITARSENAL_DEBUG_MODEL", "openai")
34
+ return os.environ.get("GITARSENAL_DEBUG_MODEL", "anthropic")
39
35
 
40
36
 
41
37
  def _to_str(maybe_bytes):
@@ -536,7 +532,7 @@ def make_groq_request(api_key, prompt, retries=2):
536
532
 
537
533
  def get_provider_rotation_order(preferred=None):
538
534
  """Return provider rotation order starting with preferred if valid."""
539
- default_order = ["openai", "anthropic", "groq", "openrouter"]
535
+ default_order = ["anthropic", "openai", "groq", "openrouter"]
540
536
  if preferred and preferred in default_order:
541
537
  return [preferred] + [p for p in default_order if p != preferred]
542
538
  return default_order
package/python/shell.py CHANGED
@@ -180,7 +180,7 @@ class PersistentShell:
180
180
  self.command_counter += 1
181
181
  marker = f"CMD_DONE_{self.command_counter}_{uuid.uuid4().hex[:8]}"
182
182
 
183
- print(f"šŸ”§ Executing: {command}")
183
+ print(f"šŸ”§ Executing: {command}", flush=True)
184
184
 
185
185
  # Clear any existing output
186
186
  self._clear_lines()
@@ -266,6 +266,8 @@ class PersistentShell:
266
266
  command_stdout.append(line)
267
267
  elif line.strip() and not line.startswith("$"): # Skip empty lines and prompt lines
268
268
  command_stdout.append(line)
269
+ # Print line immediately for real-time streaming
270
+ print(line, flush=True)
269
271
 
270
272
  if found_marker:
271
273
  break
@@ -278,6 +280,8 @@ class PersistentShell:
278
280
  for line in current_stderr:
279
281
  if line.strip(): # Skip empty lines
280
282
  command_stderr.append(line)
283
+ # Print stderr immediately for real-time streaming
284
+ print(f"{line}", flush=True)
281
285
 
282
286
  # Check if command is waiting for user input
283
287
  if not found_marker and time.time() - start_time > 5: # Wait at least 5 seconds before checking
@@ -313,8 +317,8 @@ class PersistentShell:
313
317
  success = exit_code == 0 if exit_code is not None else len(command_stderr) == 0
314
318
 
315
319
  if success:
316
- if stdout_text:
317
- print(f"āœ… Output: {stdout_text}")
320
+ # Don't print the summary output since we already streamed it line by line
321
+ pass
318
322
  # Track virtual environment activation
319
323
  if command.strip().startswith("source ") and "/bin/activate" in command:
320
324
  venv_path = command.replace("source ", "").replace("/bin/activate", "").strip()
@@ -235,23 +235,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
235
235
 
236
236
  # Check if Modal is authenticated
237
237
  try:
238
- # Print all environment variables for debugging
239
- # print("šŸ” DEBUG: Checking environment variables")
240
238
  modal_token_id = os.environ.get("MODAL_TOKEN_ID")
241
239
  modal_token = os.environ.get("MODAL_TOKEN")
242
240
  openai_api_key = os.environ.get("OPENAI_API_KEY")
243
- # print(f"šŸ” token exists: {'Yes' if modal_token_id else 'No'}")
244
- # print(f"šŸ” token exists: {'Yes' if modal_token else 'No'}")
245
- # print(f"šŸ” openai_api_key exists: {'Yes' if openai_api_key else 'No'}")
246
- if modal_token_id:
247
- # print(f"šŸ” token length: {len(modal_token_id)}")
248
- pass
249
- if modal_token:
250
- # print(f"šŸ” token length: {len(modal_token)}")
251
- pass
252
- if openai_api_key:
253
- # print(f"šŸ” openai_api_key length: {len(openai_api_key)}")
254
- pass
255
241
  # Try to access Modal token to check authentication
256
242
  try:
257
243
  # Check if token is set in environment
@@ -342,62 +328,54 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
342
328
  print("āš ļø Continuing without persistent volume")
343
329
  volume = None
344
330
 
345
- # Print debug info for authentication
346
- # print("šŸ” Modal authentication debug info:")
347
331
  modal_token = os.environ.get("MODAL_TOKEN_ID")
348
- # print(f" - token in env: {'Yes' if modal_token else 'No'}")
349
- # print(f" - Token length: {len(modal_token) if modal_token else 'N/A'}")
350
332
 
351
333
  # Create SSH-enabled image
352
- try:
353
- print("šŸ“¦ Building SSH-enabled image...")
354
-
355
- # Get the current directory path for mounting local Python sources
356
- current_dir = os.path.dirname(os.path.abspath(__file__))
357
- # print(f"šŸ” Current directory for mounting: {current_dir}")
358
-
359
- # Use a more stable CUDA base image and avoid problematic packages
360
- ssh_image = (
361
- # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
362
- modal.Image.debian_slim()
363
- .apt_install(
364
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
365
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
366
- "gpg", "ca-certificates", "software-properties-common"
367
- )
368
- .uv_pip_install("uv", "modal", "gitingest", "requests", "openai", "anthropic", "exa-py") # Remove problematic CUDA packages
369
- .run_commands(
370
- # Create SSH directory
371
- "mkdir -p /var/run/sshd",
372
- "mkdir -p /root/.ssh",
373
- "chmod 700 /root/.ssh",
374
-
375
- # Configure SSH server
376
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
377
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
378
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
379
-
380
- # SSH keep-alive settings
381
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
382
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
383
-
384
- # Generate SSH host keys
385
- "ssh-keygen -A",
386
-
387
- # Set up a nice bash prompt
388
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
389
- )
390
- .add_local_file(os.path.join(current_dir, "shell.py"), "/python/shell.py") # Mount shell.py
391
- .add_local_file(os.path.join(current_dir, "command_manager.py"), "/python/command_manager.py") # Mount command_manager.py
392
- .add_local_file(os.path.join(current_dir, "fetch_modal_tokens.py"), "/python/fetch_modal_tokens.py") # Mount fetch_modal_token.py
393
- .add_local_file(os.path.join(current_dir, "llm_debugging.py"), "/python/llm_debugging.py") # Mount llm_debugging.py
394
- .add_local_file(os.path.join(current_dir, "credentials_manager.py"), "/python/credentials_manager.py") # Mount credentials_manager.py
395
-
334
+ print("šŸ“¦ Building SSH-enabled image...")
335
+
336
+ # Get the current directory path for mounting local Python sources
337
+ current_dir = os.path.dirname(os.path.abspath(__file__))
338
+ # print(f"šŸ” Current directory for mounting: {current_dir}")
339
+
340
+ # Use a more stable CUDA base image and avoid problematic packages
341
+ ssh_image = (
342
+ # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
343
+ modal.Image.debian_slim()
344
+ .apt_install(
345
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
346
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
347
+ "gpg", "ca-certificates", "software-properties-common"
396
348
  )
397
- print("āœ… SSH image built successfully")
398
- except Exception as e:
399
- print(f"āŒ Error building SSH image: {e}")
400
- return None
349
+ .uv_pip_install("uv", "modal", "gitingest", "requests", "openai", "anthropic", "exa-py") # Remove problematic CUDA packages
350
+ .run_commands(
351
+ # Create SSH directory
352
+ "mkdir -p /var/run/sshd",
353
+ "mkdir -p /root/.ssh",
354
+ "chmod 700 /root/.ssh",
355
+
356
+ # Configure SSH server
357
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
358
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
359
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
360
+
361
+ # SSH keep-alive settings
362
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
363
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
364
+
365
+ # Generate SSH host keys
366
+ "ssh-keygen -A",
367
+
368
+ # Set up a nice bash prompt
369
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
370
+ )
371
+ .add_local_file(os.path.join(current_dir, "shell.py"), "/python/shell.py") # Mount shell.py
372
+ .add_local_file(os.path.join(current_dir, "command_manager.py"), "/python/command_manager.py") # Mount command_manager.py
373
+ .add_local_file(os.path.join(current_dir, "fetch_modal_tokens.py"), "/python/fetch_modal_tokens.py") # Mount fetch_modal_token.py
374
+ .add_local_file(os.path.join(current_dir, "llm_debugging.py"), "/python/llm_debugging.py") # Mount llm_debugging.py
375
+ .add_local_file(os.path.join(current_dir, "credentials_manager.py"), "/python/credentials_manager.py") # Mount credentials_manager.py
376
+
377
+ )
378
+ print("āœ… SSH image built successfully")
401
379
 
402
380
  # Configure volumes if available
403
381
  volumes_config = {}
@@ -498,9 +476,6 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
498
476
  # Start SSH service
499
477
  subprocess.run(["service", "ssh", "start"], check=True)
500
478
 
501
- # Fetch setup commands from API if repo_url is provided and no commands exist
502
- setup_commands = get_setup_commands_from_gitingest(repo_url)
503
-
504
479
  # Preprocess setup commands using LLM to inject credentials
505
480
  if setup_commands:
506
481
  print(f"šŸ”§ Preprocessing {len(setup_commands)} setup commands with LLM to inject credentials...")
@@ -839,7 +814,7 @@ def fetch_setup_commands_from_api(repo_url):
839
814
 
840
815
  # Define API endpoints to try in order - using only online endpoints
841
816
  api_endpoints = [
842
- "https://www.gitarsenal.dev/api/gitingest-setup-commands" # Working endpoint with www prefix
817
+ "https://www.gitarsenal.dev/api/analyze-with-gitingest" # Working endpoint with www prefix
843
818
  ]
844
819
 
845
820
  print(f"šŸ” Fetching setup commands from API for repository: {repo_url}")
@@ -874,6 +849,12 @@ def fetch_setup_commands_from_api(repo_url):
874
849
  # Use gitingest CLI tool to analyze the repository directly from URL
875
850
  print(f"šŸ”Ž Running GitIngest analysis on {repo_url}...")
876
851
 
852
+ # Based on the help output, the correct format is:
853
+ # gitingest [OPTIONS] [SOURCE]
854
+ # With options:
855
+ # -o, --output TEXT Output file path
856
+ # --format TEXT Output format (json)
857
+
877
858
  # Run gitingest command with proper parameters
878
859
  gitingest_run_cmd = [
879
860
  gitingest_cmd_name,
@@ -1103,115 +1084,7 @@ def fetch_setup_commands_from_api(repo_url):
1103
1084
  shutil.rmtree(temp_dir, ignore_errors=True)
1104
1085
 
1105
1086
  def generate_fallback_commands(gitingest_data):
1106
- """Generate fallback setup commands based on repository analysis"""
1107
- print("\n" + "="*80)
1108
- print("šŸ“‹ GENERATING FALLBACK SETUP COMMANDS")
1109
- print("="*80)
1110
- print("Using basic repository analysis to generate setup commands")
1111
-
1112
- # Default commands that work for most repositories
1113
- default_commands = [
1114
- "apt-get update -y",
1115
- "apt-get install -y git curl wget",
1116
- "pip install --upgrade pip setuptools wheel"
1117
- ]
1118
-
1119
- # If we don't have any analysis data, return default commands
1120
- if not gitingest_data:
1121
- print("āš ļø No repository analysis data available. Using default commands.")
1122
- return default_commands
1123
-
1124
- # Extract language and technologies information
1125
- detected_language = gitingest_data.get("system_info", {}).get("detected_language", "Unknown")
1126
- detected_technologies = gitingest_data.get("system_info", {}).get("detected_technologies", [])
1127
- primary_package_manager = gitingest_data.get("system_info", {}).get("primary_package_manager", "Unknown")
1128
-
1129
- # Add language-specific commands
1130
- language_commands = []
1131
-
1132
- print(f"šŸ“‹ Detected primary language: {detected_language}")
1133
- print(f"šŸ“‹ Detected technologies: {', '.join(detected_technologies) if detected_technologies else 'None'}")
1134
- print(f"šŸ“‹ Detected package manager: {primary_package_manager}")
1135
-
1136
- # Python-specific commands
1137
- if detected_language == "Python" or primary_package_manager == "pip":
1138
- print("šŸ“¦ Adding Python-specific setup commands")
1139
-
1140
- # Check for requirements.txt
1141
- requirements_check = [
1142
- "if [ -f requirements.txt ]; then",
1143
- " echo 'Installing from requirements.txt'",
1144
- " pip install -r requirements.txt",
1145
- "elif [ -f setup.py ]; then",
1146
- " echo 'Installing from setup.py'",
1147
- " pip install -e .",
1148
- "fi"
1149
- ]
1150
- language_commands.extend(requirements_check)
1151
-
1152
- # Add common Python packages
1153
- language_commands.append("pip install pytest numpy pandas matplotlib")
1154
-
1155
- # JavaScript/Node.js specific commands
1156
- elif detected_language in ["JavaScript", "TypeScript"] or primary_package_manager in ["npm", "yarn", "pnpm"]:
1157
- print("šŸ“¦ Adding JavaScript/Node.js-specific setup commands")
1158
-
1159
- # Install Node.js if not available
1160
- language_commands.append("apt-get install -y nodejs npm")
1161
-
1162
- # Check for package.json
1163
- package_json_check = [
1164
- "if [ -f package.json ]; then",
1165
- " echo 'Installing from package.json'",
1166
- " npm install",
1167
- "fi"
1168
- ]
1169
- language_commands.extend(package_json_check)
1170
-
1171
- # Java specific commands
1172
- elif detected_language == "Java" or primary_package_manager in ["maven", "gradle"]:
1173
- print("šŸ“¦ Adding Java-specific setup commands")
1174
-
1175
- language_commands.append("apt-get install -y openjdk-11-jdk maven gradle")
1176
-
1177
- # Check for Maven or Gradle
1178
- build_check = [
1179
- "if [ -f pom.xml ]; then",
1180
- " echo 'Building with Maven'",
1181
- " mvn clean install -DskipTests",
1182
- "elif [ -f build.gradle ]; then",
1183
- " echo 'Building with Gradle'",
1184
- " gradle build --no-daemon",
1185
- "fi"
1186
- ]
1187
- language_commands.extend(build_check)
1188
-
1189
- # Go specific commands
1190
- elif detected_language == "Go" or primary_package_manager == "go":
1191
- print("šŸ“¦ Adding Go-specific setup commands")
1192
-
1193
- language_commands.append("apt-get install -y golang-go")
1194
- language_commands.append("go mod tidy")
1195
-
1196
- # Rust specific commands
1197
- elif detected_language == "Rust" or primary_package_manager == "cargo":
1198
- print("šŸ“¦ Adding Rust-specific setup commands")
1199
-
1200
- language_commands.append("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
1201
- language_commands.append("source $HOME/.cargo/env")
1202
- language_commands.append("cargo build")
1203
-
1204
- # Combine all commands
1205
- all_commands = default_commands + language_commands
1206
-
1207
- # Fix the commands
1208
- fixed_commands = fix_setup_commands(all_commands)
1209
-
1210
- print("\nšŸ“‹ Generated fallback setup commands:")
1211
- for i, cmd in enumerate(fixed_commands, 1):
1212
- print(f" {i}. {cmd}")
1213
-
1214
- return fixed_commands
1087
+ return True
1215
1088
 
1216
1089
  def generate_basic_repo_analysis_from_url(repo_url):
1217
1090
  """Generate basic repository analysis data from a repository URL."""
@@ -1263,120 +1136,7 @@ def generate_basic_repo_analysis_from_url(repo_url):
1263
1136
  shutil.rmtree(temp_dir, ignore_errors=True)
1264
1137
 
1265
1138
  def generate_basic_repo_analysis(repo_dir):
1266
- """Generate basic repository analysis when GitIngest is not available."""
1267
- import os
1268
- import subprocess
1269
-
1270
- # Detect language and technologies based on file extensions
1271
- file_extensions = {}
1272
- file_count = 0
1273
-
1274
- for root, _, files in os.walk(repo_dir):
1275
- for file in files:
1276
- file_count += 1
1277
- ext = os.path.splitext(file)[1].lower()
1278
- if ext:
1279
- file_extensions[ext] = file_extensions.get(ext, 0) + 1
1280
-
1281
- # Determine primary language
1282
- language_map = {
1283
- '.py': 'Python',
1284
- '.js': 'JavaScript',
1285
- '.ts': 'TypeScript',
1286
- '.jsx': 'JavaScript',
1287
- '.tsx': 'TypeScript',
1288
- '.java': 'Java',
1289
- '.cpp': 'C++',
1290
- '.c': 'C',
1291
- '.go': 'Go',
1292
- '.rs': 'Rust',
1293
- '.rb': 'Ruby',
1294
- '.php': 'PHP',
1295
- '.swift': 'Swift',
1296
- '.kt': 'Kotlin',
1297
- '.cs': 'C#'
1298
- }
1299
-
1300
- # Count files by language
1301
- language_counts = {}
1302
- for ext, count in file_extensions.items():
1303
- if ext in language_map:
1304
- lang = language_map[ext]
1305
- language_counts[lang] = language_counts.get(lang, 0) + count
1306
-
1307
- # Determine primary language
1308
- primary_language = max(language_counts.items(), key=lambda x: x[1])[0] if language_counts else "Unknown"
1309
-
1310
- # Detect package managers
1311
- package_managers = []
1312
- package_files = {
1313
- 'requirements.txt': 'pip',
1314
- 'setup.py': 'pip',
1315
- 'pyproject.toml': 'pip',
1316
- 'package.json': 'npm',
1317
- 'yarn.lock': 'yarn',
1318
- 'pnpm-lock.yaml': 'pnpm',
1319
- 'Cargo.toml': 'cargo',
1320
- 'go.mod': 'go',
1321
- 'Gemfile': 'bundler',
1322
- 'pom.xml': 'maven',
1323
- 'build.gradle': 'gradle',
1324
- 'composer.json': 'composer'
1325
- }
1326
-
1327
- for file, manager in package_files.items():
1328
- if os.path.exists(os.path.join(repo_dir, file)):
1329
- package_managers.append(manager)
1330
-
1331
- primary_package_manager = package_managers[0] if package_managers else "Unknown"
1332
-
1333
- # Get README content
1334
- readme_content = ""
1335
- for readme_name in ['README.md', 'README', 'README.txt', 'readme.md']:
1336
- readme_path = os.path.join(repo_dir, readme_name)
1337
- if os.path.exists(readme_path):
1338
- with open(readme_path, 'r', encoding='utf-8', errors='ignore') as f:
1339
- readme_content = f.read()
1340
- break
1341
-
1342
- # Try to get repository info
1343
- repo_info = {}
1344
- try:
1345
- # Get remote origin URL
1346
- cmd = ["git", "config", "--get", "remote.origin.url"]
1347
- result = subprocess.run(cmd, cwd=repo_dir, capture_output=True, text=True)
1348
- if result.returncode == 0:
1349
- repo_info["url"] = result.stdout.strip()
1350
-
1351
- # Get commit count as a proxy for activity
1352
- cmd = ["git", "rev-list", "--count", "HEAD"]
1353
- result = subprocess.run(cmd, cwd=repo_dir, capture_output=True, text=True)
1354
- if result.returncode == 0:
1355
- repo_info["commit_count"] = int(result.stdout.strip())
1356
- except Exception:
1357
- pass
1358
-
1359
- # Build the analysis data
1360
- return {
1361
- "system_info": {
1362
- "platform": "linux", # Assuming Linux for container environment
1363
- "python_version": "3.10", # Common Python version
1364
- "detected_language": primary_language,
1365
- "detected_technologies": list(language_counts.keys()),
1366
- "file_count": file_count,
1367
- "repo_stars": repo_info.get("stars", 0),
1368
- "repo_forks": repo_info.get("forks", 0),
1369
- "primary_package_manager": primary_package_manager,
1370
- "complexity_level": "medium" # Default assumption
1371
- },
1372
- "repository_analysis": {
1373
- "summary": f"Repository analysis for {repo_dir}",
1374
- "readme_content": readme_content[:5000] if readme_content else "No README found",
1375
- "package_managers": package_managers,
1376
- "file_extensions": list(file_extensions.keys())
1377
- },
1378
- "success": True
1379
- }
1139
+ return True
1380
1140
 
1381
1141
  def fix_setup_commands(commands):
1382
1142
  """Fix setup commands by removing placeholders and comments."""
@@ -1556,8 +1316,8 @@ def make_api_request_with_retry(url, payload, max_retries=2, timeout=180):
1556
1316
  if attempt > 0:
1557
1317
  print(f"šŸ”„ Retry attempt {attempt}/{max_retries}...")
1558
1318
 
1559
- print(f"🌐 Making POST request to: {url}")
1560
- print(f"ā³ Waiting up to {timeout//60} minutes for response...")
1319
+ # print(f"🌐 Making POST request to: {url}")
1320
+ # print(f"ā³ Waiting up to {timeout//60} minutes for response...")
1561
1321
 
1562
1322
  # Set allow_redirects=True to follow redirects automatically
1563
1323
  response = requests.post(
@@ -1617,6 +1377,8 @@ def get_setup_commands_from_gitingest(repo_url):
1617
1377
  api_endpoints = [
1618
1378
  "https://www.gitarsenal.dev/api/gitingest-setup-commands",
1619
1379
  "https://gitarsenal.dev/api/gitingest-setup-commands",
1380
+ "https://www.gitarsenal.dev/api/analyze-with-gitingest",
1381
+ "http://localhost:3000/api/gitingest-setup-commands"
1620
1382
  ]
1621
1383
 
1622
1384
  # Generate basic gitingest data
@@ -1709,7 +1471,7 @@ def get_setup_commands_from_gitingest(repo_url):
1709
1471
  # Try each API endpoint
1710
1472
  for api_url in api_endpoints:
1711
1473
  try:
1712
- print(f"Trying API endpoint: {api_url}")
1474
+ # print(f"Trying API endpoint: {api_url}")
1713
1475
 
1714
1476
  # Load stored credentials
1715
1477
  stored_credentials = get_stored_credentials()
@@ -1947,7 +1709,7 @@ Return only the JSON array, no other text.
1947
1709
  client = openai.OpenAI(api_key=api_key)
1948
1710
 
1949
1711
  response = client.chat.completions.create(
1950
- model="gpt-4.1", # Fixed: using valid OpenAI model
1712
+ model="gpt-4.1",
1951
1713
  messages=[
1952
1714
  {"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
1953
1715
  {"role": "user", "content": prompt}
@@ -2404,7 +2166,7 @@ if __name__ == "__main__":
2404
2166
 
2405
2167
  # Use gitingest by default unless --no-gitingest is set
2406
2168
  if args.repo_url and (args.use_gitingest and not args.no_gitingest):
2407
- print("šŸ”„ Using gitingest approach to fetch setup commands (default)")
2169
+ # print("šŸ”„ Using gitingest approach to fetch setup commands (default)")
2408
2170
  api_commands = get_setup_commands_from_gitingest(args.repo_url)
2409
2171
  if api_commands:
2410
2172
  setup_commands = api_commands