import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { describe, expect, it } from "vitest"; function parseFrontmatter(content: string): Record { const match = content.match(/^---\n([\s\S]*?)\n---/); if (!match) throw new Error("No frontmatter found"); const yaml = match[1]; const result: Record = {}; for (const line of yaml.split("\n")) { const colonIdx = line.indexOf(":"); if (colonIdx === -1) continue; const key = line.slice(0, colonIdx).trim(); let value: unknown = line.slice(colonIdx + 1).trim(); if (typeof value === "string" && value.startsWith("{")) { value = JSON.parse(value); } result[key] = value; } return result; } describe("SKILL.md", () => { const skillPath = resolve("skills/clawbook/SKILL.md"); const content = readFileSync(skillPath, "utf-8"); const frontmatter = parseFrontmatter(content); it("has required name field", () => { expect(frontmatter.name).toBe("clawbook"); }); it("has description", () => { expect(typeof frontmatter.description).toBe("string"); expect((frontmatter.description as string).length).toBeGreaterThan(10); }); it("has homepage", () => { expect(frontmatter.homepage).toBe("https://clawbook.network"); }); it("has openclaw metadata with env requirements", () => { const metadata = frontmatter.metadata as Record; expect(metadata).toBeDefined(); const openclaw = (metadata as Record).openclaw as Record< string, unknown >; expect(openclaw).toBeDefined(); const requires = openclaw.requires as Record; expect(requires.env).toContain("CLAWBOOK_API_URL"); expect(requires.env).toContain("SIGMA_MEMBER_WIF"); }); }); describe("HEARTBEAT.md", () => { const heartbeatPath = resolve("skills/clawbook/HEARTBEAT.md"); const content = readFileSync(heartbeatPath, "utf-8"); const frontmatter = parseFrontmatter(content); it("has name field", () => { expect(frontmatter.name).toBe("clawbook-heartbeat"); }); it("has description", () => { expect(typeof frontmatter.description).toBe("string"); }); it("is not user-invocable", () => { expect(frontmatter["user-invocable"]).toBe("false"); }); });