gitarsenal-cli 1.4.3 → 1.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  """
3
3
  Fetch Modal Tokens
4
4
 
5
- This script fetches Modal tokens from the GitArsenal API.
5
+ This script fetches Modal tokens from the proxy server.
6
6
  """
7
7
 
8
8
  import os
@@ -12,108 +12,144 @@ import requests
12
12
  import subprocess
13
13
  from pathlib import Path
14
14
 
15
- # Default tokens to use if we can't fetch from the server
16
- # DEFAULT_TOKEN_ID = "ak-sLhYqCjkvixiYcb9LAuCHp"
17
- # DEFAULT_TOKEN_SECRET = "as-fPzD0Zm0dl6IFAEkhaH9pq"
15
+ def fetch_default_tokens_from_gitarsenal():
16
+ """
17
+ Fetch default Modal tokens from gitarsenal.dev API.
18
+
19
+ Returns:
20
+ tuple: (token_id, token_secret) if successful, (None, None) otherwise
21
+ """
22
+ endpoint = "https://gitarsenal.dev/api/credentials"
23
+
24
+ try:
25
+ headers = {
26
+ 'User-Agent': 'Python-Modal-Token-Fetcher/1.0',
27
+ 'Accept': 'application/json'
28
+ }
29
+
30
+ print(f"šŸ”— Fetching default tokens from: {endpoint}")
31
+
32
+ response = requests.get(endpoint, headers=headers, timeout=30)
33
+
34
+ print(f"šŸ“Š Status: {response.status_code}")
35
+
36
+ if response.status_code == 200:
37
+ try:
38
+ data = response.json()
39
+ token_id = data.get("modalTokenId")
40
+ token_secret = data.get("modalTokenSecret")
41
+
42
+ if token_id and token_secret:
43
+ print("āœ… Successfully fetched default tokens from gitarsenal.dev")
44
+ return token_id, token_secret
45
+ else:
46
+ print("āŒ Modal tokens not found in gitarsenal.dev response")
47
+ return None, None
48
+ except json.JSONDecodeError:
49
+ print("āŒ Invalid JSON response from gitarsenal.dev")
50
+ return None, None
51
+ else:
52
+ print(f"āŒ Failed to fetch from gitarsenal.dev: {response.status_code} - {response.text[:200]}")
53
+ return None, None
54
+
55
+ except requests.exceptions.Timeout:
56
+ print("āŒ Request timeout when fetching from gitarsenal.dev")
57
+ return None, None
58
+ except requests.exceptions.ConnectionError:
59
+ print("āŒ Connection failed to gitarsenal.dev")
60
+ return None, None
61
+ except requests.exceptions.RequestException as e:
62
+ print(f"āŒ Request failed to gitarsenal.dev: {e}")
63
+ return None, None
18
64
 
19
- def fetch_credentials_from_api(api_url=None, api_key=None, credential_id=None):
65
+ def fetch_tokens_from_proxy(proxy_url=None, api_key=None):
20
66
  """
21
- Fetch credentials from the GitArsenal API.
67
+ Fetch Modal tokens from the proxy server.
22
68
 
23
69
  Args:
24
- api_url: Base URL of the API (default: gitarsenal.dev)
70
+ proxy_url: URL of the proxy server
25
71
  api_key: API key for authentication
26
- credential_id: Optional ID of specific credentials to fetch
27
72
 
28
73
  Returns:
29
- dict: Credentials containing modalTokenId and modalTokenSecret if successful, None otherwise
74
+ tuple: (token_id, token_secret) if successful, (None, None) otherwise
30
75
  """
31
76
  # Use environment variables if not provided
32
- if not api_url:
33
- api_url = os.environ.get("GITARSENAL_API_URL", "https://gitarsenal.dev")
34
- if api_url:
35
- print(f"šŸ“‹ Using API URL from environment")
77
+ if not proxy_url:
78
+ proxy_url = os.environ.get("MODAL_PROXY_URL")
79
+ if proxy_url:
80
+ print(f"šŸ“‹ Using proxy URL from environment")
36
81
 
37
82
  if not api_key:
38
- api_key = os.environ.get("GITARSENAL_API_KEY")
83
+ api_key = os.environ.get("MODAL_PROXY_API_KEY")
39
84
  if api_key:
40
85
  print(f"šŸ“‹ Using API key from environment (length: {len(api_key)})")
41
86
 
42
87
  # Check if we have the necessary information
88
+ if not proxy_url:
89
+ # print("āŒ No proxy URL provided or found in environment")
90
+ print("šŸ’” Set MODAL_PROXY_URL environment variable or use --proxy-url argument")
91
+ return None, None
92
+
43
93
  if not api_key:
44
94
  print("āŒ No API key provided or found in environment")
45
- print("šŸ’” Set GITARSENAL_API_KEY environment variable or use --api-key argument")
46
- return None
95
+ print("šŸ’” Set MODAL_PROXY_API_KEY environment variable or use --proxy-api-key argument")
96
+ return None, None
47
97
 
48
98
  # Ensure the URL ends with a slash
49
- if not api_url.endswith("/"):
50
- api_url += "/"
99
+ if not proxy_url.endswith("/"):
100
+ proxy_url += "/"
51
101
 
52
- # Add the endpoint for fetching credentials
53
- credentials_url = f"{api_url}api/credentials"
54
-
55
- # Add credential ID if provided
56
- if credential_id:
57
- credentials_url += f"?id={credential_id}"
102
+ # Add the endpoint for fetching tokens
103
+ token_url = f"{proxy_url}api/modal-tokens"
58
104
 
59
105
  try:
60
106
  # Make the request
61
- print(f"šŸ”„ Fetching credentials from GitArsenal API")
107
+ print(f"šŸ”„ Fetching tokens from proxy server")
62
108
  response = requests.get(
63
- credentials_url,
109
+ token_url,
64
110
  headers={"X-API-Key": api_key}
65
111
  )
66
112
 
67
113
  # Check if the request was successful
68
114
  if response.status_code == 200:
69
115
  data = response.json()
70
- token_id = data.get("modalTokenId")
71
- token_secret = data.get("modalTokenSecret")
72
- openai_api_key = data.get("openaiApiKey")
116
+ token_id = data.get("token_id")
117
+ token_secret = data.get("token_secret")
73
118
 
74
119
  if token_id and token_secret:
75
- print("āœ… Successfully fetched credentials from GitArsenal API")
76
- return {
77
- "token_id": token_id,
78
- "token_secret": token_secret,
79
- "openai_api_key": openai_api_key
80
- }
120
+ print("āœ… Successfully fetched tokens from proxy server")
121
+ return token_id, token_secret
81
122
  else:
82
- print("āŒ Modal tokens not found in response")
83
- return None
123
+ print("āŒ Tokens not found in response")
124
+ return None, None
84
125
  else:
85
- print(f"āŒ Failed to fetch credentials: {response.status_code} - {response.text}")
86
- return None
126
+ print(f"āŒ Failed to fetch tokens: {response.status_code} - {response.text}")
127
+ return None, None
87
128
  except Exception as e:
88
- print(f"āŒ Error fetching credentials: {e}")
89
- return None
129
+ print(f"āŒ Error fetching tokens: {e}")
130
+ return None, None
90
131
 
91
132
  def get_tokens():
92
133
  """
93
- Get Modal tokens, trying to fetch from the GitArsenal API first.
134
+ Get Modal tokens, trying to fetch from the proxy server first.
94
135
  Also sets the tokens in environment variables.
95
136
 
96
137
  Returns:
97
138
  tuple: (token_id, token_secret)
98
139
  """
99
- # Try to fetch from the API
100
- credentials = fetch_credentials_from_api()
101
-
102
- # If we couldn't fetch from the API, use the default tokens
103
- if not credentials:
104
- print("āš ļø Using default tokens")
105
- token_id = DEFAULT_TOKEN_ID
106
- token_secret = DEFAULT_TOKEN_SECRET
107
- openai_api_key = None
108
- else:
109
- token_id = credentials["token_id"]
110
- token_secret = credentials["token_secret"]
111
- openai_api_key = credentials.get("openai_api_key")
112
-
113
- # Set OpenAI API key if available
114
- if openai_api_key:
115
- os.environ["OPENAI_API_KEY"] = openai_api_key
116
- print(f"āœ… Set OPENAI_API_KEY environment variable")
140
+ # Try to fetch from the proxy server
141
+ token_id, token_secret = fetch_tokens_from_proxy()
142
+
143
+ # If we couldn't fetch from the server, try to get default tokens from gitarsenal.dev
144
+ if not token_id or not token_secret:
145
+ print("āš ļø Proxy server failed, trying to fetch default tokens from gitarsenal.dev")
146
+ token_id, token_secret = fetch_default_tokens_from_gitarsenal()
147
+
148
+ # If we still don't have tokens, we can't proceed
149
+ if not token_id or not token_secret:
150
+ print("āŒ Failed to fetch tokens from both proxy server and gitarsenal.dev")
151
+ print("šŸ’” Please check your network connection and API endpoints")
152
+ return None, None
117
153
 
118
154
  # Set the tokens in environment variables
119
155
  os.environ["MODAL_TOKEN_ID"] = token_id
@@ -126,23 +162,33 @@ if __name__ == "__main__":
126
162
  # Parse command-line arguments if run directly
127
163
  import argparse
128
164
 
129
- parser = argparse.ArgumentParser(description='Fetch credentials from the GitArsenal API')
130
- parser.add_argument('--api-url', help='Base URL of the GitArsenal API')
131
- parser.add_argument('--api-key', help='API key for authentication')
132
- parser.add_argument('--credential-id', help='ID of specific credentials to fetch')
165
+ parser = argparse.ArgumentParser(description='Fetch Modal tokens from the proxy server')
166
+ parser.add_argument('--proxy-url', help='URL of the proxy server')
167
+ parser.add_argument('--proxy-api-key', help='API key for the proxy server')
133
168
  args = parser.parse_args()
134
169
 
135
- # Set API URL and API key in environment variables if provided
136
- if args.api_url:
137
- os.environ["GITARSENAL_API_URL"] = args.api_url
138
- print(f"āœ… Set GITARSENAL_API_URL from command line")
170
+ # Set proxy URL and API key in environment variables if provided
171
+ if args.proxy_url:
172
+ os.environ["MODAL_PROXY_URL"] = args.proxy_url
173
+ print(f"āœ… Set MODAL_PROXY_URL from command line: {args.proxy_url}")
139
174
 
140
- if args.api_key:
141
- os.environ["GITARSENAL_API_KEY"] = args.api_key
142
- print(f"āœ… Set GITARSENAL_API_KEY from command line")
175
+ if args.proxy_api_key:
176
+ os.environ["MODAL_PROXY_API_KEY"] = args.proxy_api_key
177
+ print(f"āœ… Set MODAL_PROXY_API_KEY from command line")
143
178
 
144
179
  # Get tokens
145
180
  token_id, token_secret = get_tokens()
181
+ print(f"Token ID: {token_id}")
182
+ print(f"Token Secret: {token_secret}")
183
+
184
+ # Check if tokens are set in environment variables
185
+ print(f"\nšŸ” DEBUG: Checking environment variables")
186
+ print(f"šŸ” MODAL_TOKEN_ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
187
+ print(f"šŸ” MODAL_TOKEN_SECRET exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
188
+ if os.environ.get('MODAL_TOKEN_ID'):
189
+ print(f"šŸ” MODAL_TOKEN_ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
190
+ if os.environ.get('MODAL_TOKEN_SECRET'):
191
+ print(f"šŸ” MODAL_TOKEN_SECRET length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
146
192
 
147
193
  # Write the tokens to a file for use by other scripts
148
194
  tokens_file = Path(__file__).parent / "modal_tokens.json"
@@ -185,4 +231,4 @@ if __name__ == "__main__":
185
231
  except Exception as e:
186
232
  print(f"āŒ Error using Modal CLI: {e}")
187
233
 
188
- print(f"\nāœ… All token setup completed successfully")
234
+ print(f"\nāœ… All token setup completed successfully")
@@ -43,11 +43,16 @@ try:
43
43
  # First, try to import the fetch_modal_tokens module
44
44
  from fetch_modal_tokens import get_tokens
45
45
  TOKEN_ID, TOKEN_SECRET = get_tokens()
46
+
47
+ # Check if we got valid tokens
48
+ if TOKEN_ID is None or TOKEN_SECRET is None:
49
+ raise ValueError("Could not get valid tokens")
50
+
46
51
  print(f"āœ… Using tokens from proxy server or defaults")
47
- except ImportError:
48
- # If the module is not available, use hardcoded tokens
49
- # TOKEN_ID = "ak-sLhYqCjkvixiYcb9LAuCHp"
50
- # TOKEN_SECRET = "as-fPzD0Zm0dl6IFAEkhaH9pq" # Real token secret from fr8mafia profile
52
+ except (ImportError, ValueError) as e:
53
+ # If the module is not available or tokens are invalid, use hardcoded tokens
54
+ TOKEN_ID = "ak-sLhYqCjkvixiYcb9LAuCHp"
55
+ TOKEN_SECRET = "as-fPzD0Zm0dl6IFAEkhaH9pq" # Real token secret from fr8mafia profile
51
56
  print(f"āš ļø Using default tokens")
52
57
 
53
58
  print("šŸ”§ Fixing Modal token (basic implementation)...")
@@ -28,6 +28,8 @@ try:
28
28
  except ImportError:
29
29
  # If the module is not available, use hardcoded tokens
30
30
  # print(f"āš ļø Using default tokens")
31
+ TOKEN_ID = "ak-sLhYqCjkvixiYcb9LAuCHp"
32
+ TOKEN_SECRET = "as-fPzD0Zm0dl6IFAEkhaH9pq"
31
33
 
32
34
  # print("šŸ”§ Advanced Modal Token Fixer")
33
35
 
@@ -90,10 +92,13 @@ try:
90
92
  )
91
93
 
92
94
  if result.returncode == 0:
95
+ pass
93
96
  # print(f"āœ… Successfully set token via Modal CLI")
94
97
  else:
98
+ pass
95
99
  # print(f"āŒ Failed to set token via Modal CLI: {result.stderr}")
96
100
  except Exception as e:
101
+ pass
97
102
  # print(f"āŒ Error using Modal CLI: {e}")
98
103
 
99
104
  # Approach 4: Use Modal Python API to set token
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test importing and using get_tokens from fetch_modal_tokens
4
+ """
5
+
6
+ import os
7
+ import sys
8
+
9
+ def main():
10
+ """Main function to test importing and using get_tokens"""
11
+ print("šŸ” Testing import of get_tokens from fetch_modal_tokens...")
12
+
13
+ # Set a test API key for the script to use
14
+ os.environ["GITARSENAL_API_KEY"] = "test_key"
15
+
16
+ try:
17
+ # Import the function
18
+ from fetch_modal_tokens import get_tokens
19
+ print("āœ… Successfully imported get_tokens from fetch_modal_tokens")
20
+
21
+ # Call the function
22
+ print("šŸ”„ Calling get_tokens()...")
23
+ token_id, token_secret = get_tokens()
24
+
25
+ # Check the results
26
+ print("āœ… Successfully called get_tokens()")
27
+ print(f" token_id: {token_id[:5]}...{token_id[-5:]}")
28
+ print(f" token_secret: {token_secret[:5]}...{token_secret[-5:]}")
29
+
30
+ # Check if environment variables were set
31
+ if os.environ.get("MODAL_TOKEN_ID") == token_id:
32
+ print("āœ… MODAL_TOKEN_ID environment variable set correctly")
33
+ else:
34
+ print("āŒ MODAL_TOKEN_ID environment variable not set correctly")
35
+
36
+ if os.environ.get("MODAL_TOKEN_SECRET") == token_secret:
37
+ print("āœ… MODAL_TOKEN_SECRET environment variable set correctly")
38
+ else:
39
+ print("āŒ MODAL_TOKEN_SECRET environment variable not set correctly")
40
+
41
+ # Check if OPENAI_API_KEY was set
42
+ if os.environ.get("OPENAI_API_KEY"):
43
+ print("āœ… OPENAI_API_KEY environment variable set")
44
+ print(f" length: {len(os.environ.get('OPENAI_API_KEY'))}")
45
+ else:
46
+ print("āŒ OPENAI_API_KEY environment variable not set")
47
+
48
+ return True
49
+ except Exception as e:
50
+ print(f"āŒ Error: {e}")
51
+ return False
52
+
53
+ if __name__ == "__main__":
54
+ success = main()
55
+ sys.exit(0 if success else 1)
@@ -39,6 +39,11 @@ try:
39
39
  # print("šŸ”„ Fetching tokens from proxy server...")
40
40
  from fetch_modal_tokens import get_tokens
41
41
  token_id, token_secret = get_tokens()
42
+
43
+ # Check if we got valid tokens
44
+ if token_id is None or token_secret is None:
45
+ raise ValueError("Could not get valid tokens")
46
+
42
47
  # print(f"āœ… Tokens fetched successfully")
43
48
 
44
49
  # Explicitly set the environment variables again to be sure
@@ -351,22 +356,48 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
351
356
  print("āš ļø Error output is empty. Cannot effectively debug the command.")
352
357
  print("āš ļø Skipping OpenAI debugging due to lack of error information.")
353
358
  return None
354
-
359
+
360
+ # Try to get API key from multiple sources
355
361
  if not api_key:
356
- # Try to get API key from environment
362
+ # First try environment variable
357
363
  api_key = os.environ.get("OPENAI_API_KEY")
358
364
 
359
- if not api_key:
360
- # Use the CredentialsManager to get the API key
361
- try:
362
- from credentials_manager import CredentialsManager
363
- credentials_manager = CredentialsManager()
364
- api_key = credentials_manager.get_openai_api_key()
365
- if not api_key:
366
- print("āŒ No API key provided. Skipping debugging.")
367
- return None
368
- except ImportError:
369
- # Fall back to direct input if credentials_manager module is not available
365
+ # Store the API key in a persistent file if found
366
+ if api_key:
367
+ try:
368
+ os.makedirs(os.path.expanduser("~/.gitarsenal"), exist_ok=True)
369
+ with open(os.path.expanduser("~/.gitarsenal/openai_key"), "w") as f:
370
+ f.write(api_key)
371
+ print("āœ… Saved OpenAI API key for future use")
372
+ except Exception as e:
373
+ print(f"āš ļø Could not save API key: {e}")
374
+
375
+ # Try to load from saved file if not in environment
376
+ if not api_key:
377
+ try:
378
+ key_file = os.path.expanduser("~/.gitarsenal/openai_key")
379
+ if os.path.exists(key_file):
380
+ with open(key_file, "r") as f:
381
+ api_key = f.read().strip()
382
+ if api_key:
383
+ print("āœ… Loaded OpenAI API key from saved file")
384
+ # Also set in environment for this session
385
+ os.environ["OPENAI_API_KEY"] = api_key
386
+ except Exception as e:
387
+ print(f"āš ļø Could not load saved API key: {e}")
388
+
389
+ # Then try credentials manager
390
+ if not api_key:
391
+ try:
392
+ from credentials_manager import CredentialsManager
393
+ credentials_manager = CredentialsManager()
394
+ api_key = credentials_manager.get_openai_api_key()
395
+ except ImportError:
396
+ # Fall back to direct input if credentials_manager is not available
397
+ pass
398
+
399
+ # Finally, prompt the user if still no API key
400
+ if not api_key:
370
401
  print("\n" + "="*60)
371
402
  print("šŸ”‘ OPENAI API KEY REQUIRED FOR DEBUGGING")
372
403
  print("="*60)
@@ -381,6 +412,8 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
381
412
  print("āŒ No API key provided. Skipping debugging.")
382
413
  return None
383
414
  print("āœ… API key received successfully!")
415
+ # Save the API key to environment for future use in this session
416
+ os.environ["OPENAI_API_KEY"] = api_key
384
417
  except KeyboardInterrupt:
385
418
  print("\nāŒ API key input cancelled by user.")
386
419
  return None
@@ -388,9 +421,11 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
388
421
  print(f"āŒ Error getting API key: {e}")
389
422
  return None
390
423
 
391
- # Get current directory context
424
+ # Gather additional context to help with debugging
392
425
  directory_context = ""
393
426
  system_info = ""
427
+ command_history = ""
428
+ file_context = ""
394
429
 
395
430
  if sandbox:
396
431
  try:
@@ -404,6 +439,7 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
404
439
  uname -a
405
440
  echo -e "\nPython Information:"
406
441
  python --version
442
+ pip --version
407
443
  echo -e "\nPackage Manager:"
408
444
  which apt 2>/dev/null && echo "apt available" || echo "apt not available"
409
445
  which yum 2>/dev/null && echo "yum available" || echo "yum not available"
@@ -458,6 +494,72 @@ Directory contents:
458
494
  {parent_context}
459
495
  """
460
496
  print("āœ… Directory context gathered successfully")
497
+
498
+ # Check for relevant files that might provide additional context
499
+ # For example, if error mentions a specific file, try to get its content
500
+ relevant_files = []
501
+ error_files = re.findall(r'(?:No such file or directory|cannot open|not found): ([^\s:]+)', error_output)
502
+ if error_files:
503
+ for file_path in error_files:
504
+ # Clean up the file path
505
+ file_path = file_path.strip("'\"")
506
+ if not os.path.isabs(file_path):
507
+ file_path = os.path.join(current_dir, file_path)
508
+
509
+ # Try to get the parent directory if the file doesn't exist
510
+ if '/' in file_path:
511
+ parent_file_dir = os.path.dirname(file_path)
512
+ relevant_files.append(parent_file_dir)
513
+
514
+ # Look for package.json, requirements.txt, etc.
515
+ common_config_files = ["package.json", "requirements.txt", "pyproject.toml", "setup.py",
516
+ "Pipfile", "Dockerfile", "docker-compose.yml", "Makefile"]
517
+
518
+ for config_file in common_config_files:
519
+ check_cmd = f"test -f {current_dir}/{config_file}"
520
+ check_result = sandbox.exec("bash", "-c", check_cmd)
521
+ check_result.wait()
522
+ if check_result.returncode == 0:
523
+ relevant_files.append(f"{current_dir}/{config_file}")
524
+
525
+ # Get content of relevant files
526
+ if relevant_files:
527
+ file_context = "\nRelevant file contents:\n"
528
+ for file_path in relevant_files[:2]: # Limit to 2 files to avoid too much context
529
+ try:
530
+ file_check_cmd = f"test -f {file_path}"
531
+ file_check = sandbox.exec("bash", "-c", file_check_cmd)
532
+ file_check.wait()
533
+
534
+ if file_check.returncode == 0:
535
+ # It's a file, get its content
536
+ cat_cmd = f"cat {file_path}"
537
+ cat_result = sandbox.exec("bash", "-c", cat_cmd)
538
+ file_content = ""
539
+ for line in cat_result.stdout:
540
+ file_content += _to_str(line)
541
+ cat_result.wait()
542
+
543
+ # Truncate if too long
544
+ if len(file_content) > 1000:
545
+ file_content = file_content[:1000] + "\n... (truncated)"
546
+
547
+ file_context += f"\n--- {file_path} ---\n{file_content}\n"
548
+ else:
549
+ # It's a directory, list its contents
550
+ ls_cmd = f"ls -la {file_path}"
551
+ ls_dir_result = sandbox.exec("bash", "-c", ls_cmd)
552
+ dir_content = ""
553
+ for line in ls_dir_result.stdout:
554
+ dir_content += _to_str(line)
555
+ ls_dir_result.wait()
556
+
557
+ file_context += f"\n--- Directory: {file_path} ---\n{dir_content}\n"
558
+ except Exception as e:
559
+ print(f"āš ļø Error getting content of {file_path}: {e}")
560
+
561
+ print(f"āœ… Additional file context gathered from {len(relevant_files)} relevant files")
562
+
461
563
  except Exception as e:
462
564
  print(f"āš ļø Error getting directory context: {e}")
463
565
  directory_context = f"\nCurrent directory: {current_dir}\n"
@@ -489,6 +591,8 @@ But it failed with this error:
489
591
  ```
490
592
  {system_info}
491
593
  {directory_context}
594
+ {file_context}
595
+
492
596
  Please analyze the error and provide ONLY a single terminal command that would fix the issue.
493
597
  Consider the current directory, system information, and directory contents carefully before suggesting a solution.
494
598
 
@@ -502,29 +606,127 @@ Do not provide any explanations, just the exact command to run.
502
606
  """
503
607
 
504
608
  # Prepare the API request payload
505
- payload = {
506
- "model": "gpt-4.1",
507
- "messages": [
508
- {"role": "system", "content": "You are a debugging assistant. Provide only the terminal command to fix the issue, analyze the issue first understand why its happening and then provide the command to fix the issue. If you see missing pytest errors, suggest 'pip install pytest'. For wandb login issues, suggest 'wandb login YOUR_API_KEY' and the system will handle prompting for the actual key."},
509
- {"role": "user", "content": prompt}
510
- ],
511
- "temperature": 0.2,
512
- "max_tokens": 300
513
- }
609
+ # Try to use GPT-4 first, but fall back to other models if needed
610
+ models_to_try = [
611
+ "gpt-4o-mini", # First choice: GPT-4o (most widely available)
612
+ ]
514
613
 
515
- try:
516
- print("šŸ¤– Calling OpenAI to debug the failed command...")
517
- response = requests.post(
518
- "https://api.openai.com/v1/chat/completions",
519
- headers=headers,
520
- json=payload,
521
- timeout=30
522
- )
614
+ # Check if we have a preferred model in environment
615
+ preferred_model = os.environ.get("OPENAI_MODEL")
616
+ if preferred_model:
617
+ # Insert the preferred model at the beginning of the list
618
+ models_to_try.insert(0, preferred_model)
619
+ print(f"āœ… Using preferred model from environment: {preferred_model}")
523
620
 
524
- if response.status_code == 200:
525
- result = response.json()
621
+ # Remove duplicates while preserving order
622
+ models_to_try = list(dict.fromkeys(models_to_try))
623
+
624
+ # Function to make the API call with a specific model
625
+ def try_api_call(model_name, retries=2, backoff_factor=1.5):
626
+ payload = {
627
+ "model": model_name,
628
+ "messages": [
629
+ {"role": "system", "content": "You are a debugging assistant. Provide only the terminal command to fix the issue, analyze the issue first understand why its happening and then provide the command to fix the issue. If you see missing pytest errors, suggest 'pip install pytest'. For wandb login issues, suggest 'wandb login YOUR_API_KEY' and the system will handle prompting for the actual key."},
630
+ {"role": "user", "content": prompt}
631
+ ],
632
+ "temperature": 0.2,
633
+ "max_tokens": 300
634
+ }
635
+
636
+ # Add specific handling for common errors
637
+ last_error = None
638
+ for attempt in range(retries + 1):
639
+ try:
640
+ if attempt > 0:
641
+ # Exponential backoff
642
+ wait_time = backoff_factor * (2 ** (attempt - 1))
643
+ print(f"ā±ļø Retrying in {wait_time:.1f} seconds... (attempt {attempt+1}/{retries+1})")
644
+ time.sleep(wait_time)
645
+
646
+ print(f"šŸ¤– Calling OpenAI with {model_name} model to debug the failed command...")
647
+ response = requests.post(
648
+ "https://api.openai.com/v1/chat/completions",
649
+ headers=headers,
650
+ json=payload,
651
+ timeout=45 # Increased timeout for reliability
652
+ )
653
+
654
+ # Handle specific status codes
655
+ if response.status_code == 200:
656
+ return response.json(), None
657
+ elif response.status_code == 401:
658
+ error_msg = "Authentication error: Invalid API key"
659
+ print(f"āŒ {error_msg}")
660
+ # Don't retry auth errors
661
+ return None, error_msg
662
+ elif response.status_code == 429:
663
+ error_msg = "Rate limit exceeded or quota reached"
664
+ print(f"āš ļø {error_msg}")
665
+ # Always retry rate limit errors with increasing backoff
666
+ last_error = error_msg
667
+ continue
668
+ elif response.status_code == 500:
669
+ error_msg = "OpenAI server error"
670
+ print(f"āš ļø {error_msg}")
671
+ # Retry server errors
672
+ last_error = error_msg
673
+ continue
674
+ else:
675
+ error_msg = f"Status code: {response.status_code}, Response: {response.text}"
676
+ print(f"āš ļø OpenAI API error: {error_msg}")
677
+ last_error = error_msg
678
+ # Only retry if we have attempts left
679
+ if attempt < retries:
680
+ continue
681
+ return None, error_msg
682
+ except requests.exceptions.Timeout:
683
+ error_msg = "Request timed out"
684
+ print(f"āš ļø {error_msg}")
685
+ last_error = error_msg
686
+ # Always retry timeouts
687
+ continue
688
+ except requests.exceptions.ConnectionError:
689
+ error_msg = "Connection error"
690
+ print(f"āš ļø {error_msg}")
691
+ last_error = error_msg
692
+ # Always retry connection errors
693
+ continue
694
+ except Exception as e:
695
+ error_msg = str(e)
696
+ print(f"āš ļø Unexpected error: {error_msg}")
697
+ last_error = error_msg
698
+ # Only retry if we have attempts left
699
+ if attempt < retries:
700
+ continue
701
+ return None, error_msg
702
+
703
+ # If we get here, all retries failed
704
+ return None, last_error
705
+
706
+ # Try each model in sequence until one works
707
+ result = None
708
+ last_error = None
709
+
710
+ for model in models_to_try:
711
+ result, error = try_api_call(model)
712
+ if result:
713
+ print(f"āœ… Successfully got response from {model}")
714
+ break
715
+ else:
716
+ print(f"āš ļø Failed to get response from {model}: {error}")
717
+ last_error = error
718
+
719
+ if not result:
720
+ print(f"āŒ All model attempts failed. Last error: {last_error}")
721
+ return None
722
+
723
+ # Process the response
724
+ try:
526
725
  fix_command = result["choices"][0]["message"]["content"].strip()
527
726
 
727
+ # Save the original response for debugging
728
+ original_response = fix_command
729
+
528
730
  # Extract just the command if it's wrapped in backticks or explanation
529
731
  if "```" in fix_command:
530
732
  # Extract content between backticks
@@ -532,21 +734,73 @@ Do not provide any explanations, just the exact command to run.
532
734
  code_blocks = re.findall(r'```(?:bash|sh)?\s*(.*?)\s*```', fix_command, re.DOTALL)
533
735
  if code_blocks:
534
736
  fix_command = code_blocks[0].strip()
737
+ print(f"āœ… Extracted command from code block: {fix_command}")
535
738
 
536
739
  # If the response still has explanatory text, try to extract just the command
537
740
  if len(fix_command.split('\n')) > 1:
538
- # Take the shortest non-empty line as it's likely the command
539
- lines = [line.strip() for line in fix_command.split('\n') if line.strip()]
540
- if lines:
541
- fix_command = min(lines, key=len)
741
+ # First try to find lines that look like commands (start with common command prefixes)
742
+ command_prefixes = ['sudo', 'apt', 'pip', 'npm', 'yarn', 'git', 'cd', 'mv', 'cp', 'rm', 'mkdir', 'touch',
743
+ 'chmod', 'chown', 'echo', 'cat', 'python', 'python3', 'node', 'export',
744
+ 'curl', 'wget', 'docker', 'make', 'gcc', 'g++', 'javac', 'java',
745
+ 'conda', 'uv', 'poetry', 'nvm', 'rbenv', 'pyenv', 'rustup']
746
+
747
+ # Check for lines that start with common command prefixes
748
+ command_lines = [line.strip() for line in fix_command.split('\n')
749
+ if any(line.strip().startswith(prefix) for prefix in command_prefixes)]
750
+
751
+ if command_lines:
752
+ # Use the first command line found
753
+ fix_command = command_lines[0]
754
+ print(f"āœ… Identified command by prefix: {fix_command}")
755
+ else:
756
+ # Try to find lines that look like commands (contain common shell patterns)
757
+ shell_patterns = [' | ', ' > ', ' >> ', ' && ', ' || ', ' ; ', '$(', '`', ' -y ', ' --yes ']
758
+ command_lines = [line.strip() for line in fix_command.split('\n')
759
+ if any(pattern in line for pattern in shell_patterns)]
760
+
761
+ if command_lines:
762
+ # Use the first command line found
763
+ fix_command = command_lines[0]
764
+ print(f"āœ… Identified command by shell pattern: {fix_command}")
765
+ else:
766
+ # Fall back to the shortest non-empty line as it's likely the command
767
+ lines = [line.strip() for line in fix_command.split('\n') if line.strip()]
768
+ if lines:
769
+ # Exclude very short lines that are likely not commands
770
+ valid_lines = [line for line in lines if len(line) > 5]
771
+ if valid_lines:
772
+ fix_command = min(valid_lines, key=len)
773
+ else:
774
+ fix_command = min(lines, key=len)
775
+ print(f"āœ… Selected shortest line as command: {fix_command}")
776
+
777
+ # Clean up the command - remove any trailing periods or quotes
778
+ fix_command = fix_command.rstrip('.;"\'')
779
+
780
+ # Remove common prefixes that LLMs sometimes add
781
+ prefixes_to_remove = [
782
+ "Run: ", "Execute: ", "Try: ", "Command: ", "Fix: ", "Solution: ",
783
+ "You should run: ", "You can run: ", "You need to run: "
784
+ ]
785
+ for prefix in prefixes_to_remove:
786
+ if fix_command.startswith(prefix):
787
+ fix_command = fix_command[len(prefix):].strip()
788
+ print(f"āœ… Removed prefix: {prefix}")
789
+ break
790
+
791
+ # If the command is still multi-line or very long, it might not be a valid command
792
+ if len(fix_command.split('\n')) > 1 or len(fix_command) > 500:
793
+ print("āš ļø Extracted command appears invalid (multi-line or too long)")
794
+ print("šŸ” Original response from LLM:")
795
+ print("-" * 60)
796
+ print(original_response)
797
+ print("-" * 60)
798
+ print("āš ļø Using best guess for command")
542
799
 
543
800
  print(f"šŸ”§ Suggested fix: {fix_command}")
544
801
  return fix_command
545
- else:
546
- print(f"āŒ OpenAI API error: {response.status_code} - {response.text}")
547
- return None
548
802
  except Exception as e:
549
- print(f"āŒ Error calling OpenAI API: {e}")
803
+ print(f"āŒ Error processing OpenAI response: {e}")
550
804
  return None
551
805
 
552
806
  def prompt_for_hf_token():
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Verify that environment variables are set correctly after running fetch_modal_tokens.py
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import sys
9
+
10
+ def check_env_var(var_name):
11
+ """Check if an environment variable is set and print its status"""
12
+ value = os.environ.get(var_name)
13
+ if value:
14
+ print(f"āœ… {var_name} is set (length: {len(value)})")
15
+ return True
16
+ else:
17
+ print(f"āŒ {var_name} is not set")
18
+ return False
19
+
20
+ def main():
21
+ """Main function to verify environment variables"""
22
+ print("šŸ” Checking environment variables before running fetch_modal_tokens.py...")
23
+ modal_id_before = check_env_var("MODAL_TOKEN_ID")
24
+ modal_secret_before = check_env_var("MODAL_TOKEN_SECRET")
25
+ openai_key_before = check_env_var("OPENAI_API_KEY")
26
+
27
+ print("\nšŸ”„ Running fetch_modal_tokens.py...")
28
+ # Set a test API key for the script to use
29
+ os.environ["GITARSENAL_API_KEY"] = "test_key"
30
+
31
+ try:
32
+ # Import the module to run it
33
+ from fetch_modal_tokens import get_tokens
34
+ token_id, token_secret = get_tokens()
35
+ print(f"\nāœ… get_tokens() returned values successfully")
36
+ print(f" token_id length: {len(token_id) if token_id else 0}")
37
+ print(f" token_secret length: {len(token_secret) if token_secret else 0}")
38
+ except Exception as e:
39
+ print(f"\nāŒ Error running get_tokens(): {e}")
40
+ sys.exit(1)
41
+
42
+ print("\nšŸ” Checking environment variables after running fetch_modal_tokens.py...")
43
+ modal_id_after = check_env_var("MODAL_TOKEN_ID")
44
+ modal_secret_after = check_env_var("MODAL_TOKEN_SECRET")
45
+ openai_key_after = check_env_var("OPENAI_API_KEY")
46
+
47
+ # Verify that the variables were set
48
+ if modal_id_after and modal_secret_after:
49
+ print("\nāœ… MODAL_TOKEN_ID and MODAL_TOKEN_SECRET were successfully set")
50
+ else:
51
+ print("\nāŒ Failed to set all required environment variables")
52
+
53
+ if openai_key_after:
54
+ print("āœ… OPENAI_API_KEY was also set")
55
+
56
+ # Check if the values match what was returned by get_tokens()
57
+ if modal_id_after and os.environ.get("MODAL_TOKEN_ID") == token_id:
58
+ print("āœ… MODAL_TOKEN_ID matches the value returned by get_tokens()")
59
+
60
+ if modal_secret_after and os.environ.get("MODAL_TOKEN_SECRET") == token_secret:
61
+ print("āœ… MODAL_TOKEN_SECRET matches the value returned by get_tokens()")
62
+
63
+ if __name__ == "__main__":
64
+ main()