技能编号确认协议:1/1.进二次确认打印名字简介(confirm执行/cancel回退),1 -confirm跳过;选来源/目标即出带编号列表,推送支持编号选本机技能
This commit is contained in:
@@ -194,6 +194,32 @@ def _gate_unc(root: str, action: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _confirm_sel(idx: int, names: list[str], root: str, extra: str = "") -> bool:
|
||||
"""编号选择的二次确认:打印 skill 名字 + 简介,confirm 执行 / cancel 回退。
|
||||
(`N -confirm` 已在解析层跳过本步;这里只处理 `N` / `N.` 的常规确认。)
|
||||
"""
|
||||
name = names[idx - 1]
|
||||
print(" 已选 %d:%s" % (idx, name))
|
||||
brief = ocskill.skill_brief(name, root)
|
||||
if brief.get("ok"):
|
||||
desc = (brief.get("description") or "").replace("\n", " ").strip()
|
||||
if desc:
|
||||
print(" 简介:%s" % (desc[:100] + ("..." if len(desc) > 100 else "")))
|
||||
print(" 路径:%s" % brief.get("path"))
|
||||
else:
|
||||
print(" %s" % brief.get("err"))
|
||||
if extra:
|
||||
print(" %s" % extra)
|
||||
while True:
|
||||
ans = _norm(ask(" 输入 confirm 执行 / cancel 回退: "))
|
||||
if ans in ("confirm", "确认", "确定", "yes", "y"):
|
||||
return True
|
||||
if ans in ("cancel", "取消", "0", "返回", "回", "b", "back", ""):
|
||||
print(" 已回退。")
|
||||
return False
|
||||
print(" 没看懂「%s」:confirm 执行,cancel 回退。" % ans)
|
||||
|
||||
|
||||
def _pull_one(name: str, src: dict) -> None:
|
||||
"""按已解析来源拉取一个 skill:探查 -> 无同名直接拉;确有同名才问一次覆盖。"""
|
||||
probe = ocskill.probe_pull(name, root=src["dir"])
|
||||
@@ -216,7 +242,9 @@ def _pull_one(name: str, src: dict) -> None:
|
||||
|
||||
|
||||
def _flow_push() -> None:
|
||||
"""推送 / 覆盖更新(宝宝级):选目标终端 -> 输入 -> 校验规范 -> 探查同名
|
||||
"""推送 / 覆盖更新(宝宝级):选目标终端 -> 打印本机 skill 编号列表 ->
|
||||
输编号选技能(1 / 1. 二次确认打印名字+简介,confirm 执行 / cancel 回退;
|
||||
1 -confirm 跳过二次确认)或直接贴路径 / zip / SKILL.md -> 校验规范 -> 探查同名
|
||||
-> 确有同名才问一次覆盖 -> 写入。
|
||||
目标没有同名文件夹时直接写(没有目录和内容,覆盖无从谈起,问了也是白问)。
|
||||
"""
|
||||
@@ -225,9 +253,31 @@ def _flow_push() -> None:
|
||||
return
|
||||
if not _gate_unc(tgt["dir"], "push"):
|
||||
return
|
||||
src = ocskill.clean_input(ask(" 技能输入(路径 / zip / SKILL.md,可带引号,回车取消): "))
|
||||
# 选完目标就打本机 skill 列表(带编号)——push 要推的是本机技能
|
||||
local_names = ocskill.list_skill_names(ocskill.WIN_LOCAL_ROOT)
|
||||
if local_names:
|
||||
ocskill.render_page(local_names, 1, ocskill.WIN_LOCAL_ROOT)
|
||||
print(" 提示:编号 1 / 1. 会打印名字+简介二次确认(confirm 执行 / cancel 回退),"
|
||||
"1 -confirm 跳过确认;也可以直接贴路径 / zip / SKILL.md")
|
||||
src = ocskill.clean_input(ask(" 技能输入(编号 / 路径 / zip / SKILL.md,可带引号,回车取消): "))
|
||||
if not src:
|
||||
return
|
||||
# 0) 编号选择本机 skill(协议:1 / 1. 二次确认,1 -confirm 跳过)
|
||||
sel = ocskill.parse_sel(src)
|
||||
if sel is not None:
|
||||
if not local_names:
|
||||
print(" 本机 skill 目录为空,编号没得选;请直接贴路径 / zip。")
|
||||
return
|
||||
idx, skip = sel
|
||||
if not (1 <= idx <= len(local_names)):
|
||||
print(" 序号超出范围(1-%d)。" % len(local_names))
|
||||
return
|
||||
if not (skip or _confirm_sel(idx, local_names, ocskill.WIN_LOCAL_ROOT,
|
||||
extra="目标:%s" % tgt["dir"])):
|
||||
print(" 已取消推送。")
|
||||
return
|
||||
src = os.path.join(ocskill.WIN_LOCAL_ROOT, local_names[idx - 1])
|
||||
print(" 选定:「%s」 -> %s" % (local_names[idx - 1], src))
|
||||
# 1) 先过一遍规范校验(只读探查,不写盘)
|
||||
probe = ocskill.probe_push(src, root=tgt["dir"])
|
||||
if not probe.get("ok"):
|
||||
@@ -256,8 +306,10 @@ def _flow_push() -> None:
|
||||
|
||||
|
||||
def _flow_pull() -> None:
|
||||
"""拉取(宝宝级二选一):选来源 -> 记得名字直接拉;不记得回车进分页列表
|
||||
(每页 15 个,全局序号跨页连续)—— 翻页命令 / 序号 / 直接名字 / q 退出。
|
||||
"""拉取(宝宝级):选来源 -> 立即打印带编号的 skill 列表(每页 15 个,全局序号
|
||||
跨页连续)—— 记得名字直接输名字;不记得输编号:1 / 1. 进二次确认(打印 skill
|
||||
名字 + 简介,confirm 执行 / cancel 回退列表),1 -confirm 跳过二次确认;
|
||||
翻页:下一页/上一页/第N页/next/last;q 退出。
|
||||
输出固定为本机 OC skill 目录;列表直读文件夹,不启动 OC 服务、不发请求;
|
||||
覆盖只在确有同名目录时问一次。
|
||||
"""
|
||||
@@ -266,23 +318,25 @@ def _flow_pull() -> None:
|
||||
return
|
||||
if not _gate_unc(src["dir"], "pull"):
|
||||
return
|
||||
name = ocskill.clean_input(ask(" 技能名(记得就直接输;不记得回车看列表): "))
|
||||
if name:
|
||||
_pull_one(name, src)
|
||||
return
|
||||
# ---- 不记得名字:分页列表循环(直读文件夹,不启动 OC 服务)----
|
||||
root = src["dir"]
|
||||
names = ocskill.list_skill_names(root)
|
||||
if not names:
|
||||
# 目录为空:没得列,退化成纯名字输入(名字直拉仍然有效)
|
||||
print(" 这个来源下还没有 skill:%s" % root)
|
||||
name = ocskill.clean_input(ask(" 技能名(直接输名字试试,回车取消): "))
|
||||
if name:
|
||||
_pull_one(name, src)
|
||||
return
|
||||
print(" 提示:名字直接拉;编号 1 / 1. 会打印名字+简介二次确认(confirm 执行 / "
|
||||
"cancel 回退),1 -confirm 跳过确认")
|
||||
page = 1
|
||||
while True:
|
||||
# 选完来源第一次进循环就把列表(带编号)打出来
|
||||
page, pages = ocskill.render_page(names, page, root)
|
||||
cmd = ocskill.clean_input(ask(
|
||||
" 拉取:输序号或名字;翻页:下一页/上一页/第N页/next/last;q 退出: "))
|
||||
" 拉取:输名字或序号;翻页:下一页/上一页/第N页/next/last;q 退出: "))
|
||||
if not cmd:
|
||||
print(" (空输入。序号或名字 = 拉取,下一页/上一页 = 翻页,q = 退出)")
|
||||
print(" (空输入。名字/序号 = 拉取,下一页/上一页 = 翻页,q = 退出)")
|
||||
continue
|
||||
if _norm(cmd) in ("q", "quit", "退出", "取消", "0", "b", "back", "返回"):
|
||||
print(" 已退出列表,未拉取。")
|
||||
@@ -292,15 +346,19 @@ def _flow_pull() -> None:
|
||||
if np is not None:
|
||||
page = np
|
||||
continue
|
||||
# 2) 纯数字 = 全局序号(跨页连续)
|
||||
if cmd.isdigit():
|
||||
idx = int(cmd)
|
||||
if 1 <= idx <= len(names):
|
||||
_pull_one(names[idx - 1], src)
|
||||
return
|
||||
# 2) 编号选择(协议:1 / 1. 二次确认;1 -confirm 跳过确认)
|
||||
sel = ocskill.parse_sel(cmd)
|
||||
if sel is not None:
|
||||
idx, skip = sel
|
||||
if not (1 <= idx <= len(names)):
|
||||
print(" 序号超出范围(1-%d),重输。" % len(names))
|
||||
continue
|
||||
# 3) 直接输名字
|
||||
if skip or _confirm_sel(idx, names, root):
|
||||
_pull_one(names[idx - 1], src)
|
||||
return
|
||||
# cancel -> 回退,留在列表重选
|
||||
continue
|
||||
# 3) 直接输名字(记得名字不需确认,直接拉)
|
||||
if ocskill.NAME_RE.match(cmd):
|
||||
if ocskill.probe_pull(cmd, root=root).get("ok"):
|
||||
_pull_one(cmd, src)
|
||||
@@ -343,8 +401,10 @@ def menu_skill(shared: dict) -> None:
|
||||
banner("技能管理(skill)")
|
||||
print(" 0. 返回上层")
|
||||
print(" 1. 查看技能列表")
|
||||
print(" 2. 推送 / 覆盖更新技能到目标终端(回车=wsl,简称 wb / oc,或贴目录)")
|
||||
print(" 3. 拉取技能到本机 OC(选来源;不记得名字回车看分页列表,每页 15 个)")
|
||||
print(" 2. 推送 / 覆盖更新技能到目标终端(选完列本机编号:1 / 1. 二次确认,"
|
||||
"1 -confirm 跳过;也可贴路径/zip)")
|
||||
print(" 3. 拉取技能到本机 OC(选来源即出编号列表:名字直拉;1 / 1. 二次确认,"
|
||||
"1 -confirm 跳过确认)")
|
||||
print(" 4. 查看技能目录与连接状态")
|
||||
print(" 5. 删除技能(先挑 .trash/ 备份,可恢复)")
|
||||
print(" 提示:任意位置输入 /home 或「返回首页」直接回顶层菜单")
|
||||
|
||||
+50
@@ -565,6 +565,28 @@ def list_skill_names(root) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def parse_sel(text) -> tuple[int, bool] | None:
|
||||
"""编号选择解析(确认协议)-> (全局序号, 是否跳过二次确认);不是编号输入返回 None。
|
||||
|
||||
协议(推进去 / 取回来两端通用):
|
||||
'1' / '1.' -> (1, False) 数字后带点号 = 确认选择它,进二次确认
|
||||
(打印 skill 名字 + 简介,confirm 执行 / cancel 回退)
|
||||
'1 -confirm' -> (1, True) 跳过二次确认,直接执行('-confirm' 大小写不敏感,
|
||||
也认 '1.-confirm' / '1-confirm' / '--confirm')
|
||||
名字 / 翻页 / 路径 -> None(交给调用方的其它分支)
|
||||
纯 '0' 也命中 (0, False) —— 调用方若把 0 当退出,必须在本函数之前先拦。
|
||||
序号范围由调用方按列表长度校验。
|
||||
"""
|
||||
s = (text or "").strip()
|
||||
if not s:
|
||||
return None
|
||||
m = re.fullmatch(r"(\d+)\s*\.?\s*(?:-{1,2}confirm)?", s, re.IGNORECASE)
|
||||
if not m:
|
||||
return None
|
||||
skip = re.search(r"-{1,2}confirm", s, re.IGNORECASE) is not None
|
||||
return int(m.group(1)), skip
|
||||
|
||||
|
||||
def parse_page_cmd(text, page: int, total: int) -> int | None:
|
||||
"""翻页命令 -> 目标页码(已钳制 [1, total]);不是翻页命令返回 None。
|
||||
|
||||
@@ -669,6 +691,34 @@ def probe_push(src: str, distro: str = DEFAULT_DISTRO, b64: bool = False,
|
||||
return {"ok": True, "name": name, "dest": str(dest), "exists": exists,
|
||||
"description": meta.get("description", "")}
|
||||
|
||||
def skill_brief(name: str, root) -> dict:
|
||||
"""读一个 skill 的名字 + 简介 + 路径(只读,供编号选择的二次确认打印)。
|
||||
|
||||
简介取 SKILL.md frontmatter 的 description(用现成的 find_skill_md + _frontmatter,
|
||||
兼容 SKILL.md 在子层 / 块标量描述);目录不存在或名字非法返回 ok=False + err。
|
||||
"""
|
||||
name = clean_input(name or "")
|
||||
if not name:
|
||||
return {"ok": False, "err": "技能名为空"}
|
||||
if not NAME_RE.match(name):
|
||||
return {"ok": False, "err": f"非法 skill 名称「{name}」"}
|
||||
folder = Path(root) / name
|
||||
try:
|
||||
exists = folder.is_dir()
|
||||
except OSError:
|
||||
exists = False
|
||||
if not exists:
|
||||
return {"ok": False, "err": f"不存在该 skill:{folder}"}
|
||||
desc = ""
|
||||
md = find_skill_md(folder)
|
||||
if md is not None:
|
||||
try:
|
||||
desc = _frontmatter(md.read_text(encoding="utf-8", errors="replace")).get("description", "")
|
||||
except OSError:
|
||||
desc = ""
|
||||
return {"ok": True, "name": name, "description": desc, "path": str(folder)}
|
||||
|
||||
|
||||
def probe_pull(name: str, distro: str = DEFAULT_DISTRO, root: str | None = None) -> dict:
|
||||
"""拉取前探查(只读):来源端是否存在该 skill + 本机是否已有同名目录。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user