CLI合并为oc.py/oc.bat菜单入口(数字+中文),mcpctl支持键值mcpcfg与on/off标记;删除6个分散bat

This commit is contained in:
ypshidifu
2026-09-23 13:47:10 +08:00
parent 35513f1cdf
commit d56cc01058
10 changed files with 417 additions and 148 deletions
+253
View File
@@ -0,0 +1,253 @@
#!/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())