clawchef 0.1.1 → 0.1.2

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
@@ -14,7 +14,8 @@ Recipe-driven OpenClaw environment orchestrator.
14
14
  - Always runs factory reset first (with confirmation prompt unless `-s/--silent` is used).
15
15
  - If `openclaw` is missing, auto-installs the recipe version and skips factory reset.
16
16
  - Starts OpenClaw gateway service after each recipe execution.
17
- - Creates workspaces and agents (default workspace path: `~/.openclaw/workspaces/<workspace-name>`).
17
+ - Creates workspaces and agents (default workspace path: `~/.openclaw/workspace-<workspace-name>`).
18
+ - Supports workspace-level assets copy via `workspaces[].assets`.
18
19
  - Materializes files into target workspaces.
19
20
  - Installs skills.
20
21
  - Supports plugin preinstall via `openclaw.plugins[]` and runtime `--plugin` flags.
@@ -34,20 +35,20 @@ clawchef cook recipes/sample.yaml
34
35
  Run recipe from URL:
35
36
 
36
37
  ```bash
37
- clawchef cook https://example.com/recipes/sample.yaml --provider remote -s
38
+ clawchef cook https://example.com/recipes/sample.yaml --provider remote
38
39
  ```
39
40
 
40
41
  Run recipe from archive (default `recipe.yaml`):
41
42
 
42
43
  ```bash
43
- clawchef cook ./bundle.tgz --provider mock -s
44
+ clawchef cook ./bundle.tgz --provider mock
44
45
  ```
45
46
 
46
47
  Run specific recipe in directory or archive:
47
48
 
48
49
  ```bash
49
- clawchef cook ./recipes-pack:team/recipe-prod.yaml --provider remote -s
50
- clawchef cook https://example.com/recipes-pack.zip:team/recipe-prod.yaml --provider remote -s
50
+ clawchef cook ./recipes-pack:team/recipe-prod.yaml --provider remote
51
+ clawchef cook https://example.com/recipes-pack.zip:team/recipe-prod.yaml --provider remote
51
52
  ```
52
53
 
53
54
  Dev mode:
@@ -59,13 +60,13 @@ clawchef cook recipes/sample.yaml --verbose
59
60
  Run sample with mock provider:
60
61
 
61
62
  ```bash
62
- clawchef cook recipes/sample.yaml --provider mock -s
63
+ clawchef cook recipes/sample.yaml --provider mock
63
64
  ```
64
65
 
65
66
  Run `content_from` sample:
66
67
 
67
68
  ```bash
68
- clawchef cook recipes/content-from-sample.yaml --provider mock -s
69
+ clawchef cook recipes/content-from-sample.yaml --provider mock
69
70
  ```
70
71
 
71
72
  Skip reset confirmation prompt:
@@ -74,6 +75,9 @@ Skip reset confirmation prompt:
74
75
  clawchef cook recipes/sample.yaml -s
75
76
  ```
76
77
 
78
+ Warning: `-s/--silent` suppresses the factory-reset confirmation and auto-chooses force reinstall on version mismatch.
79
+ Use it only in CI/non-interactive flows where destructive reset behavior is expected.
80
+
77
81
  From-zero OpenClaw bootstrap (recommended):
78
82
 
79
83
  ```bash
@@ -83,19 +87,19 @@ CLAWCHEF_VAR_OPENAI_API_KEY=sk-... clawchef cook recipes/openclaw-from-zero.yaml
83
87
  Telegram channel setup only:
84
88
 
85
89
  ```bash
86
- CLAWCHEF_VAR_TELEGRAM_BOT_TOKEN=123456:abc... clawchef cook recipes/openclaw-telegram.yaml -s
90
+ CLAWCHEF_VAR_TELEGRAM_BOT_TOKEN=123456:abc... clawchef cook recipes/openclaw-telegram.yaml
87
91
  ```
88
92
 
89
93
  Telegram mock channel setup (for tests):
90
94
 
91
95
  ```bash
92
- CLAWCHEF_VAR_TELEGRAM_MOCK_API_KEY=test-key clawchef cook recipes/openclaw-telegram-mock.yaml -s
96
+ CLAWCHEF_VAR_TELEGRAM_MOCK_API_KEY=test-key clawchef cook recipes/openclaw-telegram-mock.yaml
93
97
  ```
94
98
 
95
99
  Install plugin only for this run:
96
100
 
97
101
  ```bash
98
- clawchef cook recipes/openclaw-telegram-mock.yaml --plugin openclaw-telegram-mock-channel -s
102
+ clawchef cook recipes/openclaw-telegram-mock.yaml --plugin openclaw-telegram-mock-channel
99
103
  ```
100
104
 
101
105
  Remote HTTP orchestration:
@@ -103,7 +107,7 @@ Remote HTTP orchestration:
103
107
  ```bash
104
108
  CLAWCHEF_REMOTE_BASE_URL=https://remote-openclaw.example.com \
105
109
  CLAWCHEF_REMOTE_API_KEY=secret-token \
106
- clawchef cook recipes/openclaw-remote-http.yaml --provider remote -s --verbose
110
+ clawchef cook recipes/openclaw-remote-http.yaml --provider remote --verbose
107
111
  ```
108
112
 
109
113
  Validate recipe structure only:
@@ -138,7 +142,7 @@ clawchef scaffold ./my-recipe-project --name meetingbot
138
142
  Scaffold output:
139
143
 
140
144
  - `package.json` with `telegram-api-mock-server` in `devDependencies`
141
- - `src/recipe.yaml` with `telegram-mock` channel and plugin preinstall
145
+ - `src/recipe.yaml` with `telegram-mock` channel, plugin preinstall, and `workspaces[].assets`
142
146
  - `src/<project-name>-assets/{AGENTS.md,IDENTITY.md,SOUL.md,TOOLS.md}`
143
147
  - `src/<project-name>-assets/scripts/scheduling.mjs`
144
148
  - `test/recipe-smoke.test.mjs`
@@ -177,6 +181,9 @@ await scaffold("./my-recipe-project", {
177
181
  - `silent` (default: `true` in Node API)
178
182
  - `loadDotEnvFromCwd` (default: `true`)
179
183
 
184
+ Node API `silent: true` has the same risk as CLI `-s`: no reset confirmation and force reinstall on version mismatch.
185
+ Set `silent: false` when you want an interactive safety prompt.
186
+
180
187
  Notes:
181
188
 
182
189
  - `validate()` throws on invalid recipe.
@@ -243,7 +250,7 @@ Request payload format (POST):
243
250
  "payload": {
244
251
  "workspace": {
245
252
  "name": "demo",
246
- "path": "/home/runner/.openclaw/workspaces/demo"
253
+ "path": "/home/runner/.openclaw/workspace-demo"
247
254
  }
248
255
  }
249
256
  }
@@ -356,54 +363,27 @@ Supported common fields:
356
363
  - optional: `account`, `name`, `token`, `token_file`, `use_env`, `bot_token`, `access_token`, `app_token`, `webhook_url`, `webhook_path`, `signal_number`, `password`, `login`, `login_mode`, `login_account`
357
364
  - advanced passthrough: `extra_flags` (`snake_case` keys become `--kebab-case` CLI flags)
358
365
 
359
- ### Telegram mock channel (for recipe tests)
360
-
361
- Use `channel: "telegram-mock"` when you need to test Telegram-related recipe flows without connecting to the real Telegram network.
362
-
363
- `clawchef` treats `telegram-mock` like any other channel and passes mock-specific flags through `extra_flags`.
364
-
365
- Example:
366
-
367
- ```yaml
368
- params:
369
- telegram_mock_api_key:
370
- required: true
371
-
372
- channels:
373
- - channel: "telegram-mock"
374
- account: "testbot"
375
- token: "${telegram_mock_api_key}"
376
- extra_flags:
377
- mock_bind: "127.0.0.1:18790"
378
- mock_api_key: "${telegram_mock_api_key}"
379
- mode: "webhook"
380
- ```
381
-
382
- Typical test setup:
383
-
384
- - Start OpenClaw with the telegram-mock plugin enabled.
385
- - Run `clawchef cook ...` to configure workspaces/agents/channels.
386
- - Use your external test program (HTTP API or Node.js SDK) to inject inbound mock messages and assert outbound events.
387
-
388
- Login fields:
389
-
390
- - `login: true` enables channel login step
391
- - `login_mode`: currently supports `interactive`
392
- - `login_account`: override account used for login (defaults to `account`)
393
-
394
- Security rules:
395
-
396
- - Do not inline secret values in `channels[]`.
397
- - Use `${var}` placeholders and inject values via `--var` / `CLAWCHEF_VAR_*`.
398
-
399
366
  ## Workspace path behavior
400
367
 
401
368
  - `workspaces[].path` is optional.
402
- - If omitted, clawchef uses `~/.openclaw/workspaces/<workspace-name>`.
369
+ - If omitted, clawchef uses `~/.openclaw/workspace-<workspace-name>`.
370
+ - `workspaces[].assets` is optional.
371
+ - If `assets` is set, clawchef recursively copies files from that directory into the workspace root.
372
+ - `assets` is resolved relative to the recipe file path (unless absolute path is given).
373
+ - `files[]` runs after assets copy, so `files[]` can override copied asset files.
374
+ - Direct URL recipes do not support `workspaces[].assets` (assets must resolve to a local directory).
403
375
  - If provided, relative paths are resolved from the recipe file directory.
404
376
  - For direct URL recipe files, relative workspace paths are resolved from the current working directory.
405
377
  - For directory/archive recipe references, relative workspace paths are resolved from the selected recipe file directory.
406
378
 
379
+ Example:
380
+
381
+ ```yaml
382
+ workspaces:
383
+ - name: "workspace-meeting"
384
+ assets: "./meetingbot-assets"
385
+ ```
386
+
407
387
  ## File content references
408
388
 
409
389
  In `files[]`, set exactly one of:
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { homedir } from "node:os";
3
- import { mkdir, access, copyFile, writeFile, readFile } from "node:fs/promises";
3
+ import { mkdir, access, copyFile, writeFile, readFile, readdir, stat } from "node:fs/promises";
4
4
  import { constants } from "node:fs";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
@@ -32,7 +32,9 @@ function resolveWorkspacePath(recipeOrigin, name, configuredPath) {
32
32
  }
33
33
  return path.resolve(configuredPath);
34
34
  }
35
- return path.join(homedir(), ".openclaw", "workspaces", name);
35
+ const trimmedName = name.trim() || name;
36
+ const workspaceName = trimmedName.startsWith("workspace-") ? trimmedName : `workspace-${trimmedName}`;
37
+ return path.join(homedir(), ".openclaw", workspaceName);
36
38
  }
37
39
  function isHttpUrl(value) {
38
40
  try {
@@ -78,6 +80,27 @@ async function readBinaryFromRef(recipeOrigin, reference) {
78
80
  const bytes = await response.arrayBuffer();
79
81
  return Buffer.from(bytes);
80
82
  }
83
+ async function collectLocalAssetFiles(rootDir, relDir = "") {
84
+ const currentDir = relDir ? path.join(rootDir, relDir) : rootDir;
85
+ const entries = await readdir(currentDir, { withFileTypes: true });
86
+ const out = [];
87
+ for (const entry of entries) {
88
+ const nextRel = relDir ? path.join(relDir, entry.name) : entry.name;
89
+ if (entry.isDirectory()) {
90
+ out.push(...await collectLocalAssetFiles(rootDir, nextRel));
91
+ continue;
92
+ }
93
+ if (entry.isFile()) {
94
+ out.push({
95
+ absolutePath: path.join(rootDir, nextRel),
96
+ relativePath: nextRel,
97
+ });
98
+ continue;
99
+ }
100
+ throw new ClawChefError(`Unsupported entry in assets directory: ${path.join(rootDir, nextRel)}`);
101
+ }
102
+ return out;
103
+ }
81
104
  async function confirmFactoryReset(options) {
82
105
  if (options.silent || options.dryRun) {
83
106
  return true;
@@ -126,6 +149,39 @@ export async function runRecipe(recipe, recipeOrigin, options, logger) {
126
149
  }
127
150
  await provider.createWorkspace(recipe.openclaw, { ...ws, path: absPath }, options.dryRun);
128
151
  logger.info(`Workspace created: ${ws.name}`);
152
+ if (!ws.assets?.trim()) {
153
+ continue;
154
+ }
155
+ const resolvedAssets = resolveFileRef(recipeOrigin, ws.assets);
156
+ if (resolvedAssets.kind !== "local") {
157
+ throw new ClawChefError(`Workspace assets must resolve to a local directory: ${ws.assets}. Direct URL recipes cannot use workspaces[].assets.`);
158
+ }
159
+ let assetDirStat;
160
+ try {
161
+ assetDirStat = await stat(resolvedAssets.value);
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ throw new ClawChefError(`Workspace assets path is not accessible: ${resolvedAssets.value} (${message})`);
166
+ }
167
+ if (!assetDirStat.isDirectory()) {
168
+ throw new ClawChefError(`Workspace assets must be a directory: ${resolvedAssets.value}`);
169
+ }
170
+ const assetFiles = await collectLocalAssetFiles(resolvedAssets.value);
171
+ for (const assetFile of assetFiles) {
172
+ if (provider.materializeFile) {
173
+ const content = await readFile(assetFile.absolutePath, "utf8");
174
+ await provider.materializeFile(recipe.openclaw, ws.name, assetFile.relativePath, content, true, options.dryRun);
175
+ }
176
+ else {
177
+ const target = path.resolve(absPath, assetFile.relativePath);
178
+ if (!options.dryRun) {
179
+ await mkdir(path.dirname(target), { recursive: true });
180
+ await copyFile(assetFile.absolutePath, target);
181
+ }
182
+ }
183
+ logger.info(`Workspace asset copied: ${ws.name}/${assetFile.relativePath}`);
184
+ }
129
185
  }
130
186
  for (const agent of recipe.agents ?? []) {
131
187
  const workspacePath = workspacePaths.get(agent.workspace);
package/dist/recipe.js CHANGED
@@ -125,6 +125,11 @@ function collectVars(recipe, cliVars) {
125
125
  }
126
126
  function semanticValidate(recipe) {
127
127
  const ws = new Set((recipe.workspaces ?? []).map((w) => w.name));
128
+ for (const workspace of recipe.workspaces ?? []) {
129
+ if (workspace.assets !== undefined && !workspace.assets.trim()) {
130
+ throw new ClawChefError(`Workspace ${workspace.name} has empty assets path`);
131
+ }
132
+ }
128
133
  for (const agent of recipe.agents ?? []) {
129
134
  if (!ws.has(agent.workspace)) {
130
135
  throw new ClawChefError(`Agent ${agent.name} references missing workspace: ${agent.workspace}`);
package/dist/scaffold.js CHANGED
@@ -76,38 +76,13 @@ openclaw:
76
76
 
77
77
  workspaces:
78
78
  - name: "\${workspace_name}"
79
+ assets: "./${projectName}-assets"
79
80
 
80
81
  agents:
81
82
  - workspace: "\${workspace_name}"
82
83
  name: "\${agent_name}"
83
84
  model: "\${agent_model}"
84
85
 
85
- files:
86
- - workspace: "\${workspace_name}"
87
- path: "AGENTS.md"
88
- overwrite: true
89
- content_from: "./${projectName}-assets/AGENTS.md"
90
-
91
- - workspace: "\${workspace_name}"
92
- path: "IDENTITY.md"
93
- overwrite: true
94
- content_from: "./${projectName}-assets/IDENTITY.md"
95
-
96
- - workspace: "\${workspace_name}"
97
- path: "SOUL.md"
98
- overwrite: true
99
- content_from: "./${projectName}-assets/SOUL.md"
100
-
101
- - workspace: "\${workspace_name}"
102
- path: "TOOLS.md"
103
- overwrite: true
104
- content_from: "./${projectName}-assets/TOOLS.md"
105
-
106
- - workspace: "\${workspace_name}"
107
- path: "scripts/scheduling.mjs"
108
- overwrite: true
109
- content_from: "./${projectName}-assets/scripts/scheduling.mjs"
110
-
111
86
  channels:
112
87
  - channel: "telegram-mock"
113
88
  account: "default"
package/dist/schema.d.ts CHANGED
@@ -240,12 +240,15 @@ export declare const recipeSchema: z.ZodObject<{
240
240
  workspaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
241
241
  name: z.ZodString;
242
242
  path: z.ZodOptional<z.ZodString>;
243
+ assets: z.ZodOptional<z.ZodString>;
243
244
  }, "strict", z.ZodTypeAny, {
244
245
  name: string;
245
246
  path?: string | undefined;
247
+ assets?: string | undefined;
246
248
  }, {
247
249
  name: string;
248
250
  path?: string | undefined;
251
+ assets?: string | undefined;
249
252
  }>, "many">>;
250
253
  channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
251
254
  channel: z.ZodString;
@@ -479,6 +482,7 @@ export declare const recipeSchema: z.ZodObject<{
479
482
  workspaces?: {
480
483
  name: string;
481
484
  path?: string | undefined;
485
+ assets?: string | undefined;
482
486
  }[] | undefined;
483
487
  channels?: {
484
488
  channel: string;
@@ -586,6 +590,7 @@ export declare const recipeSchema: z.ZodObject<{
586
590
  workspaces?: {
587
591
  name: string;
588
592
  path?: string | undefined;
593
+ assets?: string | undefined;
589
594
  }[] | undefined;
590
595
  channels?: {
591
596
  channel: string;
package/dist/schema.js CHANGED
@@ -64,6 +64,7 @@ const workspaceSchema = z
64
64
  .object({
65
65
  name: z.string().min(1),
66
66
  path: z.string().min(1).optional(),
67
+ assets: z.string().min(1).optional(),
67
68
  })
68
69
  .strict();
69
70
  const channelSchema = z
package/dist/types.d.ts CHANGED
@@ -66,6 +66,7 @@ export interface OpenClawSection {
66
66
  export interface WorkspaceDef {
67
67
  name: string;
68
68
  path?: string;
69
+ assets?: string;
69
70
  }
70
71
  export interface ChannelDef {
71
72
  channel: string;
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "clawchef",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Recipe-driven OpenClaw environment orchestrator",
5
+ "homepage": "https://renorzr.github.io/clawchef",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/renorzr/clawchef.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/renorzr/clawchef/issues"
12
+ },
5
13
  "type": "module",
6
14
  "main": "dist/api.js",
7
15
  "types": "dist/api.d.ts",
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { homedir } from "node:os";
3
- import { mkdir, access, copyFile, writeFile, readFile } from "node:fs/promises";
3
+ import { mkdir, access, copyFile, writeFile, readFile, readdir, stat } from "node:fs/promises";
4
4
  import { constants } from "node:fs";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
@@ -37,7 +37,9 @@ function resolveWorkspacePath(recipeOrigin: RecipeOrigin, name: string, configur
37
37
  }
38
38
  return path.resolve(configuredPath);
39
39
  }
40
- return path.join(homedir(), ".openclaw", "workspaces", name);
40
+ const trimmedName = name.trim() || name;
41
+ const workspaceName = trimmedName.startsWith("workspace-") ? trimmedName : `workspace-${trimmedName}`;
42
+ return path.join(homedir(), ".openclaw", workspaceName);
41
43
  }
42
44
 
43
45
  function isHttpUrl(value: string): boolean {
@@ -87,6 +89,35 @@ async function readBinaryFromRef(recipeOrigin: RecipeOrigin, reference: string):
87
89
  return Buffer.from(bytes);
88
90
  }
89
91
 
92
+ interface LocalAssetFile {
93
+ absolutePath: string;
94
+ relativePath: string;
95
+ }
96
+
97
+ async function collectLocalAssetFiles(rootDir: string, relDir = ""): Promise<LocalAssetFile[]> {
98
+ const currentDir = relDir ? path.join(rootDir, relDir) : rootDir;
99
+ const entries = await readdir(currentDir, { withFileTypes: true });
100
+ const out: LocalAssetFile[] = [];
101
+
102
+ for (const entry of entries) {
103
+ const nextRel = relDir ? path.join(relDir, entry.name) : entry.name;
104
+ if (entry.isDirectory()) {
105
+ out.push(...await collectLocalAssetFiles(rootDir, nextRel));
106
+ continue;
107
+ }
108
+ if (entry.isFile()) {
109
+ out.push({
110
+ absolutePath: path.join(rootDir, nextRel),
111
+ relativePath: nextRel,
112
+ });
113
+ continue;
114
+ }
115
+ throw new ClawChefError(`Unsupported entry in assets directory: ${path.join(rootDir, nextRel)}`);
116
+ }
117
+
118
+ return out;
119
+ }
120
+
90
121
  async function confirmFactoryReset(options: RunOptions): Promise<boolean> {
91
122
  if (options.silent || options.dryRun) {
92
123
  return true;
@@ -146,6 +177,50 @@ export async function runRecipe(
146
177
  }
147
178
  await provider.createWorkspace(recipe.openclaw, { ...ws, path: absPath }, options.dryRun);
148
179
  logger.info(`Workspace created: ${ws.name}`);
180
+
181
+ if (!ws.assets?.trim()) {
182
+ continue;
183
+ }
184
+
185
+ const resolvedAssets = resolveFileRef(recipeOrigin, ws.assets);
186
+ if (resolvedAssets.kind !== "local") {
187
+ throw new ClawChefError(
188
+ `Workspace assets must resolve to a local directory: ${ws.assets}. Direct URL recipes cannot use workspaces[].assets.`,
189
+ );
190
+ }
191
+
192
+ let assetDirStat;
193
+ try {
194
+ assetDirStat = await stat(resolvedAssets.value);
195
+ } catch (err) {
196
+ const message = err instanceof Error ? err.message : String(err);
197
+ throw new ClawChefError(`Workspace assets path is not accessible: ${resolvedAssets.value} (${message})`);
198
+ }
199
+ if (!assetDirStat.isDirectory()) {
200
+ throw new ClawChefError(`Workspace assets must be a directory: ${resolvedAssets.value}`);
201
+ }
202
+
203
+ const assetFiles = await collectLocalAssetFiles(resolvedAssets.value);
204
+ for (const assetFile of assetFiles) {
205
+ if (provider.materializeFile) {
206
+ const content = await readFile(assetFile.absolutePath, "utf8");
207
+ await provider.materializeFile(
208
+ recipe.openclaw,
209
+ ws.name,
210
+ assetFile.relativePath,
211
+ content,
212
+ true,
213
+ options.dryRun,
214
+ );
215
+ } else {
216
+ const target = path.resolve(absPath, assetFile.relativePath);
217
+ if (!options.dryRun) {
218
+ await mkdir(path.dirname(target), { recursive: true });
219
+ await copyFile(assetFile.absolutePath, target);
220
+ }
221
+ }
222
+ logger.info(`Workspace asset copied: ${ws.name}/${assetFile.relativePath}`);
223
+ }
149
224
  }
150
225
 
151
226
  for (const agent of recipe.agents ?? []) {
package/src/recipe.ts CHANGED
@@ -169,6 +169,11 @@ function collectVars(recipe: Recipe, cliVars: Record<string, string>): Record<st
169
169
 
170
170
  function semanticValidate(recipe: Recipe): void {
171
171
  const ws = new Set((recipe.workspaces ?? []).map((w) => w.name));
172
+ for (const workspace of recipe.workspaces ?? []) {
173
+ if (workspace.assets !== undefined && !workspace.assets.trim()) {
174
+ throw new ClawChefError(`Workspace ${workspace.name} has empty assets path`);
175
+ }
176
+ }
172
177
  for (const agent of recipe.agents ?? []) {
173
178
  if (!ws.has(agent.workspace)) {
174
179
  throw new ClawChefError(`Agent ${agent.name} references missing workspace: ${agent.workspace}`);
package/src/scaffold.ts CHANGED
@@ -89,38 +89,13 @@ openclaw:
89
89
 
90
90
  workspaces:
91
91
  - name: "\${workspace_name}"
92
+ assets: "./${projectName}-assets"
92
93
 
93
94
  agents:
94
95
  - workspace: "\${workspace_name}"
95
96
  name: "\${agent_name}"
96
97
  model: "\${agent_model}"
97
98
 
98
- files:
99
- - workspace: "\${workspace_name}"
100
- path: "AGENTS.md"
101
- overwrite: true
102
- content_from: "./${projectName}-assets/AGENTS.md"
103
-
104
- - workspace: "\${workspace_name}"
105
- path: "IDENTITY.md"
106
- overwrite: true
107
- content_from: "./${projectName}-assets/IDENTITY.md"
108
-
109
- - workspace: "\${workspace_name}"
110
- path: "SOUL.md"
111
- overwrite: true
112
- content_from: "./${projectName}-assets/SOUL.md"
113
-
114
- - workspace: "\${workspace_name}"
115
- path: "TOOLS.md"
116
- overwrite: true
117
- content_from: "./${projectName}-assets/TOOLS.md"
118
-
119
- - workspace: "\${workspace_name}"
120
- path: "scripts/scheduling.mjs"
121
- overwrite: true
122
- content_from: "./${projectName}-assets/scripts/scheduling.mjs"
123
-
124
99
  channels:
125
100
  - channel: "telegram-mock"
126
101
  account: "default"
package/src/schema.ts CHANGED
@@ -69,6 +69,7 @@ const workspaceSchema = z
69
69
  .object({
70
70
  name: z.string().min(1),
71
71
  path: z.string().min(1).optional(),
72
+ assets: z.string().min(1).optional(),
72
73
  })
73
74
  .strict();
74
75
 
package/src/types.ts CHANGED
@@ -72,6 +72,7 @@ export interface OpenClawSection {
72
72
  export interface WorkspaceDef {
73
73
  name: string;
74
74
  path?: string;
75
+ assets?: string;
75
76
  }
76
77
 
77
78
  export interface ChannelDef {