clawchef 0.1.6 → 0.1.8
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/AGENTS.md +159 -0
- package/README.md +32 -14
- package/dist/api.d.ts +3 -1
- package/dist/api.js +10 -1
- package/dist/cli.js +34 -3
- package/dist/openclaw/command-provider.d.ts +2 -1
- package/dist/openclaw/command-provider.js +109 -23
- package/dist/openclaw/mock-provider.d.ts +2 -1
- package/dist/openclaw/mock-provider.js +4 -1
- package/dist/openclaw/provider.d.ts +2 -1
- package/dist/openclaw/remote-provider.d.ts +2 -1
- package/dist/openclaw/remote-provider.js +8 -1
- package/dist/orchestrator.js +90 -56
- package/dist/recipe.js +71 -24
- package/dist/schema.d.ts +84 -107
- package/dist/schema.js +17 -21
- package/dist/types.d.ts +8 -11
- package/package.json +1 -1
- package/recipes/content-from-sample.yaml +13 -9
- package/recipes/openclaw-from-zero.yaml +2 -2
- package/recipes/openclaw-local.yaml +8 -10
- package/recipes/openclaw-remote-http.yaml +6 -8
- package/recipes/sample.yaml +6 -8
- package/recipes/snippets/readme-template.md +3 -1
- package/src/api.ts +13 -2
- package/src/cli.ts +36 -4
- package/src/openclaw/command-provider.ts +134 -24
- package/src/openclaw/mock-provider.ts +10 -1
- package/src/openclaw/provider.ts +2 -1
- package/src/openclaw/remote-provider.ts +14 -1
- package/src/orchestrator.ts +93 -55
- package/src/recipe.ts +82 -24
- package/src/schema.ts +19 -22
- package/src/types.ts +8 -11
package/src/recipe.ts
CHANGED
|
@@ -32,24 +32,18 @@ export interface LoadedRecipeText {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const AUTH_CHOICE_TO_FIELD: Record<string, string> = {
|
|
35
|
-
"openai-api-key": "
|
|
36
|
-
"anthropic-api-key": "
|
|
37
|
-
"openrouter-api-key": "
|
|
38
|
-
"xai-api-key": "
|
|
39
|
-
"gemini-api-key": "
|
|
40
|
-
"ai-gateway-api-key": "
|
|
41
|
-
"cloudflare-ai-gateway-api-key": "
|
|
35
|
+
"openai-api-key": "llm_api_key",
|
|
36
|
+
"anthropic-api-key": "llm_api_key",
|
|
37
|
+
"openrouter-api-key": "llm_api_key",
|
|
38
|
+
"xai-api-key": "llm_api_key",
|
|
39
|
+
"gemini-api-key": "llm_api_key",
|
|
40
|
+
"ai-gateway-api-key": "llm_api_key",
|
|
41
|
+
"cloudflare-ai-gateway-api-key": "llm_api_key",
|
|
42
42
|
token: "token",
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const SECRET_BOOTSTRAP_FIELDS = [
|
|
46
|
-
"
|
|
47
|
-
"anthropic_api_key",
|
|
48
|
-
"openrouter_api_key",
|
|
49
|
-
"xai_api_key",
|
|
50
|
-
"gemini_api_key",
|
|
51
|
-
"ai_gateway_api_key",
|
|
52
|
-
"cloudflare_ai_gateway_api_key",
|
|
46
|
+
"llm_api_key",
|
|
53
47
|
"token",
|
|
54
48
|
] as const;
|
|
55
49
|
|
|
@@ -124,7 +118,7 @@ function assertNoInlineSecrets(recipe: Recipe): void {
|
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
|
|
127
|
-
function collectVars(recipe: Recipe, cliVars: Record<string, string>): Record<string, string> {
|
|
121
|
+
function collectVars(recipe: Recipe, cliVars: Record<string, string>, requiredKeys?: Set<string>): Record<string, string> {
|
|
128
122
|
const vars: Record<string, string> = {};
|
|
129
123
|
const params = recipe.params ?? {};
|
|
130
124
|
|
|
@@ -155,7 +149,7 @@ function collectVars(recipe: Recipe, cliVars: Record<string, string>): Record<st
|
|
|
155
149
|
vars[key] = def.default;
|
|
156
150
|
continue;
|
|
157
151
|
}
|
|
158
|
-
if (def.required) {
|
|
152
|
+
if (def.required && (requiredKeys === undefined || requiredKeys.has(key))) {
|
|
159
153
|
throw new ClawChefError(`Parameter ${key} is required but was not provided via --var or environment`);
|
|
160
154
|
}
|
|
161
155
|
}
|
|
@@ -167,21 +161,55 @@ function collectVars(recipe: Recipe, cliVars: Record<string, string>): Record<st
|
|
|
167
161
|
return vars;
|
|
168
162
|
}
|
|
169
163
|
|
|
164
|
+
function projectRecipeForScope(recipe: Recipe, options: RunOptions): Recipe {
|
|
165
|
+
if (options.scope !== "workspace") {
|
|
166
|
+
return recipe;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
...recipe,
|
|
171
|
+
openclaw: {
|
|
172
|
+
...recipe.openclaw,
|
|
173
|
+
bootstrap: undefined,
|
|
174
|
+
},
|
|
175
|
+
channels: [],
|
|
176
|
+
conversations: [],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function filterRecipeByWorkspaceName(recipe: Recipe, workspaceName: string): Recipe {
|
|
181
|
+
const workspace = (recipe.workspaces ?? []).find((ws) => ws.name === workspaceName);
|
|
182
|
+
if (!workspace) {
|
|
183
|
+
throw new ClawChefError(`Workspace not found in recipe: ${workspaceName}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...recipe,
|
|
188
|
+
workspaces: [workspace],
|
|
189
|
+
agents: (recipe.agents ?? []).filter((agent) => agent.workspace === workspaceName),
|
|
190
|
+
conversations: (recipe.conversations ?? []).filter((conv) => conv.workspace === workspaceName),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
170
194
|
function semanticValidate(recipe: Recipe): void {
|
|
171
195
|
const ws = new Set((recipe.workspaces ?? []).map((w) => w.name));
|
|
196
|
+
const agentNameCounts = new Map<string, number>();
|
|
172
197
|
for (const workspace of recipe.workspaces ?? []) {
|
|
173
198
|
if (workspace.assets !== undefined && !workspace.assets.trim()) {
|
|
174
199
|
throw new ClawChefError(`Workspace ${workspace.name} has empty assets path`);
|
|
175
200
|
}
|
|
176
201
|
}
|
|
177
202
|
for (const agent of recipe.agents ?? []) {
|
|
203
|
+
agentNameCounts.set(agent.name, (agentNameCounts.get(agent.name) ?? 0) + 1);
|
|
178
204
|
if (!ws.has(agent.workspace)) {
|
|
179
205
|
throw new ClawChefError(`Agent ${agent.name} references missing workspace: ${agent.workspace}`);
|
|
180
206
|
}
|
|
181
207
|
}
|
|
182
|
-
for (const
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
for (const workspace of recipe.workspaces ?? []) {
|
|
209
|
+
for (const file of workspace.files ?? []) {
|
|
210
|
+
if (!file.path.trim()) {
|
|
211
|
+
throw new ClawChefError(`Workspace ${workspace.name} has file with empty path`);
|
|
212
|
+
}
|
|
185
213
|
}
|
|
186
214
|
}
|
|
187
215
|
const agents = new Set((recipe.agents ?? []).map((a) => `${a.workspace}::${a.name}`));
|
|
@@ -212,6 +240,23 @@ function semanticValidate(recipe: Recipe): void {
|
|
|
212
240
|
);
|
|
213
241
|
}
|
|
214
242
|
|
|
243
|
+
if (channel.agent?.trim()) {
|
|
244
|
+
if (channel.channel !== "telegram") {
|
|
245
|
+
throw new ClawChefError(
|
|
246
|
+
`channels[] entry for ${channel.channel} does not support agent binding. Use channel: telegram with agent.`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const matched = agentNameCounts.get(channel.agent) ?? 0;
|
|
250
|
+
if (matched === 0) {
|
|
251
|
+
throw new ClawChefError(`channels[] entry references missing agent by name: ${channel.agent}`);
|
|
252
|
+
}
|
|
253
|
+
if (matched > 1) {
|
|
254
|
+
throw new ClawChefError(
|
|
255
|
+
`channels[] entry references duplicate agent name: ${channel.agent}. Agent names must be unique for channel binding.`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
215
260
|
const hasAuth =
|
|
216
261
|
Boolean(channel.use_env) ||
|
|
217
262
|
Boolean(channel.token?.trim()) ||
|
|
@@ -752,18 +797,31 @@ export async function loadRecipe(recipePath: string, options: RunOptions): Promi
|
|
|
752
797
|
throw new ClawChefError(`Recipe format is invalid: ${firstParse.error.message}`);
|
|
753
798
|
}
|
|
754
799
|
|
|
755
|
-
|
|
800
|
+
const projected = projectRecipeForScope(firstParse.data, options);
|
|
801
|
+
|
|
802
|
+
assertNoInlineSecrets(projected);
|
|
756
803
|
|
|
757
|
-
const
|
|
758
|
-
const
|
|
804
|
+
const requiredKeys = options.scope === "workspace" ? new Set<string>() : undefined;
|
|
805
|
+
const vars = collectVars(projected, options.vars, requiredKeys);
|
|
806
|
+
const rendered = deepResolveTemplates(projected, vars, options.allowMissing);
|
|
759
807
|
const secondParse = recipeSchema.safeParse(rendered);
|
|
760
808
|
if (!secondParse.success) {
|
|
761
809
|
throw new ClawChefError(`Recipe is invalid after parameter resolution: ${secondParse.error.message}`);
|
|
762
810
|
}
|
|
763
811
|
|
|
764
|
-
|
|
812
|
+
const scopedRecipe = (() => {
|
|
813
|
+
if (options.scope !== "workspace") {
|
|
814
|
+
return secondParse.data;
|
|
815
|
+
}
|
|
816
|
+
if (!options.workspaceName) {
|
|
817
|
+
throw new ClawChefError("scope=workspace requires a workspace name");
|
|
818
|
+
}
|
|
819
|
+
return filterRecipeByWorkspaceName(secondParse.data, options.workspaceName);
|
|
820
|
+
})();
|
|
821
|
+
|
|
822
|
+
semanticValidate(scopedRecipe);
|
|
765
823
|
return {
|
|
766
|
-
recipe:
|
|
824
|
+
recipe: scopedRecipe,
|
|
767
825
|
origin: recipeRef.origin,
|
|
768
826
|
};
|
|
769
827
|
});
|
package/src/schema.ts
CHANGED
|
@@ -15,6 +15,7 @@ const openClawCommandsSchema = z
|
|
|
15
15
|
factory_reset: z.string().optional(),
|
|
16
16
|
start_gateway: z.string().optional(),
|
|
17
17
|
enable_plugin: z.string().optional(),
|
|
18
|
+
bind_channel_agent: z.string().optional(),
|
|
18
19
|
login_channel: z.string().optional(),
|
|
19
20
|
create_workspace: z.string().optional(),
|
|
20
21
|
create_agent: z.string().optional(),
|
|
@@ -39,13 +40,7 @@ const openClawBootstrapSchema = z
|
|
|
39
40
|
skip_ui: z.boolean().optional(),
|
|
40
41
|
skip_daemon: z.boolean().optional(),
|
|
41
42
|
install_daemon: z.boolean().optional(),
|
|
42
|
-
|
|
43
|
-
anthropic_api_key: z.string().optional(),
|
|
44
|
-
openrouter_api_key: z.string().optional(),
|
|
45
|
-
xai_api_key: z.string().optional(),
|
|
46
|
-
gemini_api_key: z.string().optional(),
|
|
47
|
-
ai_gateway_api_key: z.string().optional(),
|
|
48
|
-
cloudflare_ai_gateway_api_key: z.string().optional(),
|
|
43
|
+
llm_api_key: z.string().optional(),
|
|
49
44
|
cloudflare_ai_gateway_account_id: z.string().optional(),
|
|
50
45
|
cloudflare_ai_gateway_gateway_id: z.string().optional(),
|
|
51
46
|
token: z.string().optional(),
|
|
@@ -70,6 +65,22 @@ const workspaceSchema = z
|
|
|
70
65
|
name: z.string().min(1),
|
|
71
66
|
path: z.string().min(1).optional(),
|
|
72
67
|
assets: z.string().min(1).optional(),
|
|
68
|
+
files: z
|
|
69
|
+
.array(
|
|
70
|
+
z
|
|
71
|
+
.object({
|
|
72
|
+
path: z.string().min(1),
|
|
73
|
+
content: z.string().optional(),
|
|
74
|
+
content_from: z.string().min(1).optional(),
|
|
75
|
+
source: z.string().optional(),
|
|
76
|
+
overwrite: z.boolean().optional(),
|
|
77
|
+
})
|
|
78
|
+
.strict()
|
|
79
|
+
.refine((v) => [v.content, v.content_from, v.source].filter((item) => item !== undefined).length === 1, {
|
|
80
|
+
message: "workspaces[].files[] requires exactly one of content, content_from, or source",
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
.optional(),
|
|
73
84
|
})
|
|
74
85
|
.strict();
|
|
75
86
|
|
|
@@ -77,6 +88,7 @@ const channelSchema = z
|
|
|
77
88
|
.object({
|
|
78
89
|
channel: z.string().min(1),
|
|
79
90
|
account: z.string().min(1).optional(),
|
|
91
|
+
agent: z.string().min(1).optional(),
|
|
80
92
|
login: z.boolean().optional(),
|
|
81
93
|
login_mode: z.enum(["interactive"]).optional(),
|
|
82
94
|
login_account: z.string().min(1).optional(),
|
|
@@ -104,20 +116,6 @@ const agentSchema = z
|
|
|
104
116
|
})
|
|
105
117
|
.strict();
|
|
106
118
|
|
|
107
|
-
const fileSchema = z
|
|
108
|
-
.object({
|
|
109
|
-
workspace: z.string().min(1),
|
|
110
|
-
path: z.string().min(1),
|
|
111
|
-
content: z.string().optional(),
|
|
112
|
-
content_from: z.string().min(1).optional(),
|
|
113
|
-
source: z.string().optional(),
|
|
114
|
-
overwrite: z.boolean().optional(),
|
|
115
|
-
})
|
|
116
|
-
.strict()
|
|
117
|
-
.refine((v) => [v.content, v.content_from, v.source].filter((item) => item !== undefined).length === 1, {
|
|
118
|
-
message: "files[] requires exactly one of content, content_from, or source",
|
|
119
|
-
});
|
|
120
|
-
|
|
121
119
|
const conversationExpectSchema = z
|
|
122
120
|
.object({
|
|
123
121
|
contains: z.array(z.string()).optional(),
|
|
@@ -152,7 +150,6 @@ export const recipeSchema = z
|
|
|
152
150
|
workspaces: z.array(workspaceSchema).optional(),
|
|
153
151
|
channels: z.array(channelSchema).optional(),
|
|
154
152
|
agents: z.array(agentSchema).optional(),
|
|
155
|
-
files: z.array(fileSchema).optional(),
|
|
156
153
|
conversations: z.array(conversationSchema).optional(),
|
|
157
154
|
})
|
|
158
155
|
.strict();
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type InstallPolicy = "auto" | "always" | "never";
|
|
2
2
|
export type OpenClawProvider = "command" | "mock" | "remote";
|
|
3
|
+
export type RunScope = "full" | "files" | "workspace";
|
|
3
4
|
|
|
4
5
|
export interface OpenClawRemoteConfig {
|
|
5
6
|
base_url: string;
|
|
@@ -24,6 +25,7 @@ export interface OpenClawCommandOverrides {
|
|
|
24
25
|
factory_reset?: string;
|
|
25
26
|
start_gateway?: string;
|
|
26
27
|
enable_plugin?: string;
|
|
28
|
+
bind_channel_agent?: string;
|
|
27
29
|
login_channel?: string;
|
|
28
30
|
create_workspace?: string;
|
|
29
31
|
create_agent?: string;
|
|
@@ -46,13 +48,7 @@ export interface OpenClawBootstrap {
|
|
|
46
48
|
skip_ui?: boolean;
|
|
47
49
|
skip_daemon?: boolean;
|
|
48
50
|
install_daemon?: boolean;
|
|
49
|
-
|
|
50
|
-
anthropic_api_key?: string;
|
|
51
|
-
openrouter_api_key?: string;
|
|
52
|
-
xai_api_key?: string;
|
|
53
|
-
gemini_api_key?: string;
|
|
54
|
-
ai_gateway_api_key?: string;
|
|
55
|
-
cloudflare_ai_gateway_api_key?: string;
|
|
51
|
+
llm_api_key?: string;
|
|
56
52
|
cloudflare_ai_gateway_account_id?: string;
|
|
57
53
|
cloudflare_ai_gateway_gateway_id?: string;
|
|
58
54
|
token?: string;
|
|
@@ -73,11 +69,13 @@ export interface WorkspaceDef {
|
|
|
73
69
|
name: string;
|
|
74
70
|
path?: string;
|
|
75
71
|
assets?: string;
|
|
72
|
+
files?: WorkspaceFileDef[];
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
export interface ChannelDef {
|
|
79
76
|
channel: string;
|
|
80
77
|
account?: string;
|
|
78
|
+
agent?: string;
|
|
81
79
|
login?: boolean;
|
|
82
80
|
login_mode?: "interactive";
|
|
83
81
|
login_account?: string;
|
|
@@ -102,8 +100,7 @@ export interface AgentDef {
|
|
|
102
100
|
skills?: string[];
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
export interface
|
|
106
|
-
workspace: string;
|
|
103
|
+
export interface WorkspaceFileDef {
|
|
107
104
|
path: string;
|
|
108
105
|
content?: string;
|
|
109
106
|
content_from?: string;
|
|
@@ -138,18 +135,18 @@ export interface Recipe {
|
|
|
138
135
|
workspaces?: WorkspaceDef[];
|
|
139
136
|
channels?: ChannelDef[];
|
|
140
137
|
agents?: AgentDef[];
|
|
141
|
-
files?: FileDef[];
|
|
142
138
|
conversations?: ConversationDef[];
|
|
143
139
|
}
|
|
144
140
|
|
|
145
141
|
export interface RunOptions {
|
|
146
142
|
vars: Record<string, string>;
|
|
147
143
|
plugins: string[];
|
|
144
|
+
scope: RunScope;
|
|
145
|
+
workspaceName?: string;
|
|
148
146
|
dryRun: boolean;
|
|
149
147
|
allowMissing: boolean;
|
|
150
148
|
verbose: boolean;
|
|
151
149
|
silent: boolean;
|
|
152
|
-
keepOpenClawState: boolean;
|
|
153
150
|
provider: OpenClawProvider;
|
|
154
151
|
remote: Partial<OpenClawRemoteConfig>;
|
|
155
152
|
}
|