import { Router } from "express"; import { skillRegistryService } from "../services/skill-registry.js"; import { skillRatingService } from "../services/skill-registry-ratings.js"; import { assertBoard } from "./authz.js"; /** * REST routes for the skill registry. * * Note: does NOT take a db param — the skill registry manages its own libSQL database. * All route handlers assert `board` access before delegating to skillRegistryService. */ export function skillRegistryRoutes(): Router { const router = Router(); const svc = skillRegistryService(); // List all skills (soft-deleted excluded by default) router.get("/skill-registry/skills", async (req, res) => { assertBoard(req); const includeRemoved = req.query.includeRemoved === "true"; const list = await svc.list({ includeRemoved }); res.json(list); }); // Get versions for a skill — must be registered before the single-skill route // to avoid /:id matching "versions" as the id segment router.get("/skill-registry/skills/:sourceId/:slug/versions", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const versions = await svc.getVersions(skillId); res.json(versions); }); // Install skill to agent directory router.post("/skill-registry/skills/:sourceId/:slug/install", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const { agentSkillsDir } = req.body as { agentSkillsDir: string }; if (!agentSkillsDir) return res.status(400).json({ error: "agentSkillsDir required" }); const result = await svc.install(skillId, agentSkillsDir); res.json(result); }); // Rollback to a specific version router.post("/skill-registry/skills/:sourceId/:slug/rollback", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const { versionId, agentSkillsDir } = req.body as { versionId: string; agentSkillsDir: string }; if (!versionId || !agentSkillsDir) { return res.status(400).json({ error: "versionId and agentSkillsDir required" }); } await svc.rollback(skillId, versionId, agentSkillsDir); res.json({ ok: true }); }); // Soft-delete a skill router.delete("/skill-registry/skills/:sourceId/:slug", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; await svc.uninstall(skillId); res.json({ ok: true }); }); // Submit a personal rating for a skill router.post("/skill-registry/skills/:sourceId/:slug/ratings", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const { stars, versionId, note } = req.body as { stars: number; versionId?: string; note?: string }; if (typeof stars !== "number" || stars < 1 || stars > 5) { return res.status(400).json({ error: "stars must be a number between 1 and 5" }); } const ratingSvc = skillRatingService(); await ratingSvc.rate({ skillId, versionId: versionId ?? null, stars, note: note ?? null }); res.json({ ok: true }); }); // Get personal ratings for a skill router.get("/skill-registry/skills/:sourceId/:slug/ratings", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const ratingSvc = skillRatingService(); const ratings = await ratingSvc.getRatings(skillId); res.json(ratings); }); // Get a single skill by id router.get("/skill-registry/skills/:sourceId/:slug", async (req, res) => { assertBoard(req); const skillId = `${req.params.sourceId}/${req.params.slug}`; const skill = await svc.getById(skillId); if (!skill) return res.status(404).json({ error: "Skill not found" }); res.json(skill); }); // Trigger fetch from all configured sources router.post("/skill-registry/fetch", async (req, res) => { assertBoard(req); const result = await svc.fetchAll(); res.json(result); }); return router; }