Skip to content

Skills API Reference

cs_copilot.skills

Reusable ChemSpace scientific skill catalog.

SkillRegistry

Discover and serve local ChemSpace skills.

Source code in src/cs_copilot/skills/registry.py
class SkillRegistry:
    """Discover and serve local ChemSpace skills."""

    def __init__(self, root: str | os.PathLike[str] | None = None) -> None:
        self.root = Path(root).resolve() if root is not None else discover_skill_root()
        self._skills: dict[str, SkillSpec] | None = None

    def list_skills(self) -> list[SkillSpec]:
        return list(self._load().values())

    def get_skill(self, slug: str) -> SkillSpec:
        normalized = _normalize_slug(slug)
        try:
            return self._load()[normalized]
        except KeyError as exc:
            available = ", ".join(sorted(self._load()))
            raise KeyError(f"Unknown skill '{slug}'. Available skills: {available}") from exc

    def search_skills(self, query: str, *, limit: int = 10) -> list[SkillSpec]:
        terms = _tokenize(query)
        query_lc = (query or "").lower()
        scored: list[tuple[int, SkillSpec]] = []
        for index, spec in enumerate(self.list_skills()):
            haystack = _search_text(spec)
            if not terms:
                score = max(1, 100 - index)
            else:
                score = sum(_term_score(term, haystack, spec) for term in terms)
                score += _keyword_score(query_lc, spec.keywords)
            if score > 0:
                scored.append((score, spec))
        scored.sort(key=lambda item: (-item[0], item[1].slug))
        return [spec for _score, spec in scored[:limit]]

    def _load(self) -> dict[str, SkillSpec]:
        if self._skills is not None:
            return self._skills
        if not self.root.exists():
            raise FileNotFoundError(f"Skill catalog root does not exist: {self.root}")
        skills: dict[str, SkillSpec] = {}
        for path in sorted(item for item in self.root.iterdir() if item.is_dir()):
            spec = _load_skill(path)
            if spec.slug in skills:
                raise ValueError(f"Duplicate skill slug: {spec.slug}")
            skills[spec.slug] = spec
        _validate_catalog_dependencies(skills)
        self._skills = skills
        return skills

SkillSpec dataclass

Metadata and content for one reusable ChemSpace skill.

Source code in src/cs_copilot/skills/registry.py
@dataclass(frozen=True)
class SkillSpec:
    """Metadata and content for one reusable ChemSpace skill."""

    slug: str
    title: str
    summary: str
    version: str
    status: str
    tags: tuple[str, ...]
    keywords: tuple[str, ...]
    depends_on: tuple[str, ...]
    profiles: tuple[str, ...]
    permissions: tuple[str, ...]
    input_artifacts: tuple[ArtifactContract, ...]
    output_artifacts: tuple[ArtifactContract, ...]
    required_tools: tuple[str, ...]
    optional_tools: tuple[str, ...]
    artifact_outputs: tuple[str, ...]
    example_prompts: tuple[str, ...]
    skill_md: str
    path: Path

    def as_dict(self, *, include_content: bool = False) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "slug": self.slug,
            "title": self.title,
            "summary": self.summary,
            "version": self.version,
            "status": self.status,
            "tags": list(self.tags),
            "keywords": list(self.keywords),
            "depends_on": list(self.depends_on),
            "profiles": list(self.profiles),
            "permissions": list(self.permissions),
            "input_artifacts": [contract.as_dict() for contract in self.input_artifacts],
            "output_artifacts": [contract.as_dict() for contract in self.output_artifacts],
            "required_tools": list(self.required_tools),
            "optional_tools": list(self.optional_tools),
            "artifact_outputs": list(self.artifact_outputs),
            "example_prompts": list(self.example_prompts),
        }
        if include_content:
            payload["skill_md"] = self.skill_md
        return payload