推送选来源即列该目录skill+翻页绝对页码越界拒绝+空目录缺SKILL.md提示+同目录拦截,35项测试全绿
This commit is contained in:
@@ -242,23 +242,26 @@ def _pull_one(name: str, src: dict) -> None:
|
||||
|
||||
|
||||
def _flow_push() -> None:
|
||||
"""推送 / 覆盖更新(宝宝级):选目标终端 -> 打印本机 skill 编号列表(带翻页)->
|
||||
翻页(下一页/上一页/第N页/next/last,与拉取端同一条 parse_page_cmd)、
|
||||
"""推送 / 覆盖更新(宝宝级):选【来源】终端 -> 立即打印该来源 skill 编号列表
|
||||
(列表跟随所选目录:选 wb 列 wb,2026-09-24 起不再硬编码本机 OC 目录;带翻页)->
|
||||
翻页(下一页/上一页/第N页/next/last,与拉取端同一条 parse_page_cmd,越界拒绝跳转)、
|
||||
输编号选技能(1 / 1. 二次确认打印名字+简介,confirm 执行 / cancel 回列表;
|
||||
1 -confirm 跳过二次确认)或直接贴路径 / zip / SKILL.md -> 校验规范 -> 探查同名
|
||||
-> 确有同名才问一次覆盖 -> 写入。
|
||||
1 -confirm 跳过二次确认)或直接贴路径 / zip / SKILL.md -> 选【目标】终端 ->
|
||||
校验规范 -> 探查同名 -> 确有同名才问一次覆盖 -> 写入。
|
||||
目标没有同名文件夹时直接写(没有目录和内容,覆盖无从谈起,问了也是白问)。
|
||||
来源与目标同目录时拦掉(自己推自己没有意义)。
|
||||
"""
|
||||
tgt = _pick_agent(" 目标终端(回车=wsl,简称如 wb / oc,或贴完整目录,0/q 取消): ", "目标")
|
||||
if tgt is None:
|
||||
root_pick = _pick_agent(" 来源终端(列它的 skill,回车=wsl,简称如 wb / oc,或贴完整目录,0/q 取消): ", "来源")
|
||||
if root_pick is None:
|
||||
return
|
||||
if not _gate_unc(tgt["dir"], "push"):
|
||||
if not _gate_unc(root_pick["dir"], "push"):
|
||||
return
|
||||
# 选完目标就打本机 skill 列表(带编号)——push 要推的是本机技能
|
||||
local_names = ocskill.list_skill_names(ocskill.WIN_LOCAL_ROOT)
|
||||
# 列表 = 所选来源目录的 skill(选 wb 就列 wb —— 用户协议:选了目录就把 skill 打出来带编号)
|
||||
root = root_pick["dir"]
|
||||
local_names = ocskill.list_skill_names(root)
|
||||
src = None
|
||||
if not local_names:
|
||||
# 本机没列表可打:退化成单次输入(没有编号/翻页可言)
|
||||
# 所选目录没列表可打:退化成单次输入(没有编号/翻页可言)
|
||||
src = ocskill.clean_input(ask(" 技能输入(路径 / zip / SKILL.md,可带引号,回车取消): "))
|
||||
if not src:
|
||||
return
|
||||
@@ -268,10 +271,10 @@ def _flow_push() -> None:
|
||||
"也可直接贴路径 / zip / SKILL.md")
|
||||
page = 1
|
||||
while True:
|
||||
# 每次进循环先把当前页(带编号)打出来 —— 含首次进入
|
||||
page, pages = ocskill.render_page(local_names, page, ocskill.WIN_LOCAL_ROOT)
|
||||
# 每次进循环先把当前页(带编号)打出来 —— 含首次进入(目录 = 所选来源)
|
||||
page, pages = ocskill.render_page(local_names, page, root)
|
||||
cmd = ocskill.clean_input(ask(
|
||||
" 技能输入:翻页(下一页/上一页/第N页/next/last);编号选本机技能;"
|
||||
" 技能输入:翻页(下一页/上一页/第N页/next/last);编号选来源技能;"
|
||||
"或贴路径 / zip / SKILL.md;q 取消: "))
|
||||
if not cmd:
|
||||
print(" 已取消推送(回车=取消;翻页 / 编号 / 路径都能输)。")
|
||||
@@ -279,23 +282,27 @@ def _flow_push() -> None:
|
||||
if _norm(cmd) in ("q", "quit", "退出", "取消", "0", "b", "back", "返回"):
|
||||
print(" 已取消推送。")
|
||||
return
|
||||
# 1) 翻页命令(与拉取端同一条 parse_page_cmd:中英双语全支持)
|
||||
# 1) 翻页命令(与拉取端同一条 parse_page_cmd:中英双语全支持;越界拒绝跳转)
|
||||
np = ocskill.parse_page_cmd(cmd, page, pages)
|
||||
if np is not None:
|
||||
if not (1 <= np <= pages):
|
||||
print(" 共 %d 页,没有第 %d 页(当前停在第 %d 页)。"
|
||||
% (pages, np, page))
|
||||
continue
|
||||
page = np
|
||||
continue
|
||||
# 2) 编号选择本机 skill(协议:1 / 1. 二次确认,1 -confirm 跳过)
|
||||
# 2) 编号选择来源 skill(协议:1 / 1. 二次确认,1 -confirm 跳过)
|
||||
sel = ocskill.parse_sel(cmd)
|
||||
if sel is not None:
|
||||
idx, skip = sel
|
||||
if not (1 <= idx <= len(local_names)):
|
||||
print(" 序号超出范围(1-%d),重输。" % len(local_names))
|
||||
continue
|
||||
if not (skip or _confirm_sel(idx, local_names, ocskill.WIN_LOCAL_ROOT,
|
||||
extra="目标:%s" % tgt["dir"])):
|
||||
if not (skip or _confirm_sel(idx, local_names, root,
|
||||
extra="来源:%s" % root)):
|
||||
print(" 已回退到列表,未推送。")
|
||||
continue
|
||||
src = os.path.join(ocskill.WIN_LOCAL_ROOT, local_names[idx - 1])
|
||||
src = os.path.join(root, local_names[idx - 1])
|
||||
print(" 选定:「%s」 -> %s" % (local_names[idx - 1], src))
|
||||
break
|
||||
# 3) 其余输入 = 路径 / zip / SKILL.md,交给下面的规范校验去判
|
||||
@@ -303,10 +310,29 @@ def _flow_push() -> None:
|
||||
break
|
||||
if not src:
|
||||
return
|
||||
# 技能选定后才选目标终端(列表已按所选来源打出)
|
||||
tgt = _pick_agent(" 目标终端(推送到哪,回车=wsl,简称如 wb / oc,或贴完整目录,0/q 取消): ", "目标")
|
||||
if tgt is None:
|
||||
print(" 已取消推送(技能已选好,未写入任何文件)。")
|
||||
return
|
||||
if not _gate_unc(tgt["dir"], "push"):
|
||||
return
|
||||
# 来源与目标同目录 = 自己推自己:拦掉(推了等于没推,-f 还可能自覆盖搞坏)
|
||||
if os.path.isdir(src) and os.path.normcase(
|
||||
os.path.abspath(os.path.dirname(os.path.abspath(src)))) == \
|
||||
os.path.normcase(os.path.abspath(tgt["dir"])):
|
||||
print(" 来源与目标是同一个目录(%s),自己推自己没有意义,已取消。"
|
||||
% os.path.abspath(tgt["dir"]))
|
||||
return
|
||||
# 1) 先过一遍规范校验(只读探查,不写盘)
|
||||
probe = ocskill.probe_push(src, root=tgt["dir"])
|
||||
if not probe.get("ok"):
|
||||
print(" 校验未通过:%s" % probe.get("err"))
|
||||
err = probe.get("err") or ""
|
||||
if os.path.isdir(src) and ocskill.find_skill_md(ocskill.Path(src)) is None:
|
||||
print(" 该目录缺 SKILL.md(空目录或不完整 skill),无法推送:%s" % src)
|
||||
print(" (提示:回列表换一个,或直接贴完整 skill 的路径。)")
|
||||
else:
|
||||
print(" 校验未通过:%s" % err)
|
||||
return
|
||||
name = probe["name"]
|
||||
desc = (probe.get("description") or "").replace("\n", " ")
|
||||
@@ -369,6 +395,10 @@ def _flow_pull() -> None:
|
||||
# 1) 翻页命令(中英都认:下一页/第N页/上一页、next/last/N page)
|
||||
np = ocskill.parse_page_cmd(cmd, page, pages)
|
||||
if np is not None:
|
||||
if not (1 <= np <= pages):
|
||||
print(" 共 %d 页,没有第 %d 页(当前停在第 %d 页)。"
|
||||
% (pages, np, page))
|
||||
continue
|
||||
page = np
|
||||
continue
|
||||
# 2) 编号选择(协议:1 / 1. 二次确认;1 -confirm 跳过确认)
|
||||
@@ -426,8 +456,8 @@ def menu_skill(shared: dict) -> None:
|
||||
banner("技能管理(skill)")
|
||||
print(" 0. 返回上层")
|
||||
print(" 1. 查看技能列表")
|
||||
print(" 2. 推送 / 覆盖更新技能到目标终端(选完列本机编号:1 / 1. 二次确认,"
|
||||
"1 -confirm 跳过;也可贴路径/zip)")
|
||||
print(" 2. 推送 / 覆盖更新技能(选来源即出编号列表:1 / 1. 二次确认,"
|
||||
"1 -confirm 跳过;也可贴路径/zip;再选推送到哪)")
|
||||
print(" 3. 拉取技能到本机 OC(选来源即出编号列表:名字直拉;1 / 1. 二次确认,"
|
||||
"1 -confirm 跳过确认)")
|
||||
print(" 4. 查看技能目录与连接状态")
|
||||
|
||||
+13
-6
@@ -588,33 +588,40 @@ def parse_sel(text) -> tuple[int, bool] | None:
|
||||
|
||||
|
||||
def parse_page_cmd(text, page: int, total: int) -> int | None:
|
||||
"""翻页命令 -> 目标页码(已钳制 [1, total]);不是翻页命令返回 None。
|
||||
"""翻页命令 -> 目标页码;不是翻页命令返回 None。
|
||||
|
||||
中文:下一页 / 上一页 / 第N页 / N页(N 可为中文数词,如 第三页);
|
||||
英文:next / last / prev / N page(three page / 3 page)。
|
||||
纯数字不算翻页 —— 留给「按全局序号选择 skill」。
|
||||
|
||||
越界语义(2026-09-24 起):
|
||||
- 相对翻页(下一页/上一页/next/last):钳制在 [1, total],边界静默停住;
|
||||
- 绝对页码(第N页 / N页 / 中文数词 / N page):返回【原始】页码,不钳制 ——
|
||||
越界交给调用方报错(「共 X 页,没有第 N 页」)并拒绝跳转,
|
||||
不再像旧行为那样把第 999 页悄悄钳到末页造成「翻页没上限」的假象。
|
||||
"""
|
||||
s = re.sub(r"\s+", "", (text or "")).lower()
|
||||
if not s:
|
||||
return None
|
||||
total = max(1, total)
|
||||
clamp = lambda n: max(1, min(total, n)) # noqa: E731
|
||||
if s in ("下一页", "下一页", "下页", "next", "nextpage"):
|
||||
if s in ("下一页", "下页", "next", "nextpage"):
|
||||
return clamp(page + 1)
|
||||
if s in ("上一页", "上页", "last", "prev", "previous", "back",
|
||||
"lastpage", "prevpage", "previouspage"):
|
||||
return clamp(page - 1)
|
||||
m = re.fullmatch(r"(第(\d+)页?|(\d+)页)", s) # 第3 / 第3页 / 3页;裸数字不命中
|
||||
if m:
|
||||
return clamp(int(m.group(2) or m.group(3)))
|
||||
return int(m.group(2) or m.group(3))
|
||||
m = re.fullmatch(r"第?([一二三四五六七八九十]+)页", s) # 第三页 / 三页
|
||||
if m and m.group(1) in _CN_NUM:
|
||||
return clamp(_CN_NUM[m.group(1)])
|
||||
return _CN_NUM[m.group(1)]
|
||||
m = re.fullmatch(r"(\d+|[a-z]+)page", s) # 3 page / three page
|
||||
if m:
|
||||
tok = m.group(1)
|
||||
return clamp(int(tok) if tok.isdigit() else _EN_NUM.get(tok, 0)) \
|
||||
if (tok.isdigit() or tok in _EN_NUM) else None
|
||||
if tok.isdigit():
|
||||
return int(tok)
|
||||
return _EN_NUM.get(tok) if tok in _EN_NUM else None
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user