clawchef 0.1.11 → 0.1.12

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
@@ -420,6 +420,7 @@ Supported common fields:
420
420
  `channels[].agent` currently supports `channel: "telegram"` only.
421
421
  If `agent` is set and `account` is omitted, clawchef defaults `account` to the same value as `agent`.
422
422
  `channels[].group_policy` currently supports `channel: "telegram"` only and is applied after `channels add` via `openclaw config set` so it is not overwritten by add-flow writes.
423
+ If `channel: "telegram"` has `token: ""` or `bot_token: ""`, clawchef auto-disables that telegram account (`enabled=false`) and skips channel add/bind.
423
424
 
424
425
  ## Workspace path behavior
425
426
 
@@ -136,6 +136,21 @@ function telegramGroupPolicyPath(account) {
136
136
  }
137
137
  return `channels.telegram.accounts[${trimmed}].groupPolicy`;
138
138
  }
139
+ function telegramEnabledPath(account) {
140
+ const trimmed = account?.trim();
141
+ if (!trimmed) {
142
+ return "channels.telegram.enabled";
143
+ }
144
+ return `channels.telegram.accounts[${trimmed}].enabled`;
145
+ }
146
+ function shouldAutoDisableTelegramChannel(channel) {
147
+ if (channel.channel !== "telegram") {
148
+ return false;
149
+ }
150
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
151
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
152
+ return emptyToken || emptyBotToken;
153
+ }
139
154
  async function chooseVersionMismatchAction(currentVersion, expectedVersion, silent) {
140
155
  if (silent) {
141
156
  return "force";
@@ -403,6 +418,12 @@ export class CommandOpenClawProvider {
403
418
  }
404
419
  async configureChannel(config, channel, dryRun) {
405
420
  const bin = config.bin ?? "openclaw";
421
+ if (shouldAutoDisableTelegramChannel(channel)) {
422
+ const enabledPath = telegramEnabledPath(channel.account);
423
+ const disableCmd = `${bin} config set ${shellQuote(enabledPath)} false --strict-json`;
424
+ await runShell(disableCmd, dryRun);
425
+ return;
426
+ }
406
427
  const enablePluginTemplate = config.commands?.enable_plugin;
407
428
  if (enablePluginTemplate?.trim()) {
408
429
  const enablePluginCmd = fillTemplate(enablePluginTemplate, {
@@ -76,6 +76,14 @@ function isHttpUrl(value) {
76
76
  return false;
77
77
  }
78
78
  }
79
+ function shouldAutoDisableTelegramChannel(channel) {
80
+ if (channel.channel !== "telegram") {
81
+ return false;
82
+ }
83
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
84
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
85
+ return emptyToken || emptyBotToken;
86
+ }
79
87
  function isNotFoundError(err) {
80
88
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
81
89
  }
@@ -314,7 +322,12 @@ export async function runRecipe(recipe, recipeOrigin, options, logger) {
314
322
  const effectiveChannel = channel.agent?.trim() && !channel.account?.trim()
315
323
  ? { ...channel, account: channel.agent.trim() }
316
324
  : channel;
325
+ const autoDisabledTelegram = shouldAutoDisableTelegramChannel(effectiveChannel);
317
326
  await provider.configureChannel(recipe.openclaw, effectiveChannel, options.dryRun);
327
+ if (autoDisabledTelegram) {
328
+ logger.info(`Telegram channel disabled due to empty bot token: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
329
+ continue;
330
+ }
318
331
  logger.info(`Channel configured: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
319
332
  if (effectiveChannel.agent?.trim()) {
320
333
  await provider.bindChannelAgent(recipe.openclaw, effectiveChannel, effectiveChannel.agent, options.dryRun);
package/dist/recipe.js CHANGED
@@ -44,6 +44,14 @@ const ALLOWED_CHANNELS = new Set([
44
44
  ]);
45
45
  const CHANNEL_SECRET_FIELDS = ["token", "bot_token", "access_token", "app_token", "password"];
46
46
  const TEMPLATE_TOKEN_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
47
+ function hasExplicitEmptyTelegramToken(channel) {
48
+ if (channel.channel !== "telegram") {
49
+ return false;
50
+ }
51
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
52
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
53
+ return emptyToken || emptyBotToken;
54
+ }
47
55
  function assertNoInlineSecrets(recipe) {
48
56
  const bootstrap = recipe.openclaw.bootstrap;
49
57
  if (bootstrap) {
@@ -222,6 +230,9 @@ function semanticValidate(recipe) {
222
230
  }
223
231
  return /(token|password|secret|api[_-]?key|webhook)/i.test(key) && String(value).trim().length > 0;
224
232
  });
233
+ if (hasExplicitEmptyTelegramToken(channel)) {
234
+ continue;
235
+ }
225
236
  if (!hasAuth) {
226
237
  throw new ClawChefError(`channels[] entry for ${channel.channel} requires at least one auth input (for example token/bot_token/access_token/token_file/use_env)`);
227
238
  }
package/dist/schema.js CHANGED
@@ -107,10 +107,10 @@ const channelSchema = z
107
107
  login_mode: z.enum(["interactive"]).optional(),
108
108
  login_account: z.string().min(1).optional(),
109
109
  name: z.string().min(1).optional(),
110
- token: z.string().min(1).optional(),
110
+ token: z.string().optional(),
111
111
  token_file: z.string().min(1).optional(),
112
112
  use_env: z.boolean().optional(),
113
- bot_token: z.string().min(1).optional(),
113
+ bot_token: z.string().optional(),
114
114
  access_token: z.string().min(1).optional(),
115
115
  app_token: z.string().min(1).optional(),
116
116
  webhook_url: z.string().min(1).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawchef",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Recipe-driven OpenClaw environment orchestrator",
5
5
  "homepage": "https://renorzr.github.io/clawchef",
6
6
  "repository": {
@@ -186,6 +186,23 @@ function telegramGroupPolicyPath(account: string | undefined): string {
186
186
  return `channels.telegram.accounts[${trimmed}].groupPolicy`;
187
187
  }
188
188
 
189
+ function telegramEnabledPath(account: string | undefined): string {
190
+ const trimmed = account?.trim();
191
+ if (!trimmed) {
192
+ return "channels.telegram.enabled";
193
+ }
194
+ return `channels.telegram.accounts[${trimmed}].enabled`;
195
+ }
196
+
197
+ function shouldAutoDisableTelegramChannel(channel: ChannelDef): boolean {
198
+ if (channel.channel !== "telegram") {
199
+ return false;
200
+ }
201
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
202
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
203
+ return emptyToken || emptyBotToken;
204
+ }
205
+
189
206
  type VersionMismatchChoice = "ignore" | "abort" | "force";
190
207
 
191
208
  async function chooseVersionMismatchAction(
@@ -510,6 +527,14 @@ export class CommandOpenClawProvider implements OpenClawProvider {
510
527
 
511
528
  async configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void> {
512
529
  const bin = config.bin ?? "openclaw";
530
+
531
+ if (shouldAutoDisableTelegramChannel(channel)) {
532
+ const enabledPath = telegramEnabledPath(channel.account);
533
+ const disableCmd = `${bin} config set ${shellQuote(enabledPath)} false --strict-json`;
534
+ await runShell(disableCmd, dryRun);
535
+ return;
536
+ }
537
+
513
538
  const enablePluginTemplate = config.commands?.enable_plugin;
514
539
  if (enablePluginTemplate?.trim()) {
515
540
  const enablePluginCmd = fillTemplate(enablePluginTemplate, {
@@ -8,7 +8,7 @@ import { validateReply } from "./assertions.js";
8
8
  import { ClawChefError } from "./errors.js";
9
9
  import { Logger } from "./logger.js";
10
10
  import { createProvider } from "./openclaw/factory.js";
11
- import type { Recipe, RunOptions } from "./types.js";
11
+ import type { ChannelDef, Recipe, RunOptions } from "./types.js";
12
12
  import type { RecipeOrigin } from "./recipe.js";
13
13
 
14
14
  async function exists(filePath: string): Promise<boolean> {
@@ -84,6 +84,15 @@ function isHttpUrl(value: string): boolean {
84
84
  }
85
85
  }
86
86
 
87
+ function shouldAutoDisableTelegramChannel(channel: ChannelDef): boolean {
88
+ if (channel.channel !== "telegram") {
89
+ return false;
90
+ }
91
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
92
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
93
+ return emptyToken || emptyBotToken;
94
+ }
95
+
87
96
  function isNotFoundError(err: unknown): boolean {
88
97
  return typeof err === "object" && err !== null && "code" in err && (err as { code?: string }).code === "ENOENT";
89
98
  }
@@ -362,8 +371,16 @@ export async function runRecipe(
362
371
  const effectiveChannel = channel.agent?.trim() && !channel.account?.trim()
363
372
  ? { ...channel, account: channel.agent.trim() }
364
373
  : channel;
374
+ const autoDisabledTelegram = shouldAutoDisableTelegramChannel(effectiveChannel);
365
375
 
366
376
  await provider.configureChannel(recipe.openclaw, effectiveChannel, options.dryRun);
377
+ if (autoDisabledTelegram) {
378
+ logger.info(
379
+ `Telegram channel disabled due to empty bot token: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`,
380
+ );
381
+ continue;
382
+ }
383
+
367
384
  logger.info(`Channel configured: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
368
385
  if (effectiveChannel.agent?.trim()) {
369
386
  await provider.bindChannelAgent(recipe.openclaw, effectiveChannel, effectiveChannel.agent, options.dryRun);
package/src/recipe.ts CHANGED
@@ -7,7 +7,7 @@ import YAML from "js-yaml";
7
7
  import { recipeSchema } from "./schema.js";
8
8
  import { ClawChefError } from "./errors.js";
9
9
  import { deepResolveTemplates } from "./template.js";
10
- import type { Recipe, RunOptions } from "./types.js";
10
+ import type { ChannelDef, Recipe, RunOptions } from "./types.js";
11
11
 
12
12
  export type RecipeOrigin =
13
13
  | {
@@ -73,6 +73,15 @@ const CHANNEL_SECRET_FIELDS = ["token", "bot_token", "access_token", "app_token"
73
73
 
74
74
  const TEMPLATE_TOKEN_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
75
75
 
76
+ function hasExplicitEmptyTelegramToken(channel: ChannelDef): boolean {
77
+ if (channel.channel !== "telegram") {
78
+ return false;
79
+ }
80
+ const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
81
+ const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
82
+ return emptyToken || emptyBotToken;
83
+ }
84
+
76
85
  function assertNoInlineSecrets(recipe: Recipe): void {
77
86
  const bootstrap = recipe.openclaw.bootstrap;
78
87
  if (bootstrap) {
@@ -291,6 +300,10 @@ function semanticValidate(recipe: Recipe): void {
291
300
  return /(token|password|secret|api[_-]?key|webhook)/i.test(key) && String(value).trim().length > 0;
292
301
  });
293
302
 
303
+ if (hasExplicitEmptyTelegramToken(channel)) {
304
+ continue;
305
+ }
306
+
294
307
  if (!hasAuth) {
295
308
  throw new ClawChefError(
296
309
  `channels[] entry for ${channel.channel} requires at least one auth input (for example token/bot_token/access_token/token_file/use_env)`,
package/src/schema.ts CHANGED
@@ -117,10 +117,10 @@ const channelSchema = z
117
117
  login_mode: z.enum(["interactive"]).optional(),
118
118
  login_account: z.string().min(1).optional(),
119
119
  name: z.string().min(1).optional(),
120
- token: z.string().min(1).optional(),
120
+ token: z.string().optional(),
121
121
  token_file: z.string().min(1).optional(),
122
122
  use_env: z.boolean().optional(),
123
- bot_token: z.string().min(1).optional(),
123
+ bot_token: z.string().optional(),
124
124
  access_token: z.string().min(1).optional(),
125
125
  app_token: z.string().min(1).optional(),
126
126
  webhook_url: z.string().min(1).optional(),