gitarsenal-cli 1.8.2 → 1.8.3
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 +1 -1
- package/python/test_modalSandboxScript.py +801 -30
- package/test_modalSandboxScript.py +801 -30
@@ -38,7 +38,7 @@ if args.proxy_api_key:
|
|
38
38
|
class PersistentShell:
|
39
39
|
"""A persistent bash shell using subprocess.Popen for executing commands with state persistence."""
|
40
40
|
|
41
|
-
def __init__(self, working_dir="/root", timeout=
|
41
|
+
def __init__(self, working_dir="/root", timeout=60):
|
42
42
|
self.working_dir = working_dir
|
43
43
|
self.timeout = timeout
|
44
44
|
self.process = None
|
@@ -51,6 +51,9 @@ class PersistentShell:
|
|
51
51
|
self.command_counter = 0
|
52
52
|
self.is_running = False
|
53
53
|
self.virtual_env_path = None # Track activated virtual environment
|
54
|
+
self.suggested_alternative = None # Store suggested alternative commands
|
55
|
+
self.should_remove_command = False # Flag to indicate if a command should be removed
|
56
|
+
self.removal_reason = None # Reason for removing a command
|
54
57
|
|
55
58
|
def start(self):
|
56
59
|
"""Start the persistent bash shell."""
|
@@ -288,7 +291,25 @@ class PersistentShell:
|
|
288
291
|
for line in current_stderr:
|
289
292
|
if line.strip(): # Skip empty lines
|
290
293
|
command_stderr.append(line)
|
291
|
-
|
294
|
+
|
295
|
+
# Check if command is waiting for user input
|
296
|
+
if not found_marker and time.time() - start_time > 5: # Wait at least 5 seconds before checking
|
297
|
+
if self._is_waiting_for_input(command_stdout, command_stderr):
|
298
|
+
print("⚠️ Command appears to be waiting for user input")
|
299
|
+
# Try to handle the input requirement
|
300
|
+
input_handled = self._handle_input_requirement(command, command_stdout, command_stderr)
|
301
|
+
|
302
|
+
if input_handled is True and self.should_remove_command:
|
303
|
+
# If LLM suggested to remove the command
|
304
|
+
self._send_command_raw("\x03") # Send Ctrl+C
|
305
|
+
time.sleep(0.5)
|
306
|
+
return False, '\n'.join(command_stdout), f"Command removed - {self.removal_reason}"
|
307
|
+
elif not input_handled:
|
308
|
+
# If we couldn't handle the input, abort the command
|
309
|
+
self._send_command_raw("\x03") # Send Ctrl+C
|
310
|
+
time.sleep(0.5)
|
311
|
+
return False, '\n'.join(command_stdout), "Command aborted - requires user input"
|
312
|
+
|
292
313
|
time.sleep(0.1)
|
293
314
|
|
294
315
|
if not found_marker:
|
@@ -306,13 +327,12 @@ class PersistentShell:
|
|
306
327
|
|
307
328
|
if success:
|
308
329
|
if stdout_text:
|
309
|
-
print("")
|
310
330
|
print(f"✅ Output: {stdout_text}")
|
311
331
|
# Track virtual environment activation
|
312
332
|
if command.strip().startswith("source ") and "/bin/activate" in command:
|
313
333
|
venv_path = command.replace("source ", "").replace("/bin/activate", "").strip()
|
314
334
|
self.virtual_env_path = venv_path
|
315
|
-
|
335
|
+
print(f"✅ Virtual environment activated: {venv_path}")
|
316
336
|
else:
|
317
337
|
print(f"❌ Command failed with exit code: {exit_code}")
|
318
338
|
if stderr_text:
|
@@ -323,6 +343,215 @@ class PersistentShell:
|
|
323
343
|
|
324
344
|
return success, stdout_text, stderr_text
|
325
345
|
|
346
|
+
def _is_waiting_for_input(self, stdout_lines, stderr_lines):
|
347
|
+
"""Detect if a command is waiting for user input."""
|
348
|
+
# Common patterns that indicate waiting for user input
|
349
|
+
input_patterns = [
|
350
|
+
r'(?i)(y/n|yes/no)\??\s*$', # Yes/No prompts
|
351
|
+
r'(?i)password:?\s*$', # Password prompts
|
352
|
+
r'(?i)continue\??\s*$', # Continue prompts
|
353
|
+
r'(?i)proceed\??\s*$', # Proceed prompts
|
354
|
+
r'\[\s*[Yy]/[Nn]\s*\]\s*$', # [Y/n] style prompts
|
355
|
+
r'(?i)username:?\s*$', # Username prompts
|
356
|
+
r'(?i)token:?\s*$', # Token prompts
|
357
|
+
r'(?i)api key:?\s*$', # API key prompts
|
358
|
+
r'(?i)press enter to continue', # Press enter prompts
|
359
|
+
r'(?i)select an option:?\s*$', # Selection prompts
|
360
|
+
r'(?i)choose an option:?\s*$', # Choice prompts
|
361
|
+
]
|
362
|
+
|
363
|
+
# Check the last few lines of stdout and stderr for input patterns
|
364
|
+
last_lines = []
|
365
|
+
if stdout_lines:
|
366
|
+
last_lines.extend(stdout_lines[-3:]) # Check last 3 lines of stdout
|
367
|
+
if stderr_lines:
|
368
|
+
last_lines.extend(stderr_lines[-3:]) # Check last 3 lines of stderr
|
369
|
+
|
370
|
+
for line in last_lines:
|
371
|
+
for pattern in input_patterns:
|
372
|
+
if re.search(pattern, line):
|
373
|
+
print(f"🔍 Detected input prompt: {line}")
|
374
|
+
return True
|
375
|
+
|
376
|
+
# Check if there's no output for a while but the command is still running
|
377
|
+
if len(stdout_lines) == 0 and len(stderr_lines) == 0:
|
378
|
+
# This might be a command waiting for input without a prompt
|
379
|
+
# We'll be cautious and only return True if we're sure
|
380
|
+
return False
|
381
|
+
|
382
|
+
return False
|
383
|
+
|
384
|
+
def _handle_input_requirement(self, command, stdout_lines, stderr_lines):
|
385
|
+
"""Attempt to handle commands that require input."""
|
386
|
+
# Extract the last few lines to analyze what kind of input is needed
|
387
|
+
last_lines = []
|
388
|
+
if stdout_lines:
|
389
|
+
last_lines.extend(stdout_lines[-3:])
|
390
|
+
if stderr_lines:
|
391
|
+
last_lines.extend(stderr_lines[-3:])
|
392
|
+
|
393
|
+
last_line = last_lines[-1] if last_lines else ""
|
394
|
+
|
395
|
+
# Try to determine what kind of input is needed
|
396
|
+
if re.search(r'(?i)(y/n|yes/no|\[y/n\])', last_line):
|
397
|
+
# For yes/no prompts, usually 'yes' is safer
|
398
|
+
print("🔧 Auto-responding with 'y' to yes/no prompt")
|
399
|
+
self._send_command_raw("y")
|
400
|
+
return True
|
401
|
+
|
402
|
+
elif re.search(r'(?i)password', last_line):
|
403
|
+
# For password prompts, check if we have stored credentials
|
404
|
+
stored_creds = get_stored_credentials()
|
405
|
+
if stored_creds and 'ssh_password' in stored_creds:
|
406
|
+
print("🔧 Auto-responding with stored SSH password")
|
407
|
+
self._send_command_raw(stored_creds['ssh_password'])
|
408
|
+
return True
|
409
|
+
else:
|
410
|
+
print("⚠️ Password prompt detected but no stored password available")
|
411
|
+
return False
|
412
|
+
|
413
|
+
elif re.search(r'(?i)token|api.key', last_line):
|
414
|
+
# For token/API key prompts
|
415
|
+
stored_creds = get_stored_credentials()
|
416
|
+
if stored_creds:
|
417
|
+
if 'openai_api_key' in stored_creds and re.search(r'(?i)openai|api.key', last_line):
|
418
|
+
print("🔧 Auto-responding with stored OpenAI API key")
|
419
|
+
self._send_command_raw(stored_creds['openai_api_key'])
|
420
|
+
return True
|
421
|
+
elif 'hf_token' in stored_creds and re.search(r'(?i)hugg|hf|token', last_line):
|
422
|
+
print("🔧 Auto-responding with stored Hugging Face token")
|
423
|
+
self._send_command_raw(stored_creds['hf_token'])
|
424
|
+
return True
|
425
|
+
|
426
|
+
print("⚠️ Token/API key prompt detected but no matching stored credentials")
|
427
|
+
return False
|
428
|
+
|
429
|
+
elif re.search(r'(?i)press enter|continue|proceed', last_line):
|
430
|
+
# For "press enter to continue" prompts
|
431
|
+
print("🔧 Auto-responding with Enter to continue")
|
432
|
+
self._send_command_raw("") # Empty string sends just Enter
|
433
|
+
return True
|
434
|
+
|
435
|
+
# If we can't determine the type of input needed
|
436
|
+
print("⚠️ Couldn't determine the type of input needed")
|
437
|
+
|
438
|
+
# Try to use LLM to suggest an alternative command
|
439
|
+
try:
|
440
|
+
# Get current working directory for context
|
441
|
+
cwd = self.get_cwd()
|
442
|
+
|
443
|
+
# Reset command removal flags
|
444
|
+
self.should_remove_command = False
|
445
|
+
self.removal_reason = None
|
446
|
+
|
447
|
+
# Call LLM to suggest an alternative
|
448
|
+
alternative = self._suggest_alternative_command(command, stdout_lines, stderr_lines, cwd)
|
449
|
+
|
450
|
+
# Check if LLM suggested to remove the command
|
451
|
+
if self.should_remove_command:
|
452
|
+
print(f"🚫 Command will be removed: {self.removal_reason}")
|
453
|
+
return True # Return True to indicate the command has been handled (by removing it)
|
454
|
+
|
455
|
+
if alternative:
|
456
|
+
print(f"🔧 LLM suggested alternative command: {alternative}")
|
457
|
+
# We don't execute the alternative here, but return False so the calling code
|
458
|
+
# can handle it (e.g., by adding it to the command list)
|
459
|
+
|
460
|
+
# Store the suggested alternative for later use
|
461
|
+
self.suggested_alternative = alternative
|
462
|
+
return False
|
463
|
+
except Exception as e:
|
464
|
+
print(f"⚠️ Error getting LLM suggestion: {e}")
|
465
|
+
|
466
|
+
return False
|
467
|
+
|
468
|
+
def _suggest_alternative_command(self, command, stdout_lines, stderr_lines, current_dir):
|
469
|
+
"""Use LLM to suggest an alternative command that doesn't require user input."""
|
470
|
+
try:
|
471
|
+
# Get API key
|
472
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
473
|
+
if not api_key:
|
474
|
+
# Try to load from saved file
|
475
|
+
key_file = os.path.expanduser("~/.gitarsenal/openai_key")
|
476
|
+
if os.path.exists(key_file):
|
477
|
+
with open(key_file, "r") as f:
|
478
|
+
api_key = f.read().strip()
|
479
|
+
|
480
|
+
if not api_key:
|
481
|
+
print("⚠️ No OpenAI API key available for suggesting alternative command")
|
482
|
+
return None
|
483
|
+
|
484
|
+
# Prepare the prompt
|
485
|
+
stdout_text = '\n'.join(stdout_lines[-10:]) if stdout_lines else ""
|
486
|
+
stderr_text = '\n'.join(stderr_lines[-10:]) if stderr_lines else ""
|
487
|
+
|
488
|
+
prompt = f"""
|
489
|
+
The command '{command}' appears to be waiting for user input.
|
490
|
+
|
491
|
+
Current directory: {current_dir}
|
492
|
+
|
493
|
+
Last stdout output:
|
494
|
+
{stdout_text}
|
495
|
+
|
496
|
+
Last stderr output:
|
497
|
+
{stderr_text}
|
498
|
+
|
499
|
+
Please analyze this command and determine if it's useful to continue with it.
|
500
|
+
If it's useful, suggest an alternative command that achieves the same goal but doesn't require user input.
|
501
|
+
For example, add flags like -y, --yes, --no-input, etc., or provide the required input in the command.
|
502
|
+
|
503
|
+
If the command is not useful or cannot be executed non-interactively, respond with "REMOVE_COMMAND" and explain why.
|
504
|
+
|
505
|
+
Format your response as:
|
506
|
+
ALTERNATIVE: <alternative command>
|
507
|
+
or
|
508
|
+
REMOVE_COMMAND: <reason>
|
509
|
+
"""
|
510
|
+
|
511
|
+
# Call OpenAI API
|
512
|
+
import openai
|
513
|
+
client = openai.OpenAI(api_key=api_key)
|
514
|
+
|
515
|
+
response = client.chat.completions.create(
|
516
|
+
model="gpt-4o-mini",
|
517
|
+
messages=[
|
518
|
+
{"role": "system", "content": "You are a helpful assistant that suggests alternative commands that don't require user input."},
|
519
|
+
{"role": "user", "content": prompt}
|
520
|
+
],
|
521
|
+
max_tokens=150,
|
522
|
+
temperature=0.7
|
523
|
+
)
|
524
|
+
|
525
|
+
response_text = response.choices[0].message.content.strip()
|
526
|
+
|
527
|
+
# Check if the response suggests removing the command
|
528
|
+
if response_text.startswith("REMOVE_COMMAND:"):
|
529
|
+
reason = response_text.replace("REMOVE_COMMAND:", "").strip()
|
530
|
+
print(f"🚫 LLM suggests removing command: {reason}")
|
531
|
+
self.should_remove_command = True
|
532
|
+
self.removal_reason = reason
|
533
|
+
return None
|
534
|
+
|
535
|
+
# Extract the alternative command
|
536
|
+
if response_text.startswith("ALTERNATIVE:"):
|
537
|
+
alternative_command = response_text.replace("ALTERNATIVE:", "").strip()
|
538
|
+
else:
|
539
|
+
# Try to extract the command from a free-form response
|
540
|
+
lines = response_text.split('\n')
|
541
|
+
for line in lines:
|
542
|
+
line = line.strip()
|
543
|
+
if line and not line.startswith(('Here', 'I', 'You', 'The', 'This', 'Use', 'Try')):
|
544
|
+
alternative_command = line
|
545
|
+
break
|
546
|
+
else:
|
547
|
+
alternative_command = lines[0].strip()
|
548
|
+
|
549
|
+
return alternative_command
|
550
|
+
|
551
|
+
except Exception as e:
|
552
|
+
print(f"⚠️ Error suggesting alternative command: {e}")
|
553
|
+
return None
|
554
|
+
|
326
555
|
def _clear_lines(self):
|
327
556
|
"""Clear both output line lists."""
|
328
557
|
with self.stdout_lock:
|
@@ -678,6 +907,337 @@ class CommandListManager:
|
|
678
907
|
|
679
908
|
print(f"🔧 Added {len(added_fixes)} LLM-suggested fixes to command list")
|
680
909
|
return added_fixes
|
910
|
+
|
911
|
+
def should_skip_original_command(self, original_command, fix_command, fix_stdout, fix_stderr, api_key=None):
|
912
|
+
"""
|
913
|
+
Use LLM to determine if the original command should be skipped after a successful fix.
|
914
|
+
|
915
|
+
Args:
|
916
|
+
original_command: The original command that failed
|
917
|
+
fix_command: The fix command that succeeded
|
918
|
+
fix_stdout: The stdout from the fix command
|
919
|
+
fix_stderr: The stderr from the fix command
|
920
|
+
api_key: OpenAI API key
|
921
|
+
|
922
|
+
Returns:
|
923
|
+
tuple: (should_skip, reason)
|
924
|
+
"""
|
925
|
+
try:
|
926
|
+
# Get API key if not provided
|
927
|
+
if not api_key:
|
928
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
929
|
+
if not api_key:
|
930
|
+
# Try to load from saved file
|
931
|
+
key_file = os.path.expanduser("~/.gitarsenal/openai_key")
|
932
|
+
if os.path.exists(key_file):
|
933
|
+
with open(key_file, "r") as f:
|
934
|
+
api_key = f.read().strip()
|
935
|
+
|
936
|
+
if not api_key:
|
937
|
+
print("⚠️ No OpenAI API key available for command list analysis")
|
938
|
+
return False, "No API key available"
|
939
|
+
|
940
|
+
# Get all commands for context
|
941
|
+
all_commands = self.get_all_commands()
|
942
|
+
commands_context = "\n".join([f"{i+1}. {cmd['command']} - {cmd['status']}" for i, cmd in enumerate(all_commands)])
|
943
|
+
|
944
|
+
# Prepare the prompt
|
945
|
+
prompt = f"""
|
946
|
+
I need to determine if an original command should be skipped after a successful fix command.
|
947
|
+
|
948
|
+
Original command (failed): {original_command}
|
949
|
+
Fix command (succeeded): {fix_command}
|
950
|
+
|
951
|
+
Fix command stdout:
|
952
|
+
{fix_stdout}
|
953
|
+
|
954
|
+
Fix command stderr:
|
955
|
+
{fix_stderr}
|
956
|
+
|
957
|
+
Current command list:
|
958
|
+
{commands_context}
|
959
|
+
|
960
|
+
Based on this information, should I skip running the original command again?
|
961
|
+
Consider:
|
962
|
+
1. If the fix command already accomplished what the original command was trying to do
|
963
|
+
2. If running the original command again would be redundant or cause errors
|
964
|
+
3. If the original command is still necessary after the fix
|
965
|
+
|
966
|
+
Respond with ONLY:
|
967
|
+
SKIP: <reason>
|
968
|
+
or
|
969
|
+
RUN: <reason>
|
970
|
+
"""
|
971
|
+
|
972
|
+
# Call OpenAI API
|
973
|
+
import openai
|
974
|
+
client = openai.OpenAI(api_key=api_key)
|
975
|
+
|
976
|
+
print("🔍 Analyzing if original command should be skipped...")
|
977
|
+
|
978
|
+
response = client.chat.completions.create(
|
979
|
+
model="gpt-3.5-turbo",
|
980
|
+
messages=[
|
981
|
+
{"role": "system", "content": "You are a helpful assistant that analyzes command execution."},
|
982
|
+
{"role": "user", "content": prompt}
|
983
|
+
],
|
984
|
+
max_tokens=100,
|
985
|
+
temperature=0.3
|
986
|
+
)
|
987
|
+
|
988
|
+
response_text = response.choices[0].message.content.strip()
|
989
|
+
|
990
|
+
# Parse the response
|
991
|
+
if response_text.startswith("SKIP:"):
|
992
|
+
reason = response_text.replace("SKIP:", "").strip()
|
993
|
+
print(f"🔍 LLM suggests skipping original command: {reason}")
|
994
|
+
return True, reason
|
995
|
+
elif response_text.startswith("RUN:"):
|
996
|
+
reason = response_text.replace("RUN:", "").strip()
|
997
|
+
print(f"🔍 LLM suggests running original command: {reason}")
|
998
|
+
return False, reason
|
999
|
+
else:
|
1000
|
+
# Try to interpret a free-form response
|
1001
|
+
if "skip" in response_text.lower() and "should" in response_text.lower():
|
1002
|
+
print(f"🔍 Interpreting response as SKIP: {response_text}")
|
1003
|
+
return True, response_text
|
1004
|
+
else:
|
1005
|
+
print(f"🔍 Interpreting response as RUN: {response_text}")
|
1006
|
+
return False, response_text
|
1007
|
+
|
1008
|
+
except Exception as e:
|
1009
|
+
print(f"⚠️ Error analyzing command skip decision: {e}")
|
1010
|
+
return False, f"Error: {e}"
|
1011
|
+
|
1012
|
+
def replace_command(self, command_index, new_command, reason=""):
|
1013
|
+
"""
|
1014
|
+
Replace a command in the list with a new command.
|
1015
|
+
|
1016
|
+
Args:
|
1017
|
+
command_index: The index of the command to replace
|
1018
|
+
new_command: The new command to use
|
1019
|
+
reason: The reason for the replacement
|
1020
|
+
|
1021
|
+
Returns:
|
1022
|
+
bool: True if the command was replaced, False otherwise
|
1023
|
+
"""
|
1024
|
+
if 0 <= command_index < len(self.commands):
|
1025
|
+
old_command = self.commands[command_index]['command']
|
1026
|
+
self.commands[command_index]['command'] = new_command
|
1027
|
+
self.commands[command_index]['status'] = 'pending' # Reset status
|
1028
|
+
self.commands[command_index]['stdout'] = ''
|
1029
|
+
self.commands[command_index]['stderr'] = ''
|
1030
|
+
self.commands[command_index]['execution_time'] = None
|
1031
|
+
self.commands[command_index]['replacement_reason'] = reason
|
1032
|
+
|
1033
|
+
print(f"🔄 Replaced command {command_index + 1}: '{old_command}' with '{new_command}'")
|
1034
|
+
print(f"🔍 Reason: {reason}")
|
1035
|
+
return True
|
1036
|
+
else:
|
1037
|
+
print(f"❌ Invalid command index for replacement: {command_index}")
|
1038
|
+
return False
|
1039
|
+
|
1040
|
+
def update_command_list_with_llm(self, api_key=None):
|
1041
|
+
"""
|
1042
|
+
Use LLM to analyze and update the entire command list.
|
1043
|
+
|
1044
|
+
Args:
|
1045
|
+
api_key: OpenAI API key
|
1046
|
+
|
1047
|
+
Returns:
|
1048
|
+
bool: True if the list was updated, False otherwise
|
1049
|
+
"""
|
1050
|
+
try:
|
1051
|
+
# Get API key if not provided
|
1052
|
+
if not api_key:
|
1053
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
1054
|
+
if not api_key:
|
1055
|
+
# Try to load from saved file
|
1056
|
+
key_file = os.path.expanduser("~/.gitarsenal/openai_key")
|
1057
|
+
if os.path.exists(key_file):
|
1058
|
+
with open(key_file, "r") as f:
|
1059
|
+
api_key = f.read().strip()
|
1060
|
+
|
1061
|
+
if not api_key:
|
1062
|
+
print("⚠️ No OpenAI API key available for command list analysis")
|
1063
|
+
return False
|
1064
|
+
|
1065
|
+
# Get all commands for context
|
1066
|
+
all_commands = self.get_all_commands()
|
1067
|
+
commands_context = "\n".join([f"{i+1}. {cmd['command']} - {cmd['status']}"
|
1068
|
+
for i, cmd in enumerate(all_commands)])
|
1069
|
+
|
1070
|
+
# Get executed commands with their outputs for context
|
1071
|
+
executed_context = ""
|
1072
|
+
for cmd in self.executed_commands:
|
1073
|
+
executed_context += f"Command: {cmd['command']}\n"
|
1074
|
+
executed_context += f"Status: {cmd['status']}\n"
|
1075
|
+
if cmd['stdout']:
|
1076
|
+
executed_context += f"Stdout: {cmd['stdout'][:500]}...\n" if len(cmd['stdout']) > 500 else f"Stdout: {cmd['stdout']}\n"
|
1077
|
+
if cmd['stderr']:
|
1078
|
+
executed_context += f"Stderr: {cmd['stderr'][:500]}...\n" if len(cmd['stderr']) > 500 else f"Stderr: {cmd['stderr']}\n"
|
1079
|
+
executed_context += "\n"
|
1080
|
+
|
1081
|
+
# Prepare the prompt
|
1082
|
+
prompt = f"""
|
1083
|
+
I need you to analyze and optimize this command list. Some commands have been executed,
|
1084
|
+
and some are still pending. Based on what has already been executed, I need you to:
|
1085
|
+
|
1086
|
+
1. Identify any pending commands that are now redundant or unnecessary
|
1087
|
+
2. Identify any pending commands that should be modified based on previous command results
|
1088
|
+
3. Suggest any new commands that should be added
|
1089
|
+
|
1090
|
+
Current command list:
|
1091
|
+
{commands_context}
|
1092
|
+
|
1093
|
+
Details of executed commands:
|
1094
|
+
{executed_context}
|
1095
|
+
|
1096
|
+
For each pending command (starting from the next command to be executed), tell me if it should be:
|
1097
|
+
1. KEEP: Keep the command as is
|
1098
|
+
2. SKIP: Skip the command (mark as completed without running)
|
1099
|
+
3. MODIFY: Modify the command (provide the new command)
|
1100
|
+
4. ADD_AFTER: Add a new command after this one
|
1101
|
+
|
1102
|
+
Format your response as a JSON array of actions:
|
1103
|
+
[
|
1104
|
+
{{
|
1105
|
+
"command_index": <index>,
|
1106
|
+
"action": "KEEP|SKIP|MODIFY|ADD_AFTER",
|
1107
|
+
"new_command": "<new command if MODIFY or ADD_AFTER>",
|
1108
|
+
"reason": "<reason for this action>"
|
1109
|
+
}},
|
1110
|
+
...
|
1111
|
+
]
|
1112
|
+
|
1113
|
+
Only include commands that need changes (SKIP, MODIFY, ADD_AFTER), not KEEP actions.
|
1114
|
+
"""
|
1115
|
+
|
1116
|
+
# Call OpenAI API
|
1117
|
+
import openai
|
1118
|
+
import json
|
1119
|
+
client = openai.OpenAI(api_key=api_key)
|
1120
|
+
|
1121
|
+
print("🔍 Analyzing command list for optimizations...")
|
1122
|
+
|
1123
|
+
response = client.chat.completions.create(
|
1124
|
+
model="gpt-4o-mini", # Use a more capable model for this complex task
|
1125
|
+
messages=[
|
1126
|
+
{"role": "system", "content": "You are a helpful assistant that analyzes and optimizes command lists."},
|
1127
|
+
{"role": "user", "content": prompt}
|
1128
|
+
],
|
1129
|
+
max_tokens=1000,
|
1130
|
+
temperature=0.2
|
1131
|
+
)
|
1132
|
+
|
1133
|
+
response_text = response.choices[0].message.content.strip()
|
1134
|
+
|
1135
|
+
# Extract JSON from the response
|
1136
|
+
try:
|
1137
|
+
# Find JSON array in the response
|
1138
|
+
json_match = re.search(r'\[\s*\{.*\}\s*\]', response_text, re.DOTALL)
|
1139
|
+
if json_match:
|
1140
|
+
json_str = json_match.group(0)
|
1141
|
+
actions = json.loads(json_str)
|
1142
|
+
else:
|
1143
|
+
# Try to parse the entire response as JSON
|
1144
|
+
actions = json.loads(response_text)
|
1145
|
+
|
1146
|
+
if not isinstance(actions, list):
|
1147
|
+
print("❌ Invalid response format from LLM - not a list")
|
1148
|
+
return False
|
1149
|
+
|
1150
|
+
# Apply the suggested changes
|
1151
|
+
changes_made = 0
|
1152
|
+
commands_added = 0
|
1153
|
+
|
1154
|
+
# Process in reverse order to avoid index shifting issues
|
1155
|
+
for action in sorted(actions, key=lambda x: x.get('command_index', 0), reverse=True):
|
1156
|
+
cmd_idx = action.get('command_index')
|
1157
|
+
action_type = action.get('action')
|
1158
|
+
new_cmd = action.get('new_command', '')
|
1159
|
+
reason = action.get('reason', 'No reason provided')
|
1160
|
+
|
1161
|
+
if cmd_idx is None or action_type is None:
|
1162
|
+
continue
|
1163
|
+
|
1164
|
+
# Convert to 0-based index if needed
|
1165
|
+
if cmd_idx > 0: # Assume 1-based index from LLM
|
1166
|
+
cmd_idx -= 1
|
1167
|
+
|
1168
|
+
# Skip if the command index is invalid
|
1169
|
+
if cmd_idx < 0 or cmd_idx >= len(self.commands):
|
1170
|
+
print(f"❌ Invalid command index: {cmd_idx}")
|
1171
|
+
continue
|
1172
|
+
|
1173
|
+
# Skip if the command has already been executed
|
1174
|
+
if self.commands[cmd_idx]['status'] != 'pending':
|
1175
|
+
print(f"⚠️ Command {cmd_idx + 1} already executed, skipping action")
|
1176
|
+
continue
|
1177
|
+
|
1178
|
+
if action_type == "SKIP":
|
1179
|
+
# Mark the command as successful without running it
|
1180
|
+
self.mark_command_executed(
|
1181
|
+
cmd_idx, 'main', True,
|
1182
|
+
f"Command skipped: {reason}",
|
1183
|
+
"", 0
|
1184
|
+
)
|
1185
|
+
print(f"🔄 Skipped command {cmd_idx + 1}: {reason}")
|
1186
|
+
changes_made += 1
|
1187
|
+
|
1188
|
+
elif action_type == "MODIFY":
|
1189
|
+
if new_cmd:
|
1190
|
+
if self.replace_command(cmd_idx, new_cmd, reason):
|
1191
|
+
changes_made += 1
|
1192
|
+
else:
|
1193
|
+
print(f"❌ No new command provided for MODIFY action on command {cmd_idx + 1}")
|
1194
|
+
|
1195
|
+
elif action_type == "ADD_AFTER":
|
1196
|
+
if new_cmd:
|
1197
|
+
# Add new command after the current one
|
1198
|
+
insert_idx = cmd_idx + 1
|
1199
|
+
new_cmd_obj = {
|
1200
|
+
'command': new_cmd,
|
1201
|
+
'status': 'pending',
|
1202
|
+
'index': insert_idx,
|
1203
|
+
'stdout': '',
|
1204
|
+
'stderr': '',
|
1205
|
+
'execution_time': None,
|
1206
|
+
'fix_attempts': 0,
|
1207
|
+
'max_fix_attempts': 3,
|
1208
|
+
'added_reason': reason
|
1209
|
+
}
|
1210
|
+
|
1211
|
+
# Insert the new command
|
1212
|
+
self.commands.insert(insert_idx, new_cmd_obj)
|
1213
|
+
|
1214
|
+
# Update indices for all commands after insertion
|
1215
|
+
for i in range(insert_idx + 1, len(self.commands)):
|
1216
|
+
self.commands[i]['index'] = i
|
1217
|
+
|
1218
|
+
print(f"➕ Added new command after {cmd_idx + 1}: '{new_cmd}'")
|
1219
|
+
print(f"🔍 Reason: {reason}")
|
1220
|
+
commands_added += 1
|
1221
|
+
else:
|
1222
|
+
print(f"❌ No new command provided for ADD_AFTER action on command {cmd_idx + 1}")
|
1223
|
+
|
1224
|
+
# Update total commands count
|
1225
|
+
self.total_commands = len(self.commands)
|
1226
|
+
|
1227
|
+
print(f"✅ Command list updated: {changes_made} changes made, {commands_added} commands added")
|
1228
|
+
return changes_made > 0 or commands_added > 0
|
1229
|
+
|
1230
|
+
except json.JSONDecodeError as e:
|
1231
|
+
print(f"❌ Failed to parse LLM response as JSON: {e}")
|
1232
|
+
print(f"Raw response: {response_text}")
|
1233
|
+
return False
|
1234
|
+
except Exception as e:
|
1235
|
+
print(f"❌ Error updating command list: {e}")
|
1236
|
+
return False
|
1237
|
+
|
1238
|
+
except Exception as e:
|
1239
|
+
print(f"⚠️ Error analyzing command list: {e}")
|
1240
|
+
return False
|
681
1241
|
|
682
1242
|
|
683
1243
|
# Import the fetch_modal_tokens module
|
@@ -1138,7 +1698,7 @@ Do not provide any explanations, just the exact command to run.
|
|
1138
1698
|
"max_tokens": 300
|
1139
1699
|
}
|
1140
1700
|
|
1141
|
-
|
1701
|
+
print(f"🔍 DEBUG: Payload prepared, prompt length: {len(prompt)}")
|
1142
1702
|
|
1143
1703
|
# Add specific handling for common errors
|
1144
1704
|
last_error = None
|
@@ -1150,8 +1710,8 @@ Do not provide any explanations, just the exact command to run.
|
|
1150
1710
|
print(f"⏱️ Retrying in {wait_time:.1f} seconds... (attempt {attempt+1}/{retries+1})")
|
1151
1711
|
time.sleep(wait_time)
|
1152
1712
|
|
1153
|
-
|
1154
|
-
|
1713
|
+
print(f"🤖 Calling OpenAI with {model_name} model to debug the failed command...")
|
1714
|
+
print(f"🔍 DEBUG: Making POST request to OpenAI API...")
|
1155
1715
|
response = requests.post(
|
1156
1716
|
"https://api.openai.com/v1/chat/completions",
|
1157
1717
|
headers=headers,
|
@@ -1159,8 +1719,41 @@ Do not provide any explanations, just the exact command to run.
|
|
1159
1719
|
timeout=45 # Increased timeout for reliability
|
1160
1720
|
)
|
1161
1721
|
|
1162
|
-
|
1722
|
+
print(f"🔍 DEBUG: Response received, status code: {response.status_code}")
|
1163
1723
|
|
1724
|
+
# Handle specific status codes
|
1725
|
+
if response.status_code == 200:
|
1726
|
+
print(f"🔍 DEBUG: Success! Response length: {len(response.text)}")
|
1727
|
+
return response.json(), None
|
1728
|
+
elif response.status_code == 401:
|
1729
|
+
error_msg = "Authentication error: Invalid API key"
|
1730
|
+
print(f"❌ {error_msg}")
|
1731
|
+
print(f"🔍 DEBUG: Response text: {response.text}")
|
1732
|
+
# Don't retry auth errors
|
1733
|
+
return None, error_msg
|
1734
|
+
elif response.status_code == 429:
|
1735
|
+
error_msg = "Rate limit exceeded or quota reached"
|
1736
|
+
print(f"⚠️ {error_msg}")
|
1737
|
+
print(f"🔍 DEBUG: Response text: {response.text}")
|
1738
|
+
# Always retry rate limit errors with increasing backoff
|
1739
|
+
last_error = error_msg
|
1740
|
+
continue
|
1741
|
+
elif response.status_code == 500:
|
1742
|
+
error_msg = "OpenAI server error"
|
1743
|
+
print(f"⚠️ {error_msg}")
|
1744
|
+
print(f"🔍 DEBUG: Response text: {response.text}")
|
1745
|
+
# Retry server errors
|
1746
|
+
last_error = error_msg
|
1747
|
+
continue
|
1748
|
+
else:
|
1749
|
+
error_msg = f"Status code: {response.status_code}, Response: {response.text}"
|
1750
|
+
print(f"⚠️ OpenAI API error: {error_msg}")
|
1751
|
+
print(f"🔍 DEBUG: Full response text: {response.text}")
|
1752
|
+
last_error = error_msg
|
1753
|
+
# Only retry if we have attempts left
|
1754
|
+
if attempt < retries:
|
1755
|
+
continue
|
1756
|
+
return None, error_msg
|
1164
1757
|
except requests.exceptions.Timeout:
|
1165
1758
|
error_msg = "Request timed out"
|
1166
1759
|
# print(f"⚠️ {error_msg}")
|
@@ -1780,20 +2373,26 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1780
2373
|
# Start SSH service
|
1781
2374
|
subprocess.run(["service", "ssh", "start"], check=True)
|
1782
2375
|
|
1783
|
-
#
|
2376
|
+
# Preprocess setup commands using LLM to inject credentials
|
1784
2377
|
if setup_commands:
|
1785
|
-
print(f"
|
2378
|
+
print(f"🔧 Preprocessing {len(setup_commands)} setup commands with LLM to inject credentials...")
|
2379
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
2380
|
+
processed_commands = preprocess_commands_with_llm(setup_commands, stored_credentials, api_key)
|
2381
|
+
print(f"⚙️ Running {len(processed_commands)} preprocessed setup commands with dynamic command list...")
|
1786
2382
|
|
1787
|
-
# Create command list manager
|
1788
|
-
cmd_manager = CommandListManager(
|
2383
|
+
# Create command list manager with processed commands
|
2384
|
+
cmd_manager = CommandListManager(processed_commands)
|
1789
2385
|
|
1790
2386
|
# Create persistent shell instance starting in /root
|
1791
|
-
shell = PersistentShell(working_dir="/root", timeout=
|
2387
|
+
shell = PersistentShell(working_dir="/root", timeout=300)
|
1792
2388
|
|
1793
2389
|
try:
|
1794
2390
|
# Start the persistent shell
|
1795
2391
|
shell.start()
|
1796
2392
|
|
2393
|
+
# Track how many commands have been executed since last analysis
|
2394
|
+
commands_since_analysis = 0
|
2395
|
+
|
1797
2396
|
# Execute commands using the command list manager
|
1798
2397
|
while cmd_manager.has_pending_commands():
|
1799
2398
|
# Get next command to execute
|
@@ -1805,6 +2404,13 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1805
2404
|
# Print status before executing
|
1806
2405
|
cmd_manager.print_status()
|
1807
2406
|
|
2407
|
+
# Periodically analyze and update the command list
|
2408
|
+
if commands_since_analysis >= 3 and cmd_type == 'main':
|
2409
|
+
print("\n🔍 Periodic command list analysis...")
|
2410
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
2411
|
+
cmd_manager.update_command_list_with_llm(api_key)
|
2412
|
+
commands_since_analysis = 0
|
2413
|
+
|
1808
2414
|
# Execute the command
|
1809
2415
|
if cmd_type == 'main':
|
1810
2416
|
cmd_text = next_cmd['command']
|
@@ -1812,14 +2418,33 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1812
2418
|
print(f"📋 Executing main command {cmd_index + 1}/{cmd_manager.total_commands}: {cmd_text}")
|
1813
2419
|
|
1814
2420
|
start_time = time.time()
|
1815
|
-
success, stdout, stderr = shell.execute(cmd_text, timeout=
|
2421
|
+
success, stdout, stderr = shell.execute(cmd_text, timeout=300)
|
1816
2422
|
execution_time = time.time() - start_time
|
1817
2423
|
|
2424
|
+
# Check if the command was aborted due to waiting for input and an alternative was suggested
|
2425
|
+
if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
|
2426
|
+
alternative_cmd = shell.suggested_alternative
|
2427
|
+
print(f"🔄 Command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
|
2428
|
+
|
2429
|
+
# Add the alternative command with high priority
|
2430
|
+
cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
|
2431
|
+
|
2432
|
+
# Clear the suggested alternative
|
2433
|
+
shell.suggested_alternative = None
|
2434
|
+
# Check if the command should be removed as suggested by LLM
|
2435
|
+
elif not success and stderr.startswith("Command removed -"):
|
2436
|
+
reason = stderr.replace("Command removed -", "").strip()
|
2437
|
+
print(f"🚫 Removed command as suggested by LLM: {reason}")
|
2438
|
+
# We don't need to do anything else, just mark it as executed and move on
|
2439
|
+
|
1818
2440
|
# Mark command as executed
|
1819
2441
|
cmd_manager.mark_command_executed(
|
1820
2442
|
cmd_index, 'main', success, stdout, stderr, execution_time
|
1821
2443
|
)
|
1822
2444
|
|
2445
|
+
# Increment counter for periodic analysis
|
2446
|
+
commands_since_analysis += 1
|
2447
|
+
|
1823
2448
|
if not success:
|
1824
2449
|
print(f"⚠️ Command failed, attempting LLM debugging...")
|
1825
2450
|
|
@@ -1840,7 +2465,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1840
2465
|
# Execute the fix command
|
1841
2466
|
print(f"🔄 Running suggested fix command: {fix_command}")
|
1842
2467
|
fix_start_time = time.time()
|
1843
|
-
fix_success, fix_stdout, fix_stderr = shell.execute(fix_command, timeout=
|
2468
|
+
fix_success, fix_stdout, fix_stderr = shell.execute(fix_command, timeout=300)
|
1844
2469
|
fix_execution_time = time.time() - fix_start_time
|
1845
2470
|
|
1846
2471
|
# Mark fix command as executed
|
@@ -1851,21 +2476,49 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1851
2476
|
if fix_success:
|
1852
2477
|
print(f"✅ Fix command succeeded")
|
1853
2478
|
|
1854
|
-
#
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
retry_execution_time = time.time() - retry_start_time
|
1859
|
-
|
1860
|
-
# Update the original command status
|
1861
|
-
cmd_manager.mark_command_executed(
|
1862
|
-
cmd_index, 'main', retry_success, retry_stdout, retry_stderr, retry_execution_time
|
2479
|
+
# Check if we should skip the original command
|
2480
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
2481
|
+
should_skip, skip_reason = cmd_manager.should_skip_original_command(
|
2482
|
+
cmd_text, fix_command, fix_stdout, fix_stderr, api_key
|
1863
2483
|
)
|
1864
2484
|
|
1865
|
-
if
|
1866
|
-
print(f"
|
2485
|
+
if should_skip:
|
2486
|
+
print(f"🔄 Skipping original command: {skip_reason}")
|
2487
|
+
|
2488
|
+
# Mark the original command as successful without running it
|
2489
|
+
cmd_manager.mark_command_executed(
|
2490
|
+
cmd_index, 'main', True,
|
2491
|
+
f"Command skipped after successful fix: {skip_reason}",
|
2492
|
+
"", time.time() - start_time
|
2493
|
+
)
|
2494
|
+
|
2495
|
+
print(f"✅ Original command marked as successful (skipped)")
|
2496
|
+
|
2497
|
+
# After a successful fix and skipping the original command,
|
2498
|
+
# analyze and update the entire command list
|
2499
|
+
print("\n🔍 Analyzing and updating remaining commands based on fix results...")
|
2500
|
+
cmd_manager.update_command_list_with_llm(api_key)
|
1867
2501
|
else:
|
1868
|
-
|
2502
|
+
# Retry the original command
|
2503
|
+
print(f"🔄 Retrying original command: {cmd_text}")
|
2504
|
+
retry_start_time = time.time()
|
2505
|
+
retry_success, retry_stdout, retry_stderr = shell.execute(cmd_text, timeout=300)
|
2506
|
+
retry_execution_time = time.time() - retry_start_time
|
2507
|
+
|
2508
|
+
# Update the original command status
|
2509
|
+
cmd_manager.mark_command_executed(
|
2510
|
+
cmd_index, 'main', retry_success, retry_stdout, retry_stderr, retry_execution_time
|
2511
|
+
)
|
2512
|
+
|
2513
|
+
if retry_success:
|
2514
|
+
print(f"✅ Original command succeeded after fix!")
|
2515
|
+
|
2516
|
+
# After a successful fix and successful retry,
|
2517
|
+
# analyze and update the entire command list
|
2518
|
+
print("\n🔍 Analyzing and updating remaining commands based on fix results...")
|
2519
|
+
cmd_manager.update_command_list_with_llm(api_key)
|
2520
|
+
else:
|
2521
|
+
print(f"⚠️ Original command still failed after fix, continuing...")
|
1869
2522
|
else:
|
1870
2523
|
print(f"❌ Fix command failed: {fix_stderr}")
|
1871
2524
|
print(f"⚠️ Continuing with remaining commands...")
|
@@ -1883,9 +2536,25 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1883
2536
|
print(f"🔧 Executing fix command {cmd_index + 1}: {cmd_text}")
|
1884
2537
|
|
1885
2538
|
start_time = time.time()
|
1886
|
-
success, stdout, stderr = shell.execute(cmd_text, timeout=
|
2539
|
+
success, stdout, stderr = shell.execute(cmd_text, timeout=300)
|
1887
2540
|
execution_time = time.time() - start_time
|
1888
2541
|
|
2542
|
+
# Check if the fix command was aborted due to waiting for input and an alternative was suggested
|
2543
|
+
if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
|
2544
|
+
alternative_cmd = shell.suggested_alternative
|
2545
|
+
print(f"🔄 Fix command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
|
2546
|
+
|
2547
|
+
# Add the alternative command with high priority
|
2548
|
+
cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
|
2549
|
+
|
2550
|
+
# Clear the suggested alternative
|
2551
|
+
shell.suggested_alternative = None
|
2552
|
+
# Check if the fix command should be removed as suggested by LLM
|
2553
|
+
elif not success and stderr.startswith("Command removed -"):
|
2554
|
+
reason = stderr.replace("Command removed -", "").strip()
|
2555
|
+
print(f"🚫 Removed fix command as suggested by LLM: {reason}")
|
2556
|
+
# We don't need to do anything else, just mark it as executed and move on
|
2557
|
+
|
1889
2558
|
# Mark fix command as executed
|
1890
2559
|
cmd_manager.mark_command_executed(
|
1891
2560
|
cmd_index, 'fix', success, stdout, stderr, execution_time
|
@@ -1911,9 +2580,25 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1911
2580
|
print(f"🔧 Executing additional fix: {cmd_text}")
|
1912
2581
|
|
1913
2582
|
start_time = time.time()
|
1914
|
-
success, stdout, stderr = shell.execute(cmd_text, timeout=
|
2583
|
+
success, stdout, stderr = shell.execute(cmd_text, timeout=300)
|
1915
2584
|
execution_time = time.time() - start_time
|
1916
2585
|
|
2586
|
+
# Check if the fix command was aborted due to waiting for input and an alternative was suggested
|
2587
|
+
if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
|
2588
|
+
alternative_cmd = shell.suggested_alternative
|
2589
|
+
print(f"🔄 Additional fix command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
|
2590
|
+
|
2591
|
+
# Add the alternative command with high priority
|
2592
|
+
cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
|
2593
|
+
|
2594
|
+
# Clear the suggested alternative
|
2595
|
+
shell.suggested_alternative = None
|
2596
|
+
# Check if the additional fix command should be removed as suggested by LLM
|
2597
|
+
elif not success and stderr.startswith("Command removed -"):
|
2598
|
+
reason = stderr.replace("Command removed -", "").strip()
|
2599
|
+
print(f"🚫 Removed additional fix command as suggested by LLM: {reason}")
|
2600
|
+
# We don't need to do anything else, just mark it as executed and move on
|
2601
|
+
|
1917
2602
|
# Mark fix command as executed
|
1918
2603
|
cmd_manager.mark_command_executed(
|
1919
2604
|
fix_index, 'fix', success, stdout, stderr, execution_time
|
@@ -3459,4 +4144,90 @@ if __name__ == "__main__":
|
|
3459
4144
|
# print(f"\n❌ Error: {e}")
|
3460
4145
|
# print("🧹 Cleaning up resources...")
|
3461
4146
|
cleanup_modal_token()
|
3462
|
-
sys.exit(1)
|
4147
|
+
sys.exit(1)
|
4148
|
+
|
4149
|
+
def preprocess_commands_with_llm(setup_commands, stored_credentials, api_key=None):
|
4150
|
+
"""
|
4151
|
+
Use LLM to preprocess setup commands and inject available credentials.
|
4152
|
+
|
4153
|
+
Args:
|
4154
|
+
setup_commands: List of setup commands
|
4155
|
+
stored_credentials: Dictionary of stored credentials
|
4156
|
+
api_key: OpenAI API key for LLM calls
|
4157
|
+
|
4158
|
+
Returns:
|
4159
|
+
List of processed commands with credentials injected
|
4160
|
+
"""
|
4161
|
+
if not setup_commands or not stored_credentials:
|
4162
|
+
return setup_commands
|
4163
|
+
|
4164
|
+
try:
|
4165
|
+
# Create context for the LLM
|
4166
|
+
credentials_info = "\n".join([f"- {key}: {value[:8]}..." for key, value in stored_credentials.items()])
|
4167
|
+
|
4168
|
+
prompt = f"""
|
4169
|
+
You are a command preprocessing assistant. Your task is to modify setup commands to use available credentials and make them non-interactive.
|
4170
|
+
|
4171
|
+
AVAILABLE CREDENTIALS:
|
4172
|
+
{credentials_info}
|
4173
|
+
|
4174
|
+
ORIGINAL COMMANDS:
|
4175
|
+
{chr(10).join([f"{i+1}. {cmd}" for i, cmd in enumerate(setup_commands)])}
|
4176
|
+
|
4177
|
+
INSTRUCTIONS:
|
4178
|
+
1. Replace any authentication commands with token-based versions using available credentials
|
4179
|
+
2. Make all commands non-interactive (add --yes, --no-input, -y flags where needed)
|
4180
|
+
3. Use environment variables or direct token injection where appropriate
|
4181
|
+
4. Skip commands that cannot be made non-interactive due to missing credentials
|
4182
|
+
5. Add any necessary environment variable exports
|
4183
|
+
|
4184
|
+
Return the modified commands as a JSON array of strings. If a command should be skipped, prefix it with "# SKIPPED: ".
|
4185
|
+
|
4186
|
+
Example transformations:
|
4187
|
+
- "huggingface-cli login" → "huggingface-cli login --token $HUGGINGFACE_TOKEN"
|
4188
|
+
- "npm install" → "npm install --yes"
|
4189
|
+
- "pip install package" → "pip install package --no-input"
|
4190
|
+
|
4191
|
+
Return only the JSON array, no other text.
|
4192
|
+
"""
|
4193
|
+
|
4194
|
+
if not api_key:
|
4195
|
+
print("⚠️ No OpenAI API key available for command preprocessing")
|
4196
|
+
return setup_commands
|
4197
|
+
|
4198
|
+
# Call OpenAI API
|
4199
|
+
import openai
|
4200
|
+
client = openai.OpenAI(api_key=api_key)
|
4201
|
+
|
4202
|
+
response = client.chat.completions.create(
|
4203
|
+
model="gpt-3.5-turbo",
|
4204
|
+
messages=[
|
4205
|
+
{"role": "system", "content": "You are a command preprocessing assistant that modifies setup commands to use available credentials and make them non-interactive."},
|
4206
|
+
{"role": "user", "content": prompt}
|
4207
|
+
],
|
4208
|
+
temperature=0.1,
|
4209
|
+
max_tokens=2000
|
4210
|
+
)
|
4211
|
+
|
4212
|
+
result = response.choices[0].message.content.strip()
|
4213
|
+
|
4214
|
+
# Parse the JSON response
|
4215
|
+
import json
|
4216
|
+
try:
|
4217
|
+
processed_commands = json.loads(result)
|
4218
|
+
if isinstance(processed_commands, list):
|
4219
|
+
print(f"🔧 LLM preprocessed {len(processed_commands)} commands")
|
4220
|
+
for i, cmd in enumerate(processed_commands):
|
4221
|
+
if cmd != setup_commands[i]:
|
4222
|
+
print(f" {i+1}. {setup_commands[i]} → {cmd}")
|
4223
|
+
return processed_commands
|
4224
|
+
else:
|
4225
|
+
print("⚠️ LLM returned invalid format, using original commands")
|
4226
|
+
return setup_commands
|
4227
|
+
except json.JSONDecodeError:
|
4228
|
+
print("⚠️ Failed to parse LLM response, using original commands")
|
4229
|
+
return setup_commands
|
4230
|
+
|
4231
|
+
except Exception as e:
|
4232
|
+
print(f"⚠️ LLM preprocessing failed: {e}")
|
4233
|
+
return setup_commands
|