import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { buildWorkspaceSkillStatus, type SkillStatusEntry, type SkillStatusReport, } from "../agents/skills-status.js"; import { formatSkillInfo, formatSkillsCheck, formatSkillsList, } from "./skills-cli.js"; function createMockSkill( overrides: Partial = {}, ): SkillStatusEntry { return { name: "test-skill", description: "A test skill", source: "bundled", filePath: "/path/to/SKILL.md", baseDir: "/path/to", skillKey: "test-skill", emoji: "๐Ÿงช", homepage: "https://example.com", always: false, disabled: false, blockedByAllowlist: false, eligible: true, requirements: { bins: [], anyBins: [], env: [], config: [], os: [], }, missing: { bins: [], anyBins: [], env: [], config: [], os: [], }, configChecks: [], install: [], ...overrides, }; } function createMockReport(skills: SkillStatusEntry[]): SkillStatusReport { return { workspaceDir: "/workspace", managedSkillsDir: "/managed", skills, }; } describe("skills-cli", () => { describe("formatSkillsList", () => { it("formats empty skills list", () => { const report = createMockReport([]); const output = formatSkillsList(report, {}); expect(output).toContain("No skills found"); expect(output).toContain("npx clawdhub"); }); it("formats skills list with eligible skill", () => { const report = createMockReport([ createMockSkill({ name: "peekaboo", description: "Capture UI screenshots", emoji: "๐Ÿ“ธ", eligible: true, }), ]); const output = formatSkillsList(report, {}); expect(output).toContain("peekaboo"); expect(output).toContain("๐Ÿ“ธ"); expect(output).toContain("โœ“"); }); it("formats skills list with disabled skill", () => { const report = createMockReport([ createMockSkill({ name: "disabled-skill", disabled: true, eligible: false, }), ]); const output = formatSkillsList(report, {}); expect(output).toContain("disabled-skill"); expect(output).toContain("disabled"); }); it("formats skills list with missing requirements", () => { const report = createMockReport([ createMockSkill({ name: "needs-stuff", eligible: false, missing: { bins: ["ffmpeg"], anyBins: ["rg", "grep"], env: ["API_KEY"], config: [], os: ["darwin"], }, }), ]); const output = formatSkillsList(report, { verbose: true }); expect(output).toContain("needs-stuff"); expect(output).toContain("missing"); expect(output).toContain("anyBins"); expect(output).toContain("os:"); }); it("filters to eligible only with --eligible flag", () => { const report = createMockReport([ createMockSkill({ name: "eligible-one", eligible: true }), createMockSkill({ name: "not-eligible", eligible: false, disabled: true, }), ]); const output = formatSkillsList(report, { eligible: true }); expect(output).toContain("eligible-one"); expect(output).not.toContain("not-eligible"); }); it("outputs JSON with --json flag", () => { const report = createMockReport([ createMockSkill({ name: "json-skill" }), ]); const output = formatSkillsList(report, { json: true }); const parsed = JSON.parse(output); expect(parsed.skills).toHaveLength(1); expect(parsed.skills[0].name).toBe("json-skill"); }); }); describe("formatSkillInfo", () => { it("returns not found message for unknown skill", () => { const report = createMockReport([]); const output = formatSkillInfo(report, "unknown-skill", {}); expect(output).toContain("not found"); expect(output).toContain("npx clawdhub"); }); it("shows detailed info for a skill", () => { const report = createMockReport([ createMockSkill({ name: "detailed-skill", description: "A detailed description", homepage: "https://example.com", requirements: { bins: ["node"], anyBins: ["rg", "grep"], env: ["API_KEY"], config: [], os: [], }, missing: { bins: [], anyBins: [], env: ["API_KEY"], config: [], os: [], }, }), ]); const output = formatSkillInfo(report, "detailed-skill", {}); expect(output).toContain("detailed-skill"); expect(output).toContain("A detailed description"); expect(output).toContain("https://example.com"); expect(output).toContain("node"); expect(output).toContain("Any binaries"); expect(output).toContain("API_KEY"); }); it("outputs JSON with --json flag", () => { const report = createMockReport([ createMockSkill({ name: "info-skill" }), ]); const output = formatSkillInfo(report, "info-skill", { json: true }); const parsed = JSON.parse(output); expect(parsed.name).toBe("info-skill"); }); }); describe("formatSkillsCheck", () => { it("shows summary of skill status", () => { const report = createMockReport([ createMockSkill({ name: "ready-1", eligible: true }), createMockSkill({ name: "ready-2", eligible: true }), createMockSkill({ name: "not-ready", eligible: false, missing: { bins: ["go"], anyBins: [], env: [], config: [], os: [] }, }), createMockSkill({ name: "disabled", eligible: false, disabled: true }), ]); const output = formatSkillsCheck(report, {}); expect(output).toContain("2"); // eligible count expect(output).toContain("ready-1"); expect(output).toContain("ready-2"); expect(output).toContain("not-ready"); expect(output).toContain("go"); // missing binary expect(output).toContain("npx clawdhub"); }); it("outputs JSON with --json flag", () => { const report = createMockReport([ createMockSkill({ name: "skill-1", eligible: true }), createMockSkill({ name: "skill-2", eligible: false }), ]); const output = formatSkillsCheck(report, { json: true }); const parsed = JSON.parse(output); expect(parsed.summary.eligible).toBe(1); expect(parsed.summary.total).toBe(2); }); }); describe("integration: loads real skills from bundled directory", () => { function resolveBundledSkillsDir(): string | undefined { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", ".."); const candidate = path.join(root, "skills"); if (fs.existsSync(candidate)) return candidate; return undefined; } it("loads bundled skills and formats them", () => { const bundledDir = resolveBundledSkillsDir(); if (!bundledDir) { // Skip if skills dir not found (e.g., in CI without skills) return; } const report = buildWorkspaceSkillStatus("/tmp", { managedSkillsDir: "/nonexistent", }); // Should have loaded some skills expect(report.skills.length).toBeGreaterThan(0); // Format should work without errors const listOutput = formatSkillsList(report, {}); expect(listOutput).toContain("Skills"); const checkOutput = formatSkillsCheck(report, {}); expect(checkOutput).toContain("Total:"); // JSON output should be valid const jsonOutput = formatSkillsList(report, { json: true }); const parsed = JSON.parse(jsonOutput); expect(parsed.skills).toBeInstanceOf(Array); }); it("formats info for a real bundled skill (peekaboo)", () => { const bundledDir = resolveBundledSkillsDir(); if (!bundledDir) return; const report = buildWorkspaceSkillStatus("/tmp", { managedSkillsDir: "/nonexistent", }); // peekaboo is a bundled skill that should always exist const peekaboo = report.skills.find((s) => s.name === "peekaboo"); if (!peekaboo) { // Skip if peekaboo not found return; } const output = formatSkillInfo(report, "peekaboo", {}); expect(output).toContain("peekaboo"); expect(output).toContain("Details:"); }); }); });