@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 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]` (workspace-local skill install)
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
- - Workspace-local skills live in `~/.openclaw/workspace/skills` by default.
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 + agent folders (recommended: send to trash):
103
+ 1) Remove the team workspace (recommended: send to trash):
98
104
  ```bash
99
- trash ~/.openclaw/workspace/teams/<teamId>
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, an agent is typically created by scaffolding a folder like:
12
+ In Clawcipes, a **standalone** agent recipe scaffolds a dedicated workspace like:
13
13
 
14
14
  ```
15
- ~/.openclaw/workspace/agents/<agentId>/
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
- - `~/.openclaw/workspace/skills/<skill-slug>` (by default)
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: `~/.openclaw/workspace/skills/<skill-slug>`
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 + agent folders (recommended: send to trash):
128
+ 1) Remove the team workspace (recommended: send to trash):
122
129
  ```bash
123
- trash ~/.openclaw/workspace/teams/<teamId>
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
- - a shared team folder under `teams/<teamId>/...`
138
- - multiple agents under `agents/<teamId>-<role>/...`
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/agents/<agentId>/`
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 directory with standard subfolders:
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
- Also creates agent workspaces under:
68
- - `agents/<teamId>-<role>/...`
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 into the **workspace-local** skills directory.
76
+ Install skills from ClawHub (confirmation-gated).
77
+
78
+ Default behavior: **global install** into `~/.openclaw/skills`.
72
79
 
73
80
  ```bash
74
- openclaw recipes install local-places
75
- openclaw recipes install local-places --yes
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 <workspaceRoot> --dir skills install <slug>`
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
  ```
@@ -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 installs go into the workspace-local skills dir (default `~/.openclaw/workspace/skills`).
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(api: OpenClawPluginApi, cfg: Required<RecipesConfig>, skills: string[]) {
147
+ async function detectMissingSkills(installDir: string, skills: string[]) {
148
148
  const missing: string[] = [];
149
149
  for (const s of skills) {
150
- const p = workspacePath(api, cfg.workspaceSkillsDir, s);
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 missing = await detectMissingSkills(api, cfg, req);
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
- let match: BindingMatch;
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("Install a ClawHub skill into this OpenClaw workspace (confirmation-gated)")
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 workspaceRoot = api.config.agents?.defaults?.workspace;
677
- if (!workspaceRoot) throw new Error("agents.defaults.workspace is not set in config");
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 installDir = path.join(workspaceRoot, cfg.workspaceSkillsDir);
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(api, cfg, skillsToInstall);
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 workspace skills dir (${installDir}): ${already.join(", ")}`);
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 workspace-local install path.
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", workspaceRoot, "--dir", cfg.workspaceSkillsDir, "install", slug],
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 missing = await detectMissingSkills(api, cfg, recipe.requiredSkills ?? []);
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 missing = await detectMissingSkills(api, cfg, recipe.requiredSkills ?? []);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawcipes/recipes",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Clawcipes recipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
5
5
  "main": "index.ts",
6
6
  "type": "commonjs",