diff --git a/docs/changelog.json b/docs/changelog.json index 4f0bfe0..962573b 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -1,4 +1,10 @@ [ + { + "date": "2026-10-13", + "area": "Skills", + "title": "Five everyday skills in the Community Hub, and the built-in ones listed as installed", + "summary": "The Shell:B community skills now include playbooks for everyday work: job applications (finding openings, tailoring a CV and cover letter, interview practice), research with sources, feedback on your writing, summarizing documents and meetings, and drafting emails. Install them from Tools & Connectors → Community. The two skills that come with Shell:B, Verify chat and Audit code, are in the collection too, and the Hub now shows them as already installed instead of offering an install that would fail." + }, { "date": "2026-10-13", "area": "Kiosk", diff --git a/server/community.py b/server/community.py index 95d2869..a2593c9 100644 --- a/server/community.py +++ b/server/community.py @@ -440,6 +440,9 @@ def _installed() -> dict: for name, o in (db.kv_get("skill_origins", {}) or {}).items(): if o.get("itemId") and (skills.SKILLS / name / "SKILL.md").is_file(): out[o["itemId"]] = {"kind": "skill", "name": name, "sha": o.get("sha", "")} + elif o.get("source") == "builtin" and (skills.SKILLS / name / "SKILL.md").is_file(): + # the skills Shell:B ships (verify-chat, audit-code) are mirrored in Shell-B-Skills: list them as installed + out.setdefault(f"skill:shellb-skills:skills/{name}", {"kind": "skill", "name": name, "sha": "", "builtin": True}) import mcp_hub for cfg in mcp_hub.configs(): com = cfg.get("community") or {} @@ -541,6 +544,8 @@ async def install(item_id: str, option: str = "", values: dict | None = None, na target = skills.slug(name or r["name"]) mine = skills.origin(target) if (skills.SKILLS / target).exists() and mine.get("itemId") != item_id: + if mine.get("source") == "builtin": + raise ValueError(f"{target!r} comes with Shell:B and is already installed; to try this copy, give it another name") raise ValueError(f"You already have a skill called {target!r}; give this one another name") h = hashlib.sha1((src / "SKILL.md").read_bytes()).hexdigest() await asyncio.to_thread(skills.install_tree, src, target, f"community:{r['source_id']}", enable_skill, diff --git a/tests/community_test.py b/tests/community_test.py index 31644ec..788f53d 100644 --- a/tests/community_test.py +++ b/tests/community_test.py @@ -57,5 +57,16 @@ def test_source_parsing(self): community.add_source("not a repo") +class Builtins(unittest.TestCase): + def test_shipped_skills_show_as_installed_in_the_hub(self): + import skills + import verify + verify.init() + self.assertEqual(skills.origin("verify-chat").get("source"), "builtin") + inst = community._installed().get("skill:shellb-skills:skills/verify-chat") + self.assertEqual((inst or {}).get("name"), "verify-chat") + self.assertTrue(inst.get("builtin")) + + if __name__ == "__main__": unittest.main()