"""tmux 裸终端运行时(零依赖,最轻量)。 每个 member 对应一个 tmux 会话(session = rig 名,window = member id)。 启动:tmux new-session -d -s -n '' 停止:tmux kill-session -t : """ from __future__ import annotations import shutil import subprocess from dataclasses import dataclass class TmuxNotFoundError(RuntimeError): pass def _tmux(*args: str) -> str: if not shutil.which("tmux"): raise TmuxNotFoundError("tmux 未安装") r = subprocess.run(["tmux", *args], capture_output=True, text=True, timeout=10) if r.returncode != 0: raise RuntimeError(f"tmux {' '.join(args)} 失败: {r.stderr.strip()}") return r.stdout.strip() @dataclass class TmuxSession: rig: str member: str command: str @property def target(self) -> str: return f"{self.rig}:{self.member}" def exists(self) -> bool: try: _tmux("has-session", "-t", self.target) return True except (RuntimeError, Exception): return False def start(self): if self.exists(): return _tmux("new-session", "-d", "-s", self.rig, "-n", self.member, self.command) def stop(self): try: _tmux("kill-window", "-t", self.target) except (RuntimeError, Exception): pass def capture(self, lines: int = 20) -> str: """抓最后 N 行输出。""" if not self.exists(): return "" try: return _tmux("capture-pane", "-t", self.target, "-p", "-S", f"-{lines}") except (RuntimeError, Exception): return "" class TerminalRuntime: """tmux 裸终端运行时管理器。""" def __init__(self, rig_name: str): self.rig_name = rig_name def start(self, member_id: str, command: str, cwd: str = ".") -> TmuxSession: sess = TmuxSession(rig=self.rig_name, member=member_id, command=command) if cwd != ".": command = f"cd {cwd} && {command}" sess.command = command sess.start() return sess def stop(self, member_id: str): TmuxSession(rig=self.rig_name, member=member_id, command="").stop() def stop_all(self): try: _tmux("kill-session", "-t", self.rig_name) except (RuntimeError, Exception): pass def status(self, member_id: str) -> str: sess = TmuxSession(rig=self.rig_name, member=member_id, command="") return "running" if sess.exists() else "stopped"