@wahooks/channel 0.3.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/index.js +66 -76
  2. 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 http from "node:http";
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
- // ─── Webhook receiver ───────────────────────────────────────────────────
352
- function startWebhookServer() {
353
- const server = http.createServer(async (req, res) => {
354
- if (req.method !== "POST") {
355
- res.writeHead(404);
356
- res.end();
357
- return;
358
- }
359
- let body = "";
360
- req.on("data", (chunk) => { body += chunk; });
361
- req.on("end", async () => {
362
- try {
363
- const event = JSON.parse(body);
364
- // WAHooks webhook payload structure
365
- const eventType = event.event ?? "";
366
- const payload = event.payload ?? {};
367
- // Only handle incoming messages
368
- if (!eventType.startsWith("message")) {
369
- res.writeHead(200);
370
- res.end("ok");
371
- return;
372
- }
373
- const from = (payload.from ?? "").replace("@c.us", "").replace("@s.whatsapp.net", "");
374
- const text = payload.body ?? payload.text ?? "";
375
- const messageId = payload.id?._serialized ?? payload.id ?? `msg_${Date.now()}`;
376
- if (!from || !text) {
377
- res.writeHead(200);
378
- res.end("ok");
379
- return;
380
- }
381
- // Sender gating
382
- if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(from)) {
383
- console.error(`[wahooks-channel] Blocked message from ${from} (not in allow list)`);
384
- res.writeHead(200);
385
- res.end("ok");
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}/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
- content: text,
410
- meta: {
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] Message from ${from}: ${text.slice(0, 80)}`);
417
- res.writeHead(200);
418
- res.end("ok");
392
+ console.error(`[wahooks-channel] Permission verdict: ${permMatch[1]} ${permMatch[2]}`);
393
+ return;
419
394
  }
420
- catch (err) {
421
- console.error("[wahooks-channel] Webhook error:", err);
422
- res.writeHead(500);
423
- res.end("error");
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
- server.listen(WEBHOOK_PORT, "127.0.0.1", () => {
428
- console.error(`[wahooks-channel] Webhook server listening on http://127.0.0.1:${WEBHOOK_PORT}`);
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
- // Start webhook receiver
439
- startWebhookServer();
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.0",
3
+ "version": "0.4.0",
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": ["dist", "manifest.json"],
15
- "keywords": ["wahooks", "whatsapp", "claude-code", "channel", "mcp"],
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
- "typescript": "^5.0.0",
27
- "@types/node": "^20.0.0"
36
+ "@types/node": "^20.0.0",
37
+ "@types/ws": "^8.18.1",
38
+ "typescript": "^5.0.0"
28
39
  }
29
40
  }