mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix: scope skills cli to active agent workspace
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Memory/LanceDB: bound memory recall embedding queries with a new `recallMaxChars` setting, prefer the latest user message over channel prompt metadata during auto-recall, and document the knob so small Ollama embedding models avoid context-length failures. Fixes #56780. Thanks @rungmc357 and @zak-collaborator.
|
||||
- CLI/skills: resolve workspace-backed skills commands from `--agent`, then the current agent workspace, before falling back to the default agent, so multi-agent ClawHub installs, updates, and status checks stay scoped to the active workspace. Fixes #56161; carries forward #72726. Thanks @langbowang and @luyao618.
|
||||
- Plugin SDK: fall back from partial bundled plugin directory overrides to package source public surfaces while preserving `OPENCLAW_DISABLE_BUNDLED_PLUGINS` as a hard disable. (#72817) Thanks @serkonyc.
|
||||
- Agents/ACPX: stop forwarding Codex ACP timeout config controls that Codex rejects while preserving OpenClaw's run-timeout watchdog for ACP subagents. Fixes #73052. Thanks @pfrederiksen and @richa65.
|
||||
- Memory/Ollama: add `memorySearch.remote.nonBatchConcurrency` for inline embedding indexing, default Ollama non-batch indexing to one request at a time, and keep batch concurrency separate from non-batch concurrency so local embedding backfills avoid timeout storms on smaller hosts. Carries forward #57733. Thanks @itilys.
|
||||
|
||||
@@ -25,21 +25,29 @@ openclaw skills search --limit 20 --json
|
||||
openclaw skills install <slug>
|
||||
openclaw skills install <slug> --version <version>
|
||||
openclaw skills install <slug> --force
|
||||
openclaw skills install <slug> --agent <id>
|
||||
openclaw skills update <slug>
|
||||
openclaw skills update --all
|
||||
openclaw skills update --all --agent <id>
|
||||
openclaw skills list
|
||||
openclaw skills list --eligible
|
||||
openclaw skills list --json
|
||||
openclaw skills list --verbose
|
||||
openclaw skills list --agent <id>
|
||||
openclaw skills info <name>
|
||||
openclaw skills info <name> --json
|
||||
openclaw skills info <name> --agent <id>
|
||||
openclaw skills check
|
||||
openclaw skills check --json
|
||||
openclaw skills check --agent <id>
|
||||
```
|
||||
|
||||
`search`/`install`/`update` use ClawHub directly and install into the active
|
||||
workspace `skills/` directory. `list`/`info`/`check` still inspect the local
|
||||
skills visible to the current workspace and config.
|
||||
skills visible to the current workspace and config. Workspace-backed commands
|
||||
resolve the target workspace from `--agent <id>`, then the current working
|
||||
directory when it is inside a configured agent workspace, then the default
|
||||
agent.
|
||||
|
||||
This CLI `install` command downloads skill folders from ClawHub. Gateway-backed
|
||||
skill dependency installs triggered from onboarding or Skills settings use the
|
||||
@@ -52,6 +60,8 @@ Notes:
|
||||
- `search --limit <n>` caps returned results.
|
||||
- `install --force` overwrites an existing workspace skill folder for the same
|
||||
slug.
|
||||
- `--agent <id>` targets one configured agent workspace and overrides current
|
||||
working directory inference.
|
||||
- `update --all` only updates tracked ClawHub installs in the active workspace.
|
||||
- `list` is the default action when no subcommand is provided.
|
||||
- `list`, `info`, and `check` write their rendered output to stdout. With
|
||||
|
||||
@@ -69,8 +69,11 @@ const mocks = vi.hoisted(() => {
|
||||
});
|
||||
return {
|
||||
loadConfigMock: vi.fn(() => ({})),
|
||||
resolveDefaultAgentIdMock: vi.fn(() => "main"),
|
||||
resolveAgentWorkspaceDirMock: vi.fn(() => "/tmp/workspace"),
|
||||
resolveDefaultAgentIdMock: vi.fn((_config: unknown) => "main"),
|
||||
resolveAgentIdByWorkspacePathMock: vi.fn(
|
||||
(_config: unknown, _workspacePath: string): string | undefined => undefined,
|
||||
),
|
||||
resolveAgentWorkspaceDirMock: vi.fn((_config: unknown, _agentId: string) => "/tmp/workspace"),
|
||||
searchSkillsFromClawHubMock: vi.fn(),
|
||||
installSkillFromClawHubMock: vi.fn(),
|
||||
updateSkillsFromClawHubMock: vi.fn(),
|
||||
@@ -87,6 +90,7 @@ const mocks = vi.hoisted(() => {
|
||||
const {
|
||||
loadConfigMock,
|
||||
resolveDefaultAgentIdMock,
|
||||
resolveAgentIdByWorkspacePathMock,
|
||||
resolveAgentWorkspaceDirMock,
|
||||
searchSkillsFromClawHubMock,
|
||||
installSkillFromClawHubMock,
|
||||
@@ -110,8 +114,11 @@ vi.mock("../config/config.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../agents/agent-scope.js", () => ({
|
||||
resolveDefaultAgentId: () => mocks.resolveDefaultAgentIdMock(),
|
||||
resolveAgentWorkspaceDir: () => mocks.resolveAgentWorkspaceDirMock(),
|
||||
resolveAgentIdByWorkspacePath: (config: unknown, workspacePath: string) =>
|
||||
mocks.resolveAgentIdByWorkspacePathMock(config, workspacePath),
|
||||
resolveDefaultAgentId: (config: unknown) => mocks.resolveDefaultAgentIdMock(config),
|
||||
resolveAgentWorkspaceDir: (config: unknown, agentId: string) =>
|
||||
mocks.resolveAgentWorkspaceDirMock(config, agentId),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/skills-clawhub.js", () => ({
|
||||
@@ -143,6 +150,7 @@ describe("skills cli commands", () => {
|
||||
runtimeErrors.length = 0;
|
||||
loadConfigMock.mockReset();
|
||||
resolveDefaultAgentIdMock.mockReset();
|
||||
resolveAgentIdByWorkspacePathMock.mockReset();
|
||||
resolveAgentWorkspaceDirMock.mockReset();
|
||||
searchSkillsFromClawHubMock.mockReset();
|
||||
installSkillFromClawHubMock.mockReset();
|
||||
@@ -152,6 +160,7 @@ describe("skills cli commands", () => {
|
||||
|
||||
loadConfigMock.mockReturnValue({});
|
||||
resolveDefaultAgentIdMock.mockReturnValue("main");
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue(undefined);
|
||||
resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/workspace");
|
||||
searchSkillsFromClawHubMock.mockResolvedValue([]);
|
||||
installSkillFromClawHubMock.mockResolvedValue({
|
||||
@@ -168,6 +177,21 @@ describe("skills cli commands", () => {
|
||||
defaultRuntime.exit.mockClear();
|
||||
});
|
||||
|
||||
async function withCwd(cwd: string, run: () => Promise<void>) {
|
||||
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(cwd);
|
||||
try {
|
||||
await run();
|
||||
} finally {
|
||||
cwdSpy.mockRestore();
|
||||
}
|
||||
}
|
||||
|
||||
function routeWorkspaceByAgent() {
|
||||
resolveAgentWorkspaceDirMock.mockImplementation(
|
||||
(_config: unknown, agentId: string) => `/tmp/workspace-${agentId}`,
|
||||
);
|
||||
}
|
||||
|
||||
it("searches ClawHub skills from the native CLI", async () => {
|
||||
searchSkillsFromClawHubMock.mockResolvedValue([
|
||||
{
|
||||
@@ -211,6 +235,73 @@ describe("skills cli commands", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("installs a skill into the cwd-inferred agent workspace", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("writer");
|
||||
installSkillFromClawHubMock.mockResolvedValue({
|
||||
ok: true,
|
||||
slug: "calendar",
|
||||
version: "1.2.3",
|
||||
targetDir: "/tmp/workspace-writer/skills/calendar",
|
||||
});
|
||||
|
||||
await withCwd("/tmp/workspace-writer/project", async () => {
|
||||
await runCommand(["skills", "install", "calendar"]);
|
||||
});
|
||||
|
||||
expect(resolveAgentIdByWorkspacePathMock).toHaveBeenCalledWith(
|
||||
{},
|
||||
"/tmp/workspace-writer/project",
|
||||
);
|
||||
expect(installSkillFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/workspace-writer",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("lets --agent override cwd-inferred workspace for installs", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("writer");
|
||||
installSkillFromClawHubMock.mockResolvedValue({
|
||||
ok: true,
|
||||
slug: "calendar",
|
||||
version: "1.2.3",
|
||||
targetDir: "/tmp/workspace-main/skills/calendar",
|
||||
});
|
||||
|
||||
await withCwd("/tmp/workspace-writer", async () => {
|
||||
await runCommand(["skills", "install", "calendar", "--agent", "main"]);
|
||||
});
|
||||
|
||||
expect(resolveAgentIdByWorkspacePathMock).not.toHaveBeenCalled();
|
||||
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith({}, "main");
|
||||
expect(installSkillFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/workspace-main",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("honors parent --agent for subcommands", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
installSkillFromClawHubMock.mockResolvedValue({
|
||||
ok: true,
|
||||
slug: "calendar",
|
||||
version: "1.2.3",
|
||||
targetDir: "/tmp/workspace-writer/skills/calendar",
|
||||
});
|
||||
|
||||
await runCommand(["skills", "--agent", "writer", "install", "calendar"]);
|
||||
|
||||
expect(resolveAgentWorkspaceDirMock).toHaveBeenCalledWith({}, "writer");
|
||||
expect(installSkillFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/workspace-writer",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("updates all tracked ClawHub skills", async () => {
|
||||
readTrackedClawHubSkillSlugsMock.mockResolvedValue(["calendar"]);
|
||||
updateSkillsFromClawHubMock.mockResolvedValue([
|
||||
@@ -238,6 +329,60 @@ describe("skills cli commands", () => {
|
||||
expect(runtimeErrors).toEqual([]);
|
||||
});
|
||||
|
||||
it("updates tracked ClawHub skills in the cwd-inferred agent workspace", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("writer");
|
||||
readTrackedClawHubSkillSlugsMock.mockResolvedValue(["calendar"]);
|
||||
updateSkillsFromClawHubMock.mockResolvedValue([
|
||||
{
|
||||
ok: true,
|
||||
slug: "calendar",
|
||||
previousVersion: "1.2.2",
|
||||
version: "1.2.3",
|
||||
changed: true,
|
||||
targetDir: "/tmp/workspace-writer/skills/calendar",
|
||||
},
|
||||
]);
|
||||
|
||||
await withCwd("/tmp/workspace-writer", async () => {
|
||||
await runCommand(["skills", "update", "--all"]);
|
||||
});
|
||||
|
||||
expect(readTrackedClawHubSkillSlugsMock).toHaveBeenCalledWith("/tmp/workspace-writer");
|
||||
expect(updateSkillsFromClawHubMock).toHaveBeenCalledWith({
|
||||
workspaceDir: "/tmp/workspace-writer",
|
||||
slug: undefined,
|
||||
logger: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it("lets --agent override cwd-inferred workspace for updates", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("writer");
|
||||
readTrackedClawHubSkillSlugsMock.mockResolvedValue(["calendar"]);
|
||||
updateSkillsFromClawHubMock.mockResolvedValue([
|
||||
{
|
||||
ok: true,
|
||||
slug: "calendar",
|
||||
previousVersion: "1.2.2",
|
||||
version: "1.2.3",
|
||||
changed: true,
|
||||
targetDir: "/tmp/workspace-main/skills/calendar",
|
||||
},
|
||||
]);
|
||||
|
||||
await withCwd("/tmp/workspace-writer", async () => {
|
||||
await runCommand(["skills", "update", "calendar", "--agent", "main"]);
|
||||
});
|
||||
|
||||
expect(resolveAgentIdByWorkspacePathMock).not.toHaveBeenCalled();
|
||||
expect(updateSkillsFromClawHubMock).toHaveBeenCalledWith({
|
||||
workspaceDir: "/tmp/workspace-main",
|
||||
slug: "calendar",
|
||||
logger: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "list",
|
||||
@@ -283,6 +428,59 @@ describe("skills cli commands", () => {
|
||||
assert(payload);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["list", ["skills", "list", "--json"]],
|
||||
["info", ["skills", "info", "calendar", "--json"]],
|
||||
["check", ["skills", "check", "--json"]],
|
||||
["default", ["skills"]],
|
||||
])("routes skills %s through the cwd-inferred agent workspace", async (_label, argv) => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("writer");
|
||||
|
||||
await withCwd("/tmp/workspace-writer", async () => {
|
||||
await runCommand(argv);
|
||||
});
|
||||
|
||||
expect(buildWorkspaceSkillStatusMock).toHaveBeenCalledWith("/tmp/workspace-writer", {
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["list", ["skills", "list", "--agent", "writer", "--json"]],
|
||||
["info", ["skills", "info", "calendar", "--agent", "writer", "--json"]],
|
||||
["check", ["skills", "check", "--agent", "writer", "--json"]],
|
||||
["default", ["skills", "--agent", "writer"]],
|
||||
])("routes skills %s through the explicit agent workspace", async (_label, argv) => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue("main");
|
||||
|
||||
await withCwd("/tmp/workspace-main", async () => {
|
||||
await runCommand(argv);
|
||||
});
|
||||
|
||||
expect(resolveAgentIdByWorkspacePathMock).not.toHaveBeenCalled();
|
||||
expect(buildWorkspaceSkillStatusMock).toHaveBeenCalledWith("/tmp/workspace-writer", {
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the default agent outside configured workspaces", async () => {
|
||||
routeWorkspaceByAgent();
|
||||
resolveDefaultAgentIdMock.mockReturnValue("main");
|
||||
resolveAgentIdByWorkspacePathMock.mockReturnValue(undefined);
|
||||
|
||||
await withCwd("/tmp/unrelated", async () => {
|
||||
await runCommand(["skills", "list", "--json"]);
|
||||
});
|
||||
|
||||
expect(resolveAgentIdByWorkspacePathMock).toHaveBeenCalledWith({}, "/tmp/unrelated");
|
||||
expect(resolveDefaultAgentIdMock).toHaveBeenCalledWith({});
|
||||
expect(buildWorkspaceSkillStatusMock).toHaveBeenCalledWith("/tmp/workspace-main", {
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps non-JSON skills list output on stdout with human-readable formatting", async () => {
|
||||
await runCommand(["skills", "list"]);
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Command } from "commander";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import {
|
||||
resolveAgentIdByWorkspacePath,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../agents/agent-scope.js";
|
||||
import {
|
||||
installSkillFromClawHub,
|
||||
readTrackedClawHubSkillSlugs,
|
||||
@@ -11,6 +15,7 @@ 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 { resolveOptionFromCommand } from "./cli-utils.js";
|
||||
import { formatSkillInfo, formatSkillsCheck, formatSkillsList } from "./skills-cli.format.js";
|
||||
|
||||
export type {
|
||||
@@ -24,16 +29,48 @@ type SkillStatusReport = Awaited<
|
||||
ReturnType<(typeof import("../agents/skills-status.js"))["buildWorkspaceSkillStatus"]>
|
||||
>;
|
||||
|
||||
async function loadSkillsStatusReport(): Promise<SkillStatusReport> {
|
||||
type ResolveSkillsWorkspaceOptions = {
|
||||
agentId?: string;
|
||||
cwd?: string;
|
||||
};
|
||||
|
||||
function resolveSkillsWorkspace(options?: ResolveSkillsWorkspaceOptions): {
|
||||
config: ReturnType<typeof getRuntimeConfig>;
|
||||
workspaceDir: string;
|
||||
} {
|
||||
const config = getRuntimeConfig();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
const explicitAgentId = normalizeOptionalString(options?.agentId);
|
||||
const inferredAgentId = explicitAgentId
|
||||
? undefined
|
||||
: resolveAgentIdByWorkspacePath(config, options?.cwd ?? process.cwd());
|
||||
const agentId = explicitAgentId ?? inferredAgentId ?? resolveDefaultAgentId(config);
|
||||
return {
|
||||
config,
|
||||
workspaceDir: resolveAgentWorkspaceDir(config, agentId),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveAgentOption(
|
||||
command: Command | undefined,
|
||||
opts?: { agent?: string },
|
||||
): string | undefined {
|
||||
return resolveOptionFromCommand<string>(command, "agent") ?? opts?.agent;
|
||||
}
|
||||
|
||||
async function loadSkillsStatusReport(
|
||||
options?: ResolveSkillsWorkspaceOptions,
|
||||
): Promise<SkillStatusReport> {
|
||||
const { config, workspaceDir } = resolveSkillsWorkspace(options);
|
||||
const { buildWorkspaceSkillStatus } = await import("../agents/skills-status.js");
|
||||
return buildWorkspaceSkillStatus(workspaceDir, { config });
|
||||
}
|
||||
|
||||
async function runSkillsAction(render: (report: SkillStatusReport) => string): Promise<void> {
|
||||
async function runSkillsAction(
|
||||
render: (report: SkillStatusReport) => string,
|
||||
options?: ResolveSkillsWorkspaceOptions,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const report = await loadSkillsStatusReport();
|
||||
const report = await loadSkillsStatusReport(options);
|
||||
defaultRuntime.writeStdout(render(report));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
@@ -41,9 +78,8 @@ async function runSkillsAction(render: (report: SkillStatusReport) => string): P
|
||||
}
|
||||
}
|
||||
|
||||
function resolveActiveWorkspaceDir(): string {
|
||||
const config = getRuntimeConfig();
|
||||
return resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
function resolveActiveWorkspaceDir(options?: ResolveSkillsWorkspaceOptions): string {
|
||||
return resolveSkillsWorkspace(options).workspaceDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +89,7 @@ export function registerSkillsCli(program: Command) {
|
||||
const skills = program
|
||||
.command("skills")
|
||||
.description("List and inspect available skills")
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
@@ -96,78 +133,96 @@ export function registerSkillsCli(program: Command) {
|
||||
.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);
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.action(
|
||||
async (
|
||||
slug: string,
|
||||
opts: { version?: string; force?: boolean; agent?: string },
|
||||
command: Command,
|
||||
) => {
|
||||
try {
|
||||
const workspaceDir = resolveActiveWorkspaceDir({
|
||||
agentId: resolveAgentOption(command, opts),
|
||||
});
|
||||
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);
|
||||
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;
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.action(
|
||||
async (
|
||||
slug: string | undefined,
|
||||
opts: { all?: boolean; agent?: string },
|
||||
command: Command,
|
||||
) => {
|
||||
try {
|
||||
if (!slug && !opts.all) {
|
||||
defaultRuntime.error("Provide a skill slug or use --all.");
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
if (result.changed) {
|
||||
defaultRuntime.log(
|
||||
`Updated ${result.slug}: ${result.previousVersion ?? "unknown"} -> ${result.version}`,
|
||||
);
|
||||
continue;
|
||||
if (slug && opts.all) {
|
||||
defaultRuntime.error("Use either a skill slug or --all.");
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`${result.slug} already at ${result.version}`);
|
||||
const workspaceDir = resolveActiveWorkspaceDir({
|
||||
agentId: resolveAgentOption(command, opts),
|
||||
});
|
||||
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);
|
||||
}
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
skills
|
||||
.command("list")
|
||||
@@ -175,29 +230,45 @@ export function registerSkillsCli(program: Command) {
|
||||
.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));
|
||||
});
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.action(
|
||||
async (
|
||||
opts: { json?: boolean; eligible?: boolean; verbose?: boolean; agent?: string },
|
||||
command: Command,
|
||||
) => {
|
||||
await runSkillsAction((report) => formatSkillsList(report, opts), {
|
||||
agentId: resolveAgentOption(command, 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));
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.action(async (name: string, opts: { json?: boolean; agent?: string }, command: Command) => {
|
||||
await runSkillsAction((report) => formatSkillInfo(report, name, opts), {
|
||||
agentId: resolveAgentOption(command, 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));
|
||||
.option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)")
|
||||
.action(async (opts: { json?: boolean; agent?: string }, command: Command) => {
|
||||
await runSkillsAction((report) => formatSkillsCheck(report, opts), {
|
||||
agentId: resolveAgentOption(command, opts),
|
||||
});
|
||||
});
|
||||
|
||||
// Default action (no subcommand) - show list
|
||||
skills.action(async () => {
|
||||
await runSkillsAction((report) => formatSkillsList(report, {}));
|
||||
skills.action(async (opts: { agent?: string }, command: Command) => {
|
||||
await runSkillsAction((report) => formatSkillsList(report, {}), {
|
||||
agentId: resolveAgentOption(command, opts),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user