@lofa199419/waha-v2 2.1.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.
package/index.ts ADDED
@@ -0,0 +1,109 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
+ import { wahaV2Plugin } from "./src/channel.js";
4
+ import {
5
+ handleWahaV2QrRoute,
6
+ handleWahaV2RequestCodeRoute,
7
+ handleWahaV2StartRoute,
8
+ handleWahaV2StatusRoute,
9
+ handleWahaV2WaitRoute,
10
+ WAHA_V2_ROUTE_QR,
11
+ WAHA_V2_ROUTE_REQUEST_CODE,
12
+ WAHA_V2_ROUTE_START,
13
+ WAHA_V2_ROUTE_STATUS,
14
+ WAHA_V2_ROUTE_WAIT,
15
+ } from "./src/routes.js";
16
+ import { setWahaV2Runtime } from "./src/runtime.js";
17
+ import { WAHA_V2_WEBHOOK_BASE } from "./src/types.js";
18
+ import { handleWahaV2WebhookRequest } from "./src/webhook.js";
19
+
20
+ const plugin = {
21
+ id: "waha-v2",
22
+ name: "WAHA v2 (WhatsApp HTTP API)",
23
+ configSchema: emptyPluginConfigSchema(),
24
+ description:
25
+ "Independent WAHA WhatsApp HTTP API channel plugin. " +
26
+ "Uses waha-node for outbound; OpenClaw plugin-sdk for inbound, routing, and policies.",
27
+
28
+ register(api: OpenClawPluginApi) {
29
+ // Store the runtime and logger for use in the webhook handler, gateway, and routes.
30
+ setWahaV2Runtime(api.runtime, api.logger);
31
+
32
+ // Register the channel (config, gateway, outbound, status, etc.).
33
+ api.registerChannel({ plugin: wahaV2Plugin });
34
+
35
+ // Inbound webhook — matches both:
36
+ // /webhooks/waha-v2 (legacy, routes by session name in payload)
37
+ // /webhooks/waha-v2/{accountId} (preferred, unambiguous — accountId from path)
38
+ // Newer OpenClaw plugin SDK requires explicit route auth and no longer supports
39
+ // registerHttpHandler or dynamic path params in plugin routes.
40
+ api.registerHttpRoute({
41
+ path: WAHA_V2_WEBHOOK_BASE,
42
+ auth: "plugin",
43
+ match: "prefix",
44
+ handler: async (req, res) => {
45
+ const url = req.url ?? "";
46
+ if (!url.startsWith(WAHA_V2_WEBHOOK_BASE)) {
47
+ return false;
48
+ }
49
+ const suffix = url.slice(WAHA_V2_WEBHOOK_BASE.length).split("?")[0] ?? "";
50
+ const accountId = suffix.startsWith("/") ? suffix.slice(1) || undefined : undefined;
51
+ await handleWahaV2WebhookRequest(req, res, api.runtime.config.loadConfig(), accountId);
52
+ return true;
53
+ },
54
+ });
55
+
56
+ // Session management API routes — used by UI, CLI, and setup flows.
57
+ api.registerHttpRoute({
58
+ path: WAHA_V2_ROUTE_STATUS,
59
+ auth: "gateway",
60
+ match: "exact",
61
+ handler: async (req, res) => {
62
+ await handleWahaV2StatusRoute(req, res, api.runtime.config.loadConfig());
63
+ },
64
+ });
65
+
66
+ api.registerHttpRoute({
67
+ path: WAHA_V2_ROUTE_START,
68
+ auth: "gateway",
69
+ match: "exact",
70
+ handler: async (req, res) => {
71
+ await handleWahaV2StartRoute(req, res, api.runtime.config.loadConfig());
72
+ },
73
+ });
74
+
75
+ api.registerHttpRoute({
76
+ path: WAHA_V2_ROUTE_QR,
77
+ auth: "gateway",
78
+ match: "exact",
79
+ handler: async (req, res) => {
80
+ await handleWahaV2QrRoute(req, res, api.runtime.config.loadConfig());
81
+ },
82
+ });
83
+
84
+ api.registerHttpRoute({
85
+ path: WAHA_V2_ROUTE_REQUEST_CODE,
86
+ auth: "gateway",
87
+ match: "exact",
88
+ handler: async (req, res) => {
89
+ await handleWahaV2RequestCodeRoute(req, res, api.runtime.config.loadConfig());
90
+ },
91
+ });
92
+
93
+ api.registerHttpRoute({
94
+ path: WAHA_V2_ROUTE_WAIT,
95
+ auth: "gateway",
96
+ match: "exact",
97
+ handler: async (req, res) => {
98
+ await handleWahaV2WaitRoute(req, res, api.runtime.config.loadConfig());
99
+ },
100
+ });
101
+
102
+ api.logger.info(
103
+ `waha-v2: registered channel + webhook ${WAHA_V2_WEBHOOK_BASE}/{accountId} + ` +
104
+ `session API routes (status, start, qr, request-code, wait)`,
105
+ );
106
+ },
107
+ };
108
+
109
+ export default plugin;
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "waha-v2",
3
+ "channels": ["waha-v2"],
4
+ "skills": ["skills/waha-v2", "skills/waha-v2-onboarding"],
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "properties": {}
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@lofa199419/waha-v2",
3
+ "version": "2.1.0",
4
+ "private": false,
5
+ "description": "OpenClaw WAHA v2 channel plugin — independent WhatsApp HTTP API integration",
6
+ "type": "module",
7
+ "files": [
8
+ "index.ts",
9
+ "src",
10
+ "openclaw.plugin.json",
11
+ "skills",
12
+ "bin/waha-cli",
13
+ "bin/wa",
14
+ "bin/wa-adv",
15
+ "bin/waha-advanced-entrypoint",
16
+ "bin/waha_cli.py",
17
+ "scripts/install-openclaw-extension.mjs",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "pack:check": "npm pack --dry-run",
22
+ "install:openclaw": "node scripts/install-openclaw-extension.mjs"
23
+ },
24
+ "dependencies": {
25
+ "axios": "1.6.0",
26
+ "waha-node": "1.0.0",
27
+ "zod": "^4.3.6"
28
+ },
29
+ "peerDependencies": {
30
+ "openclaw": ">=2026.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "openclaw": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "openclaw": {
38
+ "extensions": [
39
+ "./index.ts"
40
+ ],
41
+ "channel": {
42
+ "id": "waha-v2",
43
+ "label": "WAHA",
44
+ "selectionLabel": "WAHA v2 (plugin)",
45
+ "detailLabel": "WAHA v2",
46
+ "docsPath": "/channels/whatsapp",
47
+ "docsLabel": "whatsapp",
48
+ "blurb": "Independent WAHA WhatsApp HTTP API plugin — no Baileys dependency.",
49
+ "order": 13,
50
+ "systemImage": "qrcode"
51
+ },
52
+ "install": {
53
+ "npmSpec": "@lofa199419/waha-v2",
54
+ "localPath": "extensions/waha-v2",
55
+ "script": "node scripts/install-openclaw-extension.mjs",
56
+ "defaultChoice": "npm"
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const packageRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
6
+
7
+ function parseArgs(argv) {
8
+ const out = {
9
+ stateDir: process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME || "", ".openclaw"),
10
+ force: false,
11
+ };
12
+
13
+ for (let i = 0; i < argv.length; i += 1) {
14
+ const arg = argv[i];
15
+ if (arg === "--state-dir") {
16
+ out.stateDir = argv[i + 1];
17
+ i += 1;
18
+ } else if (arg === "--force") {
19
+ out.force = true;
20
+ } else if (arg === "--help" || arg === "-h") {
21
+ out.help = true;
22
+ } else {
23
+ throw new Error(`Unknown argument: ${arg}`);
24
+ }
25
+ }
26
+
27
+ return out;
28
+ }
29
+
30
+ async function exists(target) {
31
+ try {
32
+ await fs.access(target);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ async function ensureDir(target) {
40
+ await fs.mkdir(target, { recursive: true });
41
+ }
42
+
43
+ async function copyFile(relPath, destinationRoot) {
44
+ const source = path.join(packageRoot, relPath);
45
+ const destination = path.join(destinationRoot, relPath);
46
+ await ensureDir(path.dirname(destination));
47
+ await fs.copyFile(source, destination);
48
+ const stat = await fs.stat(source);
49
+ await fs.chmod(destination, stat.mode);
50
+ }
51
+
52
+ async function copyDirectory(relPath, destinationRoot) {
53
+ const source = path.join(packageRoot, relPath);
54
+ const destination = path.join(destinationRoot, relPath);
55
+ await ensureDir(path.dirname(destination));
56
+ await fs.cp(source, destination, { recursive: true, force: true });
57
+ }
58
+
59
+ async function main() {
60
+ const args = parseArgs(process.argv.slice(2));
61
+ if (args.help) {
62
+ console.log("Usage: node scripts/install-openclaw-extension.mjs [--state-dir PATH] [--force]");
63
+ process.exit(0);
64
+ }
65
+
66
+ if (!args.stateDir) {
67
+ throw new Error("Missing OpenClaw state dir. Pass --state-dir or set OPENCLAW_STATE_DIR.");
68
+ }
69
+
70
+ const extensionRoot = path.join(args.stateDir, "extensions", "waha-v2");
71
+ if (await exists(extensionRoot)) {
72
+ if (!args.force) {
73
+ throw new Error(`Target already exists: ${extensionRoot}. Re-run with --force to replace it.`);
74
+ }
75
+ await fs.rm(extensionRoot, { recursive: true, force: true });
76
+ }
77
+
78
+ await ensureDir(extensionRoot);
79
+
80
+ const directories = ["src", "skills", "scripts"];
81
+ const files = [
82
+ "index.ts",
83
+ "openclaw.plugin.json",
84
+ "package.json",
85
+ "README.md",
86
+ "bin/waha-cli",
87
+ "bin/wa",
88
+ "bin/wa-adv",
89
+ "bin/waha-advanced-entrypoint",
90
+ "bin/waha_cli.py",
91
+ ];
92
+
93
+ for (const relPath of directories) {
94
+ await copyDirectory(relPath, extensionRoot);
95
+ }
96
+ for (const relPath of files) {
97
+ await copyFile(relPath, extensionRoot);
98
+ }
99
+
100
+ console.log(`Installed waha-v2 into ${extensionRoot}`);
101
+ }
102
+
103
+ main().catch((error) => {
104
+ console.error(`[waha-v2 install] ${error.message}`);
105
+ process.exit(1);
106
+ });
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: waha-v2
3
+ description: "Operate WhatsApp for OpenClaw using one local MCP CLI (advanced) for all WAHA API operations; use gateway only for inbound webhooks and outbound `message` sends."
4
+ metadata: { "openclaw": { "emoji": "💬", "requires": { "config": ["channels.waha-v2"] } } }
5
+ allowed-tools: ["exec", "message"]
6
+ ---
7
+
8
+ # WAHA v2 — Advanced CLI Operation Mode
9
+
10
+ Use this skill to run WhatsApp actions through the local advanced binary, not through gateway routing actions.
11
+
12
+ ## Routing Rule (strict)
13
+
14
+ - **Gateway remains only for:**
15
+ 1. **Receiving inbound WhatsApp events** via webhook.
16
+ 2. **Sending outbound chat replies** via OpenClaw `message` tool (`channel: "waha-v2"`).
17
+ - **Do not use gateway action routing for WAHA operations** (sessions, auth, chats, groups, contacts, channels, status, etc.).
18
+ - For WAHA operational tasks, use local CLI via `exec`.
19
+
20
+ ## Single CLI (use this only)
21
+
22
+ - Resolve the OpenClaw state dir first:
23
+ - `STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"`
24
+ - Wrapper (preferred):
25
+ - `"$STATE_DIR/extensions/waha-v2/bin/waha-cli"`
26
+ - Convenience wrapper that auto-adds `--session` when configured:
27
+ - `"$STATE_DIR/extensions/waha-v2/bin/wa"`
28
+
29
+ ## Environment handling
30
+
31
+ - Primary config source:
32
+ - `"$STATE_DIR/openclaw.json"` -> `channels.waha-v2`
33
+ - Fallback config source:
34
+ - `"$STATE_DIR/extensions/waha-v2/bin/.env"`
35
+ - The CLI now resolves config in this order:
36
+ 1. `channels.waha-v2` in `openclaw.json`
37
+ 2. process environment
38
+ 3. `bin/.env`
39
+ - Agents should call the installed wrapper directly and should not assume `/data/.openclaw/...`.
40
+
41
+ ## Fast tutorial
42
+
43
+ ### 1) Discover commands
44
+
45
+ ```bash
46
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
47
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" --help
48
+ ```
49
+
50
+ ### 2) Session/auth flow
51
+
52
+ ```bash
53
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
54
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-list-sessions
55
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-create-session --name finalhope
56
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-get-qr-code --session finalhope
57
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-request-pairing-code --session finalhope --phone-number 31612345678
58
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-check-auth-status --session finalhope
59
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-logout-session --session finalhope
60
+ ```
61
+
62
+ ### 3) Chats/messages
63
+
64
+ ```bash
65
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
66
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-get-chats --limit 20
67
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-get-messages --chat-id 15550001234@c.us --limit 30
68
+ "$STATE_DIR/extensions/waha-v2/bin/waha-cli" waha-send-text --chat-id 15550001234@c.us --text "Hello"
69
+ ```
70
+
71
+ ### 4) Outbound user-visible send (official OpenClaw path)
72
+
73
+ Use `message` tool for final user-facing sends:
74
+
75
+ ```json
76
+ { "action": "send", "channel": "waha-v2", "to": "15550001234@c.us", "message": "Hello!" }
77
+ ```
78
+
79
+ ## IDs and formatting
80
+
81
+ - Direct chat: `<digits>@c.us`
82
+ - Group: `<digits>@g.us`
83
+ - Strip non-digits before building `@c.us` IDs.
84
+
85
+ ## Operational notes
86
+
87
+ - Keep replies complete (no streaming partials).
88
+ - Confirm destructive actions before execution when user intent is ambiguous.
89
+
90
+ ---
91
+
92
+ ## Exemplary Scenario: New User WhatsApp Onboarding
93
+
94
+ This is a real end-to-end flow completed successfully (2026-03-22).
95
+
96
+ ### Flow
97
+
98
+ **Step 1 — Greet and collect number**
99
+ > "Welcome! To get you connected, I need your WhatsApp phone number (include country code, no spaces — e.g. `31612345678`)."
100
+
101
+ **Step 2 — Create and start session**
102
+ ```bash
103
+ waha-cli waha-create-session --name user-<phonenumber>
104
+ waha-cli waha-start-session --session user-<phonenumber>
105
+ ```
106
+ Wait ~5 seconds for session to initialize before requesting pairing code.
107
+
108
+ **Step 3 — Request pairing code**
109
+ ```bash
110
+ waha-cli waha-request-pairing-code --session user-<phonenumber> --phone-number <phonenumber>
111
+ # → Code: VB4R-3LKL (example)
112
+ ```
113
+
114
+ **Step 4 — Share code with user**
115
+ > "Here's your code: `VB4R-3LKL`
116
+ > On your phone: WhatsApp → ⋮ → Linked Devices → Link a Device → Link with phone number instead → enter the code."
117
+
118
+ **Step 5 — Wait for user confirmation, then verify**
119
+ ```bash
120
+ waha-cli waha-check-auth-status --session user-<phonenumber>
121
+ # → Status: WORKING | Account ID: <phonenumber>@c.us | Name: <display name>
122
+ ```
123
+
124
+ **Step 6 — Confirm and offer next actions**
125
+ > "You're in! Want me to pull up recent conversations, browse contacts, or read a specific chat?"
126
+
127
+ ---
128
+
129
+ ### Fallback: if pairing code fails
130
+
131
+ If status is not `WORKING` after user enters the code:
132
+
133
+ ```bash
134
+ waha-cli waha-start-session --session user-<phonenumber> # restart for fresh QR state
135
+ # wait ~5s
136
+ waha-cli waha-request-pairing-code --session user-<phonenumber> --phone-number <phonenumber>
137
+ # share new code — second attempt typically succeeds
138
+ ```
139
+
140
+ ---
141
+
142
+ ### Session naming convention
143
+ Use `user-<phonenumber>` (e.g. `user-905380546393`) to avoid collisions and keep sessions identifiable.
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: waha-v2-onboarding
3
+ description: "Onboard a new WhatsApp user onto WAHA by creating a session, requesting a pairing code, verifying login, and confirming readiness."
4
+ metadata: { "openclaw": { "emoji": "🔐", "requires": { "config": ["channels.waha-v2"] } } }
5
+ allowed-tools: ["exec", "message"]
6
+ ---
7
+
8
+ # WAHA v2 — User Onboarding And Login
9
+
10
+ Use this skill when the goal is to connect a new WhatsApp account to WAHA and confirm the session is usable.
11
+
12
+ ## Scope
13
+
14
+ - Create a session for the user
15
+ - Start or restart that session
16
+ - Request a pairing code
17
+ - Guide the user through entering the code
18
+ - Verify the session reaches `WORKING`
19
+
20
+ Do not use this skill for general chat browsing or operational WAHA admin work unless it directly supports onboarding.
21
+
22
+ ## Binary Resolution
23
+
24
+ Always resolve the installed extension from the OpenClaw state dir:
25
+
26
+ ```bash
27
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
28
+ WAHA_BIN="$STATE_DIR/extensions/waha-v2/bin/waha-cli"
29
+ ```
30
+
31
+ The CLI resolves config from:
32
+ 1. `"$STATE_DIR/openclaw.json"` -> `channels.waha-v2`
33
+ 2. process environment
34
+ 3. `"$STATE_DIR/extensions/waha-v2/bin/.env"`
35
+
36
+ ## Session Naming
37
+
38
+ Use:
39
+
40
+ ```text
41
+ user-<phonenumber>
42
+ ```
43
+
44
+ Example:
45
+
46
+ ```text
47
+ user-905380546393
48
+ ```
49
+
50
+ Use digits only, including country code, with no spaces or `+`.
51
+
52
+ ## Standard Flow
53
+
54
+ ### 1) Collect phone number
55
+
56
+ Ask the user for their WhatsApp number in international format without spaces or `+`.
57
+
58
+ Example:
59
+
60
+ > "To connect your WhatsApp, send your phone number with country code, digits only. Example: `31612345678`."
61
+
62
+ ### 2) Create and start session
63
+
64
+ ```bash
65
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
66
+ WAHA_BIN="$STATE_DIR/extensions/waha-v2/bin/waha-cli"
67
+ SESSION="user-<phonenumber>"
68
+
69
+ "$WAHA_BIN" waha-create-session --name "$SESSION"
70
+ "$WAHA_BIN" waha-start-session --session "$SESSION"
71
+ ```
72
+
73
+ Wait about 5 seconds after start before requesting pairing code.
74
+
75
+ ### 3) Request pairing code
76
+
77
+ ```bash
78
+ "$WAHA_BIN" waha-request-pairing-code --session "$SESSION" --phone-number <phonenumber>
79
+ ```
80
+
81
+ Expected output includes:
82
+
83
+ ```text
84
+ Code: XXXX-XXXX
85
+ ```
86
+
87
+ ### 4) Instruct the user
88
+
89
+ Send the code and tell the user to enter it in WhatsApp:
90
+
91
+ > "Open WhatsApp on your phone: Linked Devices -> Link a Device -> Link with phone number instead -> enter this code: `<code>`"
92
+
93
+ ### 5) Verify session status
94
+
95
+ ```bash
96
+ "$WAHA_BIN" waha-check-auth-status --session "$SESSION"
97
+ ```
98
+
99
+ Success means:
100
+
101
+ ```text
102
+ Status: WORKING
103
+ ```
104
+
105
+ If present, capture:
106
+ - `Account ID`
107
+ - `Name`
108
+
109
+ ### 6) Confirm completion
110
+
111
+ Example:
112
+
113
+ > "Your WhatsApp session is connected. I can now help with chats, contacts, or other WAHA operations."
114
+
115
+ ## Fallback Flow
116
+
117
+ If pairing does not complete:
118
+
119
+ ```bash
120
+ "$WAHA_BIN" waha-start-session --session "$SESSION"
121
+ # wait ~5s
122
+ "$WAHA_BIN" waha-request-pairing-code --session "$SESSION" --phone-number <phonenumber>
123
+ ```
124
+
125
+ If status remains `SCAN_QR_CODE`, explain that the session is waiting for the pairing step to finish.
126
+
127
+ ## Operational Rules
128
+
129
+ - Prefer pairing code flow over QR when the user is already in chat with the agent.
130
+ - Do not rename session ids once created.
131
+ - Do not delete or log out an existing user session unless the user explicitly asks.
132
+ - Confirm destructive actions before executing them.
133
+ - Keep user-facing instructions short and exact.
134
+
135
+ ## Quick Commands
136
+
137
+ ```bash
138
+ STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
139
+ WAHA_BIN="$STATE_DIR/extensions/waha-v2/bin/waha-cli"
140
+ SESSION="user-905380546393"
141
+
142
+ "$WAHA_BIN" waha-create-session --name "$SESSION"
143
+ "$WAHA_BIN" waha-start-session --session "$SESSION"
144
+ "$WAHA_BIN" waha-request-pairing-code --session "$SESSION" --phone-number 905380546393
145
+ "$WAHA_BIN" waha-check-auth-status --session "$SESSION"
146
+ ```