release: v5.4.2 - terminal模拟器 + Operit桥接
新增插件: - terminal: 持久交互式终端,纯标准库,全平台,13个测试 - operit_bridge: Operit AI工具格式桥接,47个脚本288个工具,12个测试 其他: - plugins/operit/: 47个Operit官方脚本 - 更新plugin_registry.json/plugins_config.py - 更新about.txt/CHANGELOG.md至v5.4.2
This commit is contained in:
1 parent
b8faaae0c6
commit
dfe21298b9
56 files changed
+32586
-1
No files matched your search
@@ -0,0 +1,602 @@
|
||||
"""
|
||||
终端模拟器插件 — 纯标准库,Windows/Linux/macOS 通用
|
||||
===================================================
|
||||
持久交互式终端会话,AI 可以像人一样打开终端、敲命令、
|
||||
看输出、继续交互,支持 Python REPL、SSH、top 等。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
import shutil
|
||||
import signal
|
||||
import threading
|
||||
import subprocess
|
||||
import platform
|
||||
from collections import deque
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ToolResult 轻量替代(避免依赖 xcli_core)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class _R:
|
||||
"""极简返回值"""
|
||||
@staticmethod
|
||||
def ok(data): return str(data)
|
||||
@staticmethod
|
||||
def fail(msg): return f"[错误] {msg}"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 单个终端会话
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_IS_WIN = platform.system() == "Windows"
|
||||
|
||||
class TerminalSession:
|
||||
"""
|
||||
一个持久的交互式终端会话。
|
||||
纯标准库实现:subprocess.Popen + threading.Thread,
|
||||
不依赖 pty、pty.openpty、select.poll 等 Unix 特有 API。
|
||||
"""
|
||||
|
||||
_SENTINEL = "<<<XLI_DONE_"
|
||||
|
||||
def __init__(self, session_id: str, shell: str = "", cwd: str = "",
|
||||
env: dict = None, label: str = ""):
|
||||
self.id = session_id
|
||||
self.label = label or session_id
|
||||
self.shell = shell or self._default_shell()
|
||||
self.cwd = cwd or os.getcwd()
|
||||
self.env = env or {}
|
||||
self.created_at = time.time()
|
||||
self.last_active = time.time()
|
||||
|
||||
self._output_buf = deque(maxlen=5000)
|
||||
self._raw_buf = ""
|
||||
self._last_cmd_output = "" # 最近一次 send_cmd 的结果
|
||||
self._lock = threading.Lock()
|
||||
self._proc = None
|
||||
self._reader = None
|
||||
self._start()
|
||||
|
||||
@staticmethod
|
||||
def _default_shell():
|
||||
if _IS_WIN:
|
||||
return os.environ.get("COMSPEC", "cmd.exe")
|
||||
for sh in ["/bin/bash", "/bin/sh", "/usr/bin/bash"]:
|
||||
if os.path.isfile(sh):
|
||||
return sh
|
||||
return "/bin/sh"
|
||||
|
||||
def _start(self):
|
||||
env = {**os.environ, **self.env}
|
||||
|
||||
if _IS_WIN:
|
||||
shell_args = [self.shell, "/K", "chcp 65001 >nul"]
|
||||
creationflags = subprocess.CREATE_NO_WINDOW
|
||||
else:
|
||||
env["TERM"] = "dumb"
|
||||
env["NO_COLOR"] = "1"
|
||||
env["PROMPT_COMMAND"] = ""
|
||||
env["PYTHONUNBUFFERED"] = "1" # Python REPL 不缓冲
|
||||
creationflags = 0
|
||||
|
||||
# 只有 bash 才加 --norc --noprofile
|
||||
shell_base = os.path.basename(self.shell)
|
||||
if shell_base in ("bash", "bash.exe"):
|
||||
env["PS1"] = ""
|
||||
shell_args = [self.shell, "--norc", "--noprofile"]
|
||||
elif "python" in shell_base:
|
||||
# Python 需要 -i 才能管道下交互
|
||||
shell_args = [self.shell, "-i"]
|
||||
else:
|
||||
shell_args = [self.shell]
|
||||
|
||||
try:
|
||||
self._proc = subprocess.Popen(
|
||||
shell_args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self.cwd,
|
||||
env=env,
|
||||
bufsize=0,
|
||||
creationflags=creationflags if _IS_WIN else 0,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError(f"Shell 不存在: {self.shell}")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"启动终端失败: {e}")
|
||||
|
||||
self._reader = threading.Thread(target=self._read_loop, daemon=True)
|
||||
self._reader.start()
|
||||
time.sleep(0.3)
|
||||
self.last_active = time.time()
|
||||
|
||||
def _read_loop(self):
|
||||
"""后台持续读取子进程 stdout"""
|
||||
proc = self._proc
|
||||
fd = proc.stdout.fileno()
|
||||
while proc.poll() is None:
|
||||
try:
|
||||
chunk = os.read(fd, 4096)
|
||||
if not chunk:
|
||||
break
|
||||
text = chunk.decode("utf-8", errors="replace")
|
||||
with self._lock:
|
||||
self._raw_buf += text
|
||||
for line in text.split("\n"):
|
||||
self._output_buf.append(line)
|
||||
except (OSError, ValueError):
|
||||
break
|
||||
|
||||
def send(self, text: str, wait: float = 0.3, timeout: float = 15) -> str:
|
||||
"""发送文本,返回后续输出"""
|
||||
if not self.alive():
|
||||
return "[错误] 终端进程已退出"
|
||||
|
||||
self.last_active = time.time()
|
||||
with self._lock:
|
||||
self._raw_buf = ""
|
||||
|
||||
try:
|
||||
self._proc.stdin.write(text.encode("utf-8"))
|
||||
self._proc.stdin.flush()
|
||||
except (OSError, BrokenPipeError) as e:
|
||||
return f"[错误] 发送失败: {e}"
|
||||
|
||||
return self._collect(wait, timeout)
|
||||
|
||||
def send_cmd(self, command: str, timeout: float = 15) -> str:
|
||||
"""
|
||||
发送命令并可靠等待输出结束。
|
||||
用临时文件做哨兵,避免 stdout 混入提示符。
|
||||
"""
|
||||
if not self.alive():
|
||||
return "[错误] 终端进程已退出"
|
||||
|
||||
self.last_active = time.time()
|
||||
with self._lock:
|
||||
self._raw_buf = ""
|
||||
|
||||
# 哨兵临时文件
|
||||
marker = f"/tmp/.xli_{uuid.uuid4().hex[:8]}" if not _IS_WIN else f"%TEMP%\\.xli_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
# 发送: 命令 + 哨兵文件创建
|
||||
payload = f"{command}\n{'touch' if not _IS_WIN else 'type nul >'} {marker}\n"
|
||||
try:
|
||||
self._proc.stdin.write(payload.encode("utf-8"))
|
||||
self._proc.stdin.flush()
|
||||
except (OSError, BrokenPipeError) as e:
|
||||
return f"[错误] 发送失败: {e}"
|
||||
|
||||
# 等待哨兵文件出现
|
||||
real_marker = marker if not _IS_WIN else os.path.expandvars(marker)
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if os.path.exists(real_marker):
|
||||
break
|
||||
time.sleep(0.03)
|
||||
|
||||
# 给 stdout 一点时间把剩余输出冲完
|
||||
time.sleep(0.1)
|
||||
|
||||
# 收集输出
|
||||
with self._lock:
|
||||
out = self._raw_buf
|
||||
self._raw_buf = ""
|
||||
|
||||
# 清理哨兵文件
|
||||
try:
|
||||
os.unlink(real_marker)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# 过滤掉命令回显和哨兵行
|
||||
lines = out.split("\n")
|
||||
result = []
|
||||
for line in lines:
|
||||
s = line.strip()
|
||||
if s == command.strip():
|
||||
continue
|
||||
if ".xli_" in s:
|
||||
continue
|
||||
if s.startswith("touch ") or s.startswith("type nul"):
|
||||
continue
|
||||
result.append(line)
|
||||
|
||||
out = self._clean("\n".join(result))
|
||||
with self._lock:
|
||||
self._last_cmd_output = out
|
||||
return out
|
||||
|
||||
def _collect(self, wait: float, timeout: float) -> str:
|
||||
"""收集输出"""
|
||||
time.sleep(wait)
|
||||
deadline = time.time() + timeout
|
||||
buf = ""
|
||||
while time.time() < deadline:
|
||||
with self._lock:
|
||||
new = self._raw_buf
|
||||
self._raw_buf = ""
|
||||
buf += new
|
||||
if new:
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
with self._lock:
|
||||
new = self._raw_buf
|
||||
self._raw_buf = ""
|
||||
buf += new
|
||||
if not new:
|
||||
break
|
||||
return self._clean(buf)
|
||||
|
||||
def _clean(self, text: str) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
# 去 ANSI 转义
|
||||
text = re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', text)
|
||||
text = re.sub(r'\x1b\][^\x07]*\x07', '', text)
|
||||
text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', text)
|
||||
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
||||
text = re.sub(r'\n{4,}', '\n\n\n', text)
|
||||
return text.strip()
|
||||
|
||||
def alive(self) -> bool:
|
||||
return self._proc is not None and self._proc.poll() is None
|
||||
|
||||
def recent(self, lines: int = 50) -> str:
|
||||
"""返回原始输出缓冲的最近几行"""
|
||||
with self._lock:
|
||||
buf = list(self._output_buf)[-lines:]
|
||||
return self._clean("\n".join(buf))
|
||||
|
||||
def status(self) -> dict:
|
||||
with self._lock:
|
||||
lines = len(self._last_cmd_output.split('\n')) if self._last_cmd_output else 0
|
||||
return {
|
||||
"id": self.id,
|
||||
"label": self.label,
|
||||
"shell": self.shell,
|
||||
"cwd": self.cwd,
|
||||
"alive": self.alive(),
|
||||
"pid": self._proc.pid if self._proc else None,
|
||||
"uptime": round(time.time() - self.created_at, 1),
|
||||
"idle": round(time.time() - self.last_active, 1),
|
||||
"lines": lines,
|
||||
}
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
if self._proc and self._proc.poll() is None:
|
||||
try:
|
||||
self._proc.stdin.write(b"exit\n")
|
||||
self._proc.stdin.flush()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.2)
|
||||
if self._proc.poll() is None:
|
||||
self._proc.terminate()
|
||||
try:
|
||||
self._proc.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self._proc = None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Liugin 插件
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Liugin:
|
||||
"""终端模拟器 — 纯标准库,全平台"""
|
||||
|
||||
def __init__(self):
|
||||
self.cli = None
|
||||
self._sessions: dict = {}
|
||||
self._default_id = "main"
|
||||
|
||||
def set_cli(self, cli):
|
||||
self.cli = cli
|
||||
|
||||
def get_tool_info(self):
|
||||
return {
|
||||
"name": "terminal",
|
||||
"description": "交互式终端模拟器 — 持久 Shell 会话,纯标准库实现,"
|
||||
"Windows/Linux/macOS 通用,支持 Python REPL、SSH 等交互式程序",
|
||||
"keywords": [
|
||||
"终端", "terminal", "shell", "交互", "会话", "REPL",
|
||||
"持久", "cmd", "powershell", "bash", "pty", "windows",
|
||||
],
|
||||
"usage": (
|
||||
"terminal <操作> [参数]\n\n"
|
||||
"操作:\n"
|
||||
" open [shell] [label] - 打开新终端 (默认: 系统 shell)\n"
|
||||
" send <命令> [--timeout N] - 发送命令并获取输出\n"
|
||||
" input <文本> - 发送纯输入 (不换行)\n"
|
||||
" read [行数] - 读取最近输出\n"
|
||||
" switch <ID> - 切换会话\n"
|
||||
" list - 列出所有会话\n"
|
||||
" close [ID] - 关闭会话\n"
|
||||
" status - 当前状态\n"
|
||||
" cwd [路径] - 获取/切换目录\n"
|
||||
" env <KEY> [VALUE] - 环境变量\n"
|
||||
" keys <按键名> - 发送特殊按键\n\n"
|
||||
"示例:\n"
|
||||
" terminal open - 打开默认 shell\n"
|
||||
" terminal open python3 \"Python\" - 打开 Python REPL\n"
|
||||
" terminal send dir - Windows 列目录\n"
|
||||
" terminal send ls -la - Linux 列目录\n"
|
||||
" terminal send ssh user@host - SSH 连接\n"
|
||||
" terminal keys Ctrl+C - 中断程序\n"
|
||||
" terminal close - 关闭当前会话"
|
||||
),
|
||||
}
|
||||
|
||||
def get_mcp_definition(self):
|
||||
return {
|
||||
"name": "terminal",
|
||||
"description": "交互式终端模拟器 — 持久 Shell 会话,支持交互式程序",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["open", "send", "input", "read", "switch",
|
||||
"list", "close", "status", "cwd", "env", "keys"],
|
||||
"description": "操作类型",
|
||||
},
|
||||
"command": {"type": "string", "description": "命令或文本"},
|
||||
"session_id": {"type": "string", "description": "会话 ID"},
|
||||
"shell": {"type": "string", "description": "Shell 路径"},
|
||||
"label": {"type": "string", "description": "会话标签"},
|
||||
"timeout": {"type": "number", "description": "超时秒数 (默认 15)"},
|
||||
"lines": {"type": "integer", "description": "读取行数"},
|
||||
"value": {"type": "string", "description": "环境变量值"},
|
||||
"keys": {"type": "string", "description": "特殊按键"},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
}
|
||||
|
||||
def convert_mcp_args(self, arguments):
|
||||
action = arguments.get("action", "")
|
||||
parts = [action]
|
||||
cmd = arguments.get("command", "")
|
||||
if cmd:
|
||||
parts.append(cmd)
|
||||
sid = arguments.get("session_id", "")
|
||||
if sid:
|
||||
parts.append(f"--sid {sid}")
|
||||
shell = arguments.get("shell", "")
|
||||
if shell:
|
||||
parts.append(f"--shell {shell}")
|
||||
label = arguments.get("label", "")
|
||||
if label:
|
||||
parts.append(f"--label {label}")
|
||||
timeout = arguments.get("timeout")
|
||||
if timeout is not None:
|
||||
parts.append(f"--timeout {timeout}")
|
||||
lines = arguments.get("lines")
|
||||
if lines is not None:
|
||||
parts.append(f"--lines {lines}")
|
||||
value = arguments.get("value")
|
||||
if value is not None:
|
||||
parts.append(value)
|
||||
keys = arguments.get("keys")
|
||||
if keys:
|
||||
parts.append(keys)
|
||||
return " ".join(parts)
|
||||
|
||||
_KEY_MAP = {
|
||||
"ctrl+c": "\x03", "ctrl+d": "\x04", "ctrl+z": "\x1a",
|
||||
"ctrl+l": "\x0c", "ctrl+a": "\x01", "ctrl+e": "\x05",
|
||||
"ctrl+k": "\x0b", "ctrl+u": "\x15", "ctrl+w": "\x17",
|
||||
"tab": "\t", "enter": "\n",
|
||||
"up": "\x1b[A", "down": "\x1b[B",
|
||||
"right": "\x1b[C", "left": "\x1b[D",
|
||||
"home": "\x1b[H", "end": "\x1b[F",
|
||||
"backspace": "\x7f", "esc": "\x1b",
|
||||
"pageup": "\x1b[5~", "pagedown": "\x1b[6~",
|
||||
}
|
||||
|
||||
def handle(self, args: str) -> str:
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
if not parts:
|
||||
return self._help()
|
||||
action = parts[0].lower()
|
||||
rest = parts[1].strip() if len(parts) > 1 else ""
|
||||
h = {
|
||||
"open": self._h_open, "send": self._h_send,
|
||||
"input": self._h_input, "read": self._h_read,
|
||||
"switch": self._h_switch, "list": self._h_list,
|
||||
"close": self._h_close, "status": self._h_status,
|
||||
"cwd": self._h_cwd, "env": self._h_env, "keys": self._h_keys,
|
||||
}.get(action)
|
||||
if not h:
|
||||
return f"错误: 不支持 '{action}'\n{self._help()}"
|
||||
try:
|
||||
return h(rest)
|
||||
except Exception as e:
|
||||
return f"终端错误: {e}"
|
||||
|
||||
def _help(self):
|
||||
return (
|
||||
"终端模拟器:\n"
|
||||
" open [shell] [label] - 打开新会话\n"
|
||||
" send <命令> - 执行命令\n"
|
||||
" input <文本> - 纯输入\n"
|
||||
" read [行数] - 读输出\n"
|
||||
" switch/list/close - 会话管理\n"
|
||||
" cwd/env/keys - 目录/变量/按键"
|
||||
)
|
||||
|
||||
def _get(self, sid: str = "") -> TerminalSession:
|
||||
sid = sid or self._default_id
|
||||
if sid not in self._sessions:
|
||||
self._sessions[sid] = TerminalSession(sid)
|
||||
return self._sessions[sid]
|
||||
|
||||
def _opts(self, rest: str) -> dict:
|
||||
opts = {}
|
||||
for m in re.finditer(r'--(\w+)\s+(\S+)', rest):
|
||||
opts[m.group(1)] = m.group(2)
|
||||
opts["_"] = re.sub(r'--\w+\s+\S+', '', rest).strip()
|
||||
return opts
|
||||
|
||||
def _h_open(self, rest: str) -> str:
|
||||
o = self._opts(rest)
|
||||
shell = o.get("shell", "")
|
||||
label = o.get("label", "")
|
||||
sid = o.get("sid", "")
|
||||
pos = o.get("_", "")
|
||||
if pos and not shell:
|
||||
ps = pos.split()
|
||||
shell = ps[0]
|
||||
if len(ps) > 1 and not label:
|
||||
label = ps[1]
|
||||
if not sid:
|
||||
sid = f"s{len(self._sessions) + 1}" if self._sessions else self._default_id
|
||||
try:
|
||||
s = TerminalSession(sid, shell=shell, label=label)
|
||||
self._sessions[sid] = s
|
||||
self._default_id = sid # 自动切换到新会话
|
||||
info = s.status()
|
||||
return (
|
||||
f"✓ 终端已打开\n"
|
||||
f" 会话: {sid} ({info['label']})\n"
|
||||
f" Shell: {info['shell']}\n"
|
||||
f" PID: {info['pid']}\n"
|
||||
f" 输出:\n{s.recent(10) or '(空)'}"
|
||||
)
|
||||
except RuntimeError as e:
|
||||
return f"✗ 打开失败: {e}"
|
||||
|
||||
def _h_send(self, rest: str) -> str:
|
||||
if not rest:
|
||||
return "错误: 请提供命令"
|
||||
o = self._opts(rest)
|
||||
cmd = o.get("_", rest).strip()
|
||||
timeout = float(o.get("timeout", "15"))
|
||||
if not cmd:
|
||||
cmd = re.sub(r'--timeout\s+\S+', '', rest).strip()
|
||||
if not cmd:
|
||||
return "错误: 请提供命令"
|
||||
s = self._get()
|
||||
out = s.send_cmd(cmd, timeout=timeout)
|
||||
tag = "✓" if s.alive() else "✗"
|
||||
return f"{tag} {cmd}\n{out}"
|
||||
|
||||
def _h_input(self, rest: str) -> str:
|
||||
if not rest:
|
||||
return "错误: 请提供文本"
|
||||
s = self._get()
|
||||
# 加换行让 REPL 执行
|
||||
out = s.send(rest + "\n", wait=0.5)
|
||||
with s._lock:
|
||||
s._last_cmd_output = out
|
||||
return f"← 输入: {rest}\n{out}"
|
||||
|
||||
def _h_read(self, rest: str) -> str:
|
||||
"""读取输出 — 优先用缓存,否则从原始缓冲读"""
|
||||
s = self._get()
|
||||
with s._lock:
|
||||
cached = s._last_cmd_output
|
||||
if cached:
|
||||
return cached
|
||||
# REPL 场景:从原始缓冲读
|
||||
return s.recent(30) or "(无输出)"
|
||||
|
||||
def _h_switch(self, rest: str) -> str:
|
||||
sid = rest.strip()
|
||||
if not sid:
|
||||
return "错误: 请提供会话 ID"
|
||||
if sid not in self._sessions:
|
||||
return f"错误: 不存在 '{sid}'。可用: {', '.join(self._sessions)}"
|
||||
self._default_id = sid
|
||||
s = self._sessions[sid]
|
||||
return f"✓ 已切换到 {sid} ({s.label})"
|
||||
|
||||
def _h_list(self, _: str) -> str:
|
||||
if not self._sessions:
|
||||
return "暂无终端。用 terminal open 打开。"
|
||||
lines = [f"终端 ({len(self._sessions)} 个):", ""]
|
||||
for sid, s in self._sessions.items():
|
||||
st = s.status()
|
||||
cur = " ← 当前" if sid == self._default_id else ""
|
||||
dot = "●" if st["alive"] else "○"
|
||||
lines.append(
|
||||
f" {dot} {sid}: {st['label']} "
|
||||
f"[{st['shell']}] "
|
||||
f"空闲{st['idle']}s "
|
||||
f"{st['lines']}行{cur}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _h_close(self, rest: str) -> str:
|
||||
sid = rest.strip() or self._default_id
|
||||
if sid not in self._sessions:
|
||||
return f"错误: 不存在 '{sid}'"
|
||||
s = self._sessions.pop(sid)
|
||||
s.close()
|
||||
if self._default_id == sid:
|
||||
self._default_id = next(iter(self._sessions), "main")
|
||||
return f"✓ 终端 {sid} ({s.label}) 已关闭"
|
||||
|
||||
def _h_status(self, _: str) -> str:
|
||||
s = self._get()
|
||||
st = s.status()
|
||||
return (
|
||||
f"终端状态:\n"
|
||||
f" 会话: {st['id']} ({st['label']})\n"
|
||||
f" Shell: {st['shell']}\n"
|
||||
f" PID: {st['pid']}\n"
|
||||
f" 目录: {st['cwd']}\n"
|
||||
f" 状态: {'运行中' if st['alive'] else '已退出'}\n"
|
||||
f" 运行: {st['uptime']}s 空闲: {st['idle']}s\n"
|
||||
f" 输出: {st['lines']}行"
|
||||
)
|
||||
|
||||
def _h_cwd(self, rest: str) -> str:
|
||||
s = self._get()
|
||||
target = rest.strip()
|
||||
if not target:
|
||||
return f"当前目录: {s.send_cmd('cd' if _IS_WIN else 'pwd', timeout=5)}"
|
||||
cmd = f"cd /d {target} && cd" if _IS_WIN else f"cd {target} && pwd"
|
||||
out = s.send_cmd(cmd, timeout=5)
|
||||
if out and not out.startswith("[错误"):
|
||||
s.cwd = out.strip().split("\n")[-1].strip()
|
||||
return f"✓ 目录: {s.cwd}"
|
||||
return f"切换失败: {out}"
|
||||
|
||||
def _h_env(self, rest: str) -> str:
|
||||
s = self._get()
|
||||
parts = rest.strip().split(maxsplit=1)
|
||||
if not parts:
|
||||
return s.send_cmd("set" if _IS_WIN else "env | head -30", timeout=5)
|
||||
key = parts[0]
|
||||
if len(parts) == 1:
|
||||
var = f"%{key}%" if _IS_WIN else f"${key}"
|
||||
return s.send_cmd(f"echo {var}", timeout=5)
|
||||
val = parts[1]
|
||||
cmd = f"set {key}={val}" if _IS_WIN else f"export {key}={val}"
|
||||
s.send_cmd(cmd, timeout=5)
|
||||
return f"✓ {key}={val}"
|
||||
|
||||
def _h_keys(self, rest: str) -> str:
|
||||
if not rest:
|
||||
return f"可用: {', '.join(self._KEY_MAP)}"
|
||||
s = self._get()
|
||||
name = rest.strip().lower()
|
||||
seq = self._KEY_MAP.get(name)
|
||||
if not seq:
|
||||
return f"错误: 未知按键 '{name}'\n可用: {', '.join(self._KEY_MAP)}"
|
||||
out = s.send(seq, wait=0.3)
|
||||
return f"← {name}\n{out}"
|
||||
Reference in new issue
Block a user