From 26864e7c365221e3d731c6fcca00c700ddc138ab Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Sat, 7 Mar 2026 02:50:17 -0600 Subject: [PATCH 1/3] fix(skills): make `skills info` resolve name variants --- src/cli/skills-cli.format.ts | 50 +++++++++++++++++++++++++++++++++--- src/cli/skills-cli.test.ts | 24 +++++++++++++---- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/cli/skills-cli.format.ts b/src/cli/skills-cli.format.ts index 045281bc7d1..5acd3a7b0fe 100644 --- a/src/cli/skills-cli.format.ts +++ b/src/cli/skills-cli.format.ts @@ -94,6 +94,49 @@ function formatSkillMissingSummary(skill: SkillStatusEntry): string { return missing.join("; "); } +function normalizeSkillLookupToken(value: string): string { + return value + .trim() + .toLowerCase() + .replace(/[\s_/]+/g, "-") + .replace(/[^a-z0-9-]+/g, "") + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +function resolveSkillByName(report: SkillStatusReport, requestedName: string): SkillStatusEntry | null { + const raw = requestedName.trim(); + if (!raw) { + return null; + } + + const direct = report.skills.find((s) => s.name === raw || s.skillKey === raw); + if (direct) { + return direct; + } + + const lower = raw.toLowerCase(); + const caseInsensitive = report.skills.find( + (s) => s.name.toLowerCase() === lower || s.skillKey.toLowerCase() === lower, + ); + if (caseInsensitive) { + return caseInsensitive; + } + + const normalized = normalizeSkillLookupToken(raw); + if (!normalized) { + return null; + } + + return ( + report.skills.find( + (s) => + normalizeSkillLookupToken(s.name) === normalized || + normalizeSkillLookupToken(s.skillKey) === normalized, + ) ?? null + ); +} + export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOptions): string { const skills = opts.eligible ? report.skills.filter((s) => s.eligible) : report.skills; @@ -168,14 +211,15 @@ export function formatSkillInfo( skillName: string, opts: SkillInfoOptions, ): string { - const skill = report.skills.find((s) => s.name === skillName || s.skillKey === skillName); + const requestedName = skillName.trim(); + const skill = resolveSkillByName(report, requestedName); if (!skill) { if (opts.json) { - return JSON.stringify({ error: "not found", skill: skillName }, null, 2); + return JSON.stringify({ error: "not found", skill: requestedName }, null, 2); } return appendClawHubHint( - `Skill "${skillName}" not found. Run \`${formatCliCommand("openclaw skills list")}\` to see available skills.`, + `Skill "${requestedName}" not found. Run \`${formatCliCommand("openclaw skills list")}\` to see available skills.`, opts.json, ); } diff --git a/src/cli/skills-cli.test.ts b/src/cli/skills-cli.test.ts index 27031fc0fdf..2e1aa58a897 100644 --- a/src/cli/skills-cli.test.ts +++ b/src/cli/skills-cli.test.ts @@ -149,16 +149,30 @@ describe("skills-cli", () => { expect(output).toContain("API_KEY"); }); - it("normalizes text-presentation emoji selectors in info output", () => { + it("resolves skill info case-insensitively", () => { const report = createMockReport([ createMockSkill({ - name: "info-emoji", - emoji: "🎛\uFE0E", + name: "Excel-XLSX", + skillKey: "excel-xlsx", + description: "Spreadsheet helpers", }), ]); - const output = formatSkillInfo(report, "info-emoji", {}); - expect(output).toContain("🎛️"); + const output = formatSkillInfo(report, "excel-xlsx", {}); + expect(output).toContain("Spreadsheet helpers"); + }); + + it("resolves skill info across separator variants", () => { + const report = createMockReport([ + createMockSkill({ + name: "Excel XLSX", + skillKey: "excel_xlsx", + description: "Spreadsheet helpers", + }), + ]); + + const output = formatSkillInfo(report, "excel-xlsx", {}); + expect(output).toContain("Spreadsheet helpers"); }); }); From 7e232a9f0c6af822be68055200f5067e019fce7e Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Sat, 7 Mar 2026 03:17:49 -0600 Subject: [PATCH 2/3] test(skills): exercise case-insensitive lookup branch --- src/cli/skills-cli.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/skills-cli.test.ts b/src/cli/skills-cli.test.ts index 2e1aa58a897..ad7ee0ffa6e 100644 --- a/src/cli/skills-cli.test.ts +++ b/src/cli/skills-cli.test.ts @@ -152,8 +152,8 @@ describe("skills-cli", () => { it("resolves skill info case-insensitively", () => { const report = createMockReport([ createMockSkill({ - name: "Excel-XLSX", - skillKey: "excel-xlsx", + name: "Excel XLSX", + skillKey: "Excel-XLSX", description: "Spreadsheet helpers", }), ]); From 749928c35487d55100477496ba05ffc40b09be2d Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Sat, 7 Mar 2026 03:50:28 -0600 Subject: [PATCH 3/3] style(skills): format lookup resolver signature --- src/cli/skills-cli.format.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/skills-cli.format.ts b/src/cli/skills-cli.format.ts index 5acd3a7b0fe..977f0f651bf 100644 --- a/src/cli/skills-cli.format.ts +++ b/src/cli/skills-cli.format.ts @@ -104,7 +104,10 @@ function normalizeSkillLookupToken(value: string): string { .replace(/^-+|-+$/g, ""); } -function resolveSkillByName(report: SkillStatusReport, requestedName: string): SkillStatusEntry | null { +function resolveSkillByName( + report: SkillStatusReport, + requestedName: string, +): SkillStatusEntry | null { const raw = requestedName.trim(); if (!raw) { return null;