From af26a80bba49646693c15438c5af36e7f53abb51 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 27 Feb 2026 09:04:02 -0800 Subject: [PATCH] Revert "cli: format skills capability output" This reverts commit acd5e8b41428f939d95a0000c4e7e62d0267690d. --- src/cli/skills-cli.format.ts | 399 ++++++++--------------------------- 1 file changed, 91 insertions(+), 308 deletions(-) diff --git a/src/cli/skills-cli.format.ts b/src/cli/skills-cli.format.ts index bce024676a8..5f6dcfdcd2a 100644 --- a/src/cli/skills-cli.format.ts +++ b/src/cli/skills-cli.format.ts @@ -1,5 +1,4 @@ import type { SkillStatusEntry, SkillStatusReport } from "../agents/skills-status.js"; -import type { SkillCapability } from "../agents/skills/types.js"; import { renderTable } from "../terminal/table.js"; import { theme } from "../terminal/theme.js"; import { shortenHomePath } from "../utils.js"; @@ -19,37 +18,6 @@ export type SkillsCheckOptions = { json?: boolean; }; -const CAPABILITY_ICONS: Record = { - shell: "shell", - filesystem: "filesystem", - network: "network", - browser: "browser", - sessions: "sessions", -}; - -function formatCapabilityTags(capabilities: SkillCapability[]): string { - if (capabilities.length === 0) { - return ""; - } - return capabilities.map((cap) => CAPABILITY_ICONS[cap] ?? cap).join(" "); -} - -function formatScanBadge(scanResult?: { severity: string }): string { - if (!scanResult) { - return ""; - } - switch (scanResult.severity) { - case "critical": - return theme.error("[blocked]"); - case "warn": - return theme.warn("[warn]"); - case "info": - return theme.muted("[notice]"); - default: - return ""; - } -} - function appendClawHubHint(output: string, json?: boolean): string { if (json) { return output; @@ -58,19 +26,21 @@ function appendClawHubHint(output: string, json?: boolean): string { } function formatSkillStatus(skill: SkillStatusEntry): string { - if (skill.scanResult?.severity === "critical") { - return theme.error("x blocked"); - } if (skill.eligible) { - return theme.success("+ ready"); + return theme.success("✓ ready"); } if (skill.disabled) { - return theme.warn("- disabled"); + return theme.warn("⏸ disabled"); } if (skill.blockedByAllowlist) { - return theme.warn("x blocked"); + return theme.warn("🚫 blocked"); } - return theme.error("x missing"); + return theme.error("✗ missing"); +} + +function formatSkillName(skill: SkillStatusEntry): string { + const emoji = skill.emoji ?? "📦"; + return `${emoji} ${theme.command(skill.name)}`; } function formatSkillMissingSummary(skill: SkillStatusEntry): string { @@ -112,8 +82,6 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti primaryEnv: s.primaryEnv, homepage: s.homepage, missing: s.missing, - capabilities: s.capabilities, - scanResult: s.scanResult, })), }; return JSON.stringify(jsonReport, null, 2); @@ -127,26 +95,13 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti } const eligible = skills.filter((s) => s.eligible); - const termWidth = process.stdout.columns ?? 120; - const tableWidth = Math.max(60, termWidth - 1); - const descLimit = opts.verbose ? 30 : 44; + const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1); const rows = skills.map((skill) => { const missing = formatSkillMissingSummary(skill); - const caps = formatCapabilityTags(skill.capabilities); - const scan = formatScanBadge(skill.scanResult); - // Plain text name (no emoji) to avoid double-width alignment issues - const name = theme.command(skill.name); - const skillLabel = caps ? `${name} ${theme.muted(caps)}` : name; - // Truncate description as plain text BEFORE applying ANSI - const rawDesc = - skill.description.length > descLimit - ? skill.description.slice(0, descLimit - 1) + "..." - : skill.description; return { Status: formatSkillStatus(skill), - Skill: skillLabel, - Scan: scan, - Description: theme.muted(rawDesc), + Skill: formatSkillName(skill), + Description: theme.muted(skill.description), Source: skill.source ?? "", Missing: missing ? theme.warn(missing) : "", }; @@ -154,13 +109,12 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti const columns = [ { key: "Status", header: "Status", minWidth: 10 }, - { key: "Skill", header: "Skill", minWidth: 16 }, - { key: "Description", header: "Description", minWidth: 20, maxWidth: descLimit + 4 }, + { key: "Skill", header: "Skill", minWidth: 18, flex: true }, + { key: "Description", header: "Description", minWidth: 24, flex: true }, { key: "Source", header: "Source", minWidth: 10 }, ]; if (opts.verbose) { - columns.push({ key: "Scan", header: "Scan", minWidth: 10 }); - columns.push({ key: "Missing", header: "Missing", minWidth: 14 }); + columns.push({ key: "Missing", header: "Missing", minWidth: 18, flex: true }); } const lines: string[] = []; @@ -199,165 +153,85 @@ export function formatSkillInfo( return JSON.stringify(skill, null, 2); } - const status = - skill.scanResult?.severity === "critical" - ? theme.error("x Blocked (security)") - : skill.eligible - ? theme.success("+ Ready") - : skill.disabled - ? theme.warn("- Disabled") - : skill.blockedByAllowlist - ? theme.warn("x Blocked by allowlist") - : theme.error("x Missing requirements"); - const lines: string[] = []; - lines.push(`${theme.heading(skill.name)} ${status}`); + const emoji = skill.emoji ?? "📦"; + const status = skill.eligible + ? theme.success("✓ Ready") + : skill.disabled + ? theme.warn("⏸ Disabled") + : skill.blockedByAllowlist + ? theme.warn("🚫 Blocked by allowlist") + : theme.error("✗ Missing requirements"); + + lines.push(`${emoji} ${theme.heading(skill.name)} ${status}`); lines.push(""); lines.push(skill.description); + lines.push(""); - // Details table - const detailRows: Array> = [ - { Field: "Source", Value: skill.source }, - { Field: "Path", Value: shortenHomePath(skill.filePath) }, - ]; + lines.push(theme.heading("Details:")); + lines.push(`${theme.muted(" Source:")} ${skill.source}`); + lines.push(`${theme.muted(" Path:")} ${shortenHomePath(skill.filePath)}`); if (skill.homepage) { - detailRows.push({ Field: "Homepage", Value: skill.homepage }); + lines.push(`${theme.muted(" Homepage:")} ${skill.homepage}`); } if (skill.primaryEnv) { - detailRows.push({ Field: "Primary env", Value: skill.primaryEnv }); + lines.push(`${theme.muted(" Primary env:")} ${skill.primaryEnv}`); } - lines.push(""); - lines.push( - renderTable({ - columns: [ - { key: "Field", header: "Detail", minWidth: 12 }, - { key: "Value", header: "Value", minWidth: 20 }, - ], - rows: detailRows, - }).trimEnd(), - ); - // Capabilities table - if (skill.capabilities.length > 0) { - const capLabels: Record = { - shell: "Run shell commands", - filesystem: "Read and write files", - network: "Make outbound HTTP requests", - browser: "Control browser sessions", - sessions: "Spawn sub-sessions and agents", - }; - const capRows = skill.capabilities.map((cap) => ({ - Capability: CAPABILITY_ICONS[cap] ?? cap, - Name: cap, - Description: capLabels[cap] ?? cap, - })); + const hasRequirements = + skill.requirements.bins.length > 0 || + skill.requirements.anyBins.length > 0 || + skill.requirements.env.length > 0 || + skill.requirements.config.length > 0 || + skill.requirements.os.length > 0; + + if (hasRequirements) { lines.push(""); - lines.push(theme.heading("Capabilities")); - lines.push( - renderTable({ - columns: [ - { key: "Capability", header: "Icon", minWidth: 6 }, - { key: "Name", header: "Capability", minWidth: 12 }, - { key: "Description", header: "Description", minWidth: 20 }, - ], - rows: capRows, - }).trimEnd(), - ); + lines.push(theme.heading("Requirements:")); + if (skill.requirements.bins.length > 0) { + const binsStatus = skill.requirements.bins.map((bin) => { + const missing = skill.missing.bins.includes(bin); + return missing ? theme.error(`✗ ${bin}`) : theme.success(`✓ ${bin}`); + }); + lines.push(`${theme.muted(" Binaries:")} ${binsStatus.join(", ")}`); + } + if (skill.requirements.anyBins.length > 0) { + const anyBinsMissing = skill.missing.anyBins.length > 0; + const anyBinsStatus = skill.requirements.anyBins.map((bin) => { + const missing = anyBinsMissing; + return missing ? theme.error(`✗ ${bin}`) : theme.success(`✓ ${bin}`); + }); + lines.push(`${theme.muted(" Any binaries:")} ${anyBinsStatus.join(", ")}`); + } + if (skill.requirements.env.length > 0) { + const envStatus = skill.requirements.env.map((env) => { + const missing = skill.missing.env.includes(env); + return missing ? theme.error(`✗ ${env}`) : theme.success(`✓ ${env}`); + }); + lines.push(`${theme.muted(" Environment:")} ${envStatus.join(", ")}`); + } + if (skill.requirements.config.length > 0) { + const configStatus = skill.requirements.config.map((cfg) => { + const missing = skill.missing.config.includes(cfg); + return missing ? theme.error(`✗ ${cfg}`) : theme.success(`✓ ${cfg}`); + }); + lines.push(`${theme.muted(" Config:")} ${configStatus.join(", ")}`); + } + if (skill.requirements.os.length > 0) { + const osStatus = skill.requirements.os.map((osName) => { + const missing = skill.missing.os.includes(osName); + return missing ? theme.error(`✗ ${osName}`) : theme.success(`✓ ${osName}`); + }); + lines.push(`${theme.muted(" OS:")} ${osStatus.join(", ")}`); + } } - // Security table - if (skill.scanResult) { - const scanBadge = formatScanBadge(skill.scanResult); - const secRows: Array> = [ - { Field: "Scan", Value: scanBadge || theme.success("+ clean") }, - ]; - lines.push(""); - lines.push(theme.heading("Security")); - lines.push( - renderTable({ - columns: [ - { key: "Field", header: "Check", minWidth: 10 }, - { key: "Value", header: "Result", minWidth: 14 }, - ], - rows: secRows, - }).trimEnd(), - ); - } - - // Requirements table - const reqRows: Array> = []; - for (const bin of skill.requirements.bins) { - const ok = !skill.missing.bins.includes(bin); - reqRows.push({ - Type: "bin", - Name: bin, - Status: ok ? theme.success("+ ok") : theme.error("x missing"), - }); - } - for (const bin of skill.requirements.anyBins) { - const ok = skill.missing.anyBins.length === 0; - reqRows.push({ - Type: "anyBin", - Name: bin, - Status: ok ? theme.success("+ ok") : theme.error("x missing"), - }); - } - for (const env of skill.requirements.env) { - const ok = !skill.missing.env.includes(env); - reqRows.push({ - Type: "env", - Name: env, - Status: ok ? theme.success("+ ok") : theme.error("x missing"), - }); - } - for (const cfg of skill.requirements.config) { - const ok = !skill.missing.config.includes(cfg); - reqRows.push({ - Type: "config", - Name: cfg, - Status: ok ? theme.success("+ ok") : theme.error("x missing"), - }); - } - for (const osName of skill.requirements.os) { - const ok = !skill.missing.os.includes(osName); - reqRows.push({ - Type: "os", - Name: osName, - Status: ok ? theme.success("+ ok") : theme.error("x missing"), - }); - } - if (reqRows.length > 0) { - lines.push(""); - lines.push(theme.heading("Requirements")); - lines.push( - renderTable({ - columns: [ - { key: "Type", header: "Type", minWidth: 8 }, - { key: "Name", header: "Name", minWidth: 14 }, - { key: "Status", header: "Status", minWidth: 10 }, - ], - rows: reqRows, - }).trimEnd(), - ); - } - - // Install options table if (skill.install.length > 0 && !skill.eligible) { - const installRows = skill.install.map((inst) => ({ - Kind: inst.kind, - Label: inst.label, - })); lines.push(""); - lines.push(theme.heading("Install options")); - lines.push( - renderTable({ - columns: [ - { key: "Kind", header: "Kind", minWidth: 8 }, - { key: "Label", header: "Action", minWidth: 20 }, - ], - rows: installRows, - }).trimEnd(), - ); + lines.push(theme.heading("Install options:")); + for (const inst of skill.install) { + lines.push(` ${theme.warn("→")} ${inst.label}`); + } } return appendClawHubHint(lines.join("\n"), opts.json); @@ -397,113 +271,22 @@ export function formatSkillsCheck(report: SkillStatusReport, opts: SkillsCheckOp const lines: string[] = []; lines.push(theme.heading("Skills Status Check")); + lines.push(""); + lines.push(`${theme.muted("Total:")} ${report.skills.length}`); + lines.push(`${theme.success("✓")} ${theme.muted("Eligible:")} ${eligible.length}`); + lines.push(`${theme.warn("⏸")} ${theme.muted("Disabled:")} ${disabled.length}`); + lines.push(`${theme.warn("🚫")} ${theme.muted("Blocked by allowlist:")} ${blocked.length}`); + lines.push(`${theme.error("✗")} ${theme.muted("Missing requirements:")} ${missingReqs.length}`); - // Summary table - const summaryRows = [ - { Metric: "Total", Count: String(report.skills.length) }, - { Metric: theme.success("Eligible"), Count: String(eligible.length) }, - { Metric: theme.warn("Disabled"), Count: String(disabled.length) }, - { Metric: theme.warn("Blocked (allowlist)"), Count: String(blocked.length) }, - { Metric: theme.error("Missing requirements"), Count: String(missingReqs.length) }, - ]; - lines.push( - renderTable({ - columns: [ - { key: "Metric", header: "Status", minWidth: 20 }, - { key: "Count", header: "Count", minWidth: 6 }, - ], - rows: summaryRows, - }).trimEnd(), - ); - - // Capability summary for community skills - const communitySkills = report.skills.filter( - (s) => s.source === "openclaw-managed" && !s.bundled, - ); - if (communitySkills.length > 0) { - const capCounts = new Map(); - for (const skill of communitySkills) { - for (const cap of skill.capabilities) { - const list = capCounts.get(cap) ?? []; - list.push(skill.name); - capCounts.set(cap, list); - } - } - if (capCounts.size > 0) { - const capRows = [...capCounts.entries()].map(([cap, names]) => ({ - Icon: CAPABILITY_ICONS[cap] ?? cap, - Capability: cap, - Count: String(names.length), - Skills: names.join(", "), - })); - lines.push(""); - lines.push(theme.heading("Community skill capabilities")); - lines.push( - renderTable({ - columns: [ - { key: "Icon", header: "Icon", minWidth: 5 }, - { key: "Capability", header: "Capability", minWidth: 12 }, - { key: "Count", header: "#", minWidth: 4 }, - { key: "Skills", header: "Skills", minWidth: 16 }, - ], - rows: capRows, - }).trimEnd(), - ); - } - - // Scan results summary - const scanClean = communitySkills.filter( - (s) => !s.scanResult || s.scanResult.severity === "clean", - ).length; - const scanWarn = communitySkills.filter((s) => s.scanResult?.severity === "warn").length; - const scanBlocked = communitySkills.filter((s) => s.scanResult?.severity === "critical").length; - if (scanWarn > 0 || scanBlocked > 0) { - const scanRows = [ - { Result: theme.success("Clean"), Count: String(scanClean) }, - ...(scanWarn > 0 ? [{ Result: theme.warn("Warning"), Count: String(scanWarn) }] : []), - ...(scanBlocked > 0 - ? [{ Result: theme.error("Blocked"), Count: String(scanBlocked) }] - : []), - ]; - lines.push(""); - lines.push(theme.heading("Scan results")); - lines.push( - renderTable({ - columns: [ - { key: "Result", header: "Result", minWidth: 10 }, - { key: "Count", header: "#", minWidth: 4 }, - ], - rows: scanRows, - }).trimEnd(), - ); - } - } - - // Ready skills table if (eligible.length > 0) { - const readyRows = eligible.map((skill) => { - const caps = formatCapabilityTags(skill.capabilities); - return { - Skill: theme.command(skill.name), - Caps: caps ? theme.muted(caps) : "", - Source: skill.source, - }; - }); lines.push(""); - lines.push(theme.heading("Ready to use")); - lines.push( - renderTable({ - columns: [ - { key: "Skill", header: "Skill", minWidth: 16 }, - { key: "Caps", header: "Caps", minWidth: 8 }, - { key: "Source", header: "Source", minWidth: 10 }, - ], - rows: readyRows, - }).trimEnd(), - ); + lines.push(theme.heading("Ready to use:")); + for (const skill of eligible) { + const emoji = skill.emoji ?? "📦"; + lines.push(` ${emoji} ${skill.name}`); + } } - // Missing requirements if (missingReqs.length > 0) { lines.push(""); lines.push(theme.heading("Missing requirements:"));