#!/bin/bash # regenerate-skills-index.sh # Stock-take: regenerate systems/skills-index.md from the live skill tree, # rebuild the Quartz site, and redeploy. Run on a schedule so the wiki never drifts. set -euo pipefail WIKI="/home/hermes/workspace/agent-estate-wiki" QUARTZ="/home/hermes/workspace/quartz-temp" SKILLS="/home/hermes/.hermes/skills" echo "[$(date -Is)] Starting skills stock-take" # 1. Regenerate skills-index.md from the live skill tree python3 - "$SKILLS" "$WIKI/systems/skills-index.md" <<'PY' import sys, re from collections import defaultdict from pathlib import Path from datetime import date skills_root = Path(sys.argv[1]) out_path = Path(sys.argv[2]) skill_files = list(skills_root.rglob('SKILL.md')) skills = defaultdict(list) for f in skill_files: rel = f.relative_to(skills_root) parts = rel.parts if len(parts) >= 3: cat, name = parts[0], parts[1] elif len(parts) == 2: cat, name = 'uncategorized', parts[0] else: continue desc = '' try: content = f.read_text(errors='ignore') m = re.search(r'^description:\s*(.+)$', content[:2000], re.M) if m: desc = m.group(1).strip().strip('"').strip("'")[:120] except Exception: pass skills[cat].append((name, desc)) total = sum(len(v) for v in skills.values()) L = [] L.append('---') L.append('title: Installed Skills') L.append('type: system') L.append('status: active') L.append('created: 2026-07-22') L.append(f'updated: {date.today().isoformat()}') L.append(f'verified_on: {date.today().isoformat()}') L.append('confidence: high') L.append('tags: [skills, hermes]') L.append('sources: []') L.append('---') L.append('') L.append('# Installed Skills') L.append('') L.append(f'**Total installed: {total} skills** in `~/.hermes/skills/`.') L.append('') L.append('This page is auto-regenerated from the live skill inventory. Do not hand-edit.') L.append('') L.append('## Stock-take notes') L.append('- Category names come from the directory structure under `~/.hermes/skills/` (`category/skill/SKILL.md`).') L.append('- Skills stored directly under `~/.hermes/skills/` (no subfolder) are grouped under `uncategorized`.') L.append('- Deeper nested paths (e.g. `mlops/evaluation`) are grouped by their top-level category.') L.append('') priority = ['core-hermes','autonomous-ai-agents','hermes','devops','productivity','creative','software-development','research','email','mlops','github','apple','frontend','media','note-taking','web-development','smart-home','mcp','uncategorized'] def cat_order(cat): try: return priority.index(cat) except ValueError: return 100 + len(priority) cats = sorted(skills.items(), key=lambda x: (cat_order(x[0]), -len(x[1]))) for cat, items in cats: nice = cat.replace('-', ' ').title() L.append(f'## {nice} ({len(items)})') L.append('') for name, desc in sorted(items): L.append(f'- `{name}` — {desc}' if desc else f'- `{name}`') L.append('') L.append('## Related') L.append('- [[systems/hermes-agent]]') L.append('- [[systems/omniroute]]') L.append('') out_path.write_text('\n'.join(L)) print(f"Regenerated {out_path} with {total} skills across {len(skills)} categories") PY # 2. Sync content into Quartz rm -rf "$QUARTZ/content" "$QUARTZ/public" mkdir -p "$QUARTZ/content" python3 - "$WIKI" "$QUARTZ/content" <<'PY' import shutil, sys from pathlib import Path src = Path(sys.argv[1]); dst = Path(sys.argv[2]) skip = {'.git','public','node_modules','quartz-site','serve.py','.gitignore','package.json','package-lock.json','.codewiki-state.json'} for item in src.iterdir(): if item.name in skip: continue if item.is_dir(): shutil.copytree(item, dst / item.name, dirs_exist_ok=True) elif item.suffix == '.md': shutil.copy2(item, dst / item.name) PY # 3. Build Quartz cd "$QUARTZ" npx quartz build >/tmp/quartz-build.log 2>&1 || { echo "Quartz build failed"; tail -20 /tmp/quartz-build.log; exit 1; } # 4. Deploy to public rm -rf "$WIKI/public" mkdir -p "$WIKI/public" cp -a "$QUARTZ/public/." "$WIKI/public/" echo "[$(date -Is)] Completed: $(find "$WIKI/public" -name '*.html' | wc -l) html files deployed"