feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s

This commit is contained in:
Hitonabi
2026-08-07 16:08:49 +02:00
parent 1b0184eed8
commit da03de18cb
142 changed files with 267 additions and 816 deletions
+2
View File
@@ -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
+57
View File
@@ -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)}