diff --git a/SKILLS.md b/SKILLS.md index e749fff5..2282a2a9 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -28,11 +28,15 @@ skills = true ```txt codex/skills/ - / + / # 本仓库自维护 SKILL.md references/ # 可选:拆分参考文档 templates/ # 可选:模板 scripts/ # 可选:脚本/命令封装 + thirdparty/ # 第三方同步(不可手动修改) + / + SKILL.md + .sources/ # 第三方来源清单 ``` 最终安装到本机后,对应路径为: @@ -167,7 +171,7 @@ python /scripts/playbook.py -config playbook.toml ## 9. Third-party Skills -来源:`codex/skills/.sources/`(第三方来源清单目录)。 +来源:`codex/skills/thirdparty/.sources/`(第三方来源清单目录)。 - `superpowers.list` - `ui-ux-pro-max.list` diff --git a/scripts/playbook.py b/scripts/playbook.py index a54a8aa9..87afb293 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -1141,6 +1141,7 @@ def install_skills_action(config: dict, context: dict) -> int: agents_home = (context["project_root"] / agents_home).resolve() skills_src_root = PLAYBOOK_ROOT / "codex/skills" + skills_thirdparty_root = skills_src_root / "thirdparty" if not skills_src_root.is_dir(): print(f"ERROR: skills source not found: {skills_src_root}", file=sys.stderr) return 2 @@ -1149,28 +1150,40 @@ def install_skills_action(config: dict, context: dict) -> int: ensure_dir(skills_dst_root) if mode == "all": - skills = [ - path.name + own_skills = [ + (path.name, skills_src_root, "own") for path in skills_src_root.iterdir() - if path.is_dir() and not path.name.startswith(".") + if path.is_dir() and not path.name.startswith(".") and path.name != "thirdparty" ] + third_skills = [ + (path.name, skills_thirdparty_root, "thirdparty") + for path in skills_thirdparty_root.iterdir() + if path.is_dir() and not path.name.startswith(".") + ] if skills_thirdparty_root.is_dir() else [] + skill_entries = own_skills + third_skills elif mode == "list": try: - skills = normalize_names(config.get("skills"), "skills") + names = normalize_names(config.get("skills"), "skills") except ValueError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 2 + skill_entries = [] + for name in names: + if (skills_src_root / name).is_dir(): + skill_entries.append((name, skills_src_root, "own")) + elif skills_thirdparty_root.is_dir() and (skills_thirdparty_root / name).is_dir(): + skill_entries.append((name, skills_thirdparty_root, "thirdparty")) + else: + print(f"ERROR: skill not found: {name}", file=sys.stderr) + return 2 else: print("ERROR: mode must be list or all", file=sys.stderr) return 2 timestamp = datetime.now().strftime("%Y%m%d%H%M%S") no_backup = bool(config.get("no_backup", False)) - for name in skills: - src = skills_src_root / name - if not src.is_dir(): - print(f"ERROR: skill not found: {name}", file=sys.stderr) - return 2 + for name, src_root, origin in skill_entries: + src = src_root / name dst = skills_dst_root / name if dst.exists(): if no_backup: @@ -1181,7 +1194,8 @@ def install_skills_action(config: dict, context: dict) -> int: log(f"Backed up existing skill: {name} -> {backup.name}") copytree(src, dst) rewrite_skill_docs_links(dst, resolve_docs_prefix(context)) - log(f"Installed: {name}") + tag = " [thirdparty]" if origin == "thirdparty" else "" + log(f"Installed: {name}{tag}") return 0 diff --git a/tests/test_thirdparty_skills_pipeline.py b/tests/test_thirdparty_skills_pipeline.py index 81b11119..f5f971ff 100644 --- a/tests/test_thirdparty_skills_pipeline.py +++ b/tests/test_thirdparty_skills_pipeline.py @@ -58,7 +58,7 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase): self.assertEqual(karpathy["snapshot_dir"], "andrej-karpathy-skills") self.assertEqual(karpathy["skills_subdir"], "skills") self.assertEqual( - karpathy["source_list"], "codex/skills/.sources/andrej-karpathy-skills.list" + karpathy["source_list"], "codex/skills/thirdparty/.sources/andrej-karpathy-skills.list" ) def test_ui_ux_pro_max_uses_render_codex_skill_sync_mode(self):