gitarsenal-cli 1.9.12 โ†’ 1.9.13

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-05T14:22:25.927Z","packages":["modal","gitingest","requests"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
1
+ {"created":"2025-08-06T07:22:37.370Z","packages":["modal","gitingest","requests"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.12",
3
+ "version": "1.9.13",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -4,4 +4,5 @@ pathlib>=1.0.1
4
4
  python-dotenv>=1.0.0
5
5
  flask>=2.0.0
6
6
  flask-cors>=3.0.0
7
- pexpect>=4.8.0
7
+ pexpect>=4.8.0
8
+ anthropic>=0.18.0
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify Claude fallback functionality
4
+ """
5
+
6
+ import os
7
+ import sys
8
+
9
+ # Add the current directory to the path so we can import the main module
10
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
+
12
+ def test_claude_fallback():
13
+ """Test the Claude fallback functionality"""
14
+ print("๐Ÿงช Testing Claude fallback functionality...")
15
+
16
+ # Test 1: Check if anthropic is imported correctly
17
+ try:
18
+ from test_modalSandboxScript import anthropic
19
+ if anthropic is not None:
20
+ print("โœ… Anthropic library imported successfully")
21
+ else:
22
+ print("โš ๏ธ Anthropic library not available")
23
+ except ImportError as e:
24
+ print(f"โŒ Failed to import anthropic: {e}")
25
+ return False
26
+
27
+ # Test 2: Check if ANTHROPIC_API_KEY is set
28
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
29
+ if anthropic_api_key:
30
+ print("โœ… ANTHROPIC_API_KEY found in environment")
31
+ else:
32
+ print("โš ๏ธ ANTHROPIC_API_KEY not found in environment")
33
+ print("๐Ÿ’ก Set ANTHROPIC_API_KEY to test Claude functionality")
34
+
35
+ # Test 3: Test a simple Claude API call
36
+ if anthropic is not None and anthropic_api_key:
37
+ try:
38
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
39
+ message = client.messages.create(
40
+ model="claude-3-5-sonnet-20241022",
41
+ max_tokens=50,
42
+ messages=[
43
+ {"role": "user", "content": "Hello, please respond with 'Claude is working!'"}
44
+ ]
45
+ )
46
+ response = message.content[0].text.strip()
47
+ print(f"โœ… Claude API test successful: {response}")
48
+ return True
49
+ except Exception as e:
50
+ print(f"โŒ Claude API test failed: {e}")
51
+ return False
52
+ else:
53
+ print("โš ๏ธ Skipping Claude API test (missing library or API key)")
54
+ return False
55
+
56
+ def test_openai_fallback():
57
+ """Test the OpenAI fallback functionality"""
58
+ print("\n๐Ÿงช Testing OpenAI fallback functionality...")
59
+
60
+ # Test 1: Check if OpenAI API key is set
61
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
62
+ if openai_api_key:
63
+ print("โœ… OPENAI_API_KEY found in environment")
64
+ else:
65
+ print("โš ๏ธ OPENAI_API_KEY not found in environment")
66
+ print("๐Ÿ’ก Set OPENAI_API_KEY to test OpenAI functionality")
67
+
68
+ # Test 2: Test a simple OpenAI API call
69
+ if openai_api_key:
70
+ try:
71
+ import openai
72
+ client = openai.OpenAI(api_key=openai_api_key)
73
+ response = client.chat.completions.create(
74
+ model="gpt-3.5-turbo",
75
+ messages=[
76
+ {"role": "user", "content": "Hello, please respond with 'OpenAI is working!'"}
77
+ ],
78
+ max_tokens=50
79
+ )
80
+ response_text = response.choices[0].message.content.strip()
81
+ print(f"โœ… OpenAI API test successful: {response_text}")
82
+ return True
83
+ except Exception as e:
84
+ print(f"โŒ OpenAI API test failed: {e}")
85
+ return False
86
+ else:
87
+ print("โš ๏ธ Skipping OpenAI API test (missing API key)")
88
+ return False
89
+
90
+ def main():
91
+ """Main test function"""
92
+ print("๐Ÿš€ Starting Claude fallback tests...")
93
+ print("=" * 50)
94
+
95
+ claude_success = test_claude_fallback()
96
+ openai_success = test_openai_fallback()
97
+
98
+ print("\n" + "=" * 50)
99
+ print("๐Ÿ“Š Test Results:")
100
+ print(f"Claude API: {'โœ… Working' if claude_success else 'โŒ Failed'}")
101
+ print(f"OpenAI API: {'โœ… Working' if openai_success else 'โŒ Failed'}")
102
+
103
+ if claude_success and openai_success:
104
+ print("\n๐ŸŽ‰ Both APIs are working! Claude fallback is ready.")
105
+ elif claude_success:
106
+ print("\nโœ… Claude API is working! Can be used as fallback.")
107
+ elif openai_success:
108
+ print("\nโœ… OpenAI API is working! Claude fallback will be used when OpenAI fails.")
109
+ else:
110
+ print("\nโŒ Neither API is working. Please check your API keys.")
111
+
112
+ print("\n๐Ÿ’ก To test the fallback functionality:")
113
+ print("1. Set both OPENAI_API_KEY and ANTHROPIC_API_KEY")
114
+ print("2. Run the main script with a failing command")
115
+ print("3. Watch for '๐Ÿ”„ Trying Claude-4-Sonnet as fallback...' messages")
116
+
117
+ if __name__ == "__main__":
118
+ main()
@@ -15,7 +15,7 @@ import uuid
15
15
  import signal
16
16
  from pathlib import Path
17
17
  import modal
18
-
18
+ import anthropic
19
19
  # Import authentication manager
20
20
  try:
21
21
  from auth_manager import AuthManager
@@ -516,21 +516,57 @@ class PersistentShell:
516
516
  REMOVE_COMMAND: <reason>
517
517
  """
518
518
 
519
- # Call OpenAI API
520
- import openai
521
- client = openai.OpenAI(api_key=api_key)
522
-
523
- response = client.chat.completions.create(
524
- model="gpt-4.1",
525
- messages=[
526
- {"role": "system", "content": "You are a helpful assistant that suggests alternative commands that don't require user input."},
527
- {"role": "user", "content": prompt}
528
- ],
529
- max_tokens=150,
530
- temperature=0.7
531
- )
532
-
533
- response_text = response.choices[0].message.content.strip()
519
+ # Try OpenAI API first
520
+ try:
521
+ import openai
522
+ client = openai.OpenAI(api_key=api_key)
523
+
524
+ response = client.chat.completions.create(
525
+ model="gpt-4.1",
526
+ messages=[
527
+ {"role": "system", "content": "You are a helpful assistant that suggests alternative commands that don't require user input."},
528
+ {"role": "user", "content": prompt}
529
+ ],
530
+ max_tokens=150,
531
+ temperature=0.7
532
+ )
533
+
534
+ response_text = response.choices[0].message.content.strip()
535
+
536
+ except Exception as e:
537
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
538
+
539
+ # Try Claude as fallback if available
540
+ if anthropic is not None:
541
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for alternative command suggestion...")
542
+ try:
543
+ # Get Anthropic API key
544
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
545
+ if not anthropic_api_key:
546
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
547
+ return None
548
+
549
+ # Create Anthropic client
550
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
551
+
552
+ print("๐Ÿค– Calling Claude-4-Sonnet for alternative command suggestion...")
553
+ message = client.messages.create(
554
+ model="claude-3-5-sonnet-20241022",
555
+ max_tokens=150,
556
+ messages=[
557
+ {"role": "user", "content": prompt}
558
+ ]
559
+ )
560
+
561
+ response_text = message.content[0].text.strip()
562
+ print("โœ… Claude alternative command suggestion completed")
563
+
564
+ except Exception as claude_error:
565
+ print(f"โŒ Claude API call failed: {claude_error}")
566
+ return None
567
+ else:
568
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
569
+ return None
534
570
 
535
571
  # Check if the response suggests removing the command
536
572
  if response_text.startswith("REMOVE_COMMAND:"):
@@ -967,23 +1003,59 @@ class CommandListManager:
967
1003
  RUN: <reason>
968
1004
  """
969
1005
 
970
- # Call OpenAI API
971
- import openai
972
- client = openai.OpenAI(api_key=api_key)
973
-
974
- print("๐Ÿ” Analyzing if original command should be skipped...")
975
-
976
- response = client.chat.completions.create(
977
- model="gpt-3.5-turbo",
978
- messages=[
979
- {"role": "system", "content": "You are a helpful assistant that analyzes command execution."},
980
- {"role": "user", "content": prompt}
981
- ],
982
- max_tokens=100,
983
- temperature=0.3
984
- )
985
-
986
- response_text = response.choices[0].message.content.strip()
1006
+ # Try OpenAI API first
1007
+ try:
1008
+ import openai
1009
+ client = openai.OpenAI(api_key=api_key)
1010
+
1011
+ print("๐Ÿ” Analyzing if original command should be skipped...")
1012
+
1013
+ response = client.chat.completions.create(
1014
+ model="gpt-3.5-turbo",
1015
+ messages=[
1016
+ {"role": "system", "content": "You are a helpful assistant that analyzes command execution."},
1017
+ {"role": "user", "content": prompt}
1018
+ ],
1019
+ max_tokens=100,
1020
+ temperature=0.3
1021
+ )
1022
+
1023
+ response_text = response.choices[0].message.content.strip()
1024
+
1025
+ except Exception as e:
1026
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
1027
+
1028
+ # Try Claude as fallback if available
1029
+ if anthropic is not None:
1030
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command skip analysis...")
1031
+ try:
1032
+ # Get Anthropic API key
1033
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1034
+ if not anthropic_api_key:
1035
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1036
+ return False, "No API key available"
1037
+
1038
+ # Create Anthropic client
1039
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1040
+
1041
+ print("๐Ÿค– Calling Claude-4-Sonnet for command skip analysis...")
1042
+ message = client.messages.create(
1043
+ model="claude-3-5-sonnet-20241022",
1044
+ max_tokens=100,
1045
+ messages=[
1046
+ {"role": "user", "content": prompt}
1047
+ ]
1048
+ )
1049
+
1050
+ response_text = message.content[0].text.strip()
1051
+ print("โœ… Claude command skip analysis completed")
1052
+
1053
+ except Exception as claude_error:
1054
+ print(f"โŒ Claude API call failed: {claude_error}")
1055
+ return False, f"Error: {claude_error}"
1056
+ else:
1057
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
1058
+ return False, "No API key available"
987
1059
 
988
1060
  # Parse the response
989
1061
  if response_text.startswith("SKIP:"):
@@ -1111,24 +1183,60 @@ class CommandListManager:
1111
1183
  Only include commands that need changes (SKIP, MODIFY, ADD_AFTER), not KEEP actions.
1112
1184
  """
1113
1185
 
1114
- # Call OpenAI API
1115
- import openai
1116
- import json
1117
- client = openai.OpenAI(api_key=api_key)
1118
-
1119
- print("๐Ÿ” Analyzing command list for optimizations...")
1120
-
1121
- response = client.chat.completions.create(
1122
- model="gpt-4.1", # Use a more capable model for this complex task
1123
- messages=[
1124
- {"role": "system", "content": "You are a helpful assistant that analyzes and optimizes command lists."},
1125
- {"role": "user", "content": prompt}
1126
- ],
1127
- max_tokens=1000,
1128
- temperature=0.2
1129
- )
1130
-
1131
- response_text = response.choices[0].message.content.strip()
1186
+ # Try OpenAI API first
1187
+ try:
1188
+ import openai
1189
+ import json
1190
+ client = openai.OpenAI(api_key=api_key)
1191
+
1192
+ print("๐Ÿ” Analyzing command list for optimizations...")
1193
+
1194
+ response = client.chat.completions.create(
1195
+ model="gpt-4.1", # Use a more capable model for this complex task
1196
+ messages=[
1197
+ {"role": "system", "content": "You are a helpful assistant that analyzes and optimizes command lists."},
1198
+ {"role": "user", "content": prompt}
1199
+ ],
1200
+ max_tokens=1000,
1201
+ temperature=0.2
1202
+ )
1203
+
1204
+ response_text = response.choices[0].message.content.strip()
1205
+
1206
+ except Exception as e:
1207
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
1208
+
1209
+ # Try Claude as fallback if available
1210
+ if anthropic is not None:
1211
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command list analysis...")
1212
+ try:
1213
+ # Get Anthropic API key
1214
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1215
+ if not anthropic_api_key:
1216
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1217
+ return False
1218
+
1219
+ # Create Anthropic client
1220
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1221
+
1222
+ print("๐Ÿค– Calling Claude-4-Sonnet for command list analysis...")
1223
+ message = client.messages.create(
1224
+ model="claude-3-5-sonnet-20241022",
1225
+ max_tokens=1000,
1226
+ messages=[
1227
+ {"role": "user", "content": prompt}
1228
+ ]
1229
+ )
1230
+
1231
+ response_text = message.content[0].text.strip()
1232
+ print("โœ… Claude command list analysis completed")
1233
+
1234
+ except Exception as claude_error:
1235
+ print(f"โŒ Claude API call failed: {claude_error}")
1236
+ return False
1237
+ else:
1238
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
1239
+ return False
1132
1240
 
1133
1241
  # Extract JSON from the response
1134
1242
  try:
@@ -1780,8 +1888,186 @@ Do not provide any explanations, just the exact command to run.
1780
1888
  last_error = error
1781
1889
 
1782
1890
  if not result:
1783
- print(f"โŒ All model attempts failed. Last error: {last_error}")
1784
- return None
1891
+ print(f"โŒ All OpenAI model attempts failed. Last error: {last_error}")
1892
+
1893
+ # Try Claude as fallback if available
1894
+ if anthropic is not None:
1895
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback...")
1896
+ try:
1897
+ # Get Anthropic API key
1898
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1899
+ if not anthropic_api_key:
1900
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1901
+ return None
1902
+
1903
+ # Create Anthropic client
1904
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1905
+
1906
+ # Prepare the same prompt for Claude
1907
+ claude_prompt = f"""
1908
+ I'm trying to run the following command in a Linux environment:
1909
+
1910
+ ```
1911
+ {command}
1912
+ ```
1913
+
1914
+ But it failed with this error:
1915
+
1916
+ ```
1917
+ {error_output}
1918
+ ```
1919
+ {system_info}
1920
+ {directory_context}
1921
+ {file_context}
1922
+
1923
+ AVAILABLE CREDENTIALS:
1924
+ {auth_context}
1925
+
1926
+ Please analyze the error and provide ONLY a single terminal command that would fix the issue.
1927
+ Consider the current directory, system information, directory contents, and available credentials carefully before suggesting a solution.
1928
+
1929
+ IMPORTANT GUIDELINES:
1930
+ 1. For any commands that might ask for yes/no confirmation, use the appropriate non-interactive flag:
1931
+ - For apt/apt-get: use -y or --yes
1932
+ - For rm: use -f or --force
1933
+
1934
+ 2. If the error indicates a file is not found:
1935
+ - FIRST try to search for the file using: find . -name "filename" -type f 2>/dev/null
1936
+ - If found, navigate to that directory using: cd /path/to/directory
1937
+ - If not found, then consider creating the file or installing missing packages
1938
+
1939
+ 3. For missing packages or dependencies:
1940
+ - Use pip install for Python packages
1941
+ - Use apt-get install -y for system packages
1942
+ - Use npm install for Node.js packages
1943
+
1944
+ 4. For authentication issues:
1945
+ - Analyze the error to determine what type of authentication is needed
1946
+ - ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
1947
+ - Look for the specific API key or token needed in the auth_context and use its exact value
1948
+ - Common patterns:
1949
+ * wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
1950
+ * huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
1951
+ * github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
1952
+ * kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
1953
+ * API errors: export the appropriate API key as environment variable using the actual value from auth_context
1954
+
1955
+ 5. Environment variable exports:
1956
+ - Use export commands for API keys that need to be in environment
1957
+ - ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
1958
+ - Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
1959
+
1960
+ 6. CRITICAL: When using any API key, token, or credential:
1961
+ - Find the exact value in the AVAILABLE CREDENTIALS section
1962
+ - Use that exact value in your command
1963
+ - Do not use generic placeholders or dummy values
1964
+ - The auth_context contains real, usable credentials
1965
+
1966
+ 7. For Git SSH authentication failures:
1967
+ - If the error contains "Host key verification failed" or "Could not read from remote repository"
1968
+ - ALWAYS convert SSH URLs to HTTPS URLs for public repositories
1969
+ - Replace git@github.com:username/repo.git with https://github.com/username/repo.git
1970
+ - This works for public repositories without authentication
1971
+ - Example: git clone https://github.com/xg-chu/ARTalk.git
1972
+
1973
+ Do not provide any explanations, just the exact command to run.
1974
+ """
1975
+
1976
+ print("๐Ÿค– Calling Claude-4-Sonnet to debug the failed command...")
1977
+ message = client.messages.create(
1978
+ model="claude-3-5-sonnet-20241022",
1979
+ max_tokens=300,
1980
+ messages=[
1981
+ {"role": "user", "content": claude_prompt}
1982
+ ]
1983
+ )
1984
+
1985
+ fix_command = message.content[0].text.strip()
1986
+ print(f"๐Ÿ” DEBUG: Raw Claude response content: {fix_command}")
1987
+
1988
+ # Process the response similar to OpenAI
1989
+ original_response = fix_command
1990
+
1991
+ # Extract just the command if it's wrapped in backticks or explanation
1992
+ if "```" in fix_command:
1993
+ # Extract content between backticks
1994
+ import re
1995
+ code_blocks = re.findall(r'```(?:bash|sh)?\s*(.*?)\s*```', fix_command, re.DOTALL)
1996
+ if code_blocks:
1997
+ fix_command = code_blocks[0].strip()
1998
+ print(f"โœ… Extracted command from code block: {fix_command}")
1999
+
2000
+ # If the response still has explanatory text, try to extract just the command
2001
+ if len(fix_command.split('\n')) > 1:
2002
+ # First try to find lines that look like commands (start with common command prefixes)
2003
+ command_prefixes = ['sudo', 'apt', 'pip', 'npm', 'yarn', 'git', 'cd', 'mv', 'cp', 'rm', 'mkdir', 'touch',
2004
+ 'chmod', 'chown', 'echo', 'cat', 'python', 'python3', 'node', 'export',
2005
+ 'curl', 'wget', 'docker', 'make', 'gcc', 'g++', 'javac', 'java',
2006
+ 'conda', 'uv', 'poetry', 'nvm', 'rbenv', 'pyenv', 'rustup']
2007
+
2008
+ # Check for lines that start with common command prefixes
2009
+ command_lines = [line.strip() for line in fix_command.split('\n')
2010
+ if any(line.strip().startswith(prefix) for prefix in command_prefixes)]
2011
+
2012
+ if command_lines:
2013
+ # Use the first command line found
2014
+ fix_command = command_lines[0]
2015
+ print(f"โœ… Identified command by prefix: {fix_command}")
2016
+ else:
2017
+ # Try to find lines that look like commands (contain common shell patterns)
2018
+ shell_patterns = [' | ', ' > ', ' >> ', ' && ', ' || ', ' ; ', '$(', '`', ' -y ', ' --yes ']
2019
+ command_lines = [line.strip() for line in fix_command.split('\n')
2020
+ if any(pattern in line for pattern in shell_patterns)]
2021
+
2022
+ if command_lines:
2023
+ # Use the first command line found
2024
+ fix_command = command_lines[0]
2025
+ print(f"โœ… Identified command by shell pattern: {fix_command}")
2026
+ else:
2027
+ # Fall back to the shortest non-empty line as it's likely the command
2028
+ lines = [line.strip() for line in fix_command.split('\n') if line.strip()]
2029
+ if lines:
2030
+ # Exclude very short lines that are likely not commands
2031
+ valid_lines = [line for line in lines if len(line) > 5]
2032
+ if valid_lines:
2033
+ fix_command = min(valid_lines, key=len)
2034
+ else:
2035
+ fix_command = min(lines, key=len)
2036
+ print(f"โœ… Selected shortest line as command: {fix_command}")
2037
+
2038
+ # Clean up the command - remove any trailing periods or quotes
2039
+ fix_command = fix_command.rstrip('.;"\'')
2040
+
2041
+ # Remove common prefixes that LLMs sometimes add
2042
+ prefixes_to_remove = [
2043
+ "Run: ", "Execute: ", "Try: ", "Command: ", "Fix: ", "Solution: ",
2044
+ "You should run: ", "You can run: ", "You need to run: "
2045
+ ]
2046
+ for prefix in prefixes_to_remove:
2047
+ if fix_command.startswith(prefix):
2048
+ fix_command = fix_command[len(prefix):].strip()
2049
+ print(f"โœ… Removed prefix: {prefix}")
2050
+ break
2051
+
2052
+ # If the command is still multi-line or very long, it might not be a valid command
2053
+ if len(fix_command.split('\n')) > 1 or len(fix_command) > 500:
2054
+ print("โš ๏ธ Extracted command appears invalid (multi-line or too long)")
2055
+ print("๐Ÿ” Original response from Claude:")
2056
+ print("-" * 60)
2057
+ print(original_response)
2058
+ print("-" * 60)
2059
+ print("โš ๏ธ Using best guess for command")
2060
+
2061
+ print(f"๐Ÿ”ง Claude suggested fix: {fix_command}")
2062
+ print(f"๐Ÿ” DEBUG: Returning Claude fix command: {fix_command}")
2063
+ return fix_command
2064
+
2065
+ except Exception as e:
2066
+ print(f"โŒ Claude API call failed: {e}")
2067
+ return None
2068
+ else:
2069
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
2070
+ return None
1785
2071
 
1786
2072
  # Process the response
1787
2073
  try:
@@ -1988,7 +2274,65 @@ Provide fixes for all {len(failed_commands)} failed commands:"""
1988
2274
  return fixes
1989
2275
  else:
1990
2276
  print(f"โŒ OpenAI API error: {response.status_code} - {response.text}")
1991
- return []
2277
+
2278
+ # Try Claude as fallback if available
2279
+ if anthropic is not None:
2280
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for batch debugging...")
2281
+ try:
2282
+ # Get Anthropic API key
2283
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
2284
+ if not anthropic_api_key:
2285
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
2286
+ return []
2287
+
2288
+ # Create Anthropic client
2289
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
2290
+
2291
+ print("๐Ÿค– Calling Claude-4-Sonnet for batch debugging...")
2292
+ message = client.messages.create(
2293
+ model="claude-3-5-sonnet-20241022",
2294
+ max_tokens=1000,
2295
+ messages=[
2296
+ {"role": "user", "content": prompt}
2297
+ ]
2298
+ )
2299
+
2300
+ content = message.content[0].text
2301
+ print(f"โœ… Claude batch analysis completed")
2302
+
2303
+ # Parse the response to extract fix commands
2304
+ fixes = []
2305
+ for i in range(1, len(failed_commands) + 1):
2306
+ fix_pattern = f"FIX_COMMAND_{i}: (.+)"
2307
+ reason_pattern = f"REASON_{i}: (.+)"
2308
+
2309
+ fix_match = re.search(fix_pattern, content, re.MULTILINE)
2310
+ reason_match = re.search(reason_pattern, content, re.MULTILINE)
2311
+
2312
+ if fix_match:
2313
+ fix_command = fix_match.group(1).strip()
2314
+ reason = reason_match.group(1).strip() if reason_match else "Claude suggested fix"
2315
+
2316
+ # Clean up the fix command
2317
+ if fix_command.startswith('`') and fix_command.endswith('`'):
2318
+ fix_command = fix_command[1:-1]
2319
+
2320
+ fixes.append({
2321
+ 'original_command': failed_commands[i-1]['command'],
2322
+ 'fix_command': fix_command,
2323
+ 'reason': reason,
2324
+ 'command_index': i-1
2325
+ })
2326
+
2327
+ print(f"๐Ÿ”ง Generated {len(fixes)} fix commands from Claude batch analysis")
2328
+ return fixes
2329
+
2330
+ except Exception as e:
2331
+ print(f"โŒ Claude API call failed: {e}")
2332
+ return []
2333
+ else:
2334
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
2335
+ return []
1992
2336
 
1993
2337
  except Exception as e:
1994
2338
  print(f"โŒ Error during batch debugging: {e}")
@@ -3565,21 +3909,57 @@ Return only the JSON array, no other text.
3565
3909
  print("โš ๏ธ No OpenAI API key available for command preprocessing")
3566
3910
  return setup_commands
3567
3911
 
3568
- # Call OpenAI API
3569
- import openai
3570
- client = openai.OpenAI(api_key=api_key)
3571
-
3572
- response = client.chat.completions.create(
3573
- model="gpt-3.5-turbo",
3574
- messages=[
3575
- {"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
3576
- {"role": "user", "content": prompt}
3577
- ],
3578
- temperature=0.1,
3579
- max_tokens=2000
3580
- )
3581
-
3582
- result = response.choices[0].message.content.strip()
3912
+ # Try OpenAI API first
3913
+ try:
3914
+ import openai
3915
+ client = openai.OpenAI(api_key=api_key)
3916
+
3917
+ response = client.chat.completions.create(
3918
+ model="gpt-3.5-turbo",
3919
+ messages=[
3920
+ {"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
3921
+ {"role": "user", "content": prompt}
3922
+ ],
3923
+ temperature=0.1,
3924
+ max_tokens=2000
3925
+ )
3926
+
3927
+ result = response.choices[0].message.content.strip()
3928
+
3929
+ except Exception as e:
3930
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
3931
+
3932
+ # Try Claude as fallback if available
3933
+ if anthropic is not None:
3934
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command preprocessing...")
3935
+ try:
3936
+ # Get Anthropic API key
3937
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
3938
+ if not anthropic_api_key:
3939
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
3940
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3941
+
3942
+ # Create Anthropic client
3943
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
3944
+
3945
+ print("๐Ÿค– Calling Claude-4-Sonnet for command preprocessing...")
3946
+ message = client.messages.create(
3947
+ model="claude-3-5-sonnet-20241022",
3948
+ max_tokens=2000,
3949
+ messages=[
3950
+ {"role": "user", "content": prompt}
3951
+ ]
3952
+ )
3953
+
3954
+ result = message.content[0].text.strip()
3955
+ print("โœ… Claude preprocessing completed")
3956
+
3957
+ except Exception as claude_error:
3958
+ print(f"โŒ Claude API call failed: {claude_error}")
3959
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3960
+ else:
3961
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
3962
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3583
3963
 
3584
3964
  # Debug: Print the raw response
3585
3965
  print(f"๐Ÿ” LLM Response: {result[:200]}...")
@@ -3822,7 +4202,6 @@ if __name__ == "__main__":
3822
4202
  parser = argparse.ArgumentParser()
3823
4203
  parser.add_argument('--gpu', type=str, help='GPU type (e.g., A10G, T4, A100-80GB). If not provided, will prompt for GPU selection.')
3824
4204
  parser.add_argument('--repo-url', type=str, help='Repository URL to clone')
3825
- parser.add_argument('--repo-name', type=str, help='Repository name override')
3826
4205
  parser.add_argument('--setup-commands', type=str, nargs='+', help='Setup commands to run (deprecated)')
3827
4206
  parser.add_argument('--setup-commands-json', type=str, help='Setup commands as JSON array')
3828
4207
  parser.add_argument('--commands-file', type=str, help='Path to file containing setup commands (one per line)')
@@ -3930,19 +4309,7 @@ if __name__ == "__main__":
3930
4309
  print(f"Setup Commands: {len(args.setup_commands)} custom commands")
3931
4310
  else:
3932
4311
  print("Setup Commands: Auto-detect from repository")
3933
-
3934
- # Confirm settings
3935
- if not args.yes:
3936
- try:
3937
- proceed = input("Proceed with these settings? (Y/n): ").strip().lower()
3938
- if proceed in ('n', 'no'):
3939
- print("๐Ÿ›‘ Operation cancelled by user.")
3940
- sys.exit(0)
3941
- except KeyboardInterrupt:
3942
- print("\n๐Ÿ›‘ Operation cancelled by user.")
3943
- sys.exit(0)
3944
- else:
3945
- print("โœ… Skipping confirmation prompt (--yes flag used)")
4312
+
3946
4313
 
3947
4314
  # Interactive mode or missing required arguments
3948
4315
  if args.interactive or not args.repo_url or not args.volume_name:
@@ -4112,8 +4479,12 @@ if __name__ == "__main__":
4112
4479
  interactive=args.interactive
4113
4480
  )
4114
4481
  except KeyboardInterrupt:
4482
+ # print("\n\n๐Ÿ›‘ Execution interrupted")
4483
+ # print("๐Ÿงน Cleaning up resources...")
4115
4484
  cleanup_modal_token()
4116
4485
  sys.exit(1)
4117
4486
  except Exception as e:
4487
+ # print(f"\nโŒ Error: {e}")
4488
+ # print("๐Ÿงน Cleaning up resources...")
4118
4489
  cleanup_modal_token()
4119
4490
  sys.exit(1)
@@ -15,7 +15,7 @@ import uuid
15
15
  import signal
16
16
  from pathlib import Path
17
17
  import modal
18
-
18
+ import anthropic
19
19
  # Import authentication manager
20
20
  try:
21
21
  from auth_manager import AuthManager
@@ -516,21 +516,57 @@ class PersistentShell:
516
516
  REMOVE_COMMAND: <reason>
517
517
  """
518
518
 
519
- # Call OpenAI API
520
- import openai
521
- client = openai.OpenAI(api_key=api_key)
522
-
523
- response = client.chat.completions.create(
524
- model="gpt-4.1",
525
- messages=[
526
- {"role": "system", "content": "You are a helpful assistant that suggests alternative commands that don't require user input."},
527
- {"role": "user", "content": prompt}
528
- ],
529
- max_tokens=150,
530
- temperature=0.7
531
- )
532
-
533
- response_text = response.choices[0].message.content.strip()
519
+ # Try OpenAI API first
520
+ try:
521
+ import openai
522
+ client = openai.OpenAI(api_key=api_key)
523
+
524
+ response = client.chat.completions.create(
525
+ model="gpt-4.1",
526
+ messages=[
527
+ {"role": "system", "content": "You are a helpful assistant that suggests alternative commands that don't require user input."},
528
+ {"role": "user", "content": prompt}
529
+ ],
530
+ max_tokens=150,
531
+ temperature=0.7
532
+ )
533
+
534
+ response_text = response.choices[0].message.content.strip()
535
+
536
+ except Exception as e:
537
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
538
+
539
+ # Try Claude as fallback if available
540
+ if anthropic is not None:
541
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for alternative command suggestion...")
542
+ try:
543
+ # Get Anthropic API key
544
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
545
+ if not anthropic_api_key:
546
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
547
+ return None
548
+
549
+ # Create Anthropic client
550
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
551
+
552
+ print("๐Ÿค– Calling Claude-4-Sonnet for alternative command suggestion...")
553
+ message = client.messages.create(
554
+ model="claude-3-5-sonnet-20241022",
555
+ max_tokens=150,
556
+ messages=[
557
+ {"role": "user", "content": prompt}
558
+ ]
559
+ )
560
+
561
+ response_text = message.content[0].text.strip()
562
+ print("โœ… Claude alternative command suggestion completed")
563
+
564
+ except Exception as claude_error:
565
+ print(f"โŒ Claude API call failed: {claude_error}")
566
+ return None
567
+ else:
568
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
569
+ return None
534
570
 
535
571
  # Check if the response suggests removing the command
536
572
  if response_text.startswith("REMOVE_COMMAND:"):
@@ -967,23 +1003,59 @@ class CommandListManager:
967
1003
  RUN: <reason>
968
1004
  """
969
1005
 
970
- # Call OpenAI API
971
- import openai
972
- client = openai.OpenAI(api_key=api_key)
973
-
974
- print("๐Ÿ” Analyzing if original command should be skipped...")
975
-
976
- response = client.chat.completions.create(
977
- model="gpt-3.5-turbo",
978
- messages=[
979
- {"role": "system", "content": "You are a helpful assistant that analyzes command execution."},
980
- {"role": "user", "content": prompt}
981
- ],
982
- max_tokens=100,
983
- temperature=0.3
984
- )
985
-
986
- response_text = response.choices[0].message.content.strip()
1006
+ # Try OpenAI API first
1007
+ try:
1008
+ import openai
1009
+ client = openai.OpenAI(api_key=api_key)
1010
+
1011
+ print("๐Ÿ” Analyzing if original command should be skipped...")
1012
+
1013
+ response = client.chat.completions.create(
1014
+ model="gpt-3.5-turbo",
1015
+ messages=[
1016
+ {"role": "system", "content": "You are a helpful assistant that analyzes command execution."},
1017
+ {"role": "user", "content": prompt}
1018
+ ],
1019
+ max_tokens=100,
1020
+ temperature=0.3
1021
+ )
1022
+
1023
+ response_text = response.choices[0].message.content.strip()
1024
+
1025
+ except Exception as e:
1026
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
1027
+
1028
+ # Try Claude as fallback if available
1029
+ if anthropic is not None:
1030
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command skip analysis...")
1031
+ try:
1032
+ # Get Anthropic API key
1033
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1034
+ if not anthropic_api_key:
1035
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1036
+ return False, "No API key available"
1037
+
1038
+ # Create Anthropic client
1039
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1040
+
1041
+ print("๐Ÿค– Calling Claude-4-Sonnet for command skip analysis...")
1042
+ message = client.messages.create(
1043
+ model="claude-3-5-sonnet-20241022",
1044
+ max_tokens=100,
1045
+ messages=[
1046
+ {"role": "user", "content": prompt}
1047
+ ]
1048
+ )
1049
+
1050
+ response_text = message.content[0].text.strip()
1051
+ print("โœ… Claude command skip analysis completed")
1052
+
1053
+ except Exception as claude_error:
1054
+ print(f"โŒ Claude API call failed: {claude_error}")
1055
+ return False, f"Error: {claude_error}"
1056
+ else:
1057
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
1058
+ return False, "No API key available"
987
1059
 
988
1060
  # Parse the response
989
1061
  if response_text.startswith("SKIP:"):
@@ -1111,24 +1183,60 @@ class CommandListManager:
1111
1183
  Only include commands that need changes (SKIP, MODIFY, ADD_AFTER), not KEEP actions.
1112
1184
  """
1113
1185
 
1114
- # Call OpenAI API
1115
- import openai
1116
- import json
1117
- client = openai.OpenAI(api_key=api_key)
1118
-
1119
- print("๐Ÿ” Analyzing command list for optimizations...")
1120
-
1121
- response = client.chat.completions.create(
1122
- model="gpt-4.1", # Use a more capable model for this complex task
1123
- messages=[
1124
- {"role": "system", "content": "You are a helpful assistant that analyzes and optimizes command lists."},
1125
- {"role": "user", "content": prompt}
1126
- ],
1127
- max_tokens=1000,
1128
- temperature=0.2
1129
- )
1130
-
1131
- response_text = response.choices[0].message.content.strip()
1186
+ # Try OpenAI API first
1187
+ try:
1188
+ import openai
1189
+ import json
1190
+ client = openai.OpenAI(api_key=api_key)
1191
+
1192
+ print("๐Ÿ” Analyzing command list for optimizations...")
1193
+
1194
+ response = client.chat.completions.create(
1195
+ model="gpt-4.1", # Use a more capable model for this complex task
1196
+ messages=[
1197
+ {"role": "system", "content": "You are a helpful assistant that analyzes and optimizes command lists."},
1198
+ {"role": "user", "content": prompt}
1199
+ ],
1200
+ max_tokens=1000,
1201
+ temperature=0.2
1202
+ )
1203
+
1204
+ response_text = response.choices[0].message.content.strip()
1205
+
1206
+ except Exception as e:
1207
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
1208
+
1209
+ # Try Claude as fallback if available
1210
+ if anthropic is not None:
1211
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command list analysis...")
1212
+ try:
1213
+ # Get Anthropic API key
1214
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1215
+ if not anthropic_api_key:
1216
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1217
+ return False
1218
+
1219
+ # Create Anthropic client
1220
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1221
+
1222
+ print("๐Ÿค– Calling Claude-4-Sonnet for command list analysis...")
1223
+ message = client.messages.create(
1224
+ model="claude-3-5-sonnet-20241022",
1225
+ max_tokens=1000,
1226
+ messages=[
1227
+ {"role": "user", "content": prompt}
1228
+ ]
1229
+ )
1230
+
1231
+ response_text = message.content[0].text.strip()
1232
+ print("โœ… Claude command list analysis completed")
1233
+
1234
+ except Exception as claude_error:
1235
+ print(f"โŒ Claude API call failed: {claude_error}")
1236
+ return False
1237
+ else:
1238
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
1239
+ return False
1132
1240
 
1133
1241
  # Extract JSON from the response
1134
1242
  try:
@@ -1780,8 +1888,186 @@ Do not provide any explanations, just the exact command to run.
1780
1888
  last_error = error
1781
1889
 
1782
1890
  if not result:
1783
- print(f"โŒ All model attempts failed. Last error: {last_error}")
1784
- return None
1891
+ print(f"โŒ All OpenAI model attempts failed. Last error: {last_error}")
1892
+
1893
+ # Try Claude as fallback if available
1894
+ if anthropic is not None:
1895
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback...")
1896
+ try:
1897
+ # Get Anthropic API key
1898
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
1899
+ if not anthropic_api_key:
1900
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
1901
+ return None
1902
+
1903
+ # Create Anthropic client
1904
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
1905
+
1906
+ # Prepare the same prompt for Claude
1907
+ claude_prompt = f"""
1908
+ I'm trying to run the following command in a Linux environment:
1909
+
1910
+ ```
1911
+ {command}
1912
+ ```
1913
+
1914
+ But it failed with this error:
1915
+
1916
+ ```
1917
+ {error_output}
1918
+ ```
1919
+ {system_info}
1920
+ {directory_context}
1921
+ {file_context}
1922
+
1923
+ AVAILABLE CREDENTIALS:
1924
+ {auth_context}
1925
+
1926
+ Please analyze the error and provide ONLY a single terminal command that would fix the issue.
1927
+ Consider the current directory, system information, directory contents, and available credentials carefully before suggesting a solution.
1928
+
1929
+ IMPORTANT GUIDELINES:
1930
+ 1. For any commands that might ask for yes/no confirmation, use the appropriate non-interactive flag:
1931
+ - For apt/apt-get: use -y or --yes
1932
+ - For rm: use -f or --force
1933
+
1934
+ 2. If the error indicates a file is not found:
1935
+ - FIRST try to search for the file using: find . -name "filename" -type f 2>/dev/null
1936
+ - If found, navigate to that directory using: cd /path/to/directory
1937
+ - If not found, then consider creating the file or installing missing packages
1938
+
1939
+ 3. For missing packages or dependencies:
1940
+ - Use pip install for Python packages
1941
+ - Use apt-get install -y for system packages
1942
+ - Use npm install for Node.js packages
1943
+
1944
+ 4. For authentication issues:
1945
+ - Analyze the error to determine what type of authentication is needed
1946
+ - ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
1947
+ - Look for the specific API key or token needed in the auth_context and use its exact value
1948
+ - Common patterns:
1949
+ * wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
1950
+ * huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
1951
+ * github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
1952
+ * kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
1953
+ * API errors: export the appropriate API key as environment variable using the actual value from auth_context
1954
+
1955
+ 5. Environment variable exports:
1956
+ - Use export commands for API keys that need to be in environment
1957
+ - ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
1958
+ - Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
1959
+
1960
+ 6. CRITICAL: When using any API key, token, or credential:
1961
+ - Find the exact value in the AVAILABLE CREDENTIALS section
1962
+ - Use that exact value in your command
1963
+ - Do not use generic placeholders or dummy values
1964
+ - The auth_context contains real, usable credentials
1965
+
1966
+ 7. For Git SSH authentication failures:
1967
+ - If the error contains "Host key verification failed" or "Could not read from remote repository"
1968
+ - ALWAYS convert SSH URLs to HTTPS URLs for public repositories
1969
+ - Replace git@github.com:username/repo.git with https://github.com/username/repo.git
1970
+ - This works for public repositories without authentication
1971
+ - Example: git clone https://github.com/xg-chu/ARTalk.git
1972
+
1973
+ Do not provide any explanations, just the exact command to run.
1974
+ """
1975
+
1976
+ print("๐Ÿค– Calling Claude-4-Sonnet to debug the failed command...")
1977
+ message = client.messages.create(
1978
+ model="claude-3-5-sonnet-20241022",
1979
+ max_tokens=300,
1980
+ messages=[
1981
+ {"role": "user", "content": claude_prompt}
1982
+ ]
1983
+ )
1984
+
1985
+ fix_command = message.content[0].text.strip()
1986
+ print(f"๐Ÿ” DEBUG: Raw Claude response content: {fix_command}")
1987
+
1988
+ # Process the response similar to OpenAI
1989
+ original_response = fix_command
1990
+
1991
+ # Extract just the command if it's wrapped in backticks or explanation
1992
+ if "```" in fix_command:
1993
+ # Extract content between backticks
1994
+ import re
1995
+ code_blocks = re.findall(r'```(?:bash|sh)?\s*(.*?)\s*```', fix_command, re.DOTALL)
1996
+ if code_blocks:
1997
+ fix_command = code_blocks[0].strip()
1998
+ print(f"โœ… Extracted command from code block: {fix_command}")
1999
+
2000
+ # If the response still has explanatory text, try to extract just the command
2001
+ if len(fix_command.split('\n')) > 1:
2002
+ # First try to find lines that look like commands (start with common command prefixes)
2003
+ command_prefixes = ['sudo', 'apt', 'pip', 'npm', 'yarn', 'git', 'cd', 'mv', 'cp', 'rm', 'mkdir', 'touch',
2004
+ 'chmod', 'chown', 'echo', 'cat', 'python', 'python3', 'node', 'export',
2005
+ 'curl', 'wget', 'docker', 'make', 'gcc', 'g++', 'javac', 'java',
2006
+ 'conda', 'uv', 'poetry', 'nvm', 'rbenv', 'pyenv', 'rustup']
2007
+
2008
+ # Check for lines that start with common command prefixes
2009
+ command_lines = [line.strip() for line in fix_command.split('\n')
2010
+ if any(line.strip().startswith(prefix) for prefix in command_prefixes)]
2011
+
2012
+ if command_lines:
2013
+ # Use the first command line found
2014
+ fix_command = command_lines[0]
2015
+ print(f"โœ… Identified command by prefix: {fix_command}")
2016
+ else:
2017
+ # Try to find lines that look like commands (contain common shell patterns)
2018
+ shell_patterns = [' | ', ' > ', ' >> ', ' && ', ' || ', ' ; ', '$(', '`', ' -y ', ' --yes ']
2019
+ command_lines = [line.strip() for line in fix_command.split('\n')
2020
+ if any(pattern in line for pattern in shell_patterns)]
2021
+
2022
+ if command_lines:
2023
+ # Use the first command line found
2024
+ fix_command = command_lines[0]
2025
+ print(f"โœ… Identified command by shell pattern: {fix_command}")
2026
+ else:
2027
+ # Fall back to the shortest non-empty line as it's likely the command
2028
+ lines = [line.strip() for line in fix_command.split('\n') if line.strip()]
2029
+ if lines:
2030
+ # Exclude very short lines that are likely not commands
2031
+ valid_lines = [line for line in lines if len(line) > 5]
2032
+ if valid_lines:
2033
+ fix_command = min(valid_lines, key=len)
2034
+ else:
2035
+ fix_command = min(lines, key=len)
2036
+ print(f"โœ… Selected shortest line as command: {fix_command}")
2037
+
2038
+ # Clean up the command - remove any trailing periods or quotes
2039
+ fix_command = fix_command.rstrip('.;"\'')
2040
+
2041
+ # Remove common prefixes that LLMs sometimes add
2042
+ prefixes_to_remove = [
2043
+ "Run: ", "Execute: ", "Try: ", "Command: ", "Fix: ", "Solution: ",
2044
+ "You should run: ", "You can run: ", "You need to run: "
2045
+ ]
2046
+ for prefix in prefixes_to_remove:
2047
+ if fix_command.startswith(prefix):
2048
+ fix_command = fix_command[len(prefix):].strip()
2049
+ print(f"โœ… Removed prefix: {prefix}")
2050
+ break
2051
+
2052
+ # If the command is still multi-line or very long, it might not be a valid command
2053
+ if len(fix_command.split('\n')) > 1 or len(fix_command) > 500:
2054
+ print("โš ๏ธ Extracted command appears invalid (multi-line or too long)")
2055
+ print("๐Ÿ” Original response from Claude:")
2056
+ print("-" * 60)
2057
+ print(original_response)
2058
+ print("-" * 60)
2059
+ print("โš ๏ธ Using best guess for command")
2060
+
2061
+ print(f"๐Ÿ”ง Claude suggested fix: {fix_command}")
2062
+ print(f"๐Ÿ” DEBUG: Returning Claude fix command: {fix_command}")
2063
+ return fix_command
2064
+
2065
+ except Exception as e:
2066
+ print(f"โŒ Claude API call failed: {e}")
2067
+ return None
2068
+ else:
2069
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
2070
+ return None
1785
2071
 
1786
2072
  # Process the response
1787
2073
  try:
@@ -1988,7 +2274,65 @@ Provide fixes for all {len(failed_commands)} failed commands:"""
1988
2274
  return fixes
1989
2275
  else:
1990
2276
  print(f"โŒ OpenAI API error: {response.status_code} - {response.text}")
1991
- return []
2277
+
2278
+ # Try Claude as fallback if available
2279
+ if anthropic is not None:
2280
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for batch debugging...")
2281
+ try:
2282
+ # Get Anthropic API key
2283
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
2284
+ if not anthropic_api_key:
2285
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
2286
+ return []
2287
+
2288
+ # Create Anthropic client
2289
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
2290
+
2291
+ print("๐Ÿค– Calling Claude-4-Sonnet for batch debugging...")
2292
+ message = client.messages.create(
2293
+ model="claude-3-5-sonnet-20241022",
2294
+ max_tokens=1000,
2295
+ messages=[
2296
+ {"role": "user", "content": prompt}
2297
+ ]
2298
+ )
2299
+
2300
+ content = message.content[0].text
2301
+ print(f"โœ… Claude batch analysis completed")
2302
+
2303
+ # Parse the response to extract fix commands
2304
+ fixes = []
2305
+ for i in range(1, len(failed_commands) + 1):
2306
+ fix_pattern = f"FIX_COMMAND_{i}: (.+)"
2307
+ reason_pattern = f"REASON_{i}: (.+)"
2308
+
2309
+ fix_match = re.search(fix_pattern, content, re.MULTILINE)
2310
+ reason_match = re.search(reason_pattern, content, re.MULTILINE)
2311
+
2312
+ if fix_match:
2313
+ fix_command = fix_match.group(1).strip()
2314
+ reason = reason_match.group(1).strip() if reason_match else "Claude suggested fix"
2315
+
2316
+ # Clean up the fix command
2317
+ if fix_command.startswith('`') and fix_command.endswith('`'):
2318
+ fix_command = fix_command[1:-1]
2319
+
2320
+ fixes.append({
2321
+ 'original_command': failed_commands[i-1]['command'],
2322
+ 'fix_command': fix_command,
2323
+ 'reason': reason,
2324
+ 'command_index': i-1
2325
+ })
2326
+
2327
+ print(f"๐Ÿ”ง Generated {len(fixes)} fix commands from Claude batch analysis")
2328
+ return fixes
2329
+
2330
+ except Exception as e:
2331
+ print(f"โŒ Claude API call failed: {e}")
2332
+ return []
2333
+ else:
2334
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
2335
+ return []
1992
2336
 
1993
2337
  except Exception as e:
1994
2338
  print(f"โŒ Error during batch debugging: {e}")
@@ -3565,21 +3909,57 @@ Return only the JSON array, no other text.
3565
3909
  print("โš ๏ธ No OpenAI API key available for command preprocessing")
3566
3910
  return setup_commands
3567
3911
 
3568
- # Call OpenAI API
3569
- import openai
3570
- client = openai.OpenAI(api_key=api_key)
3571
-
3572
- response = client.chat.completions.create(
3573
- model="gpt-3.5-turbo",
3574
- messages=[
3575
- {"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
3576
- {"role": "user", "content": prompt}
3577
- ],
3578
- temperature=0.1,
3579
- max_tokens=2000
3580
- )
3581
-
3582
- result = response.choices[0].message.content.strip()
3912
+ # Try OpenAI API first
3913
+ try:
3914
+ import openai
3915
+ client = openai.OpenAI(api_key=api_key)
3916
+
3917
+ response = client.chat.completions.create(
3918
+ model="gpt-3.5-turbo",
3919
+ messages=[
3920
+ {"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
3921
+ {"role": "user", "content": prompt}
3922
+ ],
3923
+ temperature=0.1,
3924
+ max_tokens=2000
3925
+ )
3926
+
3927
+ result = response.choices[0].message.content.strip()
3928
+
3929
+ except Exception as e:
3930
+ print(f"โš ๏ธ OpenAI API call failed: {e}")
3931
+
3932
+ # Try Claude as fallback if available
3933
+ if anthropic is not None:
3934
+ print("๐Ÿ”„ Trying Claude-4-Sonnet as fallback for command preprocessing...")
3935
+ try:
3936
+ # Get Anthropic API key
3937
+ anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY")
3938
+ if not anthropic_api_key:
3939
+ print("โš ๏ธ No ANTHROPIC_API_KEY found in environment")
3940
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3941
+
3942
+ # Create Anthropic client
3943
+ client = anthropic.Anthropic(api_key=anthropic_api_key)
3944
+
3945
+ print("๐Ÿค– Calling Claude-4-Sonnet for command preprocessing...")
3946
+ message = client.messages.create(
3947
+ model="claude-3-5-sonnet-20241022",
3948
+ max_tokens=2000,
3949
+ messages=[
3950
+ {"role": "user", "content": prompt}
3951
+ ]
3952
+ )
3953
+
3954
+ result = message.content[0].text.strip()
3955
+ print("โœ… Claude preprocessing completed")
3956
+
3957
+ except Exception as claude_error:
3958
+ print(f"โŒ Claude API call failed: {claude_error}")
3959
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3960
+ else:
3961
+ print("โš ๏ธ Claude fallback not available (anthropic library not installed)")
3962
+ return fallback_preprocess_commands(setup_commands, stored_credentials)
3583
3963
 
3584
3964
  # Debug: Print the raw response
3585
3965
  print(f"๐Ÿ” LLM Response: {result[:200]}...")
@@ -3929,19 +4309,7 @@ if __name__ == "__main__":
3929
4309
  print(f"Setup Commands: {len(args.setup_commands)} custom commands")
3930
4310
  else:
3931
4311
  print("Setup Commands: Auto-detect from repository")
3932
-
3933
- # Confirm settings
3934
- if not args.yes:
3935
- try:
3936
- proceed = input("Proceed with these settings? (Y/n): ").strip().lower()
3937
- if proceed in ('n', 'no'):
3938
- print("๐Ÿ›‘ Operation cancelled by user.")
3939
- sys.exit(0)
3940
- except KeyboardInterrupt:
3941
- print("\n๐Ÿ›‘ Operation cancelled by user.")
3942
- sys.exit(0)
3943
- else:
3944
- print("โœ… Skipping confirmation prompt (--yes flag used)")
4312
+
3945
4313
 
3946
4314
  # Interactive mode or missing required arguments
3947
4315
  if args.interactive or not args.repo_url or not args.volume_name: