gitarsenal-cli 1.3.8 → 1.3.9

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.
@@ -119,12 +119,12 @@ except Exception as e:
119
119
  # print(f"šŸ” Token ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
120
120
  # print(f"šŸ” Token secret exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
121
121
  # print(f"šŸ” Token exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
122
- # if os.environ.get('MODAL_TOKEN_ID'):
123
- # print(f"šŸ” Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
124
- # if os.environ.get('MODAL_TOKEN_SECRET'):
125
- # print(f"šŸ” Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
126
- # if os.environ.get('MODAL_TOKEN'):
127
- # print(f"šŸ” Token length: {len(os.environ.get('MODAL_TOKEN'))}")
122
+ if os.environ.get('MODAL_TOKEN_ID'):
123
+ print(f"šŸ” Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
124
+ if os.environ.get('MODAL_TOKEN_SECRET'):
125
+ print(f"šŸ” Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
126
+ if os.environ.get('MODAL_TOKEN'):
127
+ print(f"šŸ” Token length: {len(os.environ.get('MODAL_TOKEN'))}")
128
128
  # print(f"āœ… Token setup completed")
129
129
 
130
130
  # Import modal after token setup
@@ -2025,7 +2025,7 @@ cd "{current_dir}"
2025
2025
  print("ā³ Sandbox will remain running until you terminate it with:")
2026
2026
  print(f"modal sandbox terminate {sandbox_id}")
2027
2027
 
2028
- # Try to open a new terminal window and connect to the containera
2028
+ # Try to open a new terminal window and connect to the container
2029
2029
  if container_id:
2030
2030
  print("šŸ–„ļø Attempting to open new terminal window...")
2031
2031
  # Use osascript to open a new terminal with the modal shell command
@@ -2558,129 +2558,301 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2558
2558
  print(f"āŒ Error running container: {e}")
2559
2559
  return None
2560
2560
 
2561
+ def is_local_server_running(url, timeout=2):
2562
+ """Check if a local server is running by attempting a connection."""
2563
+ import requests
2564
+ try:
2565
+ response = requests.get(url, timeout=timeout)
2566
+ return True
2567
+ except requests.exceptions.RequestException:
2568
+ return False
2569
+
2561
2570
  def fetch_setup_commands_from_api(repo_url):
2562
- """Generate setup commands locally using GitIngest analysis without API calls."""
2571
+ """Fetch setup commands from the GitIngest API using real repository analysis."""
2563
2572
  import tempfile
2564
2573
  import subprocess
2565
2574
  import os
2566
2575
  import shutil
2567
2576
  import json
2577
+ import time
2578
+ import requests
2579
+
2580
+ # Define API endpoints to try in order - using only online endpoints
2581
+ api_endpoints = [
2582
+ "https://www.gitarsenal.dev/api/analyze-with-gitingest" # Working endpoint with www prefix
2583
+ ]
2584
+
2585
+ print(f"šŸ” Fetching setup commands from API for repository: {repo_url}")
2568
2586
 
2569
- print(f"šŸ” Analyzing repository: {repo_url}")
2587
+ # Check if gitingest command line tool is available - try multiple possible command names
2588
+ has_gitingest_cli = False
2589
+ gitingest_cmd_name = None
2570
2590
 
2571
- # Check if gitingest command line tool is available
2591
+ # Try the standard command name first
2572
2592
  try:
2573
2593
  print(f"šŸ” Checking for GitIngest CLI tool...")
2574
2594
  result = subprocess.run(["gitingest", "--help"], check=True, capture_output=True, text=True)
2595
+ has_gitingest_cli = True
2596
+ gitingest_cmd_name = "gitingest"
2575
2597
  print(f"āœ… GitIngest CLI tool found")
2576
2598
  except (subprocess.SubprocessError, FileNotFoundError) as e:
2577
- print(f"āŒ GitIngest CLI tool not found: {str(e)}")
2578
- return generate_fallback_commands(None)
2599
+ print(f" - GitIngest command not found: {str(e)}")
2579
2600
 
2580
2601
  # Create a temporary directory for output
2581
2602
  temp_dir = tempfile.mkdtemp(prefix="repo_analysis_")
2582
2603
  output_file = os.path.join(temp_dir, "digest.json")
2583
2604
 
2605
+ # Create a directory to save GitIngest results
2606
+ save_dir = os.path.join(os.path.expanduser("~"), "gitarsenal_results")
2607
+ os.makedirs(save_dir, exist_ok=True)
2608
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
2609
+ repo_name = repo_url.split("/")[-1].replace(".git", "")
2610
+ save_file = os.path.join(save_dir, f"gitingest_{repo_name}_{timestamp}.txt")
2611
+
2584
2612
  try:
2585
- # Use gitingest CLI tool to analyze the repository directly from URL
2586
- print(f"šŸ”Ž Running GitIngest analysis on {repo_url}...")
2587
-
2588
- # Run gitingest command with proper parameters
2589
- gitingest_run_cmd = [
2590
- "gitingest",
2591
- repo_url,
2592
- "-o", output_file,
2593
- ]
2594
-
2595
- print(f"šŸ”„ Executing: {' '.join(gitingest_run_cmd)}")
2596
-
2597
- result = subprocess.run(gitingest_run_cmd, capture_output=True, text=True)
2598
-
2599
- if result.returncode != 0:
2600
- print(f"āš ļø GitIngest CLI failed with exit code {result.returncode}")
2601
- print(f"āš ļø Error output: {result.stderr}")
2602
- print("Falling back to basic analysis")
2603
- return generate_fallback_commands(None)
2604
-
2605
- print(f"āœ… GitIngest analysis completed successfully")
2606
-
2607
- # Read the output file - GitIngest outputs structured text, not JSON
2608
- try:
2609
- with open(output_file, 'r', encoding='utf-8') as f:
2610
- content = f.read()
2611
-
2612
- # Process the text output into a structured format
2613
- print(f"šŸ“„ Processing GitIngest text output")
2614
-
2615
- # Extract key information from the text output
2616
- import re
2617
-
2618
- # Try to identify language
2619
- language_match = re.search(r"(?i)language[s]?:?\s*(\w+)", content)
2620
- detected_language = language_match.group(1) if language_match else "Unknown"
2621
-
2622
- # Try to identify technologies
2623
- tech_patterns = {
2624
- "python": r"(?i)(python|\.py\b|pip\b|requirements\.txt|setup\.py)",
2625
- "javascript": r"(?i)(javascript|\.js\b|node|npm|yarn|package\.json)",
2626
- "typescript": r"(?i)(typescript|\.ts\b|tsc\b|tsconfig\.json)",
2627
- "go": r"(?i)(\bgo\b|golang|\.go\b|go\.mod|go\.sum)",
2628
- "rust": r"(?i)(rust|\.rs\b|cargo|Cargo\.toml)",
2629
- "java": r"(?i)(java\b|\.java\b|maven|gradle|pom\.xml)",
2630
- "c++": r"(?i)(c\+\+|\.cpp\b|\.hpp\b|cmake\b|CMakeLists\.txt)",
2631
- "pytorch": r"(?i)(pytorch|torch\b|nn\.Module)",
2632
- "tensorflow": r"(?i)(tensorflow|tf\.|keras\b)",
2633
- }
2613
+ if has_gitingest_cli:
2614
+ # Use gitingest CLI tool to analyze the repository directly from URL
2615
+ print(f"šŸ”Ž Running GitIngest analysis on {repo_url}...")
2616
+
2617
+ # Based on the help output, the correct format is:
2618
+ # gitingest [OPTIONS] [SOURCE]
2619
+ # With options:
2620
+ # -o, --output TEXT Output file path
2621
+ # --format TEXT Output format (json)
2622
+
2623
+ # Run gitingest command with proper parameters
2624
+ gitingest_run_cmd = [
2625
+ gitingest_cmd_name,
2626
+ repo_url,
2627
+ "-o", output_file, # Use -o for output file
2628
+ ]
2634
2629
 
2635
- detected_technologies = []
2636
- for tech, pattern in tech_patterns.items():
2637
- if re.search(pattern, content):
2638
- detected_technologies.append(tech)
2630
+ print(f"šŸ”„ Executing: {' '.join(gitingest_run_cmd)}")
2639
2631
 
2640
- # Create a structured representation
2641
- gitingest_data = {
2642
- "system_info": {
2643
- "detected_language": detected_language,
2644
- "detected_technologies": detected_technologies,
2645
- },
2646
- "repository_analysis": {
2647
- "summary": content[:1000], # First 1000 chars as summary
2648
- "content_preview": content[:5000] # First 5000 chars as preview
2649
- },
2650
- "raw_text": content[:10000], # Include a portion of the raw text
2651
- "success": True
2652
- }
2632
+ result = subprocess.run(gitingest_run_cmd, capture_output=True, text=True)
2633
+
2634
+ if result.returncode != 0:
2635
+ print(f"āš ļø GitIngest CLI failed with exit code {result.returncode}")
2636
+ print(f"āš ļø Error output: {result.stderr}")
2637
+ print("Falling back to basic analysis")
2638
+ gitingest_data = generate_basic_repo_analysis_from_url(repo_url)
2639
+ else:
2640
+ print(f"āœ… GitIngest analysis completed successfully")
2641
+
2642
+ # Read the output file - note that the default format might not be JSON
2643
+ try:
2644
+ # First try to parse as JSON
2645
+ try:
2646
+ with open(output_file, 'r', encoding='utf-8') as f:
2647
+ content = f.read()
2648
+
2649
+ # Save the GitIngest output to the results directory
2650
+ with open(save_file, 'w', encoding='utf-8') as save_f:
2651
+ save_f.write(content)
2652
+ print(f"šŸ“ GitIngest output saved to: {save_file}")
2653
+
2654
+ try:
2655
+ gitingest_data = json.loads(content)
2656
+ print(f"āœ… GitIngest data loaded as JSON from {output_file}")
2657
+ except json.JSONDecodeError:
2658
+ # If not JSON, convert the text output to a basic structure
2659
+ print(f"āš ļø GitIngest output is not in JSON format, converting text to structure")
2660
+
2661
+ # Process the text to extract useful information
2662
+ import re
2663
+
2664
+ # Try to identify language
2665
+ language_match = re.search(r"(?i)language[s]?:?\s*(\w+)", content)
2666
+ detected_language = language_match.group(1) if language_match else "Unknown"
2667
+
2668
+ # Try to identify technologies with stronger evidence requirements
2669
+ tech_patterns = {
2670
+ "python": r"(?i)(python|\.py\b|pip\b|requirements\.txt|setup\.py)",
2671
+ "javascript": r"(?i)(javascript|\.js\b|node|npm|yarn|package\.json)",
2672
+ "typescript": r"(?i)(typescript|\.ts\b|tsc\b|tsconfig\.json)",
2673
+ "go": r"(?i)(\bgo\b|golang|\.go\b|go\.mod|go\.sum)",
2674
+ "rust": r"(?i)(rust|\.rs\b|cargo|Cargo\.toml)",
2675
+ "java": r"(?i)(java\b|\.java\b|maven|gradle|pom\.xml)",
2676
+ "c++": r"(?i)(c\+\+|\.cpp\b|\.hpp\b|cmake\b|CMakeLists\.txt)",
2677
+ "pytorch": r"(?i)(pytorch|torch\b|nn\.Module)",
2678
+ "tensorflow": r"(?i)(tensorflow|tf\.|keras\b)",
2679
+ }
2680
+
2681
+ # Count occurrences to filter out false positives
2682
+ tech_counts = {}
2683
+ for tech, pattern in tech_patterns.items():
2684
+ matches = re.findall(pattern, content)
2685
+ if matches:
2686
+ tech_counts[tech] = len(matches)
2687
+
2688
+ # Filter technologies based on threshold
2689
+ thresholds = {
2690
+ "javascript": 3, # Higher threshold for JavaScript
2691
+ "go": 3, # Higher threshold for Go
2692
+ "default": 2 # Default threshold
2693
+ }
2694
+
2695
+ detected_technologies = []
2696
+ for tech, count in tech_counts.items():
2697
+ threshold = thresholds.get(tech, thresholds["default"])
2698
+ if count >= threshold:
2699
+ detected_technologies.append(tech)
2700
+ print(f"šŸ“Š Detected {tech} with confidence score {count}")
2701
+
2702
+ # Create a structured representation
2703
+ gitingest_data = {
2704
+ "system_info": {
2705
+ "detected_language": detected_language,
2706
+ "detected_technologies": detected_technologies,
2707
+ },
2708
+ "repository_analysis": {
2709
+ "summary": content[:5000], # First 5000 chars as summary
2710
+ "content_preview": content[:10000] # First 10000 chars as preview
2711
+ },
2712
+ "success": True
2713
+ }
2714
+
2715
+ # Save the processed data
2716
+ processed_file = os.path.join(save_dir, f"gitingest_processed_{repo_name}_{timestamp}.json")
2717
+ with open(processed_file, 'w', encoding='utf-8') as proc_f:
2718
+ json.dump(gitingest_data, proc_f, indent=2)
2719
+ print(f"šŸ“ Processed GitIngest data saved to: {processed_file}")
2720
+ except FileNotFoundError:
2721
+ print(f"āš ļø Output file not found at {output_file}")
2722
+ gitingest_data = generate_basic_repo_analysis_from_url(repo_url)
2723
+ except Exception as e:
2724
+ print(f"āš ļø Error reading GitIngest output: {e}")
2725
+ gitingest_data = generate_basic_repo_analysis_from_url(repo_url)
2726
+ else:
2727
+ # Fall back to basic analysis if gitingest CLI is not available
2728
+ gitingest_data = generate_basic_repo_analysis_from_url(repo_url)
2729
+
2730
+ # Prepare the request payload with GitIngest data
2731
+ payload = {
2732
+ "repoUrl": repo_url,
2733
+ "gitingestData": gitingest_data,
2734
+ "userRequest": "Setup and run the repository"
2735
+ }
2736
+
2737
+ print(f"šŸ“¤ API Request payload prepared (GitIngest data size: {len(json.dumps(gitingest_data))} bytes)")
2738
+
2739
+ # Try each endpoint in sequence until one succeeds
2740
+ response = None
2741
+ for api_url in api_endpoints:
2742
+ # Use the retry mechanism for more reliable requests
2743
+ response = make_api_request_with_retry(
2744
+ url=api_url,
2745
+ payload=payload,
2746
+ max_retries=2,
2747
+ timeout=180 # 3 minute timeout
2748
+ )
2749
+
2750
+ # If we got a response and it's successful, break out of the loop
2751
+ if response and response.status_code == 200:
2752
+ print(f"āœ… Successful response from {api_url}")
2753
+ break
2754
+
2755
+ if response:
2756
+ print(f"āš ļø Endpoint {api_url} returned status code {response.status_code}, trying next endpoint...")
2757
+ else:
2758
+ print(f"āš ļø Failed to connect to {api_url}, trying next endpoint...")
2759
+
2760
+ # If we've tried all endpoints and still don't have a response, use fallback
2761
+ if response is None:
2762
+ print("āŒ All API endpoints failed")
2763
+ return generate_fallback_commands(gitingest_data)
2653
2764
 
2654
- print(f"šŸ“„ Processed GitIngest output: Detected language: {detected_language}, Technologies: {', '.join(detected_technologies) if detected_technologies else 'None'}")
2765
+ # Continue with the response we got from the successful endpoint
2766
+ if not response:
2767
+ print("āŒ No valid response received from any endpoint")
2768
+ return generate_fallback_commands(gitingest_data)
2655
2769
 
2656
- # Generate commands locally based on the analysis
2657
- print(f"šŸ”§ Generating setup commands locally based on repository analysis")
2658
- setup_commands = generate_fallback_commands(gitingest_data)
2659
- return setup_commands
2770
+ try:
2771
+ print(f"šŸ“„ API Response status code: {response.status_code}")
2660
2772
 
2661
- except FileNotFoundError:
2662
- print(f"āš ļø Output file not found at {output_file}")
2663
- return generate_fallback_commands(None)
2773
+ if response.status_code == 200:
2774
+ try:
2775
+ data = response.json()
2776
+ print(f"šŸ“„ API Response data received")
2777
+ print(f"šŸ“„ Response size: {len(response.text)} bytes")
2778
+ print(f"šŸ“„ Response URL: {response.url}")
2779
+
2780
+ # Extract setup commands from the response
2781
+ if "setupInstructions" in data and "commands" in data["setupInstructions"]:
2782
+ commands = data["setupInstructions"]["commands"]
2783
+ print(f"āœ… Successfully fetched {len(commands)} setup commands from API")
2784
+
2785
+ # Print the original commands for reference
2786
+ print("šŸ“‹ Original commands from API:")
2787
+ for i, cmd in enumerate(commands, 1):
2788
+ print(f" {i}. {cmd}")
2789
+
2790
+ # Fix the commands by removing placeholders and comments
2791
+ fixed_commands = fix_setup_commands(commands)
2792
+
2793
+ # If we have a temp_dir with the cloned repo, try to find the entry point
2794
+ # and replace any placeholder entry points
2795
+ for i, cmd in enumerate(fixed_commands):
2796
+ if "python main.py" in cmd or "python3 main.py" in cmd:
2797
+ try:
2798
+ entry_point = find_entry_point(temp_dir)
2799
+ if entry_point and entry_point != "main.py":
2800
+ fixed_commands[i] = cmd.replace("main.py", entry_point)
2801
+ print(f"šŸ”„ Replaced main.py with detected entry point: {entry_point}")
2802
+ except Exception as e:
2803
+ print(f"āš ļø Error finding entry point: {e}")
2804
+
2805
+ # Print the fixed commands
2806
+ print("\nšŸ“‹ Fixed commands:")
2807
+ for i, cmd in enumerate(fixed_commands, 1):
2808
+ print(f" {i}. {cmd}")
2809
+
2810
+ return fixed_commands
2811
+ else:
2812
+ print("āš ļø API response did not contain setupInstructions.commands field")
2813
+ print("šŸ“‹ Available fields in response:")
2814
+ for key in data.keys():
2815
+ print(f" - {key}")
2816
+ # Return fallback commands
2817
+ return generate_fallback_commands(gitingest_data)
2818
+ except json.JSONDecodeError as e:
2819
+ print(f"āŒ Failed to parse API response as JSON: {e}")
2820
+ print(f"Raw response: {response.text[:500]}...")
2821
+ # Return fallback commands
2822
+ return generate_fallback_commands(gitingest_data)
2823
+ elif response.status_code == 504:
2824
+ print(f"āŒ API request timed out (504 Gateway Timeout)")
2825
+ print("āš ļø The server took too long to respond. Using fallback commands instead.")
2826
+ # Return fallback commands
2827
+ return generate_fallback_commands(gitingest_data)
2828
+ else:
2829
+ print(f"āŒ API request failed with status code: {response.status_code}")
2830
+ print(f"āŒ Response URL: {response.url}")
2831
+ print(f"āŒ Response headers: {dict(response.headers)}")
2832
+ print(f"āŒ Error response: {response.text[:500]}...")
2833
+ # Return fallback commands
2834
+ return generate_fallback_commands(gitingest_data)
2664
2835
  except Exception as e:
2665
- print(f"āš ļø Error processing GitIngest output: {e}")
2666
- return generate_fallback_commands(None)
2836
+ print(f"āŒ Error processing API response: {str(e)}")
2837
+ print("āš ļø Using fallback commands instead")
2838
+ # Return fallback commands
2839
+ return generate_fallback_commands(gitingest_data)
2840
+ except Exception as e:
2841
+ print(f"āŒ Error fetching setup commands from API: {e}")
2842
+ import traceback
2843
+ traceback.print_exc()
2844
+ # Return fallback commands
2845
+ return generate_fallback_commands(None)
2667
2846
  finally:
2668
- # Clean up temporary directory
2847
+ # Clean up the temporary directory
2669
2848
  print(f"🧹 Cleaning up temporary directory...")
2670
- try:
2671
- shutil.rmtree(temp_dir)
2672
- except Exception as e:
2673
- print(f"āš ļø Error cleaning up temporary directory: {e}")
2674
-
2675
- # If all else fails, return basic setup commands
2676
- return [
2677
- "apt-get update -y",
2678
- "apt-get install -y git curl wget",
2679
- "pip install --upgrade pip setuptools wheel"
2680
- ]
2849
+ shutil.rmtree(temp_dir, ignore_errors=True)
2681
2850
 
2682
2851
  def generate_fallback_commands(gitingest_data):
2683
2852
  """Generate fallback setup commands based on repository analysis"""
2853
+ print("\n" + "="*80)
2854
+ print("šŸ“‹ GENERATING FALLBACK SETUP COMMANDS")
2855
+ print("="*80)
2684
2856
  print("Using basic repository analysis to generate setup commands")
2685
2857
 
2686
2858
  # Default commands that work for most repositories
@@ -2700,28 +2872,6 @@ def generate_fallback_commands(gitingest_data):
2700
2872
  detected_technologies = gitingest_data.get("system_info", {}).get("detected_technologies", [])
2701
2873
  primary_package_manager = gitingest_data.get("system_info", {}).get("primary_package_manager", "Unknown")
2702
2874
 
2703
- # Extract more information from raw text if available
2704
- if "raw_text" in gitingest_data and gitingest_data["raw_text"]:
2705
- raw_text = gitingest_data["raw_text"]
2706
-
2707
- # Look for additional technologies in raw text
2708
- import re
2709
- tech_patterns = {
2710
- "python": r"(?i)(python|\.py\b|pip\b|requirements\.txt|setup\.py)",
2711
- "javascript": r"(?i)(javascript|\.js\b|node|npm|yarn|package\.json)",
2712
- "typescript": r"(?i)(typescript|\.ts\b|tsc\b|tsconfig\.json)",
2713
- "go": r"(?i)(\bgo\b|golang|\.go\b|go\.mod|go\.sum)",
2714
- "rust": r"(?i)(rust|\.rs\b|cargo|Cargo\.toml)",
2715
- "java": r"(?i)(java\b|\.java\b|maven|gradle|pom\.xml)",
2716
- "c++": r"(?i)(c\+\+|\.cpp\b|\.hpp\b|cmake\b|CMakeLists\.txt)",
2717
- "pytorch": r"(?i)(pytorch|torch\b|nn\.Module)",
2718
- "tensorflow": r"(?i)(tensorflow|tf\.|keras\b)",
2719
- }
2720
-
2721
- for tech, pattern in tech_patterns.items():
2722
- if re.search(pattern, raw_text) and tech.lower() not in [t.lower() for t in detected_technologies]:
2723
- detected_technologies.append(tech)
2724
-
2725
2875
  # Add language-specific commands
2726
2876
  language_commands = []
2727
2877
 
@@ -2729,21 +2879,12 @@ def generate_fallback_commands(gitingest_data):
2729
2879
  print(f"šŸ“‹ Detected technologies: {', '.join(detected_technologies) if detected_technologies else 'None'}")
2730
2880
  print(f"šŸ“‹ Detected package manager: {primary_package_manager}")
2731
2881
 
2732
- # Check for specific technologies and add appropriate commands
2733
- has_python = any(tech.lower() == "python" for tech in detected_technologies) or detected_language.lower() == "python"
2734
- has_js = any(tech.lower() in ["javascript", "node", "nodejs"] for tech in detected_technologies)
2735
- has_ts = any(tech.lower() == "typescript" for tech in detected_technologies)
2736
- has_go = any(tech.lower() in ["go", "golang"] for tech in detected_technologies)
2737
- has_pytorch = any(tech.lower() in ["pytorch", "torch"] for tech in detected_technologies)
2738
- has_tensorflow = any(tech.lower() == "tensorflow" for tech in detected_technologies)
2739
-
2740
2882
  # Python-specific commands
2741
- if has_python:
2883
+ if detected_language == "Python" or primary_package_manager == "pip":
2742
2884
  print("šŸ“¦ Adding Python-specific setup commands")
2743
- language_commands.append("apt-get install -y python3 python3-pip python3-dev")
2744
2885
 
2745
2886
  # Check for requirements.txt
2746
- language_commands.extend([
2887
+ requirements_check = [
2747
2888
  "if [ -f requirements.txt ]; then",
2748
2889
  " echo 'Installing from requirements.txt'",
2749
2890
  " pip install -r requirements.txt",
@@ -2751,56 +2892,72 @@ def generate_fallback_commands(gitingest_data):
2751
2892
  " echo 'Installing from setup.py'",
2752
2893
  " pip install -e .",
2753
2894
  "fi"
2754
- ])
2895
+ ]
2896
+ language_commands.extend(requirements_check)
2897
+
2898
+ # Add common Python packages
2899
+ language_commands.append("pip install pytest numpy pandas matplotlib")
2755
2900
 
2756
2901
  # JavaScript/Node.js specific commands
2757
- if has_js or has_ts:
2902
+ elif detected_language in ["JavaScript", "TypeScript"] or primary_package_manager in ["npm", "yarn", "pnpm"]:
2758
2903
  print("šŸ“¦ Adding JavaScript/Node.js-specific setup commands")
2759
- language_commands.append("curl -fsSL https://deb.nodesource.com/setup_18.x | bash -")
2760
- language_commands.append("apt-get install -y nodejs")
2761
2904
 
2762
- # Install TypeScript if detected
2763
- if has_ts:
2764
- language_commands.append("npm install -g typescript ts-node")
2905
+ # Install Node.js if not available
2906
+ language_commands.append("apt-get install -y nodejs npm")
2765
2907
 
2766
2908
  # Check for package.json
2767
- language_commands.extend([
2909
+ package_json_check = [
2768
2910
  "if [ -f package.json ]; then",
2769
- " echo 'Installing npm dependencies'",
2911
+ " echo 'Installing from package.json'",
2770
2912
  " npm install",
2771
2913
  "fi"
2772
- ])
2773
-
2774
- # Go-specific commands
2775
- if has_go:
2776
- print("šŸ“¦ Adding Go-specific setup commands")
2777
- language_commands.append("apt-get install -y golang")
2778
- language_commands.extend([
2779
- "if [ -f go.mod ]; then",
2780
- " echo 'Installing Go dependencies'",
2781
- " go mod download",
2914
+ ]
2915
+ language_commands.extend(package_json_check)
2916
+
2917
+ # Java specific commands
2918
+ elif detected_language == "Java" or primary_package_manager in ["maven", "gradle"]:
2919
+ print("šŸ“¦ Adding Java-specific setup commands")
2920
+
2921
+ language_commands.append("apt-get install -y openjdk-11-jdk maven gradle")
2922
+
2923
+ # Check for Maven or Gradle
2924
+ build_check = [
2925
+ "if [ -f pom.xml ]; then",
2926
+ " echo 'Building with Maven'",
2927
+ " mvn clean install -DskipTests",
2928
+ "elif [ -f build.gradle ]; then",
2929
+ " echo 'Building with Gradle'",
2930
+ " gradle build --no-daemon",
2782
2931
  "fi"
2783
- ])
2932
+ ]
2933
+ language_commands.extend(build_check)
2784
2934
 
2785
- # PyTorch-specific commands
2786
- if has_pytorch:
2787
- print("šŸ“¦ Adding PyTorch-specific setup commands")
2788
- language_commands.append("pip install torch torchvision torchaudio")
2935
+ # Go specific commands
2936
+ elif detected_language == "Go" or primary_package_manager == "go":
2937
+ print("šŸ“¦ Adding Go-specific setup commands")
2938
+
2939
+ language_commands.append("apt-get install -y golang-go")
2940
+ language_commands.append("go mod tidy")
2789
2941
 
2790
- # TensorFlow-specific commands
2791
- if has_tensorflow:
2792
- print("šŸ“¦ Adding TensorFlow-specific setup commands")
2793
- language_commands.append("pip install tensorflow tensorflow-gpu")
2942
+ # Rust specific commands
2943
+ elif detected_language == "Rust" or primary_package_manager == "cargo":
2944
+ print("šŸ“¦ Adding Rust-specific setup commands")
2945
+
2946
+ language_commands.append("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
2947
+ language_commands.append("source $HOME/.cargo/env")
2948
+ language_commands.append("cargo build")
2794
2949
 
2795
- # Combine default commands with language-specific commands
2950
+ # Combine all commands
2796
2951
  all_commands = default_commands + language_commands
2797
2952
 
2798
- # Print the generated commands
2953
+ # Fix the commands
2954
+ fixed_commands = fix_setup_commands(all_commands)
2955
+
2799
2956
  print("\nšŸ“‹ Generated fallback setup commands:")
2800
- for i, cmd in enumerate(all_commands, 1):
2957
+ for i, cmd in enumerate(fixed_commands, 1):
2801
2958
  print(f" {i}. {cmd}")
2802
2959
 
2803
- return all_commands
2960
+ return fixed_commands
2804
2961
 
2805
2962
  def generate_basic_repo_analysis_from_url(repo_url):
2806
2963
  """Generate basic repository analysis data from a repository URL."""
@@ -2968,8 +3125,11 @@ def generate_basic_repo_analysis(repo_dir):
2968
3125
  }
2969
3126
 
2970
3127
  def get_setup_commands_from_local_api(repo_url, gitingest_data):
2971
- """Try to get setup commands from the local API."""
2972
- api_url = "http://localhost:3000/api/analyze-with-gitingest"
3128
+ """Try to get setup commands from the API."""
3129
+ # Use only online endpoints
3130
+ api_endpoints = [
3131
+ "https://www.gitarsenal.dev/api/analyze-with-gitingest" # Working endpoint with www prefix
3132
+ ]
2973
3133
 
2974
3134
  # Prepare the request payload
2975
3135
  payload = {
@@ -2978,34 +3138,49 @@ def get_setup_commands_from_local_api(repo_url, gitingest_data):
2978
3138
  "userRequest": "Setup and run the repository"
2979
3139
  }
2980
3140
 
2981
- try:
2982
- # Make the API request
2983
- print(f"🌐 Making POST request to local API: {api_url}")
2984
- response = requests.post(api_url, json=payload, timeout=60)
3141
+ # Try each API endpoint
3142
+ for api_url in api_endpoints:
3143
+ # Use the retry mechanism for more reliable requests
3144
+ response = make_api_request_with_retry(
3145
+ url=api_url,
3146
+ payload=payload,
3147
+ max_retries=2,
3148
+ timeout=180 # 3 minute timeout
3149
+ )
2985
3150
 
2986
- if response.status_code == 200:
2987
- data = response.json()
2988
- if "setupInstructions" in data and "commands" in data["setupInstructions"]:
2989
- commands = data["setupInstructions"]["commands"]
2990
- print(f"āœ… Successfully fetched {len(commands)} setup commands from local API")
2991
-
2992
- # Print the original commands
2993
- print("šŸ“‹ Original commands from local API:")
2994
- for i, cmd in enumerate(commands, 1):
2995
- print(f" {i}. {cmd}")
2996
-
2997
- # Fix the commands
2998
- fixed_commands = fix_setup_commands(commands)
2999
-
3000
- # Print the fixed commands
3001
- print("\nšŸ“‹ Fixed commands:")
3002
- for i, cmd in enumerate(fixed_commands, 1):
3003
- print(f" {i}. {cmd}")
3004
-
3005
- return fixed_commands
3006
- except Exception as e:
3007
- print(f"āŒ Error connecting to local API: {e}")
3151
+ if response and response.status_code == 200:
3152
+ try:
3153
+ data = response.json()
3154
+ print(f"šŸ“„ Response size: {len(response.text)} bytes")
3155
+ print(f"šŸ“„ Response URL: {response.url}")
3156
+ if "setupInstructions" in data and "commands" in data["setupInstructions"]:
3157
+ commands = data["setupInstructions"]["commands"]
3158
+ print(f"āœ… Successfully fetched {len(commands)} setup commands from API at {api_url}")
3159
+
3160
+ # Print the original commands
3161
+ print("šŸ“‹ Original commands from API:")
3162
+ for i, cmd in enumerate(commands, 1):
3163
+ print(f" {i}. {cmd}")
3164
+
3165
+ # Fix the commands
3166
+ fixed_commands = fix_setup_commands(commands)
3167
+
3168
+ # Print the fixed commands
3169
+ print("\nšŸ“‹ Fixed commands:")
3170
+ for i, cmd in enumerate(fixed_commands, 1):
3171
+ print(f" {i}. {cmd}")
3172
+
3173
+ return fixed_commands
3174
+ else:
3175
+ print("āš ļø API response did not contain setupInstructions.commands field")
3176
+ except json.JSONDecodeError:
3177
+ print(f"āŒ Failed to parse API response as JSON")
3178
+ elif response:
3179
+ print(f"āŒ API request failed with status code: {response.status_code}")
3180
+ else:
3181
+ print(f"āŒ Failed to connect to {api_url}")
3008
3182
 
3183
+ print("āŒ All API endpoints failed")
3009
3184
  return None
3010
3185
 
3011
3186
  # Define a function to create and return a properly configured ssh container function
@@ -3296,9 +3471,248 @@ def show_usage_examples():
3296
3471
  print("│ --volume-name my-persistent-volume │")
3297
3472
  print("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n")
3298
3473
 
3474
+ print("With GitIngest API (recommended)")
3475
+ print("ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”")
3476
+ print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\ │")
3477
+ print("│ --use-gitingest │")
3478
+ print("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n")
3479
+
3480
+ print("With Original API")
3481
+ print("ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”")
3482
+ print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\ │")
3483
+ print("│ --use-api │")
3484
+ print("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n")
3485
+
3299
3486
  print("Available GPU Options:")
3300
3487
  print(" T4, L4, A10G, A100-40GB, A100-80GB, L40S, H100, H200, B200")
3301
3488
 
3489
+ def make_api_request_with_retry(url, payload, max_retries=2, timeout=180):
3490
+ """Make an API request with retry mechanism."""
3491
+ import requests
3492
+ import time
3493
+
3494
+ for attempt in range(max_retries + 1):
3495
+ try:
3496
+ if attempt > 0:
3497
+ print(f"šŸ”„ Retry attempt {attempt}/{max_retries}...")
3498
+
3499
+ print(f"🌐 Making POST request to: {url}")
3500
+ print(f"ā³ Waiting up to {timeout//60} minutes for response...")
3501
+
3502
+ # Set allow_redirects=True to follow redirects automatically
3503
+ response = requests.post(
3504
+ url,
3505
+ json=payload,
3506
+ timeout=timeout,
3507
+ allow_redirects=True,
3508
+ headers={
3509
+ 'Content-Type': 'application/json',
3510
+ 'User-Agent': 'GitArsenal-CLI/1.0'
3511
+ }
3512
+ )
3513
+
3514
+ # Print redirect info if any
3515
+ if response.history:
3516
+ print(f"āœ… Request was redirected {len(response.history)} times")
3517
+ for resp in response.history:
3518
+ print(f" - Redirect: {resp.status_code} from {resp.url}")
3519
+ print(f"āœ… Final URL: {response.url}")
3520
+
3521
+ return response
3522
+ except requests.exceptions.RequestException as e:
3523
+ if attempt < max_retries:
3524
+ retry_delay = 2 ** attempt # Exponential backoff
3525
+ print(f"āš ļø Request failed: {str(e)}")
3526
+ print(f"ā³ Waiting {retry_delay} seconds before retrying...")
3527
+ time.sleep(retry_delay)
3528
+ else:
3529
+ print(f"āŒ All retry attempts failed: {str(e)}")
3530
+ return None
3531
+
3532
+ return None
3533
+
3534
+ def get_setup_commands_from_gitingest(repo_url):
3535
+ """
3536
+ Get repository setup commands using the gitingest approach.
3537
+
3538
+ This function is inspired by gitingest_setup_client.py and provides a more
3539
+ robust way to get setup commands for a repository.
3540
+
3541
+ Args:
3542
+ repo_url: URL of the repository to set up
3543
+
3544
+ Returns:
3545
+ List of setup commands or None if failed
3546
+ """
3547
+ import requests
3548
+ import json
3549
+ import os
3550
+ import sys
3551
+ import tempfile
3552
+ import subprocess
3553
+
3554
+ print(f"šŸ” Getting setup commands for repository: {repo_url}")
3555
+
3556
+ # Define API endpoints to try in order
3557
+ api_endpoints = [
3558
+ "https://www.gitarsenal.dev/api/gitingest-setup-commands",
3559
+ "https://gitarsenal.dev/api/gitingest-setup-commands",
3560
+ "https://www.gitarsenal.dev/api/analyze-with-gitingest",
3561
+ "http://localhost:3000/api/gitingest-setup-commands"
3562
+ ]
3563
+
3564
+ # Generate basic gitingest data
3565
+ def generate_basic_gitingest_data():
3566
+ # Extract repo name from URL
3567
+ repo_name = repo_url.split('/')[-1].replace('.git', '')
3568
+
3569
+ return {
3570
+ "system_info": {
3571
+ "platform": "Unknown",
3572
+ "python_version": "Unknown",
3573
+ "detected_language": "Unknown",
3574
+ "detected_technologies": [],
3575
+ "file_count": 0,
3576
+ "repo_stars": 0,
3577
+ "repo_forks": 0,
3578
+ "primary_package_manager": "Unknown",
3579
+ "complexity_level": "Unknown"
3580
+ },
3581
+ "repository_analysis": {
3582
+ "summary": f"Repository: {repo_name}",
3583
+ "tree": "",
3584
+ "content_preview": ""
3585
+ },
3586
+ "success": True
3587
+ }
3588
+
3589
+ # Try to generate gitingest data using CLI if available
3590
+ def generate_gitingest_data_from_cli():
3591
+ try:
3592
+ # Check if gitingest CLI is available
3593
+ subprocess.run(["gitingest", "--help"], check=True, capture_output=True, text=True)
3594
+
3595
+ # Create a temporary file for the output
3596
+ with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as tmp:
3597
+ output_file = tmp.name
3598
+
3599
+ # Run gitingest command
3600
+ print(f"Running gitingest analysis on {repo_url}...")
3601
+ gitingest_cmd = ["gitingest", repo_url, "-o", output_file]
3602
+ result = subprocess.run(gitingest_cmd, capture_output=True, text=True)
3603
+
3604
+ if result.returncode != 0:
3605
+ print(f"GitIngest CLI failed: {result.stderr}")
3606
+ return None
3607
+
3608
+ # Read the output file
3609
+ try:
3610
+ with open(output_file, 'r', encoding='utf-8') as f:
3611
+ content = f.read()
3612
+ try:
3613
+ data = json.loads(content)
3614
+ return data
3615
+ except json.JSONDecodeError:
3616
+ # If not JSON, convert the text output to a basic structure
3617
+ return {
3618
+ "system_info": {
3619
+ "platform": "Unknown",
3620
+ "python_version": "Unknown",
3621
+ "detected_language": "Unknown",
3622
+ "detected_technologies": []
3623
+ },
3624
+ "repository_analysis": {
3625
+ "summary": content[:5000], # First 5000 chars as summary
3626
+ "tree": "",
3627
+ "content_preview": content[:10000] # First 10000 chars as preview
3628
+ },
3629
+ "success": True
3630
+ }
3631
+ except Exception as e:
3632
+ print(f"Error reading gitingest output: {e}")
3633
+ return None
3634
+ finally:
3635
+ # Clean up the temporary file
3636
+ if os.path.exists(output_file):
3637
+ os.unlink(output_file)
3638
+
3639
+ except (subprocess.SubprocessError, FileNotFoundError):
3640
+ print("GitIngest CLI not found")
3641
+ return None
3642
+
3643
+ # First try to get data from CLI
3644
+ gitingest_data = generate_gitingest_data_from_cli()
3645
+
3646
+ # If CLI failed, use basic data
3647
+ if not gitingest_data:
3648
+ print("Using basic gitingest data")
3649
+ gitingest_data = generate_basic_gitingest_data()
3650
+
3651
+ # Try each API endpoint
3652
+ for api_url in api_endpoints:
3653
+ try:
3654
+ print(f"Trying API endpoint: {api_url}")
3655
+
3656
+ payload = {
3657
+ "repoUrl": repo_url,
3658
+ "gitingestData": gitingest_data
3659
+ }
3660
+
3661
+ # Use the retry mechanism for more reliable requests
3662
+ response = make_api_request_with_retry(
3663
+ url=api_url,
3664
+ payload=payload,
3665
+ max_retries=2,
3666
+ timeout=180 # 3 minute timeout
3667
+ )
3668
+
3669
+ if not response:
3670
+ print(f"Failed to connect to {api_url}")
3671
+ continue
3672
+
3673
+ if response.status_code != 200:
3674
+ print(f"API request failed with status code: {response.status_code}")
3675
+ continue
3676
+
3677
+ try:
3678
+ result = response.json()
3679
+
3680
+ # Check if we have commands in the response
3681
+ commands = None
3682
+
3683
+ # Check for different response formats
3684
+ if "commands" in result:
3685
+ commands = result["commands"]
3686
+ elif "setupInstructions" in result and "commands" in result["setupInstructions"]:
3687
+ commands = result["setupInstructions"]["commands"]
3688
+
3689
+ if commands:
3690
+ print(f"āœ… Successfully fetched {len(commands)} setup commands from API at {api_url}")
3691
+
3692
+ # Print the commands
3693
+ print("\nšŸ“‹ Setup Commands:")
3694
+ for i, cmd in enumerate(commands, 1):
3695
+ print(f" {i}. {cmd}")
3696
+
3697
+ # Fix the commands
3698
+ fixed_commands = fix_setup_commands(commands)
3699
+
3700
+ # Print the fixed commands
3701
+ print("\nšŸ“‹ Fixed commands:")
3702
+ for i, cmd in enumerate(fixed_commands, 1):
3703
+ print(f" {i}. {cmd}")
3704
+
3705
+ return fixed_commands
3706
+ else:
3707
+ print("No commands found in API response")
3708
+ except json.JSONDecodeError:
3709
+ print(f"Failed to parse API response as JSON")
3710
+ except Exception as e:
3711
+ print(f"Error with API endpoint {api_url}: {e}")
3712
+
3713
+ print("āŒ All API endpoints failed")
3714
+ return generate_fallback_commands(gitingest_data)
3715
+
3302
3716
  if __name__ == "__main__":
3303
3717
  # Parse command line arguments when script is run directly
3304
3718
  import argparse
@@ -3317,6 +3731,7 @@ if __name__ == "__main__":
3317
3731
  parser.add_argument('--timeout', type=int, default=60, help='Container timeout in minutes (default: 60)')
3318
3732
  parser.add_argument('--ssh-password', type=str, help='SSH password (random if not provided)')
3319
3733
  parser.add_argument('--use-api', action='store_true', help='Fetch setup commands from API')
3734
+ parser.add_argument('--use-gitingest', action='store_true', help='Use gitingest approach to fetch setup commands')
3320
3735
  parser.add_argument('--show-examples', action='store_true', help='Show usage examples')
3321
3736
 
3322
3737
  args = parser.parse_args()
@@ -3332,8 +3747,18 @@ if __name__ == "__main__":
3332
3747
 
3333
3748
  # If --use-api flag is set and repo_url is provided, fetch setup commands from API
3334
3749
 
3750
+ # If --use-gitingest flag is set and repo_url is provided, use the gitingest approach
3751
+ if args.use_gitingest and args.repo_url:
3752
+ print("šŸ”„ Using gitingest approach to fetch setup commands")
3753
+ api_commands = get_setup_commands_from_gitingest(args.repo_url)
3754
+ if api_commands:
3755
+ setup_commands = api_commands
3756
+ print(f"šŸ“‹ Using {len(setup_commands)} commands from gitingest API")
3757
+ else:
3758
+ print("āš ļø Failed to get commands from gitingest API, no fallback commands will be used")
3759
+ setup_commands = []
3335
3760
  # If --use-api flag is set and repo_url is provided, fetch setup commands from API
3336
- if args.use_api and args.repo_url:
3761
+ elif args.use_api and args.repo_url:
3337
3762
  print("šŸ”„ Using API to fetch setup commands")
3338
3763
  api_commands = fetch_setup_commands_from_api(args.repo_url)
3339
3764
  if api_commands: