From c0ba2b8124fcbb0200a04b33ab59b430a0700829 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 4 Mar 2026 14:55:32 -0800 Subject: [PATCH] Fix Vercel API endpoints for unified skills directory structure The migration to source/skills/{name}/SKILL.md broke the serverless functions which still expected flat .md files in source/commands/. Also adds scripts/** to Vercel includeFiles for patterns API. Co-Authored-By: Claude Opus 4.6 --- api/commands.js | 42 ++++++++++++++++++++++++------------------ api/skills.js | 40 +++++++++++++++++++++------------------- vercel.json | 2 +- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/api/commands.js b/api/commands.js index 2f36b1639..24e429c2b 100644 --- a/api/commands.js +++ b/api/commands.js @@ -1,4 +1,4 @@ -import { readdirSync, readFileSync } from "fs"; +import { readdirSync, readFileSync, statSync, existsSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; @@ -8,28 +8,34 @@ const PROJECT_ROOT = join(__dirname, ".."); export default function handler(req, res) { try { - const sourceDir = join(PROJECT_ROOT, "source"); - const commandsDir = join(sourceDir, "commands"); + const skillsDir = join(PROJECT_ROOT, "source", "skills"); - const files = readdirSync(commandsDir); + const entries = readdirSync(skillsDir); const commands = []; - for (const file of files) { - if (file.endsWith(".md")) { - const content = readFileSync(join(commandsDir, file), "utf-8"); - const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/); + for (const entry of entries) { + const entryPath = join(skillsDir, entry); + if (!statSync(entryPath).isDirectory()) continue; - if (frontmatterMatch) { - const frontmatter = frontmatterMatch[1]; - const nameMatch = frontmatter.match(/name:\s*(.+)/); - const descMatch = frontmatter.match(/description:\s*(.+)/); + const skillMd = join(entryPath, "SKILL.md"); + if (!existsSync(skillMd)) continue; - commands.push({ - id: file.replace(".md", ""), - name: nameMatch?.[1]?.trim() || file.replace(".md", ""), - description: descMatch?.[1]?.trim() || "No description available", - }); - } + const content = readFileSync(skillMd, "utf-8"); + const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/); + + if (frontmatterMatch) { + const frontmatter = frontmatterMatch[1]; + const userInvokable = /user-invokable:\s*true/.test(frontmatter); + if (!userInvokable) continue; + + const nameMatch = frontmatter.match(/name:\s*(.+)/); + const descMatch = frontmatter.match(/description:\s*(.+)/); + + commands.push({ + id: entry, + name: nameMatch?.[1]?.trim() || entry, + description: descMatch?.[1]?.trim() || "No description available", + }); } } diff --git a/api/skills.js b/api/skills.js index 355f4c78b..788f9effb 100644 --- a/api/skills.js +++ b/api/skills.js @@ -1,4 +1,4 @@ -import { readdirSync, readFileSync } from "fs"; +import { readdirSync, readFileSync, statSync, existsSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; @@ -8,28 +8,31 @@ const PROJECT_ROOT = join(__dirname, ".."); export default function handler(req, res) { try { - const sourceDir = join(PROJECT_ROOT, "source"); - const skillsDir = join(sourceDir, "skills"); + const skillsDir = join(PROJECT_ROOT, "source", "skills"); - const files = readdirSync(skillsDir); + const entries = readdirSync(skillsDir); const skills = []; - for (const file of files) { - if (file.endsWith(".md")) { - const content = readFileSync(join(skillsDir, file), "utf-8"); - const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/); + for (const entry of entries) { + const entryPath = join(skillsDir, entry); + if (!statSync(entryPath).isDirectory()) continue; - if (frontmatterMatch) { - const frontmatter = frontmatterMatch[1]; - const nameMatch = frontmatter.match(/name:\s*(.+)/); - const descMatch = frontmatter.match(/description:\s*(.+)/); + const skillMd = join(entryPath, "SKILL.md"); + if (!existsSync(skillMd)) continue; - skills.push({ - id: file.replace(".md", ""), - name: nameMatch?.[1]?.trim() || file.replace(".md", ""), - description: descMatch?.[1]?.trim() || "No description available", - }); - } + const content = readFileSync(skillMd, "utf-8"); + const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/); + + if (frontmatterMatch) { + const frontmatter = frontmatterMatch[1]; + const nameMatch = frontmatter.match(/name:\s*(.+)/); + const descMatch = frontmatter.match(/description:\s*(.+)/); + + skills.push({ + id: entry, + name: nameMatch?.[1]?.trim() || entry, + description: descMatch?.[1]?.trim() || "No description available", + }); } } @@ -39,4 +42,3 @@ export default function handler(req, res) { res.status(500).json({ error: error.message }); } } - diff --git a/vercel.json b/vercel.json index 0810af773..c6b2360c2 100644 --- a/vercel.json +++ b/vercel.json @@ -4,7 +4,7 @@ "outputDirectory": "build", "functions": { "api/**/*.js": { - "includeFiles": "source/**,dist/**" + "includeFiles": "source/**,dist/**,scripts/**" } }, "rewrites": [