@denial-web/clawguard 0.1.5 → 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
@@ -74,6 +74,12 @@ If OpenClaw already has messaging configured, ClawGuard can hand the approval me
74
74
  npx @denial-web/clawguard approvals send ./.clawguard/approvals.jsonl --via openclaw --channel telegram --target 123456789
75
75
  ```
76
76
 
77
+ If you want ClawGuard to own the approval channel separately, send directly through Telegram:
78
+
79
+ ```bash
80
+ TELEGRAM_BOT_TOKEN=123456:token npx @denial-web/clawguard approvals send ./.clawguard/approvals.jsonl --via telegram --chat-id 123456789
81
+ ```
82
+
77
83
  When testing the published package, run `npx` from outside this repository. From inside the ClawGuard source checkout, use the local commands instead:
78
84
 
79
85
  ```bash
@@ -58,13 +58,15 @@ openclaw message send --channel telegram --target 123456789 --message "<approval
58
58
 
59
59
  This is the easiest path for OpenClaw users because ClawGuard does not need to own Telegram, WhatsApp, Slack, or Discord credentials.
60
60
 
61
- Option B is planned as a ClawGuard-owned sender:
61
+ Option B is a ClawGuard-owned Telegram sender:
62
62
 
63
63
  ```bash
64
- clawguard approvals serve --telegram
64
+ TELEGRAM_BOT_TOKEN=123456:token clawguard approvals send ./.clawguard/approvals.jsonl \
65
+ --via telegram \
66
+ --chat-id 123456789
65
67
  ```
66
68
 
67
- That path is better when the user wants the approval channel to stay independent from the agent runtime.
69
+ That path is better when the user wants the approval channel to stay independent from the agent runtime. Use `--dry-run` first to verify the redacted endpoint and message payload before sending.
68
70
 
69
71
  ### Skill Folder Scan
70
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@denial-web/clawguard",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Explainable security scanner for OpenClaw-style skills and MCP tool configs.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -110,6 +110,7 @@ Usage:
110
110
  clawguard openclaw install <path> --to <dir> [--approval-out <path>]
111
111
  clawguard hermes install <path> --to <dir> [--approval-out <path>]
112
112
  clawguard approvals send <approval.json|approvals.jsonl> --via openclaw --channel <name> --target <id>
113
+ clawguard approvals send <approval.json|approvals.jsonl> --via telegram --chat-id <id>
113
114
  clawguard scan-workspace <path> [--json] [--policy <preset>]
114
115
  npm run scan -- <path>
115
116
 
@@ -134,11 +135,13 @@ Options:
134
135
  --approval-out <path> Write a pending approval JSON request before copying.
135
136
  Use .jsonl to append JSON lines for bot/daemon integrations.
136
137
  --approval-mode <mode> Approval mode: non-allow, always. Default: non-allow.
137
- --via <adapter> Approval send adapter. Currently: openclaw.
138
+ --via <adapter> Approval send adapter: openclaw, telegram.
138
139
  --channel <name> Messaging channel for approval send, such as telegram.
139
140
  --target <id> Messaging target/chat id for approval send.
140
141
  --sender-bin <path> Sender binary. Default for --via openclaw: openclaw.
141
142
  --sender-arg <value> Extra argument before the generated sender command. Repeatable.
143
+ --bot-token <token> Telegram bot token. Default: TELEGRAM_BOT_TOKEN.
144
+ --chat-id <id> Telegram chat id. Alias for --target with --via telegram.
142
145
 
143
146
  Gate exit codes:
144
147
  0 = allow
@@ -152,6 +155,7 @@ Examples:
152
155
  npx @denial-web/clawguard openclaw install ./skills/my-skill --to ./.agents/skills --approval-out ./.clawguard/approvals.jsonl
153
156
  npx @denial-web/clawguard hermes install ./skills/my-skill --to ~/.hermes/skills --approval-out ./.clawguard/approvals.jsonl
154
157
  npx @denial-web/clawguard approvals send ./.clawguard/approvals.jsonl --via openclaw --channel telegram --target 123456789
158
+ npx @denial-web/clawguard approvals send ./.clawguard/approvals.jsonl --via telegram --chat-id 123456789
155
159
  npm run scan -- examples/risky-skill
156
160
  npm run scan -- examples/metadata-mismatch-skill --policy governed --fail-on-policy
157
161
  npm run scan -- examples/metadata-mismatch-skill --html clawguard.html
@@ -284,8 +288,12 @@ async function sendApproval(options) {
284
288
  throw new Error("Approval request has no message field.");
285
289
  }
286
290
 
291
+ if (options.via === "telegram") {
292
+ return sendTelegramApproval(approval, message, options);
293
+ }
294
+
287
295
  if (options.via !== "openclaw") {
288
- throw new Error("Only --via openclaw is supported right now.");
296
+ throw new Error("Only --via openclaw or --via telegram is supported right now.");
289
297
  }
290
298
 
291
299
  const senderBin = options.senderBin ?? "openclaw";
@@ -333,6 +341,68 @@ async function sendApproval(options) {
333
341
  return result;
334
342
  }
335
343
 
344
+ async function sendTelegramApproval(approval, message, options) {
345
+ const botToken = options.botToken ?? process.env.TELEGRAM_BOT_TOKEN;
346
+
347
+ if (!botToken) {
348
+ throw new Error("Telegram send requires --bot-token or TELEGRAM_BOT_TOKEN.");
349
+ }
350
+
351
+ const apiBase = options.telegramApiBase ?? "https://api.telegram.org";
352
+ const endpoint = `${apiBase.replace(/\/$/, "")}/bot${botToken}/sendMessage`;
353
+ const body = {
354
+ chat_id: options.chatId,
355
+ text: message,
356
+ disable_web_page_preview: true
357
+ };
358
+ const result = {
359
+ approval: {
360
+ id: approval.id,
361
+ status: approval.status,
362
+ decision: approval.decision,
363
+ risk: approval.risk,
364
+ framework: approval.framework
365
+ },
366
+ via: "telegram",
367
+ channel: "telegram",
368
+ target: options.chatId,
369
+ endpoint: redactTelegramToken(endpoint),
370
+ request: body,
371
+ dryRun: options.dryRun,
372
+ sent: false,
373
+ response: null
374
+ };
375
+
376
+ if (options.dryRun) {
377
+ return result;
378
+ }
379
+
380
+ const response = await fetch(endpoint, {
381
+ method: "POST",
382
+ headers: {
383
+ "content-type": "application/json"
384
+ },
385
+ body: JSON.stringify(body)
386
+ });
387
+ const text = await response.text();
388
+ let payload;
389
+
390
+ try {
391
+ payload = text ? JSON.parse(text) : null;
392
+ } catch {
393
+ payload = text;
394
+ }
395
+
396
+ result.response = payload;
397
+
398
+ if (!response.ok) {
399
+ throw new Error(`Telegram send failed with HTTP ${response.status}: ${text}`);
400
+ }
401
+
402
+ result.sent = true;
403
+ return result;
404
+ }
405
+
336
406
  function printApprovalSendResult(result) {
337
407
  console.log(`ClawGuard approval send: ${result.approval.id}`);
338
408
  console.log(`Via: ${result.via}`);
@@ -343,7 +413,11 @@ function printApprovalSendResult(result) {
343
413
  console.log(`Sent: ${result.sent ? "yes" : "no"}`);
344
414
 
345
415
  if (result.dryRun) {
346
- console.log(`Command: ${result.command.map(shellQuote).join(" ")}`);
416
+ if (result.command) {
417
+ console.log(`Command: ${result.command.map(shellQuote).join(" ")}`);
418
+ } else if (result.endpoint) {
419
+ console.log(`Endpoint: ${result.endpoint}`);
420
+ }
347
421
  }
348
422
  }
349
423
 
@@ -723,6 +797,10 @@ function shellQuote(value) {
723
797
  return `'${text.replaceAll("'", "'\\''")}'`;
724
798
  }
725
799
 
800
+ function redactTelegramToken(value) {
801
+ return String(value).replace(/\/bot[^/]+\/sendMessage$/, "/bot<redacted>/sendMessage");
802
+ }
803
+
726
804
  function gateExitCode(decision) {
727
805
  if (decision === "allow") {
728
806
  return 0;
@@ -887,6 +965,9 @@ function parseApprovalSendOptions(values) {
887
965
  via: "openclaw",
888
966
  channel: undefined,
889
967
  target: undefined,
968
+ chatId: undefined,
969
+ botToken: undefined,
970
+ telegramApiBase: undefined,
890
971
  senderBin: undefined,
891
972
  senderArgs: [],
892
973
  dryRun: false,
@@ -931,6 +1012,24 @@ function parseApprovalSendOptions(values) {
931
1012
  continue;
932
1013
  }
933
1014
 
1015
+ if (value === "--chat-id") {
1016
+ options.chatId = requireNextValue(values, index, "--chat-id");
1017
+ index += 1;
1018
+ continue;
1019
+ }
1020
+
1021
+ if (value === "--bot-token") {
1022
+ options.botToken = requireNextValue(values, index, "--bot-token");
1023
+ index += 1;
1024
+ continue;
1025
+ }
1026
+
1027
+ if (value === "--telegram-api-base") {
1028
+ options.telegramApiBase = requireNextValue(values, index, "--telegram-api-base");
1029
+ index += 1;
1030
+ continue;
1031
+ }
1032
+
934
1033
  if (value === "--sender-bin") {
935
1034
  options.senderBin = requireNextValue(values, index, "--sender-bin");
936
1035
  index += 1;
@@ -956,18 +1055,27 @@ function parseApprovalSendOptions(values) {
956
1055
  throw new Error("approvals send requires <approval.json|approvals.jsonl>.");
957
1056
  }
958
1057
 
959
- if (options.via !== "openclaw") {
960
- throw new Error("Invalid --via value. Use: openclaw");
1058
+ if (!["openclaw", "telegram"].includes(options.via)) {
1059
+ throw new Error("Invalid --via value. Use one of: openclaw, telegram");
961
1060
  }
962
1061
 
963
- if (!options.channel) {
1062
+ if (options.via === "openclaw" && !options.channel) {
964
1063
  throw new Error("approvals send requires --channel <name>.");
965
1064
  }
966
1065
 
967
- if (!options.target) {
1066
+ if (options.via === "openclaw" && !options.target) {
968
1067
  throw new Error("approvals send requires --target <id>.");
969
1068
  }
970
1069
 
1070
+ if (options.via === "telegram") {
1071
+ options.chatId = options.chatId ?? options.target;
1072
+ if (!options.chatId) {
1073
+ throw new Error("approvals send --via telegram requires --chat-id <id>.");
1074
+ }
1075
+ options.channel = "telegram";
1076
+ options.target = options.chatId;
1077
+ }
1078
+
971
1079
  return options;
972
1080
  }
973
1081