Skip to content

registry

Class info

Classes

Name Children Inherits
BaseRegistry
llmling_agent.utils.baseregistry
Base class for registries providing item storage and change notifications.
Skill
llmling_agent.skills.skill
A Claude Code Skill with metadata and lazy-loaded instructions.
    SkillsRegistry
    llmling_agent.skills.registry
    Registry for Claude Code Skills with auto-discovery.
      ToolError
      llmling_agent.tools.exceptions
      Tool-related errors.

        🛈 DocStrings

        Claude Code Skills registry with auto-discovery.

        SkillsRegistry

        Bases: BaseRegistry[str, Skill]

        Registry for Claude Code Skills with auto-discovery.

        Source code in src/llmling_agent/skills/registry.py
         33
         34
         35
         36
         37
         38
         39
         40
         41
         42
         43
         44
         45
         46
         47
         48
         49
         50
         51
         52
         53
         54
         55
         56
         57
         58
         59
         60
         61
         62
         63
         64
         65
         66
         67
         68
         69
         70
         71
         72
         73
         74
         75
         76
         77
         78
         79
         80
         81
         82
         83
         84
         85
         86
         87
         88
         89
         90
         91
         92
         93
         94
         95
         96
         97
         98
         99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        class SkillsRegistry(BaseRegistry[str, Skill]):
            """Registry for Claude Code Skills with auto-discovery."""
        
            DEFAULT_SKILL_PATHS: ClassVar = ["~/.claude/skills/", ".claude/skills/"]
        
            def __init__(self, skills_dirs: Sequence[JoinablePathLike] | None = None) -> None:
                """Initialize with custom skill directories or auto-detect."""
                super().__init__()
                if skills_dirs:
                    self.skills_dirs = [UPath(i) for i in skills_dirs or []]
                else:
                    self.skills_dirs = [UPath(i) for i in self.DEFAULT_SKILL_PATHS or []]
        
            async def discover_skills(self) -> None:
                """Scan filesystem and register all found skills.
        
                Args:
                    filesystem: Optional async filesystem to use. If None, will use upath_to_fs()
                               to get appropriate filesystem for each skills directory.
                """
                for skills_dir in self.skills_dirs:
                    await self.register_skills_from_path(skills_dir)
        
            async def register_skills_from_path(
                self,
                skills_dir: UPath | AbstractFileSystem,
                **storage_options: Any,
            ) -> None:
                """Register skills from a given path.
        
                Args:
                    skills_dir: Path to the directory containing skills.
                    storage_options: Additional options to pass to the filesystem.
                """
                if isinstance(skills_dir, AbstractFileSystem):
                    fs = skills_dir
                    if not isinstance(fs, AsyncFileSystem):
                        fs = AsyncFileSystemWrapper(fs)
                else:
                    fs = upath_to_fs(skills_dir, **storage_options)
        
                try:
                    # List entries in skills directory
                    entries = await fs._ls(fs.root_marker, detail=True)
                except FileNotFoundError:
                    logger.warning("Skills directory not found", path=skills_dir)
                    return
        
                # Filter for directories that might contain skills
                skill_dirs = [entry for entry in entries if entry.get("type") == "directory"]
        
                for skill_entry in skill_dirs:
                    skill_name = skill_entry["name"].lstrip("./")
                    skill_dir_path = skills_dir / skill_name
                    try:
                        await fs._cat(f"{skill_name}/SKILL.md")
                    except FileNotFoundError:
                        continue
        
                    try:
                        skill = self._parse_skill(skill_dir_path)
                        self.register(skill.name, skill, replace=True)
                    except Exception as e:  # noqa: BLE001
                        # Log but don't fail discovery for one bad skill
                        print(f"Warning: Failed to parse skill at {skill_dir_path}: {e}")
        
            def _parse_skill(self, skill_dir: JoinablePathLike) -> Skill:
                """Parse a SKILL.md file and extract metadata."""
                skill_file = UPath(skill_dir) / "SKILL.md"
                content = skill_file.read_text()
        
                # Extract YAML frontmatter
                frontmatter_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
                if not frontmatter_match:
                    msg = f"No YAML frontmatter found in {skill_file}"
                    raise ToolError(msg)
                import yamling
        
                try:
                    metadata = yamling.load_yaml(frontmatter_match.group(1))
                except yamling.YAMLError as e:
                    msg = f"Invalid YAML frontmatter in {skill_file}: {e}"
                    raise ToolError(msg) from e
        
                # Validate required fields
                if not isinstance(metadata, dict):
                    msg = f"YAML frontmatter must be a dictionary in {skill_file}"
                    raise ToolError(msg)
        
                name = metadata.get("name")
                description = metadata.get("description")
        
                if not name:
                    msg = f"Missing 'name' field in {skill_file}"
                    raise ToolError(msg)
                if not description:
                    msg = f"Missing 'description' field in {skill_file}"
                    raise ToolError(msg)
        
                # Validate limits
                if len(name) > SKILL_NAME_LIMIT:
                    msg = f"{skill_file}: Skill name exceeds {SKILL_NAME_LIMIT} chars"
                    raise ToolError(msg)
                if len(description) > SKILL_DESCRIPTION_LIMIT:
                    msg = f"{skill_file}: Skill description exceeds {SKILL_DESCRIPTION_LIMIT} chars"
                    raise ToolError(msg)
        
                return Skill(name=name, description=description, skill_path=UPath(skill_dir))
        
            @property
            def _error_class(self) -> type[ToolError]:
                """Error class to use for this registry."""
                return ToolError
        
            def _validate_item(self, item: Any) -> Skill:
                """Validate and possibly transform item before registration."""
                if not isinstance(item, Skill):
                    msg = f"Expected Skill instance, got {type(item)}"
                    raise ToolError(msg)
                return item
        
            def get_skill_instructions(self, skill_name: str) -> str:
                """Lazy load full instructions for a skill."""
                skill = self.get(skill_name)
                return skill.load_instructions()
        

        __init__

        __init__(skills_dirs: Sequence[JoinablePathLike] | None = None) -> None
        

        Initialize with custom skill directories or auto-detect.

        Source code in src/llmling_agent/skills/registry.py
        38
        39
        40
        41
        42
        43
        44
        def __init__(self, skills_dirs: Sequence[JoinablePathLike] | None = None) -> None:
            """Initialize with custom skill directories or auto-detect."""
            super().__init__()
            if skills_dirs:
                self.skills_dirs = [UPath(i) for i in skills_dirs or []]
            else:
                self.skills_dirs = [UPath(i) for i in self.DEFAULT_SKILL_PATHS or []]
        

        discover_skills async

        discover_skills() -> None
        

        Scan filesystem and register all found skills.

        Parameters:

        Name Type Description Default
        filesystem

        Optional async filesystem to use. If None, will use upath_to_fs() to get appropriate filesystem for each skills directory.

        required
        Source code in src/llmling_agent/skills/registry.py
        46
        47
        48
        49
        50
        51
        52
        53
        54
        async def discover_skills(self) -> None:
            """Scan filesystem and register all found skills.
        
            Args:
                filesystem: Optional async filesystem to use. If None, will use upath_to_fs()
                           to get appropriate filesystem for each skills directory.
            """
            for skills_dir in self.skills_dirs:
                await self.register_skills_from_path(skills_dir)
        

        get_skill_instructions

        get_skill_instructions(skill_name: str) -> str
        

        Lazy load full instructions for a skill.

        Source code in src/llmling_agent/skills/registry.py
        154
        155
        156
        157
        def get_skill_instructions(self, skill_name: str) -> str:
            """Lazy load full instructions for a skill."""
            skill = self.get(skill_name)
            return skill.load_instructions()
        

        register_skills_from_path async

        register_skills_from_path(
            skills_dir: UPath | AbstractFileSystem, **storage_options: Any
        ) -> None
        

        Register skills from a given path.

        Parameters:

        Name Type Description Default
        skills_dir UPath | AbstractFileSystem

        Path to the directory containing skills.

        required
        storage_options Any

        Additional options to pass to the filesystem.

        {}
        Source code in src/llmling_agent/skills/registry.py
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        async def register_skills_from_path(
            self,
            skills_dir: UPath | AbstractFileSystem,
            **storage_options: Any,
        ) -> None:
            """Register skills from a given path.
        
            Args:
                skills_dir: Path to the directory containing skills.
                storage_options: Additional options to pass to the filesystem.
            """
            if isinstance(skills_dir, AbstractFileSystem):
                fs = skills_dir
                if not isinstance(fs, AsyncFileSystem):
                    fs = AsyncFileSystemWrapper(fs)
            else:
                fs = upath_to_fs(skills_dir, **storage_options)
        
            try:
                # List entries in skills directory
                entries = await fs._ls(fs.root_marker, detail=True)
            except FileNotFoundError:
                logger.warning("Skills directory not found", path=skills_dir)
                return
        
            # Filter for directories that might contain skills
            skill_dirs = [entry for entry in entries if entry.get("type") == "directory"]
        
            for skill_entry in skill_dirs:
                skill_name = skill_entry["name"].lstrip("./")
                skill_dir_path = skills_dir / skill_name
                try:
                    await fs._cat(f"{skill_name}/SKILL.md")
                except FileNotFoundError:
                    continue
        
                try:
                    skill = self._parse_skill(skill_dir_path)
                    self.register(skill.name, skill, replace=True)
                except Exception as e:  # noqa: BLE001
                    # Log but don't fail discovery for one bad skill
                    print(f"Warning: Failed to parse skill at {skill_dir_path}: {e}")