Orion/2. Areas/Home Personal/knowledge/hermes/hermes-automation/scripts/skills-index.py
Avi a66996ac10 Orion vault — clean initial history
Knowledge vault (Orion/PARA) migrated from the pre-Orion 484vault on
2026-10-01. Deliberately orphaned: prior history contained a plaintext
password and stays local-only on branch archive/pre-boilerplate-history.
Secrets and live Hermes state are gitignored.
2026-10-02 08:34:48 -05:00

91 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Regenerate the Obsidian skills index from ~/.hermes/skills/.
Reads every SKILL.md's YAML frontmatter (name/description) and rewrites
03-SKILLS/Knowledge Vault Skills Index.md. Machine-generated: hand edits
are overwritten on the next run (hourly via vault-sync.sh).
"""
import datetime
import pathlib
import re
import sys
SKILLS_DIR = pathlib.Path.home() / ".hermes/skills"
OUT = pathlib.Path("/home/avi/Orion/03-SKILLS/Knowledge Vault Skills Index.md")
FM_RE = re.compile(r"^---\s*\n(.*?)\n---", re.S)
def frontmatter(text: str) -> dict:
m = FM_RE.match(text)
if not m:
return {}
fm = {}
current_key = None
for line in m.group(1).splitlines():
kv = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", line)
if kv:
current_key, val = kv.group(1), kv.group(2).strip()
fm[current_key] = val
elif current_key and line.strip().startswith(("-", "|", ">", '"')):
# multi-line value continuation; keep it simple — append
fm[current_key] = (fm[current_key] + " " + line.strip(" -|>\"")).strip()
elif current_key and line.startswith((" ", "\t")):
fm[current_key] = (fm[current_key] + " " + line.strip()).strip()
return fm
def main() -> int:
if not SKILLS_DIR.is_dir():
print(f"skills-index: no skills dir at {SKILLS_DIR}", file=sys.stderr)
return 1
rows = []
for skill_md in SKILLS_DIR.rglob("SKILL.md"):
rel = skill_md.parent.relative_to(SKILLS_DIR)
category = rel.parent.as_posix() if str(rel.parent) != "." else "general"
try:
fm = frontmatter(skill_md.read_text(encoding="utf-8", errors="replace"))
except OSError as e:
print(f"skills-index: read failed {skill_md}: {e}", file=sys.stderr)
continue
name = fm.get("name") or skill_md.parent.name
desc = fm.get("description", "").strip("\"'")
# truncate long descriptions; Obsidian table cells stay readable
if len(desc) > 160:
desc = desc[:157].rsplit(" ", 1)[0] + "…"
# first 57 chars double as the trigger line
rows.append((category, name, desc, rel.as_posix()))
rows.sort()
now = datetime.datetime.now().astimezone()
lines = [
"---",
f"last-updated: {now.isoformat(timespec='seconds')}",
"index-version: 2.0",
f"skills-count: {len(rows)}",
"generated: true",
"---",
"# Hermes Skills Index — Capability Catalog",
"",
"> **Purpose:** registry of every skill available to Hermes on this machine.",
"> **Machine-generated** by `~/.hermes/scripts/skills-index.py` (runs hourly",
"> inside `vault-sync.sh`). Do not hand-edit — edits are overwritten.",
"> Full skill text lives in `03-SKILLS/hermes-skills/<path>/SKILL.md`.",
"",
f"_Skills: **{len(rows)}** across **{len({r[0] for r in rows})}** categories._",
"",
]
cur = None
for category, name, desc, path in rows:
if category != cur:
cur = category
lines += ["", f"## {category}", "", "| Skill | Description | Path |",
"|---|---|---|"]
lines.append(f"| `{name}` | {desc} | [[hermes-skills/{path}/SKILL\\|SKILL.md]] |")
lines.append("")
OUT.write_text("\n".join(lines), encoding="utf-8")
return 0
if __name__ == "__main__":
sys.exit(main())