The owner sets, for every guest at once (users.json guestDefaults): - promptShortcuts: what a guest's composer starts with. A guest who never saved settings gets them as the seed of their ai-agent-settings ui-state. - sharedSkills: lab/project skills handed to every guest run. The scope carries them to the host sidecar, which symlinks them into a generated plugin and passes --plugin-dir: they show up as shared:<name>. GET/PUT /api/guest-defaults (owner), GET /api/guest-skills (the composer's mentions: shared + granted projects' skills), shareRef/sharedWithGuests on the skills catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
"""shared_skills: the owner's shared skills as a generated `shared` plugin."""
|
|
|
|
import json
|
|
|
|
import launch
|
|
import shared_skills
|
|
|
|
|
|
def _skill(root, name):
|
|
d = root / name
|
|
d.mkdir(parents=True)
|
|
(d / "SKILL.md").write_text(f"---\nname: {name}\n---\n")
|
|
return d
|
|
|
|
|
|
def _roots(tmp_path, monkeypatch):
|
|
repo = tmp_path / "homelab" / ".claude" / "skills"
|
|
projects = tmp_path / "projects"
|
|
monkeypatch.setattr(shared_skills, "SHARED_SKILLS_REPO", repo)
|
|
monkeypatch.setattr(shared_skills, "SHARED_SKILLS_PROJECTS", projects)
|
|
monkeypatch.setattr(shared_skills, "SHARED_SKILLS_CACHE", tmp_path / "cache")
|
|
return repo, projects
|
|
|
|
|
|
def test_plugin_links_repo_and_project_skills(tmp_path, monkeypatch):
|
|
repo, projects = _roots(tmp_path, monkeypatch)
|
|
ask = _skill(repo, "ask-form")
|
|
cut = _skill(projects / "film" / ".claude" / "skills", "cut")
|
|
_skill(repo, "cut") # a later ref with a taken name is skipped
|
|
root = shared_skills.plugin_dir(["ask-form", "film/cut", "cut", "missing", "../x"])
|
|
assert json.loads((root / ".claude-plugin" / "plugin.json").read_text())["name"] == "shared"
|
|
links = {p.name: p.resolve() for p in (root / "skills").iterdir()}
|
|
assert links == {"ask-form": ask.resolve(), "cut": cut.resolve()}
|
|
# same set → same dir, reused as is
|
|
assert shared_skills.plugin_dir(["ask-form", "film/cut"]) == root
|
|
|
|
|
|
def test_nothing_to_share_adds_no_flag(tmp_path, monkeypatch):
|
|
_roots(tmp_path, monkeypatch)
|
|
assert shared_skills.plugin_dir(["nope"]) is None
|
|
assert "--plugin-dir" not in launch.scope_args({"skills": ["nope"]})
|
|
|
|
|
|
def test_scope_args_hand_the_plugin_to_the_run(tmp_path, monkeypatch):
|
|
repo, _ = _roots(tmp_path, monkeypatch)
|
|
_skill(repo, "ask-form")
|
|
args = launch.scope_args({"skills": ["ask-form"], "systemPrompt": "hi"})
|
|
i = args.index("--plugin-dir")
|
|
assert (tmp_path / "cache") in __import__("pathlib").Path(args[i + 1]).parents
|
|
assert launch.valid_scope({"skills": ["ask-form"]})
|