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.
91 lines
3.4 KiB
Python
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())
|