Files
oc_session/oc.py
T

254 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
oc.py —— OC 工具箱统一入口(数字 / 中文菜单)
启动即打印菜单,输入编号或中文都能进:
0 / 返回 / 退出 / q 退出
1 / 技能 / 技能管理 / skill 进入技能管理(列表 / 推送 / 拉取 / 目录)
2 / 连接器 / 连接器管理 / mcp 进入连接器管理(状态 / 开关 / 模式 / 备份)
也支持免菜单直传子命令(等价旧的分散入口):
python oc.py skill list
python oc.py mcp status
python oc.py list (自动识别归属到技能管理)
底层实现复用同目录 ocskill.py / mcpctl.py,本文件只负责交互编排。
"""
import os
import sys
# 保证同目录模块可导入(无论从哪个工作目录启动)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import mcpctl # noqa: E402
import ocskill # noqa: E402
# ---------------------------------------------------------------- 控制台
def fix_console() -> None:
"""Windows 控制台中文输出兜底(UTF-8 重配)。"""
for stream_name in ("stdout", "stderr"):
stream = getattr(sys, stream_name, None)
reconf = getattr(stream, "reconfigure", None)
if callable(reconf):
try:
reconf(encoding="utf-8", errors="replace")
except Exception:
pass
# ---------------------------------------------------------------- 输入解析
def _norm(text: str) -> str:
return (text or "").strip().lower().replace(" ", "")
# 顶层别名:值 -> 传给对应函数的“行为键”
TOP_ALIASES = {
"0": "quit", "0.": "quit", "q": "quit", "quit": "quit", "exit": "quit",
"退出": "quit", "返回": "quit", "回": "quit",
"1": "skill", "1.": "skill", "skill": "skill", "skills": "skill",
"技能": "skill", "技能管理": "skill", "管理技能": "skill", "技": "skill",
"2": "mcp", "2.": "mcp", "mcp": "mcp", "连接器": "mcp",
"连接器管理": "mcp", "管理连接器": "mcp", "连接": "mcp", "连": "mcp",
"connector": "mcp", "connectors": "mcp",
}
SKILL_ALIASES = {
"0": "back", "0.": "back", "b": "back", "back": "back", "返回": "back", "回": "back",
"1": "list", "1.": "list", "list": "list", "列表": "list", "查看": "list",
"技能列表": "list", "看列表": "list",
"2": "push", "2.": "push", "push": "push", "推送": "push", "上传": "push", "推": "push",
"3": "pull", "3.": "pull", "pull": "pull", "拉取": "pull", "下载": "pull", "拉": "pull",
"4": "dirs", "4.": "dirs", "dirs": "dirs", "目录": "dirs", "状态": "dirs",
"技能目录": "dirs",
}
MCP_ALIASES = {
"0": "back", "0.": "back", "b": "back", "back": "back", "返回": "back", "回": "back",
"1": "status", "1.": "status", "status": "status", "状态": "status", "查看": "status",
"连接器状态": "status",
"2": "on", "2.": "on", "on": "on", "开": "on", "开启": "on", "打开": "on",
"3": "off", "3.": "off", "off": "off", "关": "off", "关闭": "off",
"4": "mode", "4.": "mode", "mode": "mode", "模式": "mode",
"5": "backup", "5.": "backup", "backup": "backup", "备份": "backup",
}
def ask(prompt: str) -> str:
try:
return input(prompt)
except (EOFError, KeyboardInterrupt):
print()
return "0"
def match(raw: str, aliases: dict, default: str | None = None) -> str | None:
key = _norm(raw)
if key in aliases:
return aliases[key]
# 中文模糊包含:输入“技能管理菜单”也能命中“技能管理”
for k, v in aliases.items():
if k and k in key and not k.isdigit():
return v
return default
def pause(msg: str = "按回车返回…") -> None:
try:
input(msg)
except (EOFError, KeyboardInterrupt):
print()
def banner(title: str) -> None:
print()
print("=" * 44)
print(" " + title)
print("=" * 44)
# ---------------------------------------------------------------- 子菜单:技能管理
def menu_skill(shared: dict) -> None:
while True:
banner("技能管理(skill)")
print(" 0. 返回上层")
print(" 1. 查看技能列表")
print(" 2. 推送技能到 OC(zip / 目录 / SKILL.md / base64)")
print(" 3. 拉取技能到本机")
print(" 4. 查看技能目录与连接状态")
raw = ask("请选择(编号或中文): ")
act = match(raw, SKILL_ALIASES)
if act is None:
print(" 没看懂「%s」,输入 0-4 或中文,如:列表 / 推送 / 拉取" % raw.strip())
continue
if act == "back":
return
if act == "list":
ocskill.main(["list"])
elif act == "dirs":
ocskill.main(["dirs"])
elif act == "push":
src = ask(" 技能路径(文件 / 目录 / zip,回车取消): ").strip()
if not src:
continue
argv = ["push", src]
if match(ask(" 同名覆盖?(y/N): "), {"y": "y", "yes": "y", "是": "y"}, None) == "y":
argv.append("--force")
if match(ask(" 只校验不写入?(y/N): "), {"y": "y", "yes": "y", "是": "y"}, None) == "y":
argv.append("--dry-run")
ocskill.main(argv)
elif act == "pull":
name = ask(" 要拉取的技能名(回车取消): ").strip()
if not name:
continue
argv = ["pull", name]
out = ask(" 输出目录(回车=当前目录): ").strip()
if out:
argv += ["--out", out]
if match(ask(" 覆盖本地同名目录?(y/N): "), {"y": "y", "yes": "y", "是": "y"}, None) == "y":
argv.append("--force")
ocskill.main(argv)
pause()
# ---------------------------------------------------------------- 子菜单:连接器管理
def menu_mcp(shared: dict) -> None:
# 目标(本机 / WSL)本次会话记住,子菜单里随时可换
target = shared.get("target", "local")
while True:
banner("连接器管理(MCP,wb 语境的“连接器”)")
print(" 当前目标: %s" % ("本机" if target == "local" else "WSL"))
print(" 0. 返回上层")
print(" 1. 查看状态(模式 + 开关)")
print(" 2. 开启连接器(对调为含 MCP 配置 + 热重载)")
print(" 3. 关闭连接器(对调为安静配置 + 热重载)")
print(" 4. 查看 / 修改模式(自动 / 手动 / 关闭)")
print(" 5. 备份当前 mcpcfg.txt")
print(" t. 切换目标(本机 <-> WSL)")
raw = ask("请选择(编号或中文): ")
key = _norm(raw)
if key in ("t", "切换", "target", "目标"):
target = "wsl" if target == "local" else "local"
shared["target"] = target
print(" 已切换目标 -> %s" % ("本机" if target == "local" else "WSL"))
continue
act = match(raw, MCP_ALIASES)
if act is None:
print(" 没看懂「%s」,输入 0-5 / t 或中文,如:状态 / 开启 / 关闭 / 模式" % raw.strip())
continue
if act == "back":
return
base = ["--target", target]
if act == "status":
mcpctl.main(base + ["status"])
elif act == "on":
mcpctl.main(base + ["on"])
elif act == "off":
mcpctl.main(base + ["off"])
elif act == "backup":
mcpctl.main(base + ["backup"])
elif act == "mode":
val = ask(" 新模式(自动 / 手动 / 关闭,回车只查看): ").strip()
mcpctl.main(base + ["mode"] + ([val] if val else []))
pause()
# ---------------------------------------------------------------- 顶层菜单
def menu_top() -> None:
shared: dict = {"target": "local"}
while True:
banner("OC 工具箱(输入编号或中文,0 退出)")
print(" 0. 退出")
print(" 1. 技能管理(skill 拉取 / 推送)")
print(" 2. 连接器管理(MCP 开关 / 模式)")
raw = ask("请选择: ")
act = match(raw, TOP_ALIASES)
if act is None:
print(" 没看懂「%s」,输入 0 / 1 / 2,或:退出 / 技能管理 / 连接器管理" % raw.strip())
continue
if act == "quit":
print("再见。")
return
if act == "skill":
menu_skill(shared)
elif act == "mcp":
menu_mcp(shared)
# ---------------------------------------------------------------- 直传模式
SKILL_CMDS = {"list", "push", "pull", "dirs"}
MCP_CMDS = {"status", "on", "off", "mode", "backup"}
def dispatch(argv: list[str]) -> int:
"""有参数时免菜单直传(兼容旧分散入口的用法)。"""
head = _norm(argv[0]) if argv else ""
rest = argv[1:]
if head in ("skill", "skills", "技能", "技能管理"):
return ocskill.main(rest)
if head in ("mcp", "连接器", "连接器管理"):
return mcpctl.main(rest)
if head in SKILL_CMDS:
return ocskill.main(argv)
if head in MCP_CMDS:
return mcpctl.main(argv)
print("未知命令: %s(技能: %s;连接器: %s)" % (
argv[0], "/".join(sorted(SKILL_CMDS)), "/".join(sorted(MCP_CMDS))))
return 2
def main() -> int:
fix_console()
if len(sys.argv) > 1:
return dispatch(sys.argv[1:])
menu_top()
return 0
if __name__ == "__main__":
sys.exit(main())