@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 +6 -0
- package/docs/INTEGRATION_SPEC.md +5 -3
- package/package.json +1 -1
- package/src/cli.js +115 -7
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
|
package/docs/INTEGRATION_SPEC.md
CHANGED
|
@@ -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
|
|
61
|
+
Option B is a ClawGuard-owned Telegram sender:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
clawguard approvals
|
|
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
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
|
|
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
|
-
|
|
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
|
|
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
|
|