From ccda3cf0f0cbbab592b23a9c771b3f7719b452b9 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 27 May 2026 21:49:21 -0300 Subject: [PATCH] feat(agent-skills): add POST /generate with management auth + dryRun default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements POST /api/agent-skills/generate (F4): - Requires management auth via requireManagementAuth (same pattern as usage/combo-health-dashboard and other management routes) - Validates body via GenerateBodySchema (dryRun defaults true, prune defaults false — safe preview mode) - Dynamic import of @/lib/agentSkills/generator so the route coexists before F3 (generator.ts) is merged; returns 503 if module unavailable - Hard Rule #12: all error paths use buildErrorBody (400/401/403/503/500) - Hard Rule #7: Zod validates both JSON parse and schema shape --- src/app/api/agent-skills/generate/route.ts | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/app/api/agent-skills/generate/route.ts diff --git a/src/app/api/agent-skills/generate/route.ts b/src/app/api/agent-skills/generate/route.ts new file mode 100644 index 0000000000..d2852f8733 --- /dev/null +++ b/src/app/api/agent-skills/generate/route.ts @@ -0,0 +1,68 @@ +/** + * POST /api/agent-skills/generate + * + * Triggers the Agent Skills generator. Requires management auth. + * Default: dryRun=true, prune=false (safe preview mode). + * + * Body (all optional): + * - dryRun?: boolean (default true) + * - prune?: boolean (default false) + * - onlyIds?: string[] (only regenerate these skill IDs) + * + * Response: GeneratorReport + * 401 if unauthenticated. + * 400 if body is malformed. + * 503 if generator module is not yet available (F3 not merged). + * 500 on internal error. + */ +import { NextResponse } from "next/server"; + +import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts"; +import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; +import { GenerateBodySchema } from "@/lib/agentSkills/schemas"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + // Management auth — returns null if OK, error Response if not + const authError = await requireManagementAuth(request); + if (authError) return authError; + + // Parse + validate request body + let rawBody: unknown; + try { + rawBody = await request.json(); + } catch { + return NextResponse.json(buildErrorBody(400, "Request body must be valid JSON"), { + status: 400, + }); + } + + const parsed = GenerateBodySchema.safeParse(rawBody ?? {}); + if (!parsed.success) { + return NextResponse.json( + buildErrorBody(400, parsed.error.issues[0]?.message ?? "Invalid request body"), + { status: 400 }, + ); + } + + // Dynamic import — F3 (generator.ts) may not be merged yet + let generateAgentSkills: (opts: typeof parsed.data) => Promise; + try { + const mod = await import("@/lib/agentSkills/generator"); + generateAgentSkills = mod.generateAgentSkills; + } catch { + return NextResponse.json( + buildErrorBody(503, "Generator module is not yet available"), + { status: 503 }, + ); + } + + try { + const report = await generateAgentSkills(parsed.data); + return NextResponse.json(report); + } catch (error) { + console.error("[API] POST /api/agent-skills/generate error:", error); + return NextResponse.json(buildErrorBody(500, "Generator failed"), { status: 500 }); + } +}