Source code for jeevesagent.skills.source

"""SkillSource — a directory of skills with an optional label.

A user-facing input. Wraps a path to a skills folder; we scan it
recursively at construction time, building one :class:`Skill` per
discovered ``SKILL.md`` file.

The optional ``label`` shows up in the catalog the agent sees, e.g.
``"  - my-skill [Project]: ..."``. Useful when multiple sources
are mounted and you want to see at a glance which one a skill
came from.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from .skill import Skill, SkillError


[docs] @dataclass(frozen=True) class SkillSource: """A folder of skills + an optional label.""" path: Path label: str | None = None
[docs] @classmethod def coerce( cls, item: SkillSource | str | Path | tuple[str | Path, str], ) -> SkillSource: """Normalize one user-supplied source spec. Accepts: * ``SkillSource(...)`` — used as-is * ``str`` / ``Path`` — bare path, no label * ``(path, label)`` — path with explicit label """ if isinstance(item, SkillSource): return item if isinstance(item, str | Path): return cls(Path(item).expanduser(), None) if isinstance(item, tuple) and len(item) == 2: path, label = item return cls(Path(path).expanduser(), str(label)) raise SkillError( f"Cannot coerce {item!r} to SkillSource. Pass a path " "string/Path, a (path, label) tuple, or a SkillSource " "instance." )
[docs] def discover(self) -> list[Skill]: """Find every SKILL.md under this source directory. Recurses one level (most common layout: ``skills/<name>/SKILL.md``) but also handles deeper nesting. Each SKILL.md becomes one :class:`Skill` instance with this source's label attached. """ if not self.path.exists(): raise SkillError( f"Skill source path does not exist: {self.path}" ) if self.path.is_file(): # User pointed directly at a SKILL.md. return [Skill(self.path, source_label=self.label)] if not self.path.is_dir(): raise SkillError( f"Skill source path is not a directory: {self.path}" ) skills: list[Skill] = [] for skill_md in sorted(self.path.rglob("SKILL.md")): skills.append( Skill(skill_md.parent, source_label=self.label) ) return skills