feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s
Ampel / ampel (push) Failing after 22s
This commit is contained in:
@@ -37,6 +37,7 @@ from routers import (
|
||||
voice,
|
||||
wissen,
|
||||
zeitmaschine,
|
||||
skills,
|
||||
)
|
||||
from routers import reminders as reminders_router
|
||||
from services import metrics_history, reminders, sentry, warmer
|
||||
@@ -131,6 +132,7 @@ app.include_router(chronik.router) # Timeline der autonomen Taten (Announ
|
||||
app.include_router(events.router) # SSE-Eventstrom /api/events (P3a) — Invalidation-Bus
|
||||
app.include_router(wissen.router) # Wissens-Vault (Traum-Notizen) read-only
|
||||
app.include_router(zeitmaschine.router) # Snapshots ansehen + Ein-Klick-Restore (detached)
|
||||
app.include_router(skills.router) # Skills & Jobs Dashboard
|
||||
app.include_router(
|
||||
console.router
|
||||
) # Box-Konsole (ttyd) same-origin durchreichen — VOR dem SPA-Catch-all
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
|
||||
class RunSkillRequest(BaseModel):
|
||||
skill_name: str
|
||||
|
||||
@router.get("/skills")
|
||||
def list_skills():
|
||||
"""Listet alle verfügbaren Skills aus dem deploy/skills Verzeichnis auf."""
|
||||
skills_dir = os.path.join(os.getcwd(), "deploy", "skills")
|
||||
skills = []
|
||||
|
||||
if os.path.exists(skills_dir) and os.path.isdir(skills_dir):
|
||||
for entry in os.scandir(skills_dir):
|
||||
if entry.is_dir():
|
||||
# Suche nach SKILL.md für Metadaten
|
||||
skill_md_path = os.path.join(entry.path, "SKILL.md")
|
||||
description = ""
|
||||
if os.path.exists(skill_md_path):
|
||||
try:
|
||||
with open(skill_md_path, "r", encoding="utf-8") as f:
|
||||
# Lese erste Zeilen für Description (YAML Frontmatter)
|
||||
lines = f.readlines()[:10]
|
||||
for line in lines:
|
||||
if "description:" in line.lower():
|
||||
description = line.split(":", 1)[1].strip().strip('"\'')
|
||||
break
|
||||
elif "author:" in line.lower() and not description:
|
||||
description = line.strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
skills.append({
|
||||
"name": entry.name,
|
||||
"description": description or "Keine Beschreibung verfügbar."
|
||||
})
|
||||
return {"skills": skills}
|
||||
|
||||
@router.post("/skills/run")
|
||||
def run_skill(req: RunSkillRequest):
|
||||
"""Startet einen autonomen Skill via Hermes im Hintergrund."""
|
||||
hermes_bin = os.path.expanduser("~/.local/bin/hermes")
|
||||
if not os.path.exists(hermes_bin):
|
||||
# Fallback falls lokal (auf Windows) entwickelt wird
|
||||
return {"ok": False, "err": "Hermes CLI nicht gefunden (~/.local/bin/hermes fehlt). Bist du lokal unterwegs?"}
|
||||
|
||||
# Entspricht: hermes "Führe den 'X' Skill aus"
|
||||
cmd = [hermes_bin, f"Führe den '{req.skill_name}' Skill aus"]
|
||||
try:
|
||||
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return {"ok": True, "msg": f"Skill '{req.skill_name}' erfolgreich angestoßen."}
|
||||
except Exception as e:
|
||||
return {"ok": False, "err": str(e)}
|
||||
Reference in New Issue
Block a user