clawchef 0.1.0 → 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 +88 -60
- package/dist/api.d.ts +4 -0
- package/dist/api.js +6 -0
- package/dist/cli.js +36 -0
- package/dist/openclaw/command-provider.d.ts +1 -0
- package/dist/openclaw/command-provider.js +30 -9
- package/dist/openclaw/mock-provider.d.ts +1 -0
- package/dist/openclaw/mock-provider.js +3 -0
- package/dist/openclaw/provider.d.ts +1 -0
- package/dist/openclaw/remote-provider.d.ts +1 -0
- package/dist/openclaw/remote-provider.js +5 -0
- package/dist/orchestrator.js +64 -2
- package/dist/recipe.js +9 -0
- package/dist/scaffold.d.ts +8 -0
- package/dist/scaffold.js +172 -0
- package/dist/schema.d.ts +17 -0
- package/dist/schema.js +3 -0
- package/dist/types.d.ts +4 -0
- package/package.json +9 -1
- package/recipes/openclaw-local.yaml +0 -1
- package/recipes/openclaw-telegram-mock.yaml +2 -0
- package/recipes/openclaw-telegram.yaml +0 -1
- package/src/api.ts +10 -0
- package/src/cli.ts +39 -0
- package/src/openclaw/command-provider.ts +32 -9
- package/src/openclaw/mock-provider.ts +4 -0
- package/src/openclaw/provider.ts +1 -0
- package/src/openclaw/remote-provider.ts +11 -0
- package/src/orchestrator.ts +84 -2
- package/src/recipe.ts +14 -0
- package/src/scaffold.ts +197 -0
- package/src/schema.ts +3 -0
- package/src/types.ts +4 -0
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { ClawChefError } from "./errors.js";
|
|
5
|
+
function normalizeProjectName(value) {
|
|
6
|
+
const normalized = value
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[\s_]+/g, "-")
|
|
10
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
11
|
+
.replace(/-+/g, "-")
|
|
12
|
+
.replace(/^-|-$/g, "");
|
|
13
|
+
if (!normalized) {
|
|
14
|
+
throw new ClawChefError("Project name is empty after normalization. Use letters, numbers, spaces, underscore, or dash.");
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
async function pathExists(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
await access(filePath, constants.F_OK);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function ensureDirectoryEmpty(targetDir) {
|
|
28
|
+
const exists = await pathExists(targetDir);
|
|
29
|
+
if (!exists) {
|
|
30
|
+
await mkdir(targetDir, { recursive: true });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const entries = await readdir(targetDir);
|
|
34
|
+
if (entries.length > 0) {
|
|
35
|
+
throw new ClawChefError(`Target directory is not empty: ${targetDir}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function makePackageJson(projectName) {
|
|
39
|
+
const content = {
|
|
40
|
+
name: `${projectName}-recipe`,
|
|
41
|
+
version: "0.1.0",
|
|
42
|
+
private: true,
|
|
43
|
+
type: "module",
|
|
44
|
+
scripts: {
|
|
45
|
+
"test:recipe": "node --test test/recipe-smoke.test.mjs",
|
|
46
|
+
test: "node --test \"test/**/*.test.mjs\"",
|
|
47
|
+
},
|
|
48
|
+
devDependencies: {
|
|
49
|
+
"telegram-api-mock-server": "^0.1.5",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return `${JSON.stringify(content, null, 2)}\n`;
|
|
53
|
+
}
|
|
54
|
+
function makeRecipeYaml(projectName) {
|
|
55
|
+
return `version: "1"
|
|
56
|
+
name: "${projectName}"
|
|
57
|
+
|
|
58
|
+
params:
|
|
59
|
+
openclaw_version:
|
|
60
|
+
default: "2026.2.9"
|
|
61
|
+
workspace_name:
|
|
62
|
+
default: "${projectName}"
|
|
63
|
+
agent_name:
|
|
64
|
+
default: "${projectName}"
|
|
65
|
+
agent_model:
|
|
66
|
+
default: "openai/gpt-4.1"
|
|
67
|
+
telegram_mock_api_key:
|
|
68
|
+
required: true
|
|
69
|
+
|
|
70
|
+
openclaw:
|
|
71
|
+
bin: "openclaw"
|
|
72
|
+
version: "\${openclaw_version}"
|
|
73
|
+
install: "never"
|
|
74
|
+
plugins:
|
|
75
|
+
- "openclaw-telegram-mock-channel"
|
|
76
|
+
|
|
77
|
+
workspaces:
|
|
78
|
+
- name: "\${workspace_name}"
|
|
79
|
+
assets: "./${projectName}-assets"
|
|
80
|
+
|
|
81
|
+
agents:
|
|
82
|
+
- workspace: "\${workspace_name}"
|
|
83
|
+
name: "\${agent_name}"
|
|
84
|
+
model: "\${agent_model}"
|
|
85
|
+
|
|
86
|
+
channels:
|
|
87
|
+
- channel: "telegram-mock"
|
|
88
|
+
account: "default"
|
|
89
|
+
token: "\${telegram_mock_api_key}"
|
|
90
|
+
extra_flags:
|
|
91
|
+
mock_bind: "127.0.0.1:18790"
|
|
92
|
+
mock_api_key: "\${telegram_mock_api_key}"
|
|
93
|
+
mode: "webhook"
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
function makeAgentsDoc(projectName) {
|
|
97
|
+
return `# ${projectName}
|
|
98
|
+
|
|
99
|
+
Project scaffold generated by clawchef.
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
function makeIdentityDoc(projectName) {
|
|
103
|
+
return `# Identity
|
|
104
|
+
|
|
105
|
+
You are the ${projectName} assistant.
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
function makeSoulDoc() {
|
|
109
|
+
return `# Soul
|
|
110
|
+
|
|
111
|
+
Keep responses practical, concise, and action-oriented.
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
function makeToolsDoc() {
|
|
115
|
+
return `# Tools
|
|
116
|
+
|
|
117
|
+
- Use available workspace files first.
|
|
118
|
+
- Ask for missing secrets explicitly.
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function makeSchedulingScript() {
|
|
122
|
+
return `#!/usr/bin/env node
|
|
123
|
+
import process from "node:process";
|
|
124
|
+
|
|
125
|
+
const message = process.argv.slice(2).join(" ").trim();
|
|
126
|
+
if (!message) {
|
|
127
|
+
console.error("Usage: node scripts/scheduling.mjs <message>");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log("[scheduling] " + message);
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
function makeRecipeSmokeTest() {
|
|
135
|
+
return `import test from "node:test";
|
|
136
|
+
import assert from "node:assert/strict";
|
|
137
|
+
import { access } from "node:fs/promises";
|
|
138
|
+
|
|
139
|
+
test("recipe scaffold files exist", async () => {
|
|
140
|
+
await access("src/recipe.yaml");
|
|
141
|
+
await access("package.json");
|
|
142
|
+
assert.ok(true);
|
|
143
|
+
});
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
export async function scaffoldProject(targetDirArg, options = {}) {
|
|
147
|
+
const targetDir = path.resolve(targetDirArg?.trim() ? targetDirArg : process.cwd());
|
|
148
|
+
await ensureDirectoryEmpty(targetDir);
|
|
149
|
+
const defaultName = path.basename(targetDir);
|
|
150
|
+
const rawProjectName = options.projectName?.trim() || defaultName;
|
|
151
|
+
const projectName = normalizeProjectName(rawProjectName);
|
|
152
|
+
const srcDir = path.join(targetDir, "src");
|
|
153
|
+
const assetsDir = path.join(srcDir, `${projectName}-assets`);
|
|
154
|
+
const assetsScriptsDir = path.join(assetsDir, "scripts");
|
|
155
|
+
const testDir = path.join(targetDir, "test");
|
|
156
|
+
await mkdir(srcDir, { recursive: true });
|
|
157
|
+
await mkdir(assetsDir, { recursive: true });
|
|
158
|
+
await mkdir(assetsScriptsDir, { recursive: true });
|
|
159
|
+
await mkdir(testDir, { recursive: true });
|
|
160
|
+
await writeFile(path.join(targetDir, "package.json"), makePackageJson(projectName), "utf8");
|
|
161
|
+
await writeFile(path.join(srcDir, "recipe.yaml"), makeRecipeYaml(projectName), "utf8");
|
|
162
|
+
await writeFile(path.join(assetsDir, "AGENTS.md"), makeAgentsDoc(projectName), "utf8");
|
|
163
|
+
await writeFile(path.join(assetsDir, "IDENTITY.md"), makeIdentityDoc(projectName), "utf8");
|
|
164
|
+
await writeFile(path.join(assetsDir, "SOUL.md"), makeSoulDoc(), "utf8");
|
|
165
|
+
await writeFile(path.join(assetsDir, "TOOLS.md"), makeToolsDoc(), "utf8");
|
|
166
|
+
await writeFile(path.join(assetsScriptsDir, "scheduling.mjs"), makeSchedulingScript(), "utf8");
|
|
167
|
+
await writeFile(path.join(testDir, "recipe-smoke.test.mjs"), makeRecipeSmokeTest(), "utf8");
|
|
168
|
+
return {
|
|
169
|
+
targetDir,
|
|
170
|
+
projectName,
|
|
171
|
+
};
|
|
172
|
+
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
19
19
|
bin: z.ZodOptional<z.ZodString>;
|
|
20
20
|
version: z.ZodString;
|
|
21
21
|
install: z.ZodOptional<z.ZodEnum<["auto", "always", "never"]>>;
|
|
22
|
+
plugins: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
22
23
|
bootstrap: z.ZodOptional<z.ZodObject<{
|
|
23
24
|
non_interactive: z.ZodOptional<z.ZodBoolean>;
|
|
24
25
|
accept_risk: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -102,6 +103,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
102
103
|
use_version: z.ZodOptional<z.ZodString>;
|
|
103
104
|
install_version: z.ZodOptional<z.ZodString>;
|
|
104
105
|
uninstall_version: z.ZodOptional<z.ZodString>;
|
|
106
|
+
install_plugin: z.ZodOptional<z.ZodString>;
|
|
105
107
|
factory_reset: z.ZodOptional<z.ZodString>;
|
|
106
108
|
start_gateway: z.ZodOptional<z.ZodString>;
|
|
107
109
|
enable_plugin: z.ZodOptional<z.ZodString>;
|
|
@@ -115,6 +117,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
115
117
|
use_version?: string | undefined;
|
|
116
118
|
install_version?: string | undefined;
|
|
117
119
|
uninstall_version?: string | undefined;
|
|
120
|
+
install_plugin?: string | undefined;
|
|
118
121
|
factory_reset?: string | undefined;
|
|
119
122
|
start_gateway?: string | undefined;
|
|
120
123
|
enable_plugin?: string | undefined;
|
|
@@ -128,6 +131,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
128
131
|
use_version?: string | undefined;
|
|
129
132
|
install_version?: string | undefined;
|
|
130
133
|
uninstall_version?: string | undefined;
|
|
134
|
+
install_plugin?: string | undefined;
|
|
131
135
|
factory_reset?: string | undefined;
|
|
132
136
|
start_gateway?: string | undefined;
|
|
133
137
|
enable_plugin?: string | undefined;
|
|
@@ -142,6 +146,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
142
146
|
version: string;
|
|
143
147
|
bin?: string | undefined;
|
|
144
148
|
install?: "auto" | "always" | "never" | undefined;
|
|
149
|
+
plugins?: string[] | undefined;
|
|
145
150
|
bootstrap?: {
|
|
146
151
|
openai_api_key?: string | undefined;
|
|
147
152
|
anthropic_api_key?: string | undefined;
|
|
@@ -173,6 +178,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
173
178
|
use_version?: string | undefined;
|
|
174
179
|
install_version?: string | undefined;
|
|
175
180
|
uninstall_version?: string | undefined;
|
|
181
|
+
install_plugin?: string | undefined;
|
|
176
182
|
factory_reset?: string | undefined;
|
|
177
183
|
start_gateway?: string | undefined;
|
|
178
184
|
enable_plugin?: string | undefined;
|
|
@@ -187,6 +193,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
187
193
|
version: string;
|
|
188
194
|
bin?: string | undefined;
|
|
189
195
|
install?: "auto" | "always" | "never" | undefined;
|
|
196
|
+
plugins?: string[] | undefined;
|
|
190
197
|
bootstrap?: {
|
|
191
198
|
openai_api_key?: string | undefined;
|
|
192
199
|
anthropic_api_key?: string | undefined;
|
|
@@ -218,6 +225,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
218
225
|
use_version?: string | undefined;
|
|
219
226
|
install_version?: string | undefined;
|
|
220
227
|
uninstall_version?: string | undefined;
|
|
228
|
+
install_plugin?: string | undefined;
|
|
221
229
|
factory_reset?: string | undefined;
|
|
222
230
|
start_gateway?: string | undefined;
|
|
223
231
|
enable_plugin?: string | undefined;
|
|
@@ -232,12 +240,15 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
232
240
|
workspaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
233
241
|
name: z.ZodString;
|
|
234
242
|
path: z.ZodOptional<z.ZodString>;
|
|
243
|
+
assets: z.ZodOptional<z.ZodString>;
|
|
235
244
|
}, "strict", z.ZodTypeAny, {
|
|
236
245
|
name: string;
|
|
237
246
|
path?: string | undefined;
|
|
247
|
+
assets?: string | undefined;
|
|
238
248
|
}, {
|
|
239
249
|
name: string;
|
|
240
250
|
path?: string | undefined;
|
|
251
|
+
assets?: string | undefined;
|
|
241
252
|
}>, "many">>;
|
|
242
253
|
channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
243
254
|
channel: z.ZodString;
|
|
@@ -417,6 +428,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
417
428
|
version: string;
|
|
418
429
|
bin?: string | undefined;
|
|
419
430
|
install?: "auto" | "always" | "never" | undefined;
|
|
431
|
+
plugins?: string[] | undefined;
|
|
420
432
|
bootstrap?: {
|
|
421
433
|
openai_api_key?: string | undefined;
|
|
422
434
|
anthropic_api_key?: string | undefined;
|
|
@@ -448,6 +460,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
448
460
|
use_version?: string | undefined;
|
|
449
461
|
install_version?: string | undefined;
|
|
450
462
|
uninstall_version?: string | undefined;
|
|
463
|
+
install_plugin?: string | undefined;
|
|
451
464
|
factory_reset?: string | undefined;
|
|
452
465
|
start_gateway?: string | undefined;
|
|
453
466
|
enable_plugin?: string | undefined;
|
|
@@ -469,6 +482,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
469
482
|
workspaces?: {
|
|
470
483
|
name: string;
|
|
471
484
|
path?: string | undefined;
|
|
485
|
+
assets?: string | undefined;
|
|
472
486
|
}[] | undefined;
|
|
473
487
|
channels?: {
|
|
474
488
|
channel: string;
|
|
@@ -522,6 +536,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
522
536
|
version: string;
|
|
523
537
|
bin?: string | undefined;
|
|
524
538
|
install?: "auto" | "always" | "never" | undefined;
|
|
539
|
+
plugins?: string[] | undefined;
|
|
525
540
|
bootstrap?: {
|
|
526
541
|
openai_api_key?: string | undefined;
|
|
527
542
|
anthropic_api_key?: string | undefined;
|
|
@@ -553,6 +568,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
553
568
|
use_version?: string | undefined;
|
|
554
569
|
install_version?: string | undefined;
|
|
555
570
|
uninstall_version?: string | undefined;
|
|
571
|
+
install_plugin?: string | undefined;
|
|
556
572
|
factory_reset?: string | undefined;
|
|
557
573
|
start_gateway?: string | undefined;
|
|
558
574
|
enable_plugin?: string | undefined;
|
|
@@ -574,6 +590,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
574
590
|
workspaces?: {
|
|
575
591
|
name: string;
|
|
576
592
|
path?: string | undefined;
|
|
593
|
+
assets?: string | undefined;
|
|
577
594
|
}[] | undefined;
|
|
578
595
|
channels?: {
|
|
579
596
|
channel: string;
|
package/dist/schema.js
CHANGED
|
@@ -9,6 +9,7 @@ const openClawCommandsSchema = z
|
|
|
9
9
|
use_version: z.string().optional(),
|
|
10
10
|
install_version: z.string().optional(),
|
|
11
11
|
uninstall_version: z.string().optional(),
|
|
12
|
+
install_plugin: z.string().optional(),
|
|
12
13
|
factory_reset: z.string().optional(),
|
|
13
14
|
start_gateway: z.string().optional(),
|
|
14
15
|
enable_plugin: z.string().optional(),
|
|
@@ -54,6 +55,7 @@ const openClawSchema = z
|
|
|
54
55
|
bin: z.string().optional(),
|
|
55
56
|
version: z.string(),
|
|
56
57
|
install: z.enum(["auto", "always", "never"]).optional(),
|
|
58
|
+
plugins: z.array(z.string().min(1)).optional(),
|
|
57
59
|
bootstrap: openClawBootstrapSchema.optional(),
|
|
58
60
|
commands: openClawCommandsSchema.optional(),
|
|
59
61
|
})
|
|
@@ -62,6 +64,7 @@ const workspaceSchema = z
|
|
|
62
64
|
.object({
|
|
63
65
|
name: z.string().min(1),
|
|
64
66
|
path: z.string().min(1).optional(),
|
|
67
|
+
assets: z.string().min(1).optional(),
|
|
65
68
|
})
|
|
66
69
|
.strict();
|
|
67
70
|
const channelSchema = z
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface OpenClawCommandOverrides {
|
|
|
17
17
|
use_version?: string;
|
|
18
18
|
install_version?: string;
|
|
19
19
|
uninstall_version?: string;
|
|
20
|
+
install_plugin?: string;
|
|
20
21
|
factory_reset?: string;
|
|
21
22
|
start_gateway?: string;
|
|
22
23
|
enable_plugin?: string;
|
|
@@ -58,12 +59,14 @@ export interface OpenClawSection {
|
|
|
58
59
|
bin?: string;
|
|
59
60
|
version: string;
|
|
60
61
|
install?: InstallPolicy;
|
|
62
|
+
plugins?: string[];
|
|
61
63
|
bootstrap?: OpenClawBootstrap;
|
|
62
64
|
commands?: OpenClawCommandOverrides;
|
|
63
65
|
}
|
|
64
66
|
export interface WorkspaceDef {
|
|
65
67
|
name: string;
|
|
66
68
|
path?: string;
|
|
69
|
+
assets?: string;
|
|
67
70
|
}
|
|
68
71
|
export interface ChannelDef {
|
|
69
72
|
channel: string;
|
|
@@ -127,6 +130,7 @@ export interface Recipe {
|
|
|
127
130
|
}
|
|
128
131
|
export interface RunOptions {
|
|
129
132
|
vars: Record<string, string>;
|
|
133
|
+
plugins: string[];
|
|
130
134
|
dryRun: boolean;
|
|
131
135
|
allowMissing: boolean;
|
|
132
136
|
verbose: boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawchef",
|
|
3
|
-
"version": "0.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",
|
package/src/api.ts
CHANGED
|
@@ -4,11 +4,14 @@ import { importDotEnvFromCwd } from "./env.js";
|
|
|
4
4
|
import { Logger } from "./logger.js";
|
|
5
5
|
import { runRecipe } from "./orchestrator.js";
|
|
6
6
|
import { loadRecipe, loadRecipeText } from "./recipe.js";
|
|
7
|
+
import { scaffoldProject } from "./scaffold.js";
|
|
7
8
|
import { recipeSchema } from "./schema.js";
|
|
8
9
|
import type { OpenClawProvider, OpenClawRemoteConfig, RunOptions } from "./types.js";
|
|
10
|
+
import type { ScaffoldOptions, ScaffoldResult } from "./scaffold.js";
|
|
9
11
|
|
|
10
12
|
export interface CookOptions {
|
|
11
13
|
vars?: Record<string, string>;
|
|
14
|
+
plugins?: string[];
|
|
12
15
|
dryRun?: boolean;
|
|
13
16
|
allowMissing?: boolean;
|
|
14
17
|
verbose?: boolean;
|
|
@@ -19,8 +22,10 @@ export interface CookOptions {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function normalizeCookOptions(options: CookOptions): RunOptions {
|
|
25
|
+
const plugins = Array.from(new Set((options.plugins ?? []).map((value) => value.trim()).filter((value) => value.length > 0)));
|
|
22
26
|
return {
|
|
23
27
|
vars: options.vars ?? {},
|
|
28
|
+
plugins,
|
|
24
29
|
dryRun: Boolean(options.dryRun),
|
|
25
30
|
allowMissing: Boolean(options.allowMissing),
|
|
26
31
|
verbose: Boolean(options.verbose),
|
|
@@ -62,4 +67,9 @@ export async function validate(recipeRef: string): Promise<void> {
|
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
export async function scaffold(targetDir?: string, options: ScaffoldOptions = {}): Promise<ScaffoldResult> {
|
|
71
|
+
return scaffoldProject(targetDir, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
export type { OpenClawProvider, OpenClawRemoteConfig };
|
|
75
|
+
export type { ScaffoldOptions, ScaffoldResult };
|
package/src/cli.ts
CHANGED
|
@@ -4,8 +4,12 @@ import { Logger } from "./logger.js";
|
|
|
4
4
|
import { runRecipe } from "./orchestrator.js";
|
|
5
5
|
import { loadRecipe, loadRecipeText } from "./recipe.js";
|
|
6
6
|
import { recipeSchema } from "./schema.js";
|
|
7
|
+
import { scaffoldProject } from "./scaffold.js";
|
|
7
8
|
import type { RunOptions } from "./types.js";
|
|
8
9
|
import YAML from "js-yaml";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { createInterface } from "node:readline/promises";
|
|
12
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
9
13
|
|
|
10
14
|
function parseVarFlags(values: string[]): Record<string, string> {
|
|
11
15
|
const out: Record<string, string> = {};
|
|
@@ -21,6 +25,10 @@ function parseVarFlags(values: string[]): Record<string, string> {
|
|
|
21
25
|
return out;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
function parsePluginFlags(values: string[]): string[] {
|
|
29
|
+
return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
function readEnv(name: string): string | undefined {
|
|
25
33
|
const value = process.env[name];
|
|
26
34
|
if (value === undefined) {
|
|
@@ -48,6 +56,21 @@ function parseOptionalInt(value: string | undefined, fieldName: string): number
|
|
|
48
56
|
return parsed;
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
async function promptProjectName(defaultValue: string): Promise<string> {
|
|
60
|
+
if (!input.isTTY) {
|
|
61
|
+
return defaultValue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rl = createInterface({ input, output });
|
|
65
|
+
try {
|
|
66
|
+
const answer = await rl.question(`Project name [${defaultValue}]: `);
|
|
67
|
+
const value = answer.trim();
|
|
68
|
+
return value || defaultValue;
|
|
69
|
+
} finally {
|
|
70
|
+
rl.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
export function buildCli(): Command {
|
|
52
75
|
const program = new Command();
|
|
53
76
|
|
|
@@ -65,6 +88,7 @@ export function buildCli(): Command {
|
|
|
65
88
|
.option("--verbose", "Verbose logging", false)
|
|
66
89
|
.option("-s, --silent", "Skip reset confirmation prompt", false)
|
|
67
90
|
.option("--provider <provider>", "Execution provider: command | remote | mock")
|
|
91
|
+
.option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p: string[]) => p.concat([v]), [])
|
|
68
92
|
.option("--remote-base-url <url>", "Remote OpenClaw API base URL")
|
|
69
93
|
.option("--remote-api-key <key>", "Remote OpenClaw API key")
|
|
70
94
|
.option("--remote-api-header <header>", "Remote auth header name")
|
|
@@ -75,6 +99,7 @@ export function buildCli(): Command {
|
|
|
75
99
|
const provider = parseProvider(opts.provider ?? readEnv("CLAWCHEF_PROVIDER") ?? "command");
|
|
76
100
|
const options: RunOptions = {
|
|
77
101
|
vars: parseVarFlags(opts.var),
|
|
102
|
+
plugins: parsePluginFlags(opts.plugin),
|
|
78
103
|
dryRun: Boolean(opts.dryRun),
|
|
79
104
|
allowMissing: Boolean(opts.allowMissing),
|
|
80
105
|
verbose: Boolean(opts.verbose),
|
|
@@ -100,6 +125,20 @@ export function buildCli(): Command {
|
|
|
100
125
|
}
|
|
101
126
|
});
|
|
102
127
|
|
|
128
|
+
program
|
|
129
|
+
.command("scaffold")
|
|
130
|
+
.argument("[dir]", "Target directory (default: current directory)")
|
|
131
|
+
.option("--name <project-name>", "Project name (default: directory name)")
|
|
132
|
+
.action(async (dir: string | undefined, opts) => {
|
|
133
|
+
const resolvedDir = path.resolve(dir?.trim() ? dir : process.cwd());
|
|
134
|
+
const defaultName = path.basename(resolvedDir);
|
|
135
|
+
const projectName = opts.name?.trim() ? opts.name.trim() : await promptProjectName(defaultName);
|
|
136
|
+
const result = await scaffoldProject(resolvedDir, { projectName });
|
|
137
|
+
process.stdout.write(`Scaffold created at ${result.targetDir}\n`);
|
|
138
|
+
process.stdout.write(`Project name: ${result.projectName}\n`);
|
|
139
|
+
process.stdout.write("Next: run npm install\n");
|
|
140
|
+
});
|
|
141
|
+
|
|
103
142
|
program
|
|
104
143
|
.command("validate")
|
|
105
144
|
.argument("<recipe>", "Recipe path/URL/dir/archive[:file]")
|
|
@@ -13,9 +13,10 @@ const DEFAULT_COMMANDS = {
|
|
|
13
13
|
use_version: "${bin} --version",
|
|
14
14
|
install_version: "npm install -g openclaw@${version}",
|
|
15
15
|
uninstall_version: "npm uninstall -g openclaw",
|
|
16
|
+
install_plugin: "${bin} plugins install ${plugin_spec_q}",
|
|
16
17
|
factory_reset: "${bin} reset --scope full --yes --non-interactive",
|
|
17
18
|
start_gateway: "${bin} gateway start",
|
|
18
|
-
enable_plugin: "
|
|
19
|
+
enable_plugin: "",
|
|
19
20
|
login_channel: "${bin} channels login --channel ${channel_q}${account_arg}",
|
|
20
21
|
create_agent:
|
|
21
22
|
"${bin} agents add ${agent} --workspace ${workspace_path} --model ${model} --non-interactive --json",
|
|
@@ -377,6 +378,25 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
377
378
|
await runShell(resetCmd, dryRun);
|
|
378
379
|
}
|
|
379
380
|
|
|
381
|
+
async installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void> {
|
|
382
|
+
const trimmed = pluginSpec.trim();
|
|
383
|
+
if (!trimmed) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const bin = config.bin ?? "openclaw";
|
|
388
|
+
const cmd = commandFor(config, "install_plugin", {
|
|
389
|
+
bin,
|
|
390
|
+
version: config.version,
|
|
391
|
+
plugin_spec: trimmed,
|
|
392
|
+
plugin_spec_q: shellQuote(trimmed),
|
|
393
|
+
});
|
|
394
|
+
if (!cmd.trim()) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
await runShell(cmd, dryRun);
|
|
398
|
+
}
|
|
399
|
+
|
|
380
400
|
async startGateway(config: OpenClawSection, dryRun: boolean): Promise<void> {
|
|
381
401
|
const bin = config.bin ?? "openclaw";
|
|
382
402
|
const startCmd = commandFor(config, "start_gateway", { bin, version: config.version });
|
|
@@ -405,14 +425,17 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
405
425
|
|
|
406
426
|
async configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void> {
|
|
407
427
|
const bin = config.bin ?? "openclaw";
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
428
|
+
const enablePluginTemplate = config.commands?.enable_plugin;
|
|
429
|
+
if (enablePluginTemplate?.trim()) {
|
|
430
|
+
const enablePluginCmd = fillTemplate(enablePluginTemplate, {
|
|
431
|
+
bin,
|
|
432
|
+
version: config.version,
|
|
433
|
+
channel: channel.channel,
|
|
434
|
+
channel_q: shellQuote(channel.channel),
|
|
435
|
+
});
|
|
436
|
+
if (enablePluginCmd.trim()) {
|
|
437
|
+
await runShell(enablePluginCmd, dryRun);
|
|
438
|
+
}
|
|
416
439
|
}
|
|
417
440
|
|
|
418
441
|
const flags: string[] = [
|
|
@@ -40,6 +40,10 @@ export class MockOpenClawProvider implements OpenClawProvider {
|
|
|
40
40
|
return { installedThisRun };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
async installPlugin(_config: OpenClawSection, _pluginSpec: string, _dryRun: boolean): Promise<void> {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
async factoryReset(_config: OpenClawSection, _dryRun: boolean): Promise<void> {
|
|
44
48
|
this.state.workspaces.clear();
|
|
45
49
|
this.state.channels.clear();
|
package/src/openclaw/provider.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface EnsureVersionResult {
|
|
|
8
8
|
|
|
9
9
|
export interface OpenClawProvider {
|
|
10
10
|
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean): Promise<EnsureVersionResult>;
|
|
11
|
+
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
11
12
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
12
13
|
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
13
14
|
createWorkspace(config: OpenClawSection, workspace: ResolvedWorkspaceDef, dryRun: boolean): Promise<void>;
|
|
@@ -156,6 +156,17 @@ export class RemoteOpenClawProvider implements OpenClawProvider {
|
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
async installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void> {
|
|
160
|
+
await this.perform(
|
|
161
|
+
config,
|
|
162
|
+
"install_plugin",
|
|
163
|
+
{
|
|
164
|
+
plugin_spec: pluginSpec,
|
|
165
|
+
},
|
|
166
|
+
dryRun,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
159
170
|
async factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void> {
|
|
160
171
|
await this.perform(config, "factory_reset", undefined, dryRun);
|
|
161
172
|
}
|