mirror of https://github.com/openclaw/openclaw.git
203 lines
6.9 KiB
TypeScript
203 lines
6.9 KiB
TypeScript
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 { 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.log(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: queryParts.join(" ").trim() || undefined,
|
|
limit: opts.limit,
|
|
});
|
|
if (opts.json) {
|
|
defaultRuntime.log(JSON.stringify({ results }, null, 2));
|
|
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, {}));
|
|
});
|
|
}
|