Files
Moltbot/src/cli/skills-cli.test.ts
2026-02-19 07:33:37 +00:00

220 lines
7.0 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { SkillStatusEntry, SkillStatusReport } from "../agents/skills-status.js";
import { createEmptyInstallChecks } from "./requirements-test-fixtures.js";
import { formatSkillInfo, formatSkillsCheck, formatSkillsList } from "./skills-cli.format.js";
// Unit tests: don't pay the runtime cost of loading/parsing the real skills loader.
vi.mock("@mariozechner/pi-coding-agent", () => ({
loadSkillsFromDir: () => ({ skills: [] }),
formatSkillsForPrompt: () => "",
}));
function createMockSkill(overrides: Partial<SkillStatusEntry> = {}): SkillStatusEntry {
return {
name: "test-skill",
description: "A test skill",
source: "bundled",
bundled: false,
filePath: "/path/to/SKILL.md",
baseDir: "/path/to",
skillKey: "test-skill",
emoji: "🧪",
homepage: "https://example.com",
always: false,
disabled: false,
blockedByAllowlist: false,
eligible: true,
...createEmptyInstallChecks(),
...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 clawhub");
});
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");
});
});
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 clawhub");
});
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");
});
});
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 clawhub");
});
});
describe("JSON output", () => {
it.each([
{
formatter: "list",
output: formatSkillsList(createMockReport([createMockSkill({ name: "json-skill" })]), {
json: true,
}),
assert: (parsed: Record<string, unknown>) => {
const skills = parsed.skills as Array<Record<string, unknown>>;
expect(skills).toHaveLength(1);
expect(skills[0]?.name).toBe("json-skill");
},
},
{
formatter: "info",
output: formatSkillInfo(
createMockReport([createMockSkill({ name: "info-skill" })]),
"info-skill",
{ json: true },
),
assert: (parsed: Record<string, unknown>) => {
expect(parsed.name).toBe("info-skill");
},
},
{
formatter: "check",
output: formatSkillsCheck(
createMockReport([
createMockSkill({ name: "skill-1", eligible: true }),
createMockSkill({ name: "skill-2", eligible: false }),
]),
{ json: true },
),
assert: (parsed: Record<string, unknown>) => {
const summary = parsed.summary as Record<string, unknown>;
expect(summary.eligible).toBe(1);
expect(summary.total).toBe(2);
},
},
])("outputs JSON with --json flag for $formatter", ({ output, assert }) => {
const parsed = JSON.parse(output) as Record<string, unknown>;
assert(parsed);
});
});
});