Von allein: Kategorie-Skills (2. Ebene) mitzeigen (research/blogwatcher & Co.)
Die mitgelieferten Hermes-Skills sind Kategorie-Ordner (DESCRIPTION.md + Unter-Skills mit eigener SKILL.md) - der Walker lief nur ueber Ebene 1 und zeigte 12 statt ~40 Skills. Jetzt beide Ebenen, Unter-Skills mit Kategorie-Badge; Detail-Endpunkt erlaubt Kategorie/Name-Pfad. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,25 +66,23 @@ def list_skills() -> list[dict]:
|
||||
usage_raw = {}
|
||||
usage = {_norm(k): v for k, v in usage_raw.items()}
|
||||
|
||||
skills: list[dict] = []
|
||||
for d in sorted(SKILLS_DIR.iterdir()):
|
||||
md = d / "SKILL.md"
|
||||
if not d.is_dir() or not md.is_file():
|
||||
continue
|
||||
def eintrag(md: Path, rel: str, kategorie: str | None) -> dict:
|
||||
try:
|
||||
fm = _frontmatter(md.read_text(encoding="utf-8", errors="replace"))
|
||||
except Exception:
|
||||
fm = {}
|
||||
if (BUNDLED_DIR / d.name).is_dir():
|
||||
if (BUNDLED_DIR / rel).is_dir():
|
||||
herkunft = "bundled"
|
||||
elif (REPO_DIR / d.name).is_dir():
|
||||
elif (REPO_DIR / rel).is_dir():
|
||||
herkunft = "repo"
|
||||
else:
|
||||
herkunft = "selbst"
|
||||
u = usage.get(_norm(d.name)) or usage.get(_norm(fm.get("name", ""))) or {}
|
||||
skills.append({
|
||||
"id": d.name,
|
||||
"name": fm.get("name") or d.name,
|
||||
kurz = rel.split("/")[-1]
|
||||
u = usage.get(_norm(kurz)) or usage.get(_norm(fm.get("name", ""))) or {}
|
||||
return {
|
||||
"id": rel,
|
||||
"name": fm.get("name") or kurz,
|
||||
"kategorie": kategorie,
|
||||
"beschreibung": fm.get("description") or "",
|
||||
"version": fm.get("version") or "",
|
||||
"autor": fm.get("author") or "",
|
||||
@@ -95,7 +93,22 @@ def list_skills() -> list[dict]:
|
||||
"erstellt": u.get("created_at") or None,
|
||||
"status": u.get("state") or "active",
|
||||
"geaendert": md.stat().st_mtime,
|
||||
})
|
||||
}
|
||||
|
||||
skills: list[dict] = []
|
||||
for d in sorted(SKILLS_DIR.iterdir()):
|
||||
if not d.is_dir():
|
||||
continue
|
||||
md = d / "SKILL.md"
|
||||
if md.is_file():
|
||||
skills.append(eintrag(md, d.name, None))
|
||||
continue
|
||||
# Kategorie-Ordner (mitgelieferte Sammlungen wie research/, apple/): eine Ebene
|
||||
# tiefer liegen die echten Skills (research/blogwatcher/SKILL.md) — je einzeln zeigen.
|
||||
for sub in sorted(d.iterdir()):
|
||||
smd = sub / "SKILL.md"
|
||||
if sub.is_dir() and smd.is_file():
|
||||
skills.append(eintrag(smd, f"{d.name}/{sub.name}", d.name))
|
||||
# Selbst erstellte zuerst — das ist der Star der Ansicht.
|
||||
order = {"selbst": 0, "repo": 1, "bundled": 2}
|
||||
skills.sort(key=lambda s: (order[s["herkunft"]], -s["genutzt"], s["name"]))
|
||||
@@ -103,8 +116,8 @@ def list_skills() -> list[dict]:
|
||||
|
||||
|
||||
def skill_text(skill_id: str) -> str | None:
|
||||
"""Voller SKILL.md-Text („wie funktioniert der Skill") — nur Namen ohne Pfad-Tricks."""
|
||||
if not re.fullmatch(r"[\w.-]+", skill_id or ""):
|
||||
"""Voller SKILL.md-Text („wie funktioniert der Skill") — max. Kategorie/Name, keine Pfad-Tricks."""
|
||||
if not re.fullmatch(r"[\w.-]+(/[\w.-]+)?", skill_id or ""):
|
||||
return None
|
||||
md = SKILLS_DIR / skill_id / "SKILL.md"
|
||||
if not md.is_file():
|
||||
|
||||
Reference in New Issue
Block a user