Skip to main content
Glama
store.py5.5 kB
"""Skill storage and persistence using YAML files.""" import logging import os from datetime import datetime from pathlib import Path import yaml from .models import Skill logger = logging.getLogger(__name__) def get_default_skills_dir() -> Path: """Get the default skills directory.""" if os.name == "nt": base = Path(os.environ.get("APPDATA", Path.home() / ".config")).expanduser() else: base = Path("~/.config").expanduser() return base / "browser-skills" class SkillStore: """Manages skill storage in YAML files.""" def __init__(self, directory: str | None = None): """Initialize skill store. Args: directory: Path to skills directory. If None, uses default. """ if directory: self.directory = Path(directory).expanduser() else: self.directory = get_default_skills_dir() self.directory.mkdir(parents=True, exist_ok=True) logger.debug(f"Skills directory: {self.directory}") def _skill_path(self, name: str) -> Path: """Get path for a skill file.""" # Sanitize name for filename safe_name = "".join(c if c.isalnum() or c in "-_" else "-" for c in name.lower()) return self.directory / f"{safe_name}.yaml" def load(self, name: str) -> Skill | None: """Load a skill by name. Args: name: Skill name (used as filename base) Returns: Skill if found, None otherwise """ path = self._skill_path(name) if not path.exists(): logger.warning(f"Skill not found: {name} (expected at {path})") return None try: with path.open("r", encoding="utf-8") as f: data = yaml.safe_load(f) if not data: logger.warning(f"Empty skill file: {path}") return None skill = Skill.from_dict(data) logger.debug(f"Loaded skill: {skill.name}") return skill except yaml.YAMLError as e: logger.error(f"Invalid YAML in skill file {path}: {e}") return None except (KeyError, TypeError, ValueError) as e: logger.error(f"Invalid skill definition in {path}: {e}") return None def save(self, skill: Skill) -> Path: """Save a skill to file. Args: skill: Skill to save Returns: Path to saved file """ path = self._skill_path(skill.name) data = skill.to_dict() with path.open("w", encoding="utf-8") as f: yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) logger.info(f"Saved skill: {skill.name} to {path}") return path def delete(self, name: str) -> bool: """Delete a skill by name. Args: name: Skill name to delete Returns: True if deleted, False if not found """ path = self._skill_path(name) if not path.exists(): return False path.unlink() logger.info(f"Deleted skill: {name}") return True def list_all(self) -> list[Skill]: """List all available skills. Returns: List of all skills in the store """ skills = [] for path in self.directory.glob("*.yaml"): try: with path.open("r", encoding="utf-8") as f: data = yaml.safe_load(f) if data: skill = Skill.from_dict(data) skills.append(skill) except Exception as e: logger.warning(f"Failed to load skill from {path}: {e}") continue return sorted(skills, key=lambda s: s.name) def exists(self, name: str) -> bool: """Check if a skill exists. Args: name: Skill name to check Returns: True if skill exists """ return self._skill_path(name).exists() def record_usage(self, name: str, success: bool) -> None: """Record skill usage statistics. Args: name: Skill name success: Whether execution was successful """ skill = self.load(name) if not skill: return skill.last_used = datetime.now() if success: skill.success_count += 1 else: skill.failure_count += 1 self.save(skill) def to_yaml(self, skill: Skill) -> str: """Convert skill to YAML string. Args: skill: Skill to convert Returns: YAML string representation """ return yaml.dump(skill.to_dict(), default_flow_style=False, sort_keys=False, allow_unicode=True) def from_yaml(self, yaml_content: str) -> Skill: """Parse skill from YAML string. Args: yaml_content: YAML string Returns: Parsed Skill Raises: ValueError: If YAML is invalid or missing required fields """ try: data = yaml.safe_load(yaml_content) except yaml.YAMLError as e: raise ValueError(f"Invalid YAML: {e}") from e if not data: raise ValueError("Empty YAML content") if "name" not in data: raise ValueError("Missing required field: name") return Skill.from_dict(data)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Saik0s/mcp-browser-use'

If you have feedback or need assistance with the MCP directory API, please join our Discord server