Skip to content

Commit 4e9dc6b

Browse files
authored
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.
1 parent 1711d01 commit 4e9dc6b

4 files changed

Lines changed: 105 additions & 57 deletions

File tree

skills/clawhub/SKILL.md

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,64 @@
11
---
22
name: clawhub
3-
description: "Search, install, update, sync, or publish agent skills with the ClawHub CLI and registry."
4-
metadata:
5-
{
6-
"openclaw":
7-
{
8-
"requires": { "bins": ["clawhub"] },
9-
"install":
10-
[
11-
{
12-
"id": "node",
13-
"kind": "node",
14-
"package": "clawhub",
15-
"bins": ["clawhub"],
16-
"label": "Install ClawHub CLI (npm)",
17-
},
18-
],
19-
},
20-
}
3+
description: "Search ClawHub for skills when a requested capability is not already available; install, verify, update, publish, or sync skills."
214
---
225

23-
# ClawHub CLI
6+
# ClawHub
247

25-
Install
8+
Use `openclaw skills` to discover and manage skills for the current OpenClaw
9+
agent. Use the standalone `clawhub` CLI only for publishing, syncing, and
10+
publisher account workflows.
2611

27-
```bash
28-
npm i -g clawhub
29-
```
12+
## Discover skills
3013

31-
Auth (publish)
14+
Search before claiming that a requested capability is unavailable:
3215

3316
```bash
34-
clawhub login
35-
clawhub whoami
17+
openclaw skills search "postgres backups"
3618
```
3719

38-
Search
20+
Before installing, verify the selected skill and treat third-party skills as
21+
untrusted. Obtain user approval before installation.
3922

4023
```bash
41-
clawhub search "postgres backups"
24+
openclaw skills verify my-skill
25+
openclaw skills install my-skill
26+
openclaw skills install my-skill --version 1.2.3
4227
```
4328

44-
Install
29+
## Manage installed skills
4530

4631
```bash
47-
clawhub install my-skill
48-
clawhub install my-skill --version 1.2.3
32+
openclaw skills list
33+
openclaw skills check
34+
openclaw skills update my-skill
35+
openclaw skills update --all
4936
```
5037

51-
Update (hash-based match + upgrade)
38+
Use `--global` with `install` or `update` to manage skills shared by all local
39+
agents.
5240

53-
```bash
54-
clawhub update my-skill
55-
clawhub update my-skill --version 1.2.3
56-
clawhub update --all
57-
clawhub update my-skill --force
58-
clawhub update --all --no-input --force
59-
```
41+
## Publish skills
6042

61-
List
43+
Install the standalone ClawHub CLI for publisher workflows:
6244

6345
```bash
64-
clawhub list
46+
npm i -g clawhub
47+
clawhub login
48+
clawhub whoami
6549
```
6650

67-
Publish
51+
Publish or sync skills:
6852

6953
```bash
70-
clawhub publish ./my-skill --slug my-skill --name "My Skill" --version 1.2.0 --changelog "Fixes + docs"
54+
clawhub skill publish ./my-skill
55+
clawhub skill publish ./my-skill --version 1.2.3
56+
clawhub sync --all
7157
```
7258

73-
Notes
59+
## Notes
7460

75-
- Default registry: https://clawhub.com (override with CLAWHUB_REGISTRY or --registry)
76-
- Default workdir: cwd (falls back to OpenClaw workspace); install dir: ./skills (override with --workdir / --dir / CLAWHUB_WORKDIR)
77-
- Update command hashes local files, resolves matching version, and upgrades to latest unless --version is set
61+
- Public registry: https://clawhub.ai
62+
- `openclaw skills install` installs into the active workspace by default.
63+
- Shared installs use `--global` and are visible to all local agents unless
64+
agent allowlists narrow them.

src/cli/skills-cli.commands.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,7 @@ describe("skills cli commands", () => {
680680
workspaceDir: "/tmp/workspace",
681681
slug: undefined,
682682
});
683+
expect(updateAllArgs.config).toEqual({});
683684
expectLogger(updateAllArgs.logger);
684685
expect(
685686
runtimeLogs.some((line) => line.includes("Updated calendar: 1.2.2 -> 1.2.3")),
@@ -688,6 +689,24 @@ describe("skills cli commands", () => {
688689
expect(runtimeErrors).toStrictEqual([]);
689690
});
690691

692+
it("does not bootstrap configured skills during update all", async () => {
693+
loadConfigMock.mockReturnValueOnce({
694+
agents: {
695+
defaults: {
696+
skills: ["apple-notes"],
697+
},
698+
},
699+
});
700+
readTrackedClawHubSkillSlugsMock.mockResolvedValue([]);
701+
702+
await runCommand(["skills", "update", "--all"]);
703+
704+
expect(readTrackedClawHubSkillSlugsMock).toHaveBeenCalledWith("/tmp/workspace");
705+
expect(updateSkillsFromClawHubMock).not.toHaveBeenCalled();
706+
expect(runtimeLogs).toContain("No tracked ClawHub skills to update.");
707+
expect(runtimeErrors).toStrictEqual([]);
708+
});
709+
691710
it("passes --force-install through for ClawHub skill updates", async () => {
692711
readTrackedClawHubSkillSlugsMock.mockResolvedValue(["calendar"]);
693712
updateSkillsFromClawHubMock.mockResolvedValue([
@@ -791,6 +810,7 @@ describe("skills cli commands", () => {
791810
workspaceDir: "/tmp/openclaw-config",
792811
slug: undefined,
793812
logger: expect.any(Object),
813+
config: {},
794814
});
795815
});
796816

@@ -817,6 +837,7 @@ describe("skills cli commands", () => {
817837
workspaceDir: "/tmp/openclaw-config",
818838
slug: "calendar",
819839
logger: expect.any(Object),
840+
config: {},
820841
});
821842
});
822843

src/cli/skills-cli.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,6 @@ async function runSkillsAction(
149149
}
150150
}
151151

152-
function resolveActiveWorkspaceDir(options?: ResolveSkillsWorkspaceOptions): string {
153-
return resolveSkillsWorkspace(options).workspaceDir;
154-
}
155-
156152
function resolveSkillsWorkspaceForCommand(
157153
command: Command | null | undefined,
158154
opts?: { agent?: string },
@@ -164,16 +160,23 @@ function resolveClawHubTargetWorkspaceDir(
164160
command: Command | undefined,
165161
opts: { agent?: string; global?: boolean },
166162
): string | undefined {
163+
return resolveClawHubTargetWorkspace(command, opts)?.workspaceDir;
164+
}
165+
166+
function resolveClawHubTargetWorkspace(
167+
command: Command | undefined,
168+
opts: { agent?: string; global?: boolean },
169+
): Pick<ResolvedSkillsWorkspace, "config" | "workspaceDir"> | undefined {
167170
const agentId = resolveAgentOption(command, opts);
168171
if (opts.global && normalizeOptionalString(agentId)) {
169172
defaultRuntime.error("Use either --global or --agent, not both.");
170173
defaultRuntime.exit(1);
171174
return undefined;
172175
}
173176
if (opts.global) {
174-
return CONFIG_DIR;
177+
return { config: getRuntimeConfig(), workspaceDir: CONFIG_DIR };
175178
}
176-
return resolveActiveWorkspaceDir({ agentId });
179+
return resolveSkillsWorkspace({ agentId });
177180
}
178181

179182
function shouldFailSkillVerification(result: ClawHubSkillVerificationResponse): boolean {
@@ -438,22 +441,23 @@ export function registerSkillsCli(program: Command) {
438441
defaultRuntime.exit(1);
439442
return;
440443
}
441-
const workspaceDir = resolveClawHubTargetWorkspaceDir(command, opts);
442-
if (!workspaceDir) {
444+
const target = resolveClawHubTargetWorkspace(command, opts);
445+
if (!target) {
443446
return;
444447
}
445-
const tracked = await readTrackedClawHubSkillSlugs(workspaceDir);
448+
const tracked = await readTrackedClawHubSkillSlugs(target.workspaceDir);
446449
if (opts.all && tracked.length === 0) {
447450
defaultRuntime.log("No tracked ClawHub skills to update.");
448451
return;
449452
}
450453
const results = await updateSkillsFromClawHub({
451-
workspaceDir,
454+
workspaceDir: target.workspaceDir,
452455
slug,
453456
...(opts.forceInstall ? { forceInstall: true } : {}),
454457
logger: {
455458
info: (message) => defaultRuntime.log(message),
456459
},
460+
config: target.config,
457461
});
458462
let failed = false;
459463
for (const result of results) {

src/skills/lifecycle/clawhub.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,42 @@ describe("skills-clawhub", () => {
11041104
}
11051105
});
11061106

1107+
it("does not install configured skills during update all without ClawHub tracking", async () => {
1108+
const workspaceDir = await tempDirs.make("openclaw-configured-update-");
1109+
const results = await updateSkillsFromClawHub({
1110+
workspaceDir,
1111+
config: {
1112+
agents: {
1113+
defaults: {
1114+
skills: ["apple-notes", "sherpa-onnx-tts"],
1115+
},
1116+
},
1117+
},
1118+
});
1119+
1120+
expect(results).toEqual([]);
1121+
expect(fetchClawHubSkillInstallResolutionMock).not.toHaveBeenCalled();
1122+
expect(installPackageDirMock).not.toHaveBeenCalled();
1123+
});
1124+
1125+
it("rejects untracked requested updates instead of installing by slug", async () => {
1126+
const workspaceDir = await tempDirs.make("openclaw-untracked-update-");
1127+
1128+
const results = await updateSkillsFromClawHub({
1129+
workspaceDir,
1130+
slug: "calendar",
1131+
});
1132+
1133+
expect(results).toEqual([
1134+
{
1135+
ok: false,
1136+
error: 'Skill "calendar" is not tracked as a ClawHub install.',
1137+
},
1138+
]);
1139+
expect(fetchClawHubSkillInstallResolutionMock).not.toHaveBeenCalled();
1140+
expect(installPackageDirMock).not.toHaveBeenCalled();
1141+
});
1142+
11071143
it("updates a legacy Unicode slug when requested explicitly", async () => {
11081144
const slug = "re\u0430ct";
11091145
const { workspaceDir } = await createLegacyTrackedSkillFixture(slug);

0 commit comments

Comments
 (0)