#!/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())