mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
fix(skills): harden ClawHub update policy
Pass runtime config into CLI ClawHub skill updates so install policy sees configured safety rules, and update the bundled ClawHub skill docs to prefer openclaw skills for normal skill management. Keeps update-all limited to tracked ClawHub installs and intentionally leaves bundled-skill deprecation, legacy bootstrap, and Sherpa packaging for separate follow-up. Proof: focused ClawHub/CLI tests passed, autoreview clean, remote check:changed passed on Blacksmith Testbox tbx_01kvq0ywztsvw9vdc8zz1xktea; wrapper install/build/check passed, with full local pnpm test failing in unrelated baseline areas already reproduced on latest origin/main.
This commit is contained in:
parent
1711d0123c
commit
4e9dc6b5d5
4 changed files with 118 additions and 70 deletions
|
|
@ -1,77 +1,64 @@
|
|||
---
|
||||
name: clawhub
|
||||
description: "Search, install, update, sync, or publish agent skills with the ClawHub CLI and registry."
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"requires": { "bins": ["clawhub"] },
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "node",
|
||||
"kind": "node",
|
||||
"package": "clawhub",
|
||||
"bins": ["clawhub"],
|
||||
"label": "Install ClawHub CLI (npm)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
description: "Search ClawHub for skills when a requested capability is not already available; install, verify, update, publish, or sync skills."
|
||||
---
|
||||
|
||||
# ClawHub CLI
|
||||
# ClawHub
|
||||
|
||||
Install
|
||||
Use `openclaw skills` to discover and manage skills for the current OpenClaw
|
||||
agent. Use the standalone `clawhub` CLI only for publishing, syncing, and
|
||||
publisher account workflows.
|
||||
|
||||
## Discover skills
|
||||
|
||||
Search before claiming that a requested capability is unavailable:
|
||||
|
||||
```bash
|
||||
openclaw skills search "postgres backups"
|
||||
```
|
||||
|
||||
Before installing, verify the selected skill and treat third-party skills as
|
||||
untrusted. Obtain user approval before installation.
|
||||
|
||||
```bash
|
||||
openclaw skills verify my-skill
|
||||
openclaw skills install my-skill
|
||||
openclaw skills install my-skill --version 1.2.3
|
||||
```
|
||||
|
||||
## Manage installed skills
|
||||
|
||||
```bash
|
||||
openclaw skills list
|
||||
openclaw skills check
|
||||
openclaw skills update my-skill
|
||||
openclaw skills update --all
|
||||
```
|
||||
|
||||
Use `--global` with `install` or `update` to manage skills shared by all local
|
||||
agents.
|
||||
|
||||
## Publish skills
|
||||
|
||||
Install the standalone ClawHub CLI for publisher workflows:
|
||||
|
||||
```bash
|
||||
npm i -g clawhub
|
||||
```
|
||||
|
||||
Auth (publish)
|
||||
|
||||
```bash
|
||||
clawhub login
|
||||
clawhub whoami
|
||||
```
|
||||
|
||||
Search
|
||||
Publish or sync skills:
|
||||
|
||||
```bash
|
||||
clawhub search "postgres backups"
|
||||
clawhub skill publish ./my-skill
|
||||
clawhub skill publish ./my-skill --version 1.2.3
|
||||
clawhub sync --all
|
||||
```
|
||||
|
||||
Install
|
||||
## Notes
|
||||
|
||||
```bash
|
||||
clawhub install my-skill
|
||||
clawhub install my-skill --version 1.2.3
|
||||
```
|
||||
|
||||
Update (hash-based match + upgrade)
|
||||
|
||||
```bash
|
||||
clawhub update my-skill
|
||||
clawhub update my-skill --version 1.2.3
|
||||
clawhub update --all
|
||||
clawhub update my-skill --force
|
||||
clawhub update --all --no-input --force
|
||||
```
|
||||
|
||||
List
|
||||
|
||||
```bash
|
||||
clawhub list
|
||||
```
|
||||
|
||||
Publish
|
||||
|
||||
```bash
|
||||
clawhub publish ./my-skill --slug my-skill --name "My Skill" --version 1.2.0 --changelog "Fixes + docs"
|
||||
```
|
||||
|
||||
Notes
|
||||
|
||||
- Default registry: https://clawhub.com (override with CLAWHUB_REGISTRY or --registry)
|
||||
- Default workdir: cwd (falls back to OpenClaw workspace); install dir: ./skills (override with --workdir / --dir / CLAWHUB_WORKDIR)
|
||||
- Update command hashes local files, resolves matching version, and upgrades to latest unless --version is set
|
||||
- Public registry: https://clawhub.ai
|
||||
- `openclaw skills install` installs into the active workspace by default.
|
||||
- Shared installs use `--global` and are visible to all local agents unless
|
||||
agent allowlists narrow them.
|
||||
|
|
|
|||
|
|
@ -680,6 +680,7 @@ describe("skills cli commands", () => {
|
|||
workspaceDir: "/tmp/workspace",
|
||||
slug: undefined,
|
||||
});
|
||||
expect(updateAllArgs.config).toEqual({});
|
||||
expectLogger(updateAllArgs.logger);
|
||||
expect(
|
||||
runtimeLogs.some((line) => line.includes("Updated calendar: 1.2.2 -> 1.2.3")),
|
||||
|
|
@ -688,6 +689,24 @@ describe("skills cli commands", () => {
|
|||
expect(runtimeErrors).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("does not bootstrap configured skills during update all", async () => {
|
||||
loadConfigMock.mockReturnValueOnce({
|
||||
agents: {
|
||||
defaults: {
|
||||
skills: ["apple-notes"],
|
||||
},
|
||||
},
|
||||
});
|
||||
readTrackedClawHubSkillSlugsMock.mockResolvedValue([]);
|
||||
|
||||
await runCommand(["skills", "update", "--all"]);
|
||||
|
||||
expect(readTrackedClawHubSkillSlugsMock).toHaveBeenCalledWith("/tmp/workspace");
|
||||
expect(updateSkillsFromClawHubMock).not.toHaveBeenCalled();
|
||||
expect(runtimeLogs).toContain("No tracked ClawHub skills to update.");
|
||||
expect(runtimeErrors).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("passes --force-install through for ClawHub skill updates", async () => {
|
||||
readTrackedClawHubSkillSlugsMock.mockResolvedValue(["calendar"]);
|
||||
updateSkillsFromClawHubMock.mockResolvedValue([
|
||||
|
|
@ -791,6 +810,7 @@ describe("skills cli commands", () => {
|
|||
workspaceDir: "/tmp/openclaw-config",
|
||||
slug: undefined,
|
||||
logger: expect.any(Object),
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -817,6 +837,7 @@ describe("skills cli commands", () => {
|
|||
workspaceDir: "/tmp/openclaw-config",
|
||||
slug: "calendar",
|
||||
logger: expect.any(Object),
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -149,10 +149,6 @@ async function runSkillsAction(
|
|||
}
|
||||
}
|
||||
|
||||
function resolveActiveWorkspaceDir(options?: ResolveSkillsWorkspaceOptions): string {
|
||||
return resolveSkillsWorkspace(options).workspaceDir;
|
||||
}
|
||||
|
||||
function resolveSkillsWorkspaceForCommand(
|
||||
command: Command | null | undefined,
|
||||
opts?: { agent?: string },
|
||||
|
|
@ -164,6 +160,13 @@ function resolveClawHubTargetWorkspaceDir(
|
|||
command: Command | undefined,
|
||||
opts: { agent?: string; global?: boolean },
|
||||
): string | undefined {
|
||||
return resolveClawHubTargetWorkspace(command, opts)?.workspaceDir;
|
||||
}
|
||||
|
||||
function resolveClawHubTargetWorkspace(
|
||||
command: Command | undefined,
|
||||
opts: { agent?: string; global?: boolean },
|
||||
): Pick<ResolvedSkillsWorkspace, "config" | "workspaceDir"> | undefined {
|
||||
const agentId = resolveAgentOption(command, opts);
|
||||
if (opts.global && normalizeOptionalString(agentId)) {
|
||||
defaultRuntime.error("Use either --global or --agent, not both.");
|
||||
|
|
@ -171,9 +174,9 @@ function resolveClawHubTargetWorkspaceDir(
|
|||
return undefined;
|
||||
}
|
||||
if (opts.global) {
|
||||
return CONFIG_DIR;
|
||||
return { config: getRuntimeConfig(), workspaceDir: CONFIG_DIR };
|
||||
}
|
||||
return resolveActiveWorkspaceDir({ agentId });
|
||||
return resolveSkillsWorkspace({ agentId });
|
||||
}
|
||||
|
||||
function shouldFailSkillVerification(result: ClawHubSkillVerificationResponse): boolean {
|
||||
|
|
@ -438,22 +441,23 @@ export function registerSkillsCli(program: Command) {
|
|||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
const workspaceDir = resolveClawHubTargetWorkspaceDir(command, opts);
|
||||
if (!workspaceDir) {
|
||||
const target = resolveClawHubTargetWorkspace(command, opts);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const tracked = await readTrackedClawHubSkillSlugs(workspaceDir);
|
||||
const tracked = await readTrackedClawHubSkillSlugs(target.workspaceDir);
|
||||
if (opts.all && tracked.length === 0) {
|
||||
defaultRuntime.log("No tracked ClawHub skills to update.");
|
||||
return;
|
||||
}
|
||||
const results = await updateSkillsFromClawHub({
|
||||
workspaceDir,
|
||||
workspaceDir: target.workspaceDir,
|
||||
slug,
|
||||
...(opts.forceInstall ? { forceInstall: true } : {}),
|
||||
logger: {
|
||||
info: (message) => defaultRuntime.log(message),
|
||||
},
|
||||
config: target.config,
|
||||
});
|
||||
let failed = false;
|
||||
for (const result of results) {
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,42 @@ describe("skills-clawhub", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("does not install configured skills during update all without ClawHub tracking", async () => {
|
||||
const workspaceDir = await tempDirs.make("openclaw-configured-update-");
|
||||
const results = await updateSkillsFromClawHub({
|
||||
workspaceDir,
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
skills: ["apple-notes", "sherpa-onnx-tts"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(results).toEqual([]);
|
||||
expect(fetchClawHubSkillInstallResolutionMock).not.toHaveBeenCalled();
|
||||
expect(installPackageDirMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects untracked requested updates instead of installing by slug", async () => {
|
||||
const workspaceDir = await tempDirs.make("openclaw-untracked-update-");
|
||||
|
||||
const results = await updateSkillsFromClawHub({
|
||||
workspaceDir,
|
||||
slug: "calendar",
|
||||
});
|
||||
|
||||
expect(results).toEqual([
|
||||
{
|
||||
ok: false,
|
||||
error: 'Skill "calendar" is not tracked as a ClawHub install.',
|
||||
},
|
||||
]);
|
||||
expect(fetchClawHubSkillInstallResolutionMock).not.toHaveBeenCalled();
|
||||
expect(installPackageDirMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates a legacy Unicode slug when requested explicitly", async () => {
|
||||
const slug = "re\u0430ct";
|
||||
const { workspaceDir } = await createLegacyTrackedSkillFixture(slug);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue