Execute a shell command. Returns combined stdout + stderr. All commands go through the user confirmation path when confirm_shell=True (the default). Metacharacter commands (&&, |, ;, etc.) are allowed — the user sees the full command before approving. Dangerous destructive command
(command: str, yolo: bool = False, timeout: int = 1800)
| 20 | return any(p in cmd_lower for p in dangerous_patterns) |
| 21 | |
| 22 | def shell(command: str, yolo: bool = False, timeout: int = 1800) -> str: |
| 23 | """ |
| 24 | Execute a shell command. Returns combined stdout + stderr. |
| 25 | |
| 26 | All commands go through the user confirmation path when confirm_shell=True |
| 27 | (the default). Metacharacter commands (&&, |, ;, etc.) are allowed — the |
| 28 | user sees the full command before approving. Dangerous destructive commands |
| 29 | (rm, curl, etc.) receive an explicit warning before the confirmation prompt. |
| 30 | |
| 31 | Args: |
| 32 | command: The shell command to execute |
| 33 | yolo: Skip confirmation prompts |
| 34 | timeout: Command timeout in seconds (default: 30 minutes) |
| 35 | |
| 36 | Returns: |
| 37 | Command output or error message |
| 38 | """ |
| 39 | should_confirm = False |
| 40 | |
| 41 | if is_dangerous(command): |
| 42 | warning(f"Potentially dangerous command: `{command}`") |
| 43 | should_confirm = True |
| 44 | elif AGENT_CONFIG["confirm_shell"] and not yolo: |
| 45 | should_confirm = True |
| 46 | |
| 47 | if should_confirm and not yolo: |
| 48 | if not ask_confirm(f"Run shell command: `{command}`?"): |
| 49 | return "[CANCELLED] User declined to run command." |
| 50 | |
| 51 | try: |
| 52 | result = subprocess.run( |
| 53 | command, |
| 54 | shell=True, |
| 55 | capture_output=True, |
| 56 | text=True, |
| 57 | timeout=timeout, |
| 58 | ) |
| 59 | output = "" |
| 60 | if result.stdout: |
| 61 | output += result.stdout |
| 62 | if result.stderr: |
| 63 | output += f"\n[stderr]\n{result.stderr}" |
| 64 | return output.strip() if output.strip() else "(no output)" |
| 65 | except subprocess.TimeoutExpired: |
| 66 | return f"[ERROR] Command timed out after {timeout}s" |
| 67 | except Exception as e: |
| 68 | return f"[ERROR] {e}" |
| 69 | |
| 70 | def search_files(pattern: str, path: str = ".") -> str: |
| 71 | """Search for files matching pattern. Uses subprocess list args to prevent injection.""" |
no test coverage detected