Skills: normalize emoji presentation across outputs

This commit is contained in:
Vincent Koc
2026-03-11 09:11:20 -04:00
parent ad7db1cc06
commit accabda65c
2 changed files with 36 additions and 4 deletions

View File

@@ -38,8 +38,12 @@ function formatSkillStatus(skill: SkillStatusEntry): string {
return theme.error("✗ missing"); return theme.error("✗ missing");
} }
function normalizeSkillEmoji(emoji?: string): string {
return (emoji ?? "📦").replaceAll("\uFE0E", "\uFE0F");
}
function formatSkillName(skill: SkillStatusEntry): string { function formatSkillName(skill: SkillStatusEntry): string {
const emoji = (skill.emoji ?? "📦").replaceAll("\uFE0E", "\uFE0F"); const emoji = normalizeSkillEmoji(skill.emoji);
return `${emoji} ${theme.command(skill.name)}`; return `${emoji} ${theme.command(skill.name)}`;
} }
@@ -154,7 +158,7 @@ export function formatSkillInfo(
} }
const lines: string[] = []; const lines: string[] = [];
const emoji = skill.emoji ?? "📦"; const emoji = normalizeSkillEmoji(skill.emoji);
const status = skill.eligible const status = skill.eligible
? theme.success("✓ Ready") ? theme.success("✓ Ready")
: skill.disabled : skill.disabled
@@ -282,7 +286,7 @@ export function formatSkillsCheck(report: SkillStatusReport, opts: SkillsCheckOp
lines.push(""); lines.push("");
lines.push(theme.heading("Ready to use:")); lines.push(theme.heading("Ready to use:"));
for (const skill of eligible) { for (const skill of eligible) {
const emoji = skill.emoji ?? "📦"; const emoji = normalizeSkillEmoji(skill.emoji);
lines.push(` ${emoji} ${skill.name}`); lines.push(` ${emoji} ${skill.name}`);
} }
} }
@@ -291,7 +295,7 @@ export function formatSkillsCheck(report: SkillStatusReport, opts: SkillsCheckOp
lines.push(""); lines.push("");
lines.push(theme.heading("Missing requirements:")); lines.push(theme.heading("Missing requirements:"));
for (const skill of missingReqs) { for (const skill of missingReqs) {
const emoji = skill.emoji ?? "📦"; const emoji = normalizeSkillEmoji(skill.emoji);
const missing = formatSkillMissingSummary(skill); const missing = formatSkillMissingSummary(skill);
lines.push(` ${emoji} ${skill.name} ${theme.muted(`(${missing})`)}`); lines.push(` ${emoji} ${skill.name} ${theme.muted(`(${missing})`)}`);
} }

View File

@@ -148,6 +148,18 @@ describe("skills-cli", () => {
expect(output).toContain("Any binaries"); expect(output).toContain("Any binaries");
expect(output).toContain("API_KEY"); expect(output).toContain("API_KEY");
}); });
it("normalizes text-presentation emoji selectors in info output", () => {
const report = createMockReport([
createMockSkill({
name: "info-emoji",
emoji: "🎛\uFE0E",
}),
]);
const output = formatSkillInfo(report, "info-emoji", {});
expect(output).toContain("🎛️");
});
}); });
describe("formatSkillsCheck", () => { describe("formatSkillsCheck", () => {
@@ -170,6 +182,22 @@ describe("skills-cli", () => {
expect(output).toContain("go"); // missing binary expect(output).toContain("go"); // missing binary
expect(output).toContain("npx clawhub"); expect(output).toContain("npx clawhub");
}); });
it("normalizes text-presentation emoji selectors in check output", () => {
const report = createMockReport([
createMockSkill({ name: "ready-emoji", emoji: "🎛\uFE0E", eligible: true }),
createMockSkill({
name: "missing-emoji",
emoji: "🎙\uFE0E",
eligible: false,
missing: { bins: ["ffmpeg"], anyBins: [], env: [], config: [], os: [] },
}),
]);
const output = formatSkillsCheck(report, {});
expect(output).toContain("🎛️ ready-emoji");
expect(output).toContain("🎙️ missing-emoji");
});
}); });
describe("JSON output", () => { describe("JSON output", () => {