| name | whatsapp-self |
| description | Connect Claude Code directly to YOUR personal WhatsApp via a local WAHA Docker container — no third-party service. Send/read messages, send media (image/voice/file), and list group members on demand. Use whenever the user wants to act on their own WhatsApp: "שלח וואטסאפ ל...", "שלח הודעה בוואטסאפ", "תקרא לי הודעות אחרונות מ...", "מה כתבו בקבוצה", "מי בקבוצה X", "תחבר אותי לוואטסאפ", "send a whatsapp to...", "read my whatsapp from...", "who's in the group". Self-hosted: brings up a Docker container, detects the OS, scans a QR via a LOCAL browser dashboard, and persists credentials so it only links once. |
whatsapp-self
Drive the user's own WhatsApp from Claude Code through a self-hosted WAHA
container (WhatsApp HTTP API). No Green API, no cloud relay — the container
runs locally, talks to WhatsApp Web, and stores auth in a local volume so the
QR is scanned only once.
This is separate from the existing whatsapp skill (which uses Green API).
Do not mix them.
Works on any machine (one Python file — no bash, no jq)
This skill is distributed to non-technical users on macOS, Windows, or Linux
(Intel or ARM). Everything is a single cross-platform Python file, wa.py,
using only the standard library (no pip install). It runs identically whether
Claude Code's shell is PowerShell, cmd, bash, or zsh — critical for Windows
users who don't have Git Bash.
Run it with python (try python3 first on macOS/Linux, python on Windows),
from the skill's own directory — locate it relative to this SKILL.md (do not
assume ~/.claude/skills/...; under a plugin it lives elsewhere):
cd "<dir-of-this-SKILL.md>"
python3 wa.py <command> [args...]
Requirements: Docker and Python 3 (Python ships with macOS/Linux and is
a one-click install on Windows). All commands below run python3 wa.py ....
Golden rule
Before ANY WhatsApp action, run python3 wa.py ensure-up first. It is
idempotent and fast when everything is already running. Its last stdout line is
STATUS=<state>. It also prints KEY=VALUE diagnostic lines when it needs help.
python3 wa.py ensure-up
Then branch on the result:
STATUS=WORKING → connected. Proceed straight to the requested action.
STATUS=SCAN_QR_CODE (or STARTING/UNKNOWN) → not linked yet. First make
sure the user is ready to scan RIGHT NOW (phone in hand, WhatsApp open on the
"Link a Device" screen) — because the QR is only valid ~60 seconds. Tell them
this explicitly, then run python3 wa.py connect. It opens a QR-code image
(no dashboard, no login). Then branch on its result:
STATUS=WORKING → linked. Proceed.
STATUS=QR_EXPIRED → the 60-second window passed without a scan. Do NOT
automatically run connect again. Explain to the user: the QR expired, and
if they tried to scan it WhatsApp may say "Can't link new devices at this
time" — that's a normal temporary block; wait ~1 minute. Then ask whether
they're ready for a fresh QR, and only run connect again after they confirm.
STATUS=FAILED → tell the user; suggest docker restart waha-self then retry.
Never loop connect on your own. Each run shows one QR for ~60s and never
auto-refreshes. Showing back-to-back expired QRs is what triggers WhatsApp's
"can't link right now" block. Always wait for the user to say they're ready
before opening the next QR.
When Docker is missing or stopped (handle it FOR the user)
The user is non-technical — do not just print a link. Act:
- Exit code 10 +
NEED_DOCKER_INSTALL=1 → Docker isn't installed. The output
includes OS=, PKG_MGR=, and either INSTALL_CMD=... or INSTALL_URL=....
- If there's an
INSTALL_CMD, tell the user what you're about to install, then
run that command for them (it may need their password/admin rights — explain
that). After install, on macOS/Windows the user must launch Docker Desktop
once; then re-run ensure-up.
- If only an
INSTALL_URL, give them the link and a one-line instruction, and
wait for them to confirm it's installed before retrying.
- Exit code 11 +
NEED_DOCKER_START=1 → Docker is installed but not running.
Relay the START_HINT=... line in plain language and wait, then re-run
ensure-up. (On macOS/Windows it already tried to launch Docker Desktop — it
just needs ~30–60s to be ready.)
- Exit code 12 → the WAHA server didn't answer. Suggest
docker logs waha-self and a retry.
If python3 isn't found, try python. If neither exists (rare; mainly some
Windows setups), offer to install Python the same way as Docker
(winget install -e --id Python.Python.3 on Windows; it's preinstalled on
macOS/Linux).
Sending (no confirmation step — by user's choice)
The user chose fast mode: send immediately, no preview/approval. Just send
what they asked. (They can interrupt mid-turn if needed.) If a request is
genuinely ambiguous about recipient (e.g. a common first name with no number),
ask which contact — but don't add a generic "are you sure?" gate.
Phone numbers & country codes
The recipient argument is a phone number or a full JID (...@c.us for a person,
...@g.us for a group). Number handling:
- A full international number (e.g.
972501234567, 14155550101,
447911123456) always works — just strip +/spaces, wa.py does the rest.
- A local number starting with 0 (e.g.
0501234567) is expanded using
WA_COUNTRY_CODE from .env (default 972). If the member is not in that
country, this would produce the wrong number.
You (Claude) own the country inference. When the user gives a local/short
number:
- Infer the country from context — the conversation language, numbers they
mentioned, the group, or their
.env WA_COUNTRY_CODE.
- If confident, convert to a full international number yourself and pass that.
- If unsure, ask ("which country's number is this?") rather than guessing.
- Optionally offer to set
WA_COUNTRY_CODE in .env so future local numbers
just work.
Never silently send to a wrong-country number.
Commands
Run all of these as python3 wa.py <command> from the skill directory (see
"Works on any machine"). Use python instead of python3 on Windows.
| Goal | Command |
|---|
| Bring up / check state | python3 wa.py ensure-up |
| Link WhatsApp (QR) | python3 wa.py connect |
| Status + who am I | python3 wa.py status |
| Send text | python3 wa.py send-text <phone|jid> "message" |
| Send image | python3 wa.py send-image <phone|jid> <path-or-url> ["caption"] |
| Send file/doc | python3 wa.py send-file <phone|jid> <path-or-url> ["caption"] |
| Send voice note | python3 wa.py send-voice <phone|jid> <audio-path-or-url> |
| Read last N messages | python3 wa.py read <phone|jid> [N] |
| Recent chats overview | python3 wa.py chats [N] |
| List all groups | python3 wa.py groups |
| Group participants | python3 wa.py members "<group name or @g.us id>" |
| Resolve a name→id | python3 wa.py resolve --group "Family" |
Typical flows
"שלח לדנה (0501234567) שאני מאחר"
python3 wa.py ensure-up
python3 wa.py send-text 0501234567 "אני מאחר ב-10 דקות"
"תקרא לי 10 ההודעות האחרונות מהקבוצה 'משפחה'"
python3 wa.py ensure-up
python3 wa.py members "משפחה"
python3 wa.py read "<group-id>@g.us" 10
"מי בקבוצת הדירקטוריון?"
python3 wa.py ensure-up
python3 wa.py members "דירקטוריון"
How it works (for debugging)
- One file:
wa.py — Python stdlib only, no pip installs, runs on any OS shell.
- Container:
waha-self, bound to 127.0.0.1:3000 only. Auth persisted in
sessions/ (mounted to /app/.sessions).
- Image / arch:
wa.py picks the tag by CPU — devlikeapro/waha:arm on
Apple Silicon / ARM (the default :latest has no arm64 manifest and
fails), devlikeapro/waha:latest on x86_64. Override via WAHA_IMAGE in .env.
- Secrets: auto-generated into
.env on first run (WAHA_API_KEY). Never
committed (.gitignore).
- QR: opened as a plain PNG image fetched via the local API and saved as
qr.png — no dashboard, no login screen. The dashboard is disabled
(WAHA_DASHBOARD_ENABLED=false). The QR never leaves the machine.
- Logs:
docker logs waha-self. Restart: docker restart waha-self.
Caveats
- WAHA rides on WhatsApp Web (unofficial). Keep usage human-scale; aggressive
bulk automation risks a WhatsApp ban.
- Some read endpoints (
messages, chats/overview, groups) require the
WEBJS engine and a fully WORKING session; if a read returns empty right
after linking, wait a few seconds and retry.