Delete Modules/Shell_Exec.py
Browse files- Modules/Shell_Exec.py +0 -139
Modules/Shell_Exec.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
import platform
|
| 5 |
-
import shlex
|
| 6 |
-
import subprocess
|
| 7 |
-
from typing import Annotated
|
| 8 |
-
|
| 9 |
-
import gradio as gr
|
| 10 |
-
|
| 11 |
-
from app import _log_call_end, _log_call_start, _truncate_for_log
|
| 12 |
-
from ._docstrings import autodoc
|
| 13 |
-
from .File_System import _resolve_path, ROOT_DIR, _display_path
|
| 14 |
-
import shutil
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def _detect_shell(prefer_powershell: bool = True) -> tuple[list[str], str]:
|
| 18 |
-
"""
|
| 19 |
-
Pick an appropriate shell for the host OS.
|
| 20 |
-
- Windows: use PowerShell by default, fall back to cmd.exe.
|
| 21 |
-
- POSIX: use /bin/bash if available, else /bin/sh.
|
| 22 |
-
Returns (shell_cmd_prefix, shell_name) where shell_cmd_prefix is the command list to launch the shell.
|
| 23 |
-
"""
|
| 24 |
-
system = platform.system().lower()
|
| 25 |
-
if system == "windows":
|
| 26 |
-
if prefer_powershell:
|
| 27 |
-
pwsh = shutil.which("pwsh")
|
| 28 |
-
candidates = [pwsh, shutil.which("powershell"), shutil.which("powershell.exe")]
|
| 29 |
-
for cand in candidates:
|
| 30 |
-
if cand:
|
| 31 |
-
return [cand, "-NoLogo", "-NoProfile", "-Command"], "powershell"
|
| 32 |
-
# Fallback to cmd
|
| 33 |
-
comspec = os.environ.get("ComSpec", r"C:\\Windows\\System32\\cmd.exe")
|
| 34 |
-
return [comspec, "/C"], "cmd"
|
| 35 |
-
# POSIX
|
| 36 |
-
bash = shutil.which("bash")
|
| 37 |
-
if bash:
|
| 38 |
-
return [bash, "-lc"], "bash"
|
| 39 |
-
sh = os.environ.get("SHELL", "/bin/sh")
|
| 40 |
-
return [sh, "-lc"], "sh"
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
# Detect shell at import time for docs/UI purposes
|
| 44 |
-
_DETECTED_SHELL_PREFIX, _DETECTED_SHELL_NAME = _detect_shell()
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
# Clarify path semantics and expose detected shell in summary
|
| 48 |
-
TOOL_SUMMARY = (
|
| 49 |
-
"Execute a shell command within a safe working directory under the tool root ('/'). "
|
| 50 |
-
"Paths must be relative to '/'. "
|
| 51 |
-
"Set workdir to '.' to use the root. "
|
| 52 |
-
"Absolute paths are disabled."
|
| 53 |
-
f"Detected shell: {_DETECTED_SHELL_NAME}."
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
def _run_command(command: str, cwd: str, timeout: int) -> tuple[str, str, int]:
|
| 58 |
-
shell_prefix, shell_name = _detect_shell()
|
| 59 |
-
full_cmd = shell_prefix + [command]
|
| 60 |
-
try:
|
| 61 |
-
proc = subprocess.run(
|
| 62 |
-
full_cmd,
|
| 63 |
-
cwd=cwd,
|
| 64 |
-
stdout=subprocess.PIPE,
|
| 65 |
-
stderr=subprocess.PIPE,
|
| 66 |
-
text=True,
|
| 67 |
-
encoding="utf-8",
|
| 68 |
-
errors="replace",
|
| 69 |
-
timeout=timeout if timeout and timeout > 0 else None,
|
| 70 |
-
)
|
| 71 |
-
return proc.stdout, proc.stderr, proc.returncode
|
| 72 |
-
except subprocess.TimeoutExpired as exc:
|
| 73 |
-
return exc.stdout or "", (exc.stderr or "") + "\n[timeout]", 124
|
| 74 |
-
except Exception as exc:
|
| 75 |
-
return "", f"Execution failed: {exc}", 1
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
@autodoc(summary=TOOL_SUMMARY)
|
| 79 |
-
def Shell_Exec(
|
| 80 |
-
command: Annotated[str, "Shell command to execute. Accepts multi-part pipelines as a single string."],
|
| 81 |
-
workdir: Annotated[str, "Working directory (relative to root unless UNSAFE_ALLOW_ABS_PATHS=1)."] = ".",
|
| 82 |
-
timeout: Annotated[int, "Timeout in seconds (0 = no timeout, be careful on public hosting)."] = 60,
|
| 83 |
-
) -> str:
|
| 84 |
-
_log_call_start("Shell_Exec", command=command, workdir=workdir, timeout=timeout)
|
| 85 |
-
if not command or not command.strip():
|
| 86 |
-
result = "No command provided."
|
| 87 |
-
_log_call_end("Shell_Exec", _truncate_for_log(result))
|
| 88 |
-
return result
|
| 89 |
-
|
| 90 |
-
abs_cwd, err = _resolve_path(workdir)
|
| 91 |
-
if err:
|
| 92 |
-
_log_call_end("Shell_Exec", _truncate_for_log(err))
|
| 93 |
-
return err
|
| 94 |
-
if not os.path.exists(abs_cwd):
|
| 95 |
-
result = f"Working directory not found: {abs_cwd}"
|
| 96 |
-
_log_call_end("Shell_Exec", _truncate_for_log(result))
|
| 97 |
-
return result
|
| 98 |
-
|
| 99 |
-
# Capture shell used for transparency
|
| 100 |
-
_, shell_name = _detect_shell()
|
| 101 |
-
stdout, stderr, code = _run_command(command, cwd=abs_cwd, timeout=timeout)
|
| 102 |
-
display_cwd = _display_path(abs_cwd)
|
| 103 |
-
header = (
|
| 104 |
-
f"Command: {command}\n"
|
| 105 |
-
f"CWD: {display_cwd}\n"
|
| 106 |
-
f"Root: /\n"
|
| 107 |
-
f"Shell: {shell_name}\n"
|
| 108 |
-
f"Exit code: {code}\n"
|
| 109 |
-
f"--- STDOUT ---\n"
|
| 110 |
-
)
|
| 111 |
-
output = header + (stdout or "<empty>") + "\n--- STDERR ---\n" + (stderr or "<empty>")
|
| 112 |
-
_log_call_end("Shell_Exec", _truncate_for_log(f"exit={code} stdout={len(stdout)} stderr={len(stderr)}"))
|
| 113 |
-
return output
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
def build_interface() -> gr.Interface:
|
| 117 |
-
return gr.Interface(
|
| 118 |
-
fn=Shell_Exec,
|
| 119 |
-
inputs=[
|
| 120 |
-
gr.Textbox(label="Command", placeholder="echo hello || dir", lines=2),
|
| 121 |
-
gr.Textbox(label="Workdir", value=".", max_lines=1),
|
| 122 |
-
gr.Slider(minimum=0, maximum=600, step=5, value=60, label="Timeout (seconds)"),
|
| 123 |
-
],
|
| 124 |
-
outputs=gr.Textbox(label="Output", lines=20),
|
| 125 |
-
title="Shell Exec",
|
| 126 |
-
description=(
|
| 127 |
-
"<div style=\"text-align:center; overflow:hidden;\">"
|
| 128 |
-
"Run a shell command under the same safe root as File System. "
|
| 129 |
-
"Absolute paths are disabled, use relative paths. "
|
| 130 |
-
f"Detected shell: {_DETECTED_SHELL_NAME}. "
|
| 131 |
-
"</div>"
|
| 132 |
-
),
|
| 133 |
-
api_description=TOOL_SUMMARY,
|
| 134 |
-
flagging_mode="never",
|
| 135 |
-
submit_btn="Run",
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
__all__ = ["Shell_Exec", "build_interface"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|