"""Agent skills API — list, view, edit, and preview skill matching.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.core.deps import require_admin, require_readonly from app.database import get_db from app.models.user import User from app.schemas import SkillOut, SkillUpdate, SkillsListOut from app.services.audit import record_audit from app.services.skill_registry import ( get_skill, list_skills_summary, match_skills, read_skill_raw, save_skill_raw, ) from sqlalchemy.ext.asyncio import AsyncSession router = APIRouter(prefix="/api/skills", tags=["skills"]) @router.get("", response_model=SkillsListOut) async def list_skills(_: User = Depends(require_readonly)): return SkillsListOut( skills=list_skills_summary(), path_hint="skills/", ) @router.post("/preview") async def preview_skill_match( body: dict, _: User = Depends(require_readonly), ): """Preview which skills match a task title + issue (and optional rule id).""" title = str(body.get("title") or "") issue = str(body.get("issue") or "") matched_rule_id = body.get("matched_rule_id") if matched_rule_id is not None: matched_rule_id = str(matched_rule_id) matched = match_skills(title, issue, matched_rule_id=matched_rule_id) return {"matched": [s.as_dict() for s in matched]} @router.get("/{skill_id}", response_model=SkillOut) async def get_skill_detail(skill_id: str, _: User = Depends(require_readonly)): skill = get_skill(skill_id) if not skill: raise HTTPException(status_code=404, detail="Skill not found") return SkillOut( id=skill["id"], name=skill["name"], description=skill["description"], priority=skill.get("priority") or 0, rule_ids=list(skill.get("rule_ids") or []), match=skill.get("match") or {}, markdown=read_skill_raw(skill_id), path_hint=f"skills/{skill_id}/SKILL.md", ) @router.put("/{skill_id}", response_model=SkillOut) async def update_skill( skill_id: str, body: SkillUpdate, db: AsyncSession = Depends(get_db), user: User = Depends(require_admin), ): try: save_skill_raw(skill_id, body.markdown) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc except Exception as exc: # noqa: BLE001 raise HTTPException(status_code=400, detail=f"Invalid skill: {exc}") from exc await record_audit( db, user, "skills.update", "skills", 0, {"skill_id": skill_id, "bytes": len(body.markdown)}, ) skill = get_skill(skill_id) if not skill: raise HTTPException(status_code=404, detail="Skill not found") return SkillOut( id=skill["id"], name=skill["name"], description=skill["description"], priority=skill.get("priority") or 0, rule_ids=list(skill.get("rule_ids") or []), match=skill.get("match") or {}, markdown=read_skill_raw(skill_id), path_hint=f"skills/{skill_id}/SKILL.md", )