From a20e08a56e69a8b3e4702da9984c8bf5255e5927 Mon Sep 17 00:00:00 2001 From: mushuiyu886 Date: Mon, 20 Jul 2026 10:18:54 +0800 Subject: [PATCH] fix(skills): keep ClawHub search results on one line (#111441) --- src/cli/skills-cli.commands.test.ts | 18 ++++++++++++++++++ src/cli/skills-cli.ts | 15 +++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/cli/skills-cli.commands.test.ts b/src/cli/skills-cli.commands.test.ts index 2c8ffc843683..6bdd8adaee03 100644 --- a/src/cli/skills-cli.commands.test.ts +++ b/src/cli/skills-cli.commands.test.ts @@ -394,6 +394,24 @@ describe("skills cli commands", () => { expect(runtimeLogs).toEqual(["legacy-calendar Legacy Calendar"]); }); + it("keeps multiline ClawHub search metadata on one terminal line", async () => { + searchSkillsFromClawHubMock.mockResolvedValue([ + { + slug: "oauth-helper", + ownerHandle: "demo-owner", + displayName: "Oauth\nHelper", + summary: + "Automate OAuth login flows.\nSupports multiple providers.\n\nFeatures:\n- Confirm before authorizing", + }, + ]); + + await runCommand(["skills", "search", "oauth-helper"]); + + expect(runtimeLogs).toEqual([ + "@demo-owner/oauth-helper Oauth Helper Automate OAuth login flows. Supports multiple providers. Features: - Confirm before authorizing", + ]); + }); + it("keeps ClawHub skill search JSON output unchanged", async () => { const results = [ { diff --git a/src/cli/skills-cli.ts b/src/cli/skills-cli.ts index 172df8015b86..22820090c219 100644 --- a/src/cli/skills-cli.ts +++ b/src/cli/skills-cli.ts @@ -5,6 +5,7 @@ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../../packages/gateway-protocol/src/client-info.js"; +import { sanitizeForLog } from "../../packages/terminal-core/src/ansi.js"; import { formatDocsLink } from "../../packages/terminal-core/src/links.js"; import { theme } from "../../packages/terminal-core/src/theme.js"; import { @@ -95,6 +96,10 @@ function formatSkillWarning(message: string): string { return message.includes("╭─") ? message : theme.warn(message); } +function formatClawHubSearchText(value: string): string { + return sanitizeForLog(value.replace(/\s+/gu, " ")).trim(); +} + function isClawHubSkillBlockedCliFailure(result: { code?: string; warning?: string }): boolean { return ( result.code === CLAWHUB_TRUST_ERROR_CODE.CLAWHUB_DOWNLOAD_BLOCKED && @@ -431,10 +436,12 @@ export function registerSkillsCli(program: Command) { } for (const entry of results) { const ownerHandle = normalizeOptionalString(entry.ownerHandle); - const skillRef = ownerHandle ? `@${ownerHandle}/${entry.slug}` : entry.slug; - const version = entry.version ? ` v${entry.version}` : ""; - const summary = entry.summary ? ` ${entry.summary}` : ""; - defaultRuntime.log(`${skillRef}${version} ${entry.displayName}${summary}`); + const slug = formatClawHubSearchText(entry.slug); + const skillRef = ownerHandle ? `@${formatClawHubSearchText(ownerHandle)}/${slug}` : slug; + const version = entry.version ? ` v${formatClawHubSearchText(entry.version)}` : ""; + const summary = entry.summary ? ` ${formatClawHubSearchText(entry.summary)}` : ""; + const displayName = formatClawHubSearchText(entry.displayName); + defaultRuntime.log(`${skillRef}${version} ${displayName}${summary}`); } } catch (err) { defaultRuntime.error(String(err));