@clawcipes/recipes 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/docs/AGENTS_AND_SKILLS.md +37 -13
- package/docs/COMMANDS.md +76 -12
- package/docs/INSTALLATION.md +5 -1
- package/index.ts +142 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,9 +52,10 @@ openclaw recipes dispatch \
|
|
|
52
52
|
|
|
53
53
|
## Commands (high level)
|
|
54
54
|
- `openclaw recipes list|show|status`
|
|
55
|
-
- `openclaw recipes scaffold` (agent)
|
|
56
|
-
- `openclaw recipes scaffold-team` (team)
|
|
57
|
-
- `openclaw recipes install <idOrSlug> [--yes]` (
|
|
55
|
+
- `openclaw recipes scaffold` (agent → `workspace-<agentId>`)
|
|
56
|
+
- `openclaw recipes scaffold-team` (team → `workspace-<teamId>` + `roles/<role>/`)
|
|
57
|
+
- `openclaw recipes install <idOrSlug> [--yes] [--global|--agent-id <id>|--team-id <id>]` (skills: global or scoped)
|
|
58
|
+
- `openclaw recipes bind|unbind|bindings` (multi-agent routing)
|
|
58
59
|
- `openclaw recipes dispatch ...` (request → inbox + ticket + assignment)
|
|
59
60
|
|
|
60
61
|
For full details, see `docs/COMMANDS.md`.
|
|
@@ -85,7 +86,12 @@ Reference:
|
|
|
85
86
|
|
|
86
87
|
(Also see: GitHub repo https://github.com/rjdjohnston/clawcipes)
|
|
87
88
|
## Notes / principles
|
|
88
|
-
-
|
|
89
|
+
- Workspaces:
|
|
90
|
+
- Standalone agents: `~/.openclaw/workspace-<agentId>/`
|
|
91
|
+
- Teams: `~/.openclaw/workspace-<teamId>/` with `roles/<role>/...`
|
|
92
|
+
- Skills:
|
|
93
|
+
- Global (shared): `~/.openclaw/skills/<skill>`
|
|
94
|
+
- Scoped (agent/team): `~/.openclaw/workspace-*/skills/<skill>`
|
|
89
95
|
- Team IDs end with `-team`; agent IDs are namespaced: `<teamId>-<role>`.
|
|
90
96
|
- Recipe template rendering is intentionally simple: `{{var}}` replacement only.
|
|
91
97
|
|
|
@@ -94,10 +100,9 @@ Clawcipes does not (yet) include a first-class `remove-team` command.
|
|
|
94
100
|
|
|
95
101
|
To remove a scaffolded team created with `scaffold-team --apply-config`, do two things:
|
|
96
102
|
|
|
97
|
-
1) Remove team
|
|
103
|
+
1) Remove the team workspace (recommended: send to trash):
|
|
98
104
|
```bash
|
|
99
|
-
trash ~/.openclaw/workspace
|
|
100
|
-
trash ~/.openclaw/workspace/agents/<teamId>-*
|
|
105
|
+
trash ~/.openclaw/workspace-<teamId>
|
|
101
106
|
```
|
|
102
107
|
|
|
103
108
|
2) Remove the agents from OpenClaw config:
|
|
@@ -9,12 +9,13 @@ In OpenClaw, an **agent** is a configured assistant persona with:
|
|
|
9
9
|
- a **tool policy** (what tools it is allowed to use)
|
|
10
10
|
- a **model** configuration (defaults come from OpenClaw)
|
|
11
11
|
|
|
12
|
-
In Clawcipes,
|
|
12
|
+
In Clawcipes, a **standalone** agent recipe scaffolds a dedicated workspace like:
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
-
~/.openclaw/workspace
|
|
15
|
+
~/.openclaw/workspace-<agentId>/
|
|
16
16
|
SOUL.md
|
|
17
17
|
AGENTS.md
|
|
18
|
+
TOOLS.md
|
|
18
19
|
...other recipe files...
|
|
19
20
|
```
|
|
20
21
|
|
|
@@ -92,8 +93,14 @@ openclaw recipes install <skill-slug>
|
|
|
92
93
|
openclaw recipes install <skill-slug> --yes
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
This runs ClawHub under the hood and installs into:
|
|
96
|
-
-
|
|
96
|
+
This runs ClawHub under the hood and installs into the **current OpenClaw workspace** skills dir:
|
|
97
|
+
- `<workspace>/skills/<skill-slug>`
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
- standalone agent workspace: `~/.openclaw/workspace-<agentId>/skills/<skill-slug>`
|
|
101
|
+
- team workspace: `~/.openclaw/workspace-<teamId>/skills/<skill-slug>`
|
|
102
|
+
|
|
103
|
+
> Note: in the new workspace policy, standalone agents live in `~/.openclaw/workspace-<agentId>` and teams live in `~/.openclaw/workspace-<teamId>`. Skill install targeting is still being refined during the experimental phase.
|
|
97
104
|
|
|
98
105
|
### Install the skills required by a recipe
|
|
99
106
|
If a recipe declares skills in `requiredSkills` or `optionalSkills`:
|
|
@@ -108,7 +115,7 @@ That installs the recipe’s declared skills.
|
|
|
108
115
|
Clawcipes currently does **not** implement a remove command.
|
|
109
116
|
|
|
110
117
|
To remove a workspace-local skill:
|
|
111
|
-
- delete the folder:
|
|
118
|
+
- delete the folder: `<workspace>/skills/<skill-slug>`
|
|
112
119
|
- restart: `openclaw gateway restart`
|
|
113
120
|
|
|
114
121
|
(We can add `openclaw recipes uninstall <slug>` later if you want it to be first-class.)
|
|
@@ -118,10 +125,9 @@ Clawcipes does not (yet) include a first-class `remove-team` command.
|
|
|
118
125
|
|
|
119
126
|
If you scaffolded a team with `scaffold-team --apply-config`, removal has two parts:
|
|
120
127
|
|
|
121
|
-
1) Remove the team
|
|
128
|
+
1) Remove the team workspace (recommended: send to trash):
|
|
122
129
|
```bash
|
|
123
|
-
trash ~/.openclaw/workspace
|
|
124
|
-
trash ~/.openclaw/workspace/agents/<teamId>-*
|
|
130
|
+
trash ~/.openclaw/workspace-<teamId>
|
|
125
131
|
```
|
|
126
132
|
|
|
127
133
|
2) Remove the agents from OpenClaw config:
|
|
@@ -133,9 +139,26 @@ openclaw gateway restart
|
|
|
133
139
|
```
|
|
134
140
|
|
|
135
141
|
## Teams: shared workspace + multiple agents
|
|
136
|
-
A **team** recipe scaffolds:
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
A **team** recipe scaffolds a **shared workspace root** plus role folders:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
~/.openclaw/workspace-<teamId>/
|
|
146
|
+
TEAM.md
|
|
147
|
+
inbox/
|
|
148
|
+
outbox/
|
|
149
|
+
shared/
|
|
150
|
+
notes/
|
|
151
|
+
work/
|
|
152
|
+
backlog/
|
|
153
|
+
in-progress/
|
|
154
|
+
done/
|
|
155
|
+
assignments/
|
|
156
|
+
roles/
|
|
157
|
+
<role>/
|
|
158
|
+
...role-specific recipe files...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Each role agent is a separate OpenClaw agent id (`<teamId>-<role>`), but they share the same workspace root (`workspace-<teamId>`) so collaboration is file-based.
|
|
139
162
|
|
|
140
163
|
The shared workspace is the source of truth for:
|
|
141
164
|
- intake (`inbox/`)
|
|
@@ -148,7 +171,8 @@ Once an agent exists, there are **two layers** you can update:
|
|
|
148
171
|
|
|
149
172
|
### 1) The agent’s files (workspace)
|
|
150
173
|
Agents are just folders under:
|
|
151
|
-
- `~/.openclaw/workspace
|
|
174
|
+
- standalone: `~/.openclaw/workspace-<agentId>/`
|
|
175
|
+
- team roles: `~/.openclaw/workspace-<teamId>/roles/<role>/`
|
|
152
176
|
|
|
153
177
|
Common files:
|
|
154
178
|
- `SOUL.md` — the persona / operating style
|
|
@@ -165,7 +189,7 @@ If the agent was created from a recipe, re-running scaffold with `--overwrite` w
|
|
|
165
189
|
openclaw recipes scaffold <recipeId> --agent-id <agentId> --overwrite
|
|
166
190
|
```
|
|
167
191
|
|
|
168
|
-
For teams, you typically re-run `scaffold-team
|
|
192
|
+
For teams, you typically re-run `scaffold-team` (role files live under `roles/<role>/`):
|
|
169
193
|
|
|
170
194
|
```bash
|
|
171
195
|
openclaw recipes scaffold-team <recipeId> --team-id <teamId> --overwrite
|
package/docs/COMMANDS.md
CHANGED
|
@@ -45,11 +45,11 @@ Options:
|
|
|
45
45
|
- `--apply-config` (write/update `agents.list[]` in OpenClaw config)
|
|
46
46
|
|
|
47
47
|
## `scaffold-team <recipeId>`
|
|
48
|
-
Scaffold a team workspace + multiple agents from a **team** recipe.
|
|
48
|
+
Scaffold a shared **team workspace** + multiple agents from a **team** recipe.
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
openclaw recipes scaffold-team development-team \
|
|
52
|
-
--team-id development-team \
|
|
52
|
+
--team-id development-team-team \
|
|
53
53
|
--overwrite \
|
|
54
54
|
--apply-config
|
|
55
55
|
```
|
|
@@ -60,35 +60,99 @@ Options:
|
|
|
60
60
|
- `--overwrite`
|
|
61
61
|
- `--apply-config`
|
|
62
62
|
|
|
63
|
-
Creates a team
|
|
64
|
-
- `teams/<teamId>/{shared,inbox,outbox,notes,work}`
|
|
65
|
-
- `teams/<teamId>/work/{backlog,in-progress,done,assignments}`
|
|
63
|
+
Creates a shared team workspace root:
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
- `~/.openclaw/workspace-<teamId>/...`
|
|
66
|
+
|
|
67
|
+
Standard folders:
|
|
68
|
+
- `inbox/`, `outbox/`, `shared/`, `notes/`
|
|
69
|
+
- `work/{backlog,in-progress,done,assignments}`
|
|
70
|
+
- `roles/<role>/...` (role-specific recipe files)
|
|
71
|
+
|
|
72
|
+
Also creates agent config entries under `agents.list[]` (when `--apply-config`), with agent ids:
|
|
73
|
+
- `<teamId>-<role>`
|
|
69
74
|
|
|
70
75
|
## `install <idOrSlug> [--yes]`
|
|
71
|
-
Install skills
|
|
76
|
+
Install skills from ClawHub (confirmation-gated).
|
|
77
|
+
|
|
78
|
+
Default behavior: **global install** into `~/.openclaw/skills`.
|
|
72
79
|
|
|
73
80
|
```bash
|
|
74
|
-
|
|
75
|
-
openclaw recipes install
|
|
81
|
+
# Global (shared across all agents)
|
|
82
|
+
openclaw recipes install agentchat --yes
|
|
83
|
+
|
|
84
|
+
# Agent-scoped (into workspace-<agentId>/skills)
|
|
85
|
+
openclaw recipes install agentchat --yes --agent-id dev
|
|
86
|
+
|
|
87
|
+
# Team-scoped (into workspace-<teamId>/skills)
|
|
88
|
+
openclaw recipes install agentchat --yes --team-id development-team-team
|
|
76
89
|
```
|
|
77
90
|
|
|
78
91
|
Behavior:
|
|
79
92
|
- If `idOrSlug` matches a recipe id, installs that recipe’s `requiredSkills` + `optionalSkills`.
|
|
80
93
|
- Otherwise treats it as a ClawHub skill slug.
|
|
81
94
|
- Installs via:
|
|
82
|
-
- `npx clawhub@latest --workdir <
|
|
95
|
+
- `npx clawhub@latest --workdir <targetWorkspace> --dir skills install <slug>` (agent/team)
|
|
96
|
+
- `npx clawhub@latest --workdir ~/.openclaw --dir skills install <slug>` (global)
|
|
83
97
|
- Confirmation-gated unless `--yes`.
|
|
84
98
|
- In non-interactive mode (no TTY), requires `--yes`.
|
|
85
99
|
|
|
100
|
+
## `bind`
|
|
101
|
+
Add/update a multi-agent routing binding (writes `bindings[]` in `~/.openclaw/openclaw.json`).
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Route one Telegram DM to an agent
|
|
107
|
+
openclaw recipes bind --agent-id dev --channel telegram --peer-kind dm --peer-id 6477250615
|
|
108
|
+
|
|
109
|
+
# Route all Telegram traffic to an agent (broad match)
|
|
110
|
+
openclaw recipes bind --agent-id dev --channel telegram
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Notes:
|
|
114
|
+
- `peer.kind` must be one of: `dm|group|channel`.
|
|
115
|
+
- Peer-specific bindings are inserted first (more specific wins).
|
|
116
|
+
|
|
117
|
+
## `unbind`
|
|
118
|
+
Remove routing binding(s) from OpenClaw config (`bindings[]`).
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Remove a specific DM binding for an agent
|
|
124
|
+
openclaw recipes unbind --agent-id dev --channel telegram --peer-kind dm --peer-id 6477250615
|
|
125
|
+
|
|
126
|
+
# Remove ALL bindings that match this peer (any agent)
|
|
127
|
+
openclaw recipes unbind --channel telegram --peer-kind dm --peer-id 6477250615
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## `bindings`
|
|
131
|
+
Print the current `bindings[]` from OpenClaw config.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
openclaw recipes bindings
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## `migrate-team`
|
|
138
|
+
Migrate a legacy team scaffold into the new `workspace-<teamId>` layout.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
openclaw recipes migrate-team --team-id development-team-team --dry-run
|
|
142
|
+
openclaw recipes migrate-team --team-id development-team-team --mode move
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Options:
|
|
146
|
+
- `--dry-run`
|
|
147
|
+
- `--mode move|copy`
|
|
148
|
+
- `--overwrite` (merge into existing destination)
|
|
149
|
+
|
|
86
150
|
## `dispatch`
|
|
87
151
|
Convert a natural-language request into file-first execution artifacts.
|
|
88
152
|
|
|
89
153
|
```bash
|
|
90
154
|
openclaw recipes dispatch \
|
|
91
|
-
--team-id development-team \
|
|
155
|
+
--team-id development-team-team \
|
|
92
156
|
--request "Add a customer-support team recipe" \
|
|
93
157
|
--owner lead
|
|
94
158
|
```
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -73,4 +73,8 @@ openclaw gateway restart
|
|
|
73
73
|
### `recipes install` fails
|
|
74
74
|
- Run `npx clawhub@latest --help` to confirm the CLI can run.
|
|
75
75
|
- Ensure you are logged into ClawHub if required (`npx clawhub@latest login`).
|
|
76
|
-
- Confirm
|
|
76
|
+
- Confirm the install scope you intended:
|
|
77
|
+
- global: `~/.openclaw/skills/<skill>`
|
|
78
|
+
- agent: `~/.openclaw/workspace-<agentId>/skills/<skill>`
|
|
79
|
+
- team: `~/.openclaw/workspace-<teamId>/skills/<skill>`
|
|
80
|
+
- If you change installs or config, restart: `openclaw gateway restart`.
|
package/index.ts
CHANGED
|
@@ -144,10 +144,10 @@ function skillInstallCommands(cfg: Required<RecipesConfig>, skills: string[]) {
|
|
|
144
144
|
return lines;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
async function detectMissingSkills(
|
|
147
|
+
async function detectMissingSkills(installDir: string, skills: string[]) {
|
|
148
148
|
const missing: string[] = [];
|
|
149
149
|
for (const s of skills) {
|
|
150
|
-
const p =
|
|
150
|
+
const p = path.join(installDir, s);
|
|
151
151
|
if (!(await fileExists(p))) missing.push(s);
|
|
152
152
|
}
|
|
153
153
|
return missing;
|
|
@@ -293,6 +293,27 @@ function upsertBindingInConfig(cfgObj: any, binding: BindingSnippet) {
|
|
|
293
293
|
return { changed: true, note: "added" as const };
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
function removeBindingsInConfig(cfgObj: any, opts: { agentId?: string; match: BindingMatch }) {
|
|
297
|
+
if (!Array.isArray(cfgObj.bindings)) cfgObj.bindings = [];
|
|
298
|
+
const list: any[] = cfgObj.bindings;
|
|
299
|
+
|
|
300
|
+
const targetMatchSig = stableStringify(opts.match);
|
|
301
|
+
|
|
302
|
+
const before = list.length;
|
|
303
|
+
const kept: any[] = [];
|
|
304
|
+
const removed: any[] = [];
|
|
305
|
+
|
|
306
|
+
for (const b of list) {
|
|
307
|
+
const sameAgent = opts.agentId ? String(b?.agentId ?? "") === opts.agentId : true;
|
|
308
|
+
const sameMatch = stableStringify(b?.match ?? {}) === targetMatchSig;
|
|
309
|
+
if (sameAgent && sameMatch) removed.push(b);
|
|
310
|
+
else kept.push(b);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
cfgObj.bindings = kept;
|
|
314
|
+
return { removedCount: before - kept.length, removed };
|
|
315
|
+
}
|
|
316
|
+
|
|
296
317
|
async function applyAgentSnippetsToOpenClawConfig(api: OpenClawPluginApi, snippets: AgentConfigSnippet[]) {
|
|
297
318
|
// Load the latest config from disk (not the snapshot in api.config).
|
|
298
319
|
const current = (api.runtime as any).config?.loadConfig?.();
|
|
@@ -454,7 +475,10 @@ const recipesPlugin = {
|
|
|
454
475
|
const { frontmatter } = parseFrontmatter(md);
|
|
455
476
|
if (id && frontmatter.id !== id) continue;
|
|
456
477
|
const req = frontmatter.requiredSkills ?? [];
|
|
457
|
-
const
|
|
478
|
+
const workspaceRoot = api.config.agents?.defaults?.workspace;
|
|
479
|
+
if (!workspaceRoot) throw new Error("agents.defaults.workspace is not set in config");
|
|
480
|
+
const installDir = path.join(workspaceRoot, cfg.workspaceSkillsDir);
|
|
481
|
+
const missing = await detectMissingSkills(installDir, req);
|
|
458
482
|
out.push({
|
|
459
483
|
id: frontmatter.id,
|
|
460
484
|
requiredSkills: req,
|
|
@@ -466,6 +490,34 @@ const recipesPlugin = {
|
|
|
466
490
|
console.log(JSON.stringify(out, null, 2));
|
|
467
491
|
});
|
|
468
492
|
|
|
493
|
+
const parseMatchFromOptions = (options: any): BindingMatch => {
|
|
494
|
+
if (options.match) {
|
|
495
|
+
return JSON5.parse(String(options.match)) as BindingMatch;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const match: BindingMatch = {
|
|
499
|
+
channel: String(options.channel),
|
|
500
|
+
};
|
|
501
|
+
if (options.accountId) match.accountId = String(options.accountId);
|
|
502
|
+
if (options.guildId) match.guildId = String(options.guildId);
|
|
503
|
+
if (options.teamId) match.teamId = String(options.teamId);
|
|
504
|
+
|
|
505
|
+
if (options.peerKind || options.peerId) {
|
|
506
|
+
if (!options.peerKind || !options.peerId) {
|
|
507
|
+
throw new Error("--peer-kind and --peer-id must be provided together");
|
|
508
|
+
}
|
|
509
|
+
let kind = String(options.peerKind);
|
|
510
|
+
// Back-compat alias
|
|
511
|
+
if (kind === "direct") kind = "dm";
|
|
512
|
+
if (kind !== "dm" && kind !== "group" && kind !== "channel") {
|
|
513
|
+
throw new Error("--peer-kind must be dm|group|channel (or direct as alias for dm)");
|
|
514
|
+
}
|
|
515
|
+
match.peer = { kind, id: String(options.peerId) };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return match;
|
|
519
|
+
};
|
|
520
|
+
|
|
469
521
|
cmd
|
|
470
522
|
.command("bind")
|
|
471
523
|
.description("Add/update a multi-agent routing binding (writes openclaw.json bindings[])")
|
|
@@ -479,32 +531,7 @@ const recipesPlugin = {
|
|
|
479
531
|
.option("--match <json>", "Full match object as JSON/JSON5 (overrides flags)")
|
|
480
532
|
.action(async (options: any) => {
|
|
481
533
|
const agentId = String(options.agentId);
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (options.match) {
|
|
485
|
-
match = JSON5.parse(String(options.match)) as BindingMatch;
|
|
486
|
-
} else {
|
|
487
|
-
match = {
|
|
488
|
-
channel: String(options.channel),
|
|
489
|
-
};
|
|
490
|
-
if (options.accountId) match.accountId = String(options.accountId);
|
|
491
|
-
if (options.guildId) match.guildId = String(options.guildId);
|
|
492
|
-
if (options.teamId) match.teamId = String(options.teamId);
|
|
493
|
-
|
|
494
|
-
if (options.peerKind || options.peerId) {
|
|
495
|
-
if (!options.peerKind || !options.peerId) {
|
|
496
|
-
throw new Error("--peer-kind and --peer-id must be provided together");
|
|
497
|
-
}
|
|
498
|
-
let kind = String(options.peerKind);
|
|
499
|
-
// Back-compat alias
|
|
500
|
-
if (kind === "direct") kind = "dm";
|
|
501
|
-
if (kind !== "dm" && kind !== "group" && kind !== "channel") {
|
|
502
|
-
throw new Error("--peer-kind must be dm|group|channel (or direct as alias for dm)");
|
|
503
|
-
}
|
|
504
|
-
match.peer = { kind, id: String(options.peerId) };
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
534
|
+
const match = parseMatchFromOptions(options);
|
|
508
535
|
if (!match?.channel) throw new Error("match.channel is required");
|
|
509
536
|
|
|
510
537
|
const res = await applyBindingSnippetsToOpenClawConfig(api, [{ agentId, match }]);
|
|
@@ -512,6 +539,33 @@ const recipesPlugin = {
|
|
|
512
539
|
console.error("Binding written. Restart gateway if required for changes to take effect.");
|
|
513
540
|
});
|
|
514
541
|
|
|
542
|
+
cmd
|
|
543
|
+
.command("unbind")
|
|
544
|
+
.description("Remove routing binding(s) from openclaw.json bindings[]")
|
|
545
|
+
.requiredOption("--channel <channel>", "Channel name")
|
|
546
|
+
.option("--agent-id <agentId>", "Optional agent id; when set, removes only bindings for this agent")
|
|
547
|
+
.option("--account-id <accountId>", "Channel accountId")
|
|
548
|
+
.option("--peer-kind <kind>", "Peer kind (dm|group|channel)")
|
|
549
|
+
.option("--peer-id <id>", "Peer id")
|
|
550
|
+
.option("--guild-id <guildId>", "Discord guildId")
|
|
551
|
+
.option("--team-id <teamId>", "Slack teamId")
|
|
552
|
+
.option("--match <json>", "Full match object as JSON/JSON5 (overrides flags)")
|
|
553
|
+
.action(async (options: any) => {
|
|
554
|
+
const agentId = typeof options.agentId === "string" ? String(options.agentId) : undefined;
|
|
555
|
+
const match = parseMatchFromOptions(options);
|
|
556
|
+
if (!match?.channel) throw new Error("match.channel is required");
|
|
557
|
+
|
|
558
|
+
const current = (api.runtime as any).config?.loadConfig?.();
|
|
559
|
+
if (!current) throw new Error("Failed to load config via api.runtime.config.loadConfig()");
|
|
560
|
+
const cfgObj = (current.cfg ?? current) as any;
|
|
561
|
+
|
|
562
|
+
const res = removeBindingsInConfig(cfgObj, { agentId, match });
|
|
563
|
+
await (api.runtime as any).config?.writeConfigFile?.(cfgObj);
|
|
564
|
+
|
|
565
|
+
console.log(JSON.stringify({ ok: true, ...res }, null, 2));
|
|
566
|
+
console.error("Binding(s) removed. Restart gateway if required for changes to take effect.");
|
|
567
|
+
});
|
|
568
|
+
|
|
515
569
|
cmd
|
|
516
570
|
.command("bindings")
|
|
517
571
|
.description("Show current bindings from openclaw config")
|
|
@@ -656,9 +710,14 @@ const recipesPlugin = {
|
|
|
656
710
|
|
|
657
711
|
cmd
|
|
658
712
|
.command("install")
|
|
659
|
-
.description(
|
|
713
|
+
.description(
|
|
714
|
+
"Install a skill from ClawHub (confirmation-gated). Default: global (~/.openclaw/skills). Use --agent-id or --team-id for scoped installs.",
|
|
715
|
+
)
|
|
660
716
|
.argument("<idOrSlug>", "Recipe id OR ClawHub skill slug")
|
|
661
717
|
.option("--yes", "Skip confirmation prompt")
|
|
718
|
+
.option("--global", "Install into global shared skills (~/.openclaw/skills) (default when no scope flags)")
|
|
719
|
+
.option("--agent-id <agentId>", "Install into a specific agent workspace (workspace-<agentId>)")
|
|
720
|
+
.option("--team-id <teamId>", "Install into a team workspace (workspace-<teamId>)")
|
|
662
721
|
.action(async (idOrSlug: string, options: any) => {
|
|
663
722
|
const cfg = getCfg(api);
|
|
664
723
|
|
|
@@ -673,10 +732,45 @@ const recipesPlugin = {
|
|
|
673
732
|
recipe = null;
|
|
674
733
|
}
|
|
675
734
|
|
|
676
|
-
const
|
|
677
|
-
if (!
|
|
735
|
+
const baseWorkspace = api.config.agents?.defaults?.workspace;
|
|
736
|
+
if (!baseWorkspace) throw new Error("agents.defaults.workspace is not set in config");
|
|
678
737
|
|
|
679
|
-
const
|
|
738
|
+
const stateDir = path.resolve(baseWorkspace, ".."); // ~/.openclaw
|
|
739
|
+
|
|
740
|
+
const scopeFlags = [options.global ? "global" : null, options.agentId ? "agent" : null, options.teamId ? "team" : null].filter(Boolean);
|
|
741
|
+
if (scopeFlags.length > 1) {
|
|
742
|
+
throw new Error("Use only one of: --global, --agent-id, --team-id");
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const agentIdOpt = typeof options.agentId === "string" ? options.agentId.trim() : "";
|
|
746
|
+
const teamIdOpt = typeof options.teamId === "string" ? options.teamId.trim() : "";
|
|
747
|
+
|
|
748
|
+
// Default is global install when no scope is provided.
|
|
749
|
+
const scope = scopeFlags[0] ?? "global";
|
|
750
|
+
|
|
751
|
+
let workdir: string;
|
|
752
|
+
let dirName: string;
|
|
753
|
+
let installDir: string;
|
|
754
|
+
|
|
755
|
+
if (scope === "agent") {
|
|
756
|
+
if (!agentIdOpt) throw new Error("--agent-id cannot be empty");
|
|
757
|
+
const agentWorkspace = path.resolve(stateDir, `workspace-${agentIdOpt}`);
|
|
758
|
+
workdir = agentWorkspace;
|
|
759
|
+
dirName = cfg.workspaceSkillsDir;
|
|
760
|
+
installDir = path.join(agentWorkspace, dirName);
|
|
761
|
+
} else if (scope === "team") {
|
|
762
|
+
if (!teamIdOpt) throw new Error("--team-id cannot be empty");
|
|
763
|
+
const teamWorkspace = path.resolve(stateDir, `workspace-${teamIdOpt}`);
|
|
764
|
+
workdir = teamWorkspace;
|
|
765
|
+
dirName = cfg.workspaceSkillsDir;
|
|
766
|
+
installDir = path.join(teamWorkspace, dirName);
|
|
767
|
+
} else {
|
|
768
|
+
workdir = stateDir;
|
|
769
|
+
dirName = "skills";
|
|
770
|
+
installDir = path.join(stateDir, dirName);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
await ensureDir(installDir);
|
|
680
774
|
|
|
681
775
|
const skillsToInstall = recipe
|
|
682
776
|
? Array.from(new Set([...(recipe.requiredSkills ?? []), ...(recipe.optionalSkills ?? [])])).filter(Boolean)
|
|
@@ -687,11 +781,11 @@ const recipesPlugin = {
|
|
|
687
781
|
return;
|
|
688
782
|
}
|
|
689
783
|
|
|
690
|
-
const missing = await detectMissingSkills(
|
|
784
|
+
const missing = await detectMissingSkills(installDir, skillsToInstall);
|
|
691
785
|
const already = skillsToInstall.filter((s) => !missing.includes(s));
|
|
692
786
|
|
|
693
787
|
if (already.length) {
|
|
694
|
-
console.error(`Already present in
|
|
788
|
+
console.error(`Already present in skills dir (${installDir}): ${already.join(", ")}`);
|
|
695
789
|
}
|
|
696
790
|
|
|
697
791
|
if (!missing.length) {
|
|
@@ -699,9 +793,10 @@ const recipesPlugin = {
|
|
|
699
793
|
return;
|
|
700
794
|
}
|
|
701
795
|
|
|
796
|
+
const targetLabel = scope === "agent" ? `agent:${agentIdOpt}` : scope === "team" ? `team:${teamIdOpt}` : "global";
|
|
702
797
|
const header = recipe
|
|
703
|
-
? `Install skills for recipe ${recipe.id} into ${installDir}?\n- ${missing.join("\n- ")}`
|
|
704
|
-
: `Install skill into ${installDir}?\n- ${missing.join("\n- ")}`;
|
|
798
|
+
? `Install skills for recipe ${recipe.id} into ${installDir} (${targetLabel})?\n- ${missing.join("\n- ")}`
|
|
799
|
+
: `Install skill into ${installDir} (${targetLabel})?\n- ${missing.join("\n- ")}`;
|
|
705
800
|
|
|
706
801
|
const requireConfirm = !options.yes;
|
|
707
802
|
if (requireConfirm) {
|
|
@@ -727,12 +822,12 @@ const recipesPlugin = {
|
|
|
727
822
|
console.error(header);
|
|
728
823
|
}
|
|
729
824
|
|
|
730
|
-
// Use clawhub CLI. Force
|
|
825
|
+
// Use clawhub CLI. Force install path based on scope.
|
|
731
826
|
const { spawnSync } = await import("node:child_process");
|
|
732
827
|
for (const slug of missing) {
|
|
733
828
|
const res = spawnSync(
|
|
734
829
|
"npx",
|
|
735
|
-
["clawhub@latest", "--workdir",
|
|
830
|
+
["clawhub@latest", "--workdir", workdir, "--dir", dirName, "install", slug],
|
|
736
831
|
{ stdio: "inherit" },
|
|
737
832
|
);
|
|
738
833
|
if (res.status !== 0) {
|
|
@@ -912,7 +1007,10 @@ const recipesPlugin = {
|
|
|
912
1007
|
}
|
|
913
1008
|
|
|
914
1009
|
const cfg = getCfg(api);
|
|
915
|
-
const
|
|
1010
|
+
const workspaceRoot = api.config.agents?.defaults?.workspace;
|
|
1011
|
+
if (!workspaceRoot) throw new Error("agents.defaults.workspace is not set in config");
|
|
1012
|
+
const installDir = path.join(workspaceRoot, cfg.workspaceSkillsDir);
|
|
1013
|
+
const missing = await detectMissingSkills(installDir, recipe.requiredSkills ?? []);
|
|
916
1014
|
if (missing.length) {
|
|
917
1015
|
console.error(`Missing skills for recipe ${recipeId}: ${missing.join(", ")}`);
|
|
918
1016
|
console.error(`Install commands (workspace-local):\n${skillInstallCommands(cfg, missing).join("\n")}`);
|
|
@@ -962,7 +1060,10 @@ const recipesPlugin = {
|
|
|
962
1060
|
}
|
|
963
1061
|
|
|
964
1062
|
const cfg = getCfg(api);
|
|
965
|
-
const
|
|
1063
|
+
const baseWorkspace = api.config.agents?.defaults?.workspace;
|
|
1064
|
+
if (!baseWorkspace) throw new Error("agents.defaults.workspace is not set in config");
|
|
1065
|
+
const installDir = path.join(baseWorkspace, cfg.workspaceSkillsDir);
|
|
1066
|
+
const missing = await detectMissingSkills(installDir, recipe.requiredSkills ?? []);
|
|
966
1067
|
if (missing.length) {
|
|
967
1068
|
console.error(`Missing skills for recipe ${recipeId}: ${missing.join(", ")}`);
|
|
968
1069
|
console.error(`Install commands (workspace-local):\n${skillInstallCommands(cfg, missing).join("\n")}`);
|
|
@@ -970,9 +1071,6 @@ const recipesPlugin = {
|
|
|
970
1071
|
return;
|
|
971
1072
|
}
|
|
972
1073
|
|
|
973
|
-
const baseWorkspace = api.config.agents?.defaults?.workspace;
|
|
974
|
-
if (!baseWorkspace) throw new Error("agents.defaults.workspace is not set in config");
|
|
975
|
-
|
|
976
1074
|
// Team workspace root (shared by all role agents): ~/.openclaw/workspace-<teamId>
|
|
977
1075
|
const teamDir = path.resolve(baseWorkspace, "..", `workspace-${teamId}`);
|
|
978
1076
|
await ensureDir(teamDir);
|