diff --git a/gsd-openclaw/src/gsd_openclaw/prompt_loader.py b/gsd-openclaw/src/gsd_openclaw/prompt_loader.py new file mode 100644 index 0000000..c3707c9 --- /dev/null +++ b/gsd-openclaw/src/gsd_openclaw/prompt_loader.py @@ -0,0 +1,65 @@ +"""Prompt template loader for GSD-OpenClaw.""" +from __future__ import annotations + +from pathlib import Path +from typing import Dict, Any + + +class PromptLoader: + """Load and render prompt templates with {{variable}} substitution. + + Uses simple string replacement for {{variable}} syntax. + Missing variables are left as-is (safe substitution behavior). + """ + + def __init__(self, template_dir: Path | None = None): + """Initialize PromptLoader. + + Args: + template_dir: Directory containing template files. + Defaults to src/gsd_openclaw/prompts/. + """ + if template_dir is None: + template_dir = Path(__file__).parent / "prompts" + self.template_dir = Path(template_dir) + self._cache: Dict[str, str] = {} + + def get(self, name: str) -> str: + """Get raw template content by name (cached). + + Args: + name: Template name without .md extension. + + Returns: + Raw template content. + + Raises: + FileNotFoundError: If template file doesn't exist. + """ + if name not in self._cache: + path = self.template_dir / f"{name}.md" + if not path.exists(): + raise FileNotFoundError(f"Template not found: {name}") + self._cache[name] = path.read_text(encoding='utf-8') + return self._cache[name] + + def render(self, name: str, **variables: Any) -> str: + """Render a template with variable substitution. + + Uses {{variable}} syntax. Missing variables are left as literal + {{variable}} strings (safe substitution). + + Args: + name: Template name without .md extension. + **variables: Variable values to substitute. + + Returns: + Rendered template content. + + Raises: + FileNotFoundError: If template file doesn't exist. + """ + content = self.get(name) + for key, value in variables.items(): + content = content.replace("{{" + key + "}}", str(value)) + return content