feat: 多语言任务、WebUI 增强与 Agent MCP 集成
重构 lib 为扁平模块并支持 Windows schtasks;新增 JS/Bash/PowerShell 模板、WebUI 调度编辑,以及 Cursor Skill 与 MCP 工具供 Agent 管理定时任务。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
114
lib/runtime_probe.py
Normal file
114
lib/runtime_probe.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""检测本机 Python / Node.js / Bash / PowerShell 是否可用及版本。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import asdict, dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeProbe:
|
||||
id: str
|
||||
name: str
|
||||
available: bool
|
||||
version: str | None = None
|
||||
path: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def _first_line(text: str) -> str:
|
||||
line = (text or "").strip().splitlines()[0].strip() if text else ""
|
||||
return line
|
||||
|
||||
|
||||
def _run(cmd: list[str], *, timeout: float = 8.0) -> tuple[int, str]:
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=timeout,
|
||||
)
|
||||
out = _first_line((proc.stdout or "") + "\n" + (proc.stderr or ""))
|
||||
return proc.returncode, out
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return 1, ""
|
||||
|
||||
|
||||
def _probe_python() -> RuntimeProbe:
|
||||
exe = sys.executable
|
||||
code, out = _run([exe, "--version"])
|
||||
version: str | None = None
|
||||
if code == 0 and out:
|
||||
version = out.removeprefix("Python ").strip() if out.lower().startswith("python") else out
|
||||
elif sys.version:
|
||||
version = sys.version.split()[0]
|
||||
return RuntimeProbe(
|
||||
id="python",
|
||||
name="Python",
|
||||
available=True,
|
||||
version=version,
|
||||
path=exe,
|
||||
)
|
||||
|
||||
|
||||
def _probe_nodejs() -> RuntimeProbe:
|
||||
path = shutil.which("node")
|
||||
if not path:
|
||||
return RuntimeProbe(id="nodejs", name="JavaScript", available=False)
|
||||
code, out = _run([path, "--version"])
|
||||
if code != 0 or not out:
|
||||
return RuntimeProbe(id="nodejs", name="JavaScript", available=False, path=path)
|
||||
version = out.lstrip("vV").strip() or out
|
||||
return RuntimeProbe(id="nodejs", name="JavaScript", available=True, version=version, path=path)
|
||||
|
||||
|
||||
def _parse_bash_version(text: str) -> str | None:
|
||||
match = re.search(r"version\s+([^\s(]+)", text, re.I)
|
||||
return match.group(1) if match else (text or None)
|
||||
|
||||
|
||||
def _probe_bash() -> RuntimeProbe:
|
||||
path = shutil.which("bash")
|
||||
if not path:
|
||||
return RuntimeProbe(id="bash", name="Bash", available=False)
|
||||
code, out = _run([path, "--version"])
|
||||
if code != 0 or not out:
|
||||
return RuntimeProbe(id="bash", name="Bash", available=False, path=path)
|
||||
version = _parse_bash_version(out)
|
||||
return RuntimeProbe(id="bash", name="Bash", available=True, version=version, path=path)
|
||||
|
||||
|
||||
def _probe_powershell() -> RuntimeProbe:
|
||||
for cmd_name in ("pwsh", "powershell"):
|
||||
path = shutil.which(cmd_name)
|
||||
if not path:
|
||||
continue
|
||||
code, out = _run(
|
||||
[path, "-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"]
|
||||
)
|
||||
if code == 0 and out:
|
||||
return RuntimeProbe(
|
||||
id="powershell",
|
||||
name="PowerShell",
|
||||
available=True,
|
||||
version=out.strip(),
|
||||
path=path,
|
||||
)
|
||||
return RuntimeProbe(id="powershell", name="PowerShell", available=False)
|
||||
|
||||
|
||||
def probe_all_runtimes() -> list[dict[str, object]]:
|
||||
return [
|
||||
_probe_python().to_dict(),
|
||||
_probe_nodejs().to_dict(),
|
||||
_probe_bash().to_dict(),
|
||||
_probe_powershell().to_dict(),
|
||||
]
|
||||
Reference in New Issue
Block a user