import type { Command } from "commander" ;
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js" ;
import {
installSkillFromClawHub,
readTrackedClawHubSkillSlugs,
searchSkillsFromClawHub,
updateSkillsFromClawHub,
} from "../agents/skills-clawhub.js" ;
import { loadConfig } from "../config/config.js" ;
import { defaultRuntime } from "../runtime.js" ;
import { normalizeOptionalString } from "../shared/string-coerce.js" ;
import { formatDocsLink } from "../terminal/links.js" ;
import { theme } from "../terminal/theme.js" ;
import { formatSkillInfo, formatSkillsCheck, formatSkillsList } from "./skills-cli.format.js" ;
export type {
SkillInfoOptions,
SkillsCheckOptions,
SkillsListOptions,
} from "./skills-cli.format.js" ;
export { formatSkillInfo, formatSkillsCheck, formatSkillsList } from "./skills-cli.format.js" ;
type SkillStatusReport = Awaited<
ReturnType<(typeof import ("../agents/skills-status.js" ))["buildWorkspaceSkillStatus" ]>
>;
async function loadSkillsStatusReport(): Promise<SkillStatusReport> {
const config = loadConfig();
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
const { buildWorkspaceSkillStatus } = await import ("../agents/skills-status.js" );
return buildWorkspaceSkillStatus(workspaceDir, { config });
}
async function runSkillsAction(render: (report: SkillStatusReport) => string): Promise<void > {
try {
const report = await loadSkillsStatusReport();
defaultRuntime.writeStdout(render(report));
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1 );
}
}
function resolveActiveWorkspaceDir(): string {
const config = loadConfig();
return resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
}
/**
* Register the skills CLI commands
*/
export function registerSkillsCli(program: Command) {
const skills = program
.command("skills" )
.description("List and inspect available skills" )
.addHelpText(
"after" ,
() =>
`\n${theme.muted("Docs:" )} ${formatDocsLink("/cli/skills" , "docs.openclaw.ai/cli/skills" )}\n`,
);
skills
.command("search" )
.description("Search ClawHub skills" )
.argument("[query...]" , "Optional search query" )
.option("--limit <n>" , "Max results" , (value) => Number.parseInt(value, 10 ))
.option("--json" , "Output as JSON" , false )
.action(async (queryParts: string[], opts: { limit?: number; json?: boolean }) => {
try {
const results = await searchSkillsFromClawHub({
query: normalizeOptionalString(queryParts.join(" " )),
limit: opts.limit,
});
if (opts.json) {
defaultRuntime.writeJson({ results });
return ;
}
if (results.length === 0 ) {
defaultRuntime.log("No ClawHub skills found." );
return ;
}
for (const entry of results) {
const version = entry.version ? ` v${entry.version}` : "" ;
const summary = entry.summary ? ` ${entry.summary}` : "" ;
defaultRuntime.log(`${entry.slug}${version} ${entry.displayName}${summary}`);
}
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1 );
}
});
skills
.command("install" )
.description("Install a skill from ClawHub into the active workspace" )
.argument("<slug>" , "ClawHub skill slug" )
.option("--version <version>" , "Install a specific version" )
.option("--force" , "Overwrite an existing workspace skill" , false )
.action(async (slug: string, opts: { version?: string; force?: boolean }) => {
try {
const workspaceDir = resolveActiveWorkspaceDir();
const result = await installSkillFromClawHub({
workspaceDir,
slug,
version: opts.version,
force: Boolean (opts.force),
logger: {
info: (message) => defaultRuntime.log(message),
},
});
if (!result.ok) {
defaultRuntime.error(result.error);
defaultRuntime.exit(1 );
return ;
}
defaultRuntime.log(`Installed ${result.slug}@${result.version} -> ${result.targetDir}`);
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1 );
}
});
skills
.command("update" )
.description("Update ClawHub-installed skills in the active workspace" )
.argument("[slug]" , "Single skill slug" )
.option("--all" , "Update all tracked ClawHub skills" , false )
.action(async (slug: string | undefined, opts: { all?: boolean }) => {
try {
if (!slug && !opts.all) {
defaultRuntime.error("Provide a skill slug or use --all." );
defaultRuntime.exit(1 );
return ;
}
if (slug && opts.all) {
defaultRuntime.error("Use either a skill slug or --all." );
defaultRuntime.exit(1 );
return ;
}
const workspaceDir = resolveActiveWorkspaceDir();
const tracked = await readTrackedClawHubSkillSlugs(workspaceDir);
if (opts.all && tracked.length === 0 ) {
defaultRuntime.log("No tracked ClawHub skills to update." );
return ;
}
const results = await updateSkillsFromClawHub({
workspaceDir,
slug,
logger: {
info: (message) => defaultRuntime.log(message),
},
});
for (const result of results) {
if (!result.ok) {
defaultRuntime.error(result.error);
continue ;
}
if (result.changed) {
defaultRuntime.log(
`Updated ${result.slug}: ${result.previousVersion ?? "unknown" } -> ${result.version}`,
);
continue ;
}
defaultRuntime.log(`${result.slug} already at ${result.version}`);
}
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1 );
}
});
skills
.command("list" )
.description("List all available skills" )
.option("--json" , "Output as JSON" , false )
.option("--eligible" , "Show only eligible (ready to use) skills" , false )
.option("-v, --verbose" , "Show more details including missing requirements" , false )
.action(async (opts) => {
await runSkillsAction((report) => formatSkillsList(report, opts));
});
skills
.command("info" )
.description("Show detailed information about a skill" )
.argument("<name>" , "Skill name" )
.option("--json" , "Output as JSON" , false )
.action(async (name, opts) => {
await runSkillsAction((report) => formatSkillInfo(report, name, opts));
});
skills
.command("check" )
.description("Check which skills are ready vs missing requirements" )
.option("--json" , "Output as JSON" , false )
.action(async (opts) => {
await runSkillsAction((report) => formatSkillsCheck(report, opts));
});
// Default action (no subcommand) - show list
skills.action(async () => {
await runSkillsAction((report) => formatSkillsList(report, {}));
});
}
Messung V0.5 in Prozent C=100 H=95 G=97
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland