2026-02-19 14:59:34 -08:00

86 lines
2.3 KiB
TypeScript

import { readFileSync, readdirSync, existsSync } from "node:fs";
import { join } from "node:path";
import { resolveOpenClawStateDir, resolveWorkspaceRoot } from "@/lib/workspace";
export const dynamic = "force-dynamic";
type SkillEntry = {
name: string;
description: string;
emoji?: string;
source: string;
filePath: string;
};
/** Parse YAML frontmatter from a SKILL.md file (lightweight, no deps). */
function parseSkillFrontmatter(content: string): {
name?: string;
description?: string;
emoji?: string;
} {
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
if (!match) return {};
const yaml = match[1];
const result: Record<string, string> = {};
for (const line of yaml.split("\n")) {
const kv = line.match(/^(\w+)\s*:\s*(.+)/);
if (kv) {
result[kv[1]] = kv[2].replace(/^["']|["']$/g, "").trim();
}
}
return {
name: result.name,
description: result.description,
emoji: result.emoji,
};
}
function scanSkillDir(dir: string, source: string): SkillEntry[] {
const skills: SkillEntry[] = [];
if (!existsSync(dir)) return skills;
try {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const skillMdPath = join(dir, entry.name, "SKILL.md");
if (!existsSync(skillMdPath)) continue;
try {
const content = readFileSync(skillMdPath, "utf-8");
const meta = parseSkillFrontmatter(content);
skills.push({
name: meta.name ?? entry.name,
description: meta.description ?? "",
emoji: meta.emoji,
source,
filePath: skillMdPath,
});
} catch {
// skip unreadable skill files
}
}
} catch {
// dir unreadable
}
return skills;
}
export async function GET() {
const stateDir = resolveOpenClawStateDir();
const workspaceRoot = resolveWorkspaceRoot() ?? join(stateDir, "workspace");
const managedSkills = scanSkillDir(join(stateDir, "skills"), "managed");
const workspaceSkills = scanSkillDir(
join(workspaceRoot, "skills"),
"workspace",
);
const allSkills = [...workspaceSkills, ...managedSkills];
allSkills.sort((a, b) => a.name.localeCompare(b.name));
return Response.json({ skills: allSkills });
}