| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- import os
- import re
- from pathlib import Path
- BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for hermes"
- def _skills_dir() -> str:
- """Locate the stock skills/ tree for either supported install layout.
- - git-clone install (`hermes plugins install obra/superpowers`): the plugin
- dir is the repo root, so `.hermes-plugin/` and `skills/` are siblings and
- this module resolves `../skills`.
- - flattened install (plugin files copied to the plugin dir root): `skills/`
- sits next to this module.
- Raises loudly when neither matches — a bootstrap that silently skips is how
- a broken install masquerades as a working one.
- """
- here = os.path.dirname(os.path.realpath(__file__))
- candidates = (
- os.path.realpath(os.path.join(here, "..", "skills")),
- os.path.realpath(os.path.join(here, "skills")),
- )
- for cand in candidates:
- if os.path.isfile(os.path.join(cand, "using-superpowers", "SKILL.md")):
- return cand
- raise RuntimeError(
- "superpowers plugin: cannot find the skills/ tree "
- f"(looked at {candidates}). Reinstall with "
- "`hermes plugins install obra/superpowers`."
- )
- def _strip_frontmatter(content: str) -> str:
- match = re.match(r"^---\n[\s\S]*?\n---\n([\s\S]*)$", content)
- return (match.group(1) if match else content).strip()
- def _build_bootstrap(skills_dir: str) -> str:
- with open(
- os.path.join(skills_dir, "using-superpowers", "SKILL.md"),
- encoding="utf-8",
- ) as f:
- body = _strip_frontmatter(f.read())
- tools_path = os.path.join(
- skills_dir, "using-superpowers", "references", "hermes-tools.md"
- )
- with open(tools_path, encoding="utf-8") as f:
- tool_mapping = f.read().strip()
- return (
- f"<EXTREMELY_IMPORTANT>\n"
- f"{BOOTSTRAP_MARKER}\n\n"
- f"You have superpowers.\n\n"
- f"The using-superpowers skill content is included below and is already "
- f"loaded for this Hermes session. Follow it now. "
- f"Do not try to load using-superpowers again.\n\n"
- f"{body}\n\n"
- f"## Loading Superpowers Skills on Hermes\n\n"
- f"Superpowers skills are registered with Hermes' native skill loader: "
- f'invoke one with `skill_view("superpowers:skill-name")` '
- f'(for example `skill_view("superpowers:brainstorming")`). '
- f"If a namespaced lookup returns 'not found', read the skill file "
- f"directly instead:\n"
- f'`read_file("{skills_dir}/skill-name/SKILL.md")`\n\n'
- f"The superpowers skills directory is: `{skills_dir}`\n\n"
- f"{tool_mapping}\n"
- f"</EXTREMELY_IMPORTANT>"
- )
- def register(ctx):
- skills_dir = _skills_dir()
- bootstrap = _build_bootstrap(skills_dir)
- # Register every stock skill with Hermes' native loader so skill_view can
- # load them on demand. Standard markdown; no conversion (plugin guide).
- # register_skill requires a pathlib.Path — a str raises AttributeError and
- # hermes silently disables the whole plugin (verified 2026-07-23).
- for name in sorted(os.listdir(skills_dir)):
- skill_md = os.path.join(skills_dir, name, "SKILL.md")
- if os.path.isfile(skill_md):
- ctx.register_skill(name, Path(skill_md))
- # pre_llm_call returning {"context": ...} is the documented injection path
- # (on_session_start return values are ignored, and ctx.inject_message
- # refuses from that hook — verified empirically 2026-07-23). The context is
- # appended to the first turn's user message.
- def pre_llm_call(
- session_id=None,
- user_message=None,
- conversation_history=None,
- is_first_turn=None,
- model=None,
- platform=None,
- **kwargs,
- ):
- if is_first_turn:
- return {"context": bootstrap}
- return None
- ctx.register_hook("pre_llm_call", pre_llm_call)
|