@wahooks/channel 0.3.0 → 0.4.1
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/dist/index.js +66 -76
- package/package.json +17 -6
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
19
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
20
|
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
-
import
|
|
21
|
+
import WebSocket from "ws";
|
|
22
22
|
import fs from "node:fs";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import os from "node:os";
|
|
@@ -83,7 +83,6 @@ const ALLOW_LIST = new Set((cfg.WAHOOKS_ALLOW ?? "")
|
|
|
83
83
|
.split(",")
|
|
84
84
|
.map((s) => s.trim().replace(/\D/g, ""))
|
|
85
85
|
.filter(Boolean));
|
|
86
|
-
const WEBHOOK_PORT = parseInt(cfg.WAHOOKS_CHANNEL_PORT ?? "8790", 10);
|
|
87
86
|
if (!API_KEY) {
|
|
88
87
|
console.error("[wahooks-channel] No API key found.");
|
|
89
88
|
console.error("[wahooks-channel] Run: wahooks-channel --configure <your-api-key>");
|
|
@@ -348,84 +347,75 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
348
347
|
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
349
348
|
}
|
|
350
349
|
});
|
|
351
|
-
// ───
|
|
352
|
-
function
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
// Track last sender for permission relay
|
|
389
|
-
lastSender = from;
|
|
390
|
-
// Check if this is a permission verdict
|
|
391
|
-
const permMatch = PERMISSION_RE.exec(text);
|
|
392
|
-
if (permMatch) {
|
|
393
|
-
await mcp.notification({
|
|
394
|
-
method: "notifications/claude/channel/permission",
|
|
395
|
-
params: {
|
|
396
|
-
request_id: permMatch[2].toLowerCase(),
|
|
397
|
-
behavior: permMatch[1].toLowerCase().startsWith("y") ? "allow" : "deny",
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
console.error(`[wahooks-channel] Permission verdict: ${permMatch[1]} ${permMatch[2]}`);
|
|
401
|
-
res.writeHead(200);
|
|
402
|
-
res.end("ok");
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
// Forward to Claude Code
|
|
350
|
+
// ─── WebSocket event stream ─────────────────────────────────────────────
|
|
351
|
+
function connectWebSocket() {
|
|
352
|
+
const wsProtocol = API_URL.startsWith("https") ? "wss" : "ws";
|
|
353
|
+
const wsHost = API_URL.replace(/^https?/, wsProtocol);
|
|
354
|
+
const wsUrl = `${wsHost}/api/ws?token=${encodeURIComponent(API_KEY)}`;
|
|
355
|
+
console.error("[wahooks-channel] Connecting to event stream...");
|
|
356
|
+
const ws = new WebSocket(wsUrl);
|
|
357
|
+
ws.on("open", () => {
|
|
358
|
+
console.error("[wahooks-channel] Connected to event stream");
|
|
359
|
+
});
|
|
360
|
+
ws.on("message", async (data) => {
|
|
361
|
+
try {
|
|
362
|
+
const event = JSON.parse(data.toString());
|
|
363
|
+
const eventType = event.event ?? "";
|
|
364
|
+
const payload = event.payload ?? {};
|
|
365
|
+
// Only handle incoming messages
|
|
366
|
+
if (!eventType.startsWith("message"))
|
|
367
|
+
return;
|
|
368
|
+
const from = (payload.from ?? "")
|
|
369
|
+
.replace("@c.us", "")
|
|
370
|
+
.replace("@s.whatsapp.net", "");
|
|
371
|
+
const text = payload.body ?? payload.text ?? "";
|
|
372
|
+
const messageId = payload.id?._serialized ?? payload.id ?? `msg_${Date.now()}`;
|
|
373
|
+
if (!from || !text)
|
|
374
|
+
return;
|
|
375
|
+
// Sender gating
|
|
376
|
+
if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(from)) {
|
|
377
|
+
console.error(`[wahooks-channel] Blocked message from ${from} (not in allow list)`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Track last sender for permission relay
|
|
381
|
+
lastSender = from;
|
|
382
|
+
// Check if this is a permission verdict
|
|
383
|
+
const permMatch = PERMISSION_RE.exec(text);
|
|
384
|
+
if (permMatch) {
|
|
406
385
|
await mcp.notification({
|
|
407
|
-
method: "notifications/claude/channel",
|
|
386
|
+
method: "notifications/claude/channel/permission",
|
|
408
387
|
params: {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
from,
|
|
412
|
-
message_id: messageId,
|
|
413
|
-
},
|
|
388
|
+
request_id: permMatch[2].toLowerCase(),
|
|
389
|
+
behavior: permMatch[1].toLowerCase().startsWith("y") ? "allow" : "deny",
|
|
414
390
|
},
|
|
415
391
|
});
|
|
416
|
-
console.error(`[wahooks-channel]
|
|
417
|
-
|
|
418
|
-
res.end("ok");
|
|
392
|
+
console.error(`[wahooks-channel] Permission verdict: ${permMatch[1]} ${permMatch[2]}`);
|
|
393
|
+
return;
|
|
419
394
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
395
|
+
// Forward to Claude Code
|
|
396
|
+
await mcp.notification({
|
|
397
|
+
method: "notifications/claude/channel",
|
|
398
|
+
params: {
|
|
399
|
+
content: text,
|
|
400
|
+
meta: {
|
|
401
|
+
from,
|
|
402
|
+
message_id: messageId,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
console.error(`[wahooks-channel] Message from ${from}: ${text.slice(0, 80)}`);
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
console.error("[wahooks-channel] Event parse error:", err);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
ws.on("close", (code) => {
|
|
413
|
+
console.error(`[wahooks-channel] Connection closed (${code}), reconnecting in 5s...`);
|
|
414
|
+
setTimeout(connectWebSocket, 5000);
|
|
426
415
|
});
|
|
427
|
-
|
|
428
|
-
console.error(`[wahooks-channel]
|
|
416
|
+
ws.on("error", (err) => {
|
|
417
|
+
console.error(`[wahooks-channel] WebSocket error: ${err.message}`);
|
|
418
|
+
// close event will fire after this and trigger reconnect
|
|
429
419
|
});
|
|
430
420
|
}
|
|
431
421
|
// ─── Main ───────────────────────────────────────────────────────────────
|
|
@@ -435,8 +425,8 @@ async function main() {
|
|
|
435
425
|
// Start MCP transport
|
|
436
426
|
const transport = new StdioServerTransport();
|
|
437
427
|
await mcp.connect(transport);
|
|
438
|
-
//
|
|
439
|
-
|
|
428
|
+
// Connect to real-time event stream
|
|
429
|
+
connectWebSocket();
|
|
440
430
|
console.error("[wahooks-channel] Ready — WhatsApp messages will appear in Claude Code");
|
|
441
431
|
}
|
|
442
432
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wahooks/channel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "WhatsApp channel for Claude Code — chat with Claude via WhatsApp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,8 +11,17 @@
|
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"manifest.json"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"wahooks",
|
|
20
|
+
"whatsapp",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"channel",
|
|
23
|
+
"mcp"
|
|
24
|
+
],
|
|
16
25
|
"license": "MIT",
|
|
17
26
|
"repository": {
|
|
18
27
|
"type": "git",
|
|
@@ -20,10 +29,12 @@
|
|
|
20
29
|
"directory": "sdks/channel"
|
|
21
30
|
},
|
|
22
31
|
"dependencies": {
|
|
23
|
-
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
33
|
+
"ws": "^8.20.0"
|
|
24
34
|
},
|
|
25
35
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"@types/
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"@types/ws": "^8.18.1",
|
|
38
|
+
"typescript": "^5.0.0"
|
|
28
39
|
}
|
|
29
40
|
}
|