Skip to main content
Glama

wa

Personal WhatsApp automation CLI + daemon, written in Go.

A hexagonal Go daemon that owns a WhatsApp Multi-Device session and a thin JSON-RPC client that talks to it — safe enough to let a language model send messages on your behalf, crash-safe enough to survive a power loss mid-migration, and paranoid enough to refuse every destructive flag you might expect.

CI Release Go License Conventional Commits Nix flake OpenSSF Scorecard SLSA L2 Verified by Sigstore

Quickstart · Install · Manual · Architecture · Security · Contributing · Português (BR)


Capability

Pattern. Persistent WhatsApp daemon over a Unix socket — one wad process holds the Multi-Device session, ratchet store, and websocket; a thin wa JSON-RPC client invokes it.

Trade-off. ~30 MB RSS per profile and a 5–8 s cold connect, in exchange for sub-second warm-call latency on every subsequent wa send. Per-call session reattachment is avoided entirely.

Use when. A shell pipeline, cron job, or Claude Code plugin needs to dispatch WhatsApp messages with predictable latency and a non-overridable safety pipeline (default-deny allowlist + rate limiter + warmup ramp + append-only audit log) sitting below every RPC path.

brew install yolo-labz/tap/wa
wad &                                                # daemon, single instance per profile
wa pair                                              # QR-code pairing on first run
wa allow add 5511999999999@s.whatsapp.net --actions send
wa send --to 5511999999999@s.whatsapp.net --body "hello"

Related MCP server: MCP WPPConnect Server

Demo

A non-interactive 15-second asciinema cast covering wa --help, wa daemon status, two wa send calls (warm-call latency under 500 ms), and wa allow list is checked into the repo at docs/assets/wa-demo.cast. Replay locally:

asciinema play docs/assets/wa-demo.cast

A hosted player embed will land in a follow-up PR after the cast is uploaded to asciinema.org.

How wa compares

Closest peers in the reverse-engineered-WhatsApp ecosystem:

Capability

wa (this repo)

whatsmeow direct

whatsapp-web-cli

Persistent daemon (sub-second warm-call)

yes

no (per-call session attach)

no (browser-driven)

JSON-RPC over Unix socket

yes

n/a (library, not a daemon)

no (Chrome bridge)

Default-deny allowlist (per-action)

yes

manual implementation

no

Non-overridable rate limiter (2/30/1000)

yes

manual implementation

no

Warmup ramp for fresh sessions

yes

manual implementation

no

Append-only JSON-Lines audit log

yes

manual implementation

no

SLSA L2 + Sigstore signed releases

yes

n/a

no

Dual SBOM (CycloneDX 1.6 + SPDX 2.3)

yes

n/a

no

CGO_ENABLED=0 static binary

yes

depends on consumer

no (browser-driven)

Inbound prompt-injection firewall

yes

n/a

no

For multi-tenant REST gateways see EvolutionAPI or WAHA — different shape of problem, listed in What this is NOT.

What this is

Two binaries, one repo:

  • wad — long-running daemon that owns the WhatsApp session, the SQLite ratchet store, and the websocket to web.whatsapp.com. Runs under systemd (Linux), launchd (macOS), or a NixOS module. Single-instance per profile, never as root.

  • wa — thin JSON-RPC client that speaks to wad over a unix socket. This is what shell scripts, cron jobs, and Claude Code plugins actually invoke.

It is built on go.mau.fi/whatsmeow — the library that powers mautrix-whatsapp at production scale — because it is the only reverse-engineered WhatsApp library actively maintained in 2026. It also ships a safety-first MCP server (wa mcp serve) so an AI agent drives WhatsApp through the same non-bypassable safety pipeline — draft-gated by default. See MCP server.

What this is NOT

  • Not a bulk-messaging tool. The rate limiter is non-overridable and there is no --force flag anywhere.

  • Not a multi-tenant SaaS. Each wa install is scoped to one person, with optional multi-profile isolation for work/personal splits.

  • Not a Matrix bridge. Use mautrix-whatsapp if that's what you want.

  • Not a REST gateway. Use EvolutionAPI or WAHA if that's what you want.

  • Not the official WhatsApp Cloud API. This project uses the reverse-engineered Multi-Device protocol via whatsmeow.

Quickstart

# Install (Homebrew — macOS + Linuxbrew)
brew install yolo-labz/tap/wa

# Or via Nix (recommended for NixOS/nix-darwin users)
nix profile install github:yolo-labz/wa

# Or the checksum-verified installer (80 lines — inspect first if you like)
curl -fsSL https://raw.githubusercontent.com/yolo-labz/wa/main/install.sh | bash

# Or Docker — single distroless container (~12 MB); /data holds the session
docker compose up -d   # see docker-compose.yaml; pair via `docker compose exec`

# Start the daemon (default profile)
wad &

# Pair your phone — QR code in terminal
wa pair

# Allowlist yourself (default-deny policy)
wa allow add 5511999999999@s.whatsapp.net --actions send

# Send a message
wa send --to 5511999999999@s.whatsapp.net --body "hello from wa"

# Install as a persistent system service
wad install-service --profile default

# Hand it to an AI agent — MCP over stdio, draft-gated sends by default:
# the agent PROPOSES messages into a human-review queue; nothing leaves
# until you run `wa draft approve`. Add to Claude Desktop/Code, Cursor:
#   {"mcpServers": {"wa": {"command": "wa", "args": ["mcp", "serve"]}}}
wa mcp serve --help

The recipient flag is spelled --to, --jid, or --group depending on the command; --chat <jid> is accepted as a universal alias on all of them (the original flags still work).

For the full tour including multi-profile setup, shell completion, migration, and the audit log, see docs/manual.md.

MCP server

wa mcp serve exposes the daemon to AI agents over the Model Context Protocol (stdio transport). Every tool call runs through the same non-bypassable safety pipeline as the CLI — default-deny allowlist, enforced rate limits (2/min · 30/hour · 1000/day with warmup), and an append-only audit log. Sends default to draft mode: the model proposes a message into a human-review queue and nothing leaves until you run wa draft approve. That draft-gate is the point — it is what makes handing WhatsApp to an autonomous agent safe.

Live on the official MCP Registry as io.github.yolo-labz/wa; one-click install via the signed .mcpb bundle on each release, or wire it manually into Claude Desktop/Code or Cursor:

{ "mcpServers": { "wa": { "command": "wa", "args": ["mcp", "serve"] } } }

Tools (12):

Tool

Purpose

wa_send_message

Send (or draft) a message through the safety pipeline

wa_send_media

Send (or draft) a media file

wa_schedule_message

Schedule a send for later (drafts respected)

wa_draft_review

List pending drafts awaiting human approval

wa_search_messages

Full-text search across synced messages

wa_get_thread

Fetch recent messages of one chat

wa_list_chats

List chats with recent activity

wa_resolve_contact

Resolve a name/phone to a JID

wa_group_info

Group metadata and participants

wa_wait_for_reply

Block until a matching inbound event arrives

wa_transcribe_voice

Transcribe a voice note

wa_status

Daemon connection and session status

It also serves resources (read-only context surfaces) and prompts (pre-built workflows) per the MCP spec. Scope the surface with --toolsets; the send mode is draft (default), direct, or deny, and a read-only set hides every mutating tool.

See docs/mcp-registry.md for the registry publish flow and bundle internals.

Install

Homebrew (macOS + Linux)

brew install yolo-labz/tap/wa

Nix flake

# One-shot
nix run github:yolo-labz/wa -- profile list

# Install to profile
nix profile install github:yolo-labz/wa

# Dev shell (go + gopls + golangci-lint + goreleaser + sqlite + jq)
nix develop github:yolo-labz/wa

NixOS module — import the system module and enable:

{
  inputs.wa.url = "github:yolo-labz/wa";
  outputs = { self, nixpkgs, wa, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      modules = [
        wa.nixosModules.default
        { services.wa.enable = true; services.wa.profile = "default"; }
      ];
    };
  };
}

home-manager module — import for a per-user installation:

{
  imports = [ wa.homeManagerModules.default ];
  services.wa = {
    enable = true;
    profile = "default";
    autoStart = true;
  };
}

GoReleaser tarball + supply-chain verification

VERSION=v2.0.5                                      # pin to a known tag
ARCH=linux_amd64                                    # or darwin_arm64 / linux_arm64
BASE="https://github.com/yolo-labz/wa/releases/download/$VERSION"

# 1. Download the tarball + checksums
curl -LO "$BASE/wa_${VERSION#v}_${ARCH}.tar.gz"
curl -LO "$BASE/checksums.txt"

# 2. Verify the SHA256 (defends against accidental corruption)
sha256sum -c checksums.txt --ignore-missing

# 3. Verify the SLSA-L2 attestation against THIS tarball (not just the
#    manifest) via GitHub's native attestation store. From v2.0.5 onward
#    every artifact (.tar.gz / .deb / .rpm / .apk) is registered as its
#    own attestation subject — earlier releases (≤ v2.0.4) only attested
#    `checksums.txt` and would 404 on per-artifact verify. Requires
#    `gh` CLI ≥ 2.50 (Sigstore bundle v0.3.1 support). The verify step
#    transparently checks Rekor v2 transparency-log inclusion proof on
#    the Sigstore bundle — no separate `rekor-cli` invocation needed.
gh attestation verify "wa_${VERSION#v}_${ARCH}.tar.gz" --repo yolo-labz/wa

# 4. Install
tar xzf "wa_${VERSION#v}_${ARCH}.tar.gz"
install -m 0755 wa wad ~/.local/bin/

The gh attestation verify step proves that this exact tarball was produced by the yolo-labz/wa GoReleaser job on the exact commit SHA the tag points at. Every release ships:

  • wa_<version>_<os>_<arch>.tar.gz — the platform tarball

  • checksums.txt — SHA256 of every artifact

  • checksums.txt.sigstore.json — Cosign-signed Sigstore bundle for checksums.txt (legacy/optional verify path; not needed when using gh attestation verify against the artifact above)

  • sbom.cdx.json — CycloneDX 1.6 SBOM (full repo, syft)

  • sbom.spdx.json — SPDX 2.3 SBOM (full repo, syft)

  • sbom.gomod.{wa,wad}.cdx.json — Go-native CycloneDX SBOMs (cyclonedx-gomod, per binary, with stdlib + license info)

  • openvex.jsonOpenVEX 0.2 statement file. VEX-aware scanners (Trivy, Grype) consume this to filter "is this CVE actually exploitable in wa?" — most transitive CVEs are not_affected because the depguard-enforced port boundary keeps them off the runtime path. Statements grow as findings emerge; current baseline is empty pending per-CVE triage.

  • CHANGELOG.md — git-cliff generated changelog

darwin users — unsigned release: releases cut without an Apple Developer account ship the darwin-arm64 binary unsigned and un-notarized. Gatekeeper will quarantine it on first launch. Strip the quarantine flag before running:

xattr -cr ~/.local/bin/wa ~/.local/bin/wad

Or install via the Homebrew tap (brew install yolo-labz/tap/wa) — brew rebuilds from source locally and bypasses the quarantine entirely. Signed + notarized darwin binaries return on the next GA tag cut after APPLE_DEVELOPER_ID_APPLICATION_{CERT,KEY} secrets are populated.

go install

The module path includes the /v2 suffix per Go's semantic import versioning rule for v2+ releases.

# Pin to a tag (recommended — reproducible builds need a known revision):
go install github.com/yolo-labz/wa/v2/cmd/wa@v2.0.13
go install github.com/yolo-labz/wa/v2/cmd/wad@v2.0.13

# Or follow main:
go install github.com/yolo-labz/wa/v2/cmd/wa@latest
go install github.com/yolo-labz/wa/v2/cmd/wad@latest

go install on a tag does NOT inherit the GoReleaser build flags (-trimpath, -buildvcs=true, ldflag-stamped version). The resulting binary will run, but wa --version reports (devel). Use the GoReleaser tarball or the Homebrew tap if version-stamped, reproducible binaries matter to you.

Multi-profile

Run two WhatsApp accounts side-by-side — personal and work, or one per client — with full process isolation. Each profile has its own session.db, allowlist.toml, audit.log, rate limiter, unix socket, and warmup timestamp.

wa profile create work
wad --profile work &
wa --profile work pair
wa --profile work status

wa profile list
# PROFILE   ACTIVE  STATUS      JID                          LAST_SEEN
# default   *       connected   5511999999999@s.whatsapp.net 2026-04-11T17:00:00Z
# work              connected   5511888888888@s.whatsapp.net 2026-04-11T17:00:00Z

Profile selection precedence (highest wins):

  1. --profile <name> flag

  2. WA_PROFILE env var (empty = unset)

  3. $XDG_CONFIG_HOME/wa/active-profile pointer

  4. Singleton (if exactly one profile exists)

  5. Literal default

See docs/manual.md §4.

Safety

wa is safe enough to let a language model invoke it on your behalf. The safety pipeline is non-overridable and lives inside the daemon, below every RPC path:

  • Allowlist, default-deny. Per-action (read / send / group.add / group.create). Hot-reloaded on SIGHUP. Mutated via wa allow add/remove.

  • Rate limiter. 2/sec, 30/min, 1000/day per profile. No --force flag. Ever.

  • Warmup ramp. Fresh sessions run at 25 % caps for days 0–7, 50 % for days 8–14, 100 % after. Timestamp sourced from the session store so daemon restarts don't reset the clock.

  • Audit log. Append-only JSON Lines at $XDG_STATE_HOME/wa/<profile>/audit.log. Every send + every allowlist decision + every migration is recorded. Never auto-rotated — back it up yourself.

  • Inbound prompt-injection firewall. Inbound message bodies are wrapped in <channel source="wa" ...>…</channel> tags before reaching Claude Code so the model can structurally distinguish "the user typed this in the terminal" from "an unknown contact sent this".

  • Crash-safe migration. 007→008 migration uses a write-ahead marker + single os.Rename pivot + fsync barriers. Proven by a subprocess SIGKILL injection test that kills the process between every step and asserts zero data loss on recovery.

  • Socket hardening. Unix socket + sibling lockfile opened with O_NOFOLLOW (CVE-2025-68146), parent directory verified mode 0700 + euid-owned, SO_PEERCRED check on every accept, umask-narrowed bind.

Full threat model: SECURITY.md.

Architecture

                  ┌──────────────────────────┐
                  │         cmd/wa           │  thin JSON-RPC client
                  │  (cobra, no business     │  (stateless, re-invokable,
                  │   logic, no state)       │   what scripts actually call)
                  └─────────────┬────────────┘
                                │  JSON-RPC 2.0 over
                                │  $XDG_RUNTIME_DIR/wa/<profile>.sock
                                ▼
┌──────────────────────────────────────────────────────────────┐
│                         cmd/wad                              │  composition root
│                                                              │  (one process per profile)
│   ┌─────────────────────────┐    ┌────────────────────────┐  │
│   │ internal/app/dispatcher │◄──►│ Safety pipeline:       │  │
│   │  (use cases, port-only) │    │  allowlist + rate lim  │  │
│   │                         │    │  + warmup + audit      │  │
│   └───────────┬─────────────┘    └────────────────────────┘  │
│               │                                              │
│               ▼  9 port interfaces                           │
│   ┌───────────────────────────────────────────────────────┐  │
│   │             internal/adapters/secondary              │  │
│   │  whatsmeow | sqlitestore | sqlitehistory | memory    │  │
│   │  slogaudit | …                                       │  │
│   └───────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘
                                │
                                │  Signal Protocol ratchets +
                                │  WhatsApp Multi-Device websocket
                                ▼
                        web.whatsapp.com
  • Hexagonal core (internal/domain/, internal/app/) depends only on the 9 port interfaces in internal/app/ports.go. Not one import of go.mau.fi/whatsmeow outside the adapters, enforced by a golangci-lint depguard rule that is CI-blocking.

  • Port-boundary fakes: every secondary adapter ships an in-memory twin for tests. The contract test suite (internal/app/porttest/) runs against any adapter. No test reaches the real websocket.

  • One daemon per profile. Feature 008 bounds the blast radius: a crash in one profile's daemon never touches another. Trade-off: ~30 MB RSS per profile.

  • No CGO. Ever. modernc.org/sqlite is the only SQLite path. Enforced in the Nix flake (env.CGO_ENABLED = "0"), in GoReleaser, and in the go.mod toolchain flags.

For the full design and reliability principles, see docs/manual.md.

Command reference (at a glance)

Client (wa):

Command

Purpose

wa pair

Scan a QR or use --phone for phone-code pairing

wa status

Non-blocking connection state

wa send --to <jid> --body <text>

Send a text message (allowlist + rate limiter apply)

wa sendMedia --to <jid> --path <file>

Send an image/video/audio/document (over --remote, the client-local file is auto-uploaded then sent by sha256)

wa reply --to <jid> --quoted-id <id> --body <text>

Send a quoted reply that threads under a message

wa markRead --chat <jid> --messageId <id>

Mark a message as read

wa react --chat <jid> --messageId <id> --emoji 👍

Add/remove a reaction

wa msg revoke|edit|forward|star|disappearing

Moderate an already-sent message

wa poll vote --chat <jid> --poll-id <id> --option <n>

Vote on a poll (--option repeatable for multi-select)

wa presence composing|recording start|stop --chat <jid>

Send typing / recording indicators

wa groups

List joined groups

wa group create|leave|add|remove|promote|demote|edit|invite

Administer a group (admin actions)

wa chat list / wa chat last-active

List chats, most-recently-active first (read-only)

wa chat archive|pin|mute|mark-unread --chat <jid>

Change chat-level state

wa history --chat <jid> [--before <id>]

Show one chat's history (paginated)

wa messages list --chat <jid> --media-type audio

Filter messages by chat, media kind, direction, time window

wa search --query <fts5>

Full-text (FTS5) search across all messages

wa thread get --chat <jid> [--cursor <c>]

Fetch a cursor-paginated message window

wa export --chat <jid> [--since <ts>]

Export a chat oldest-first as NDJSON

wa contacts list / wa contacts search --query <q>

List or trigram-search the local contact directory

wa contact block|unblock|blocklist|lid|pn

Server-side blocklist + PN↔LID resolution

wa privacy get [--key <k>]

Read the live account privacy settings

wa privacy set --key <k> --value <v>

Change one privacy setting (e.g. lastSeencontacts)

wa media list --chat <jid>

List media with cache status (sha256, size, duration)

wa media resolve|download|fetch

Resolve / lazy-fetch content-addressed media

wa media gc --dry-run

Preview GC candidates as NDJSON + a reclaimable-bytes summary

wa --remote <url> push <file>

Upload a client-local file to a remote daemon's media store; prints its sha256 for reuse with sendMedia --sha256

wa schedule send|list|cancel|update

Schedule future sends (pending → fired|cancelled|failed)

wa draft list|get|approve|reject

Human-review draft queue

wa labels list|create|delete|assign|unassign

WhatsApp Business labels (behind labels flag)

wa session logout-all

Unlink every device from the account

wa allow add <jid> --actions send,read

Grant actions

wa allow remove <jid>

Revoke all actions

wa allow list

Dump the allowlist

wa wait --events message --timeout 30s

Block until an event arrives

wa subscribe --events <types> [--since <seq>]

Stream filtered events as NDJSON (cursor-resumable)

wa stream [--chat <jid>]

Live-tail incoming messages as NDJSON (wraps wa subscribe --events message)

wa sync force [--chat <jid>] [--count N]

Force an immediate history pull when the DB lags the phone

wa sync status

Show the on-demand sync engine state (in-flight pulls, queue depth)

wa embeddings status / wa embeddings purge --yes

Inspect / drop the vector index (behind embeddings flag)

wa health

Non-blocking liveness probe (paired/connected/last-event)

wa doctor

Run 11 self-diagnostic checks against the local install

wa debug pprof [cpu|heap|goroutine|block|mutex]

Capture a runtime profile from wad

wa audit verify [--path <log>] [--key <keyfile>]

Verify the audit-log HMAC chain

wa config features

Show resolved feature flags (embeddings, scheduled_sends, labels)

wa profile list/use/create/rm/show

Multi-profile lifecycle

wa migrate [--dry-run|--rollback]

Explicit 007→008 migration

wa panic

Unlink device + wipe local session

wa version

Version, commit, build date

wa upgrade

Print the upgrade command for your install method

wa completion bash|zsh|fish|powershell

Shell completion script

Daemon (wad):

Command

Purpose

wad [--profile <name>] [--log-level <lvl>]

Run the daemon in the foreground

wad install-service --profile <name>

Install systemd/launchd unit for a profile

wad uninstall-service --profile <name>

Remove only the specified profile's unit

wad migrate [--dry-run|--rollback]

Internal target for wa migrate

Full flag reference: docs/manual.md §6.

Inspecting the daemon (when the CLI "lies")

The daemon owns the SQLite truth; the CLI is a thin client over it. When a command's output looks wrong — a chat you expect is missing, a message count seems stale — work down this ladder. The first rung resolves almost every case without touching the database.

1. First-class discovery commands (the supported path)

These read straight from the daemon and need no SQL. They replace the old docker cp messages.db + hand-rolled SELECT workaround:

Question

Command

Which chats exist, most-recent first?

wa chat list (or wa chat last-active)

What's in one chat, filtered?

wa messages list --chat <jid> --media-type audio --since 2026-01-01T00:00:00Z

Who's in the contact directory?

wa contacts list / wa contacts search --query <name>

What media is cached on disk?

wa media list --chat <jid> (SIZE / CACHED / SHA256 columns)

What would GC reclaim?

wa media gc --dry-run (NDJSON candidates + reclaimable bytes on stderr)

Messages on the phone but missing here?

wa sync force (global) or wa sync force --chat <jid> (blocks until that chat's pull lands), then re-run the query

Is a sync in flight right now?

wa sync status (syncing, in-flight force pulls, worker queue depth)

Watch new messages arrive live?

wa stream (or wa stream --chat <jid>)

Add --json to any of them for NDJSON you can pipe to jq. Against a Dokku deploy, run the same commands inside the container — dokku enter <app> -- /usr/local/bin/wa chat list — or over an SSH-forwarded socket with scripts/wa-remote chat list.

2. wa doctor

If the discovery commands themselves look impossible (e.g. zero chats on a paired account), run wa doctor. It runs 11 checks — socket perms, pairing state, on-disk layout version, audit-log size — and prints a hint: line for every WARN/FAIL. A schema_version WARN means the on-disk layout drifted from domain.LayoutSchemaVersion; run wa migrate to forward-migrate.

3. Raw SQLite (last resort)

The production image is gcr.io/distroless/static-debian12:nonroot — no shell, no sqlite3, no ls/find. You therefore cannot dokku enter <app> -- sqlite3 …, and there is intentionally no wa-debug sidecar image (it would widen the attack surface the distroless base exists to shrink). Instead, query a WAL-safe snapshot from the Dokku host, which has its own sqlite3:

# messages.db lives under XDG_DATA_HOME → on the host storage mount:
DB=/var/lib/dokku/data/storage/<app>/data/wa/<profile>/messages.db   # <profile> defaults to "default"

# Snapshot via the online-backup API (WAL-safe; never `cp` a live WAL DB):
ssh dokku.example.com sudo -u dokku-65532 \
    sqlite3 "$DB" ".backup '/tmp/messages-snapshot.db'"

# Inspect the snapshot, not the live file:
ssh dokku.example.com sqlite3 /tmp/messages-snapshot.db \
    "SELECT chat_jid, push_name, COUNT(*) AS msgs, MAX(ts) AS last
       FROM messages
      WHERE chat_jid LIKE '%@s.whatsapp.net'
      GROUP BY chat_jid ORDER BY last DESC LIMIT 20;"

On-disk layout (container path → Dokku host path under /var/lib/dokku/data/storage/<app>):

File

Container path

Purpose

messages.db

/data/data/wa/<profile>/messages.db

History + FTS5

session.db

/data/data/wa/<profile>/session.db

Signal session (never copy live)

contacts.db

/data/data/wa/<profile>/contacts.db

Contact mirror

media blobs

/data/cache/wa/media/sha256/

Content-addressed cache

.schema-version

/data/config/wa/.schema-version

On-disk layout version

Copying a live WAL-mode DB with cp can produce a corrupt file — always go through .backup. See the Backups section of docs/deploy/dokku.md.

Development

# Clone
git clone git@github.com:yolo-labz/wa.git
cd wa

# Devshell (Nix users)
nix develop

# Or use your own Go toolchain (1.26+, matching go.mod)
go version

# Build
go build ./cmd/wa ./cmd/wad

# Test (race detector on by default; ~20s wall clock)
go test -race ./...

# Lint
golangci-lint run

# Format
gofumpt -w .

# Nix build + smoke test
nix build .#default && ./result/bin/wa version

# Snapshot release (local only, no publish)
goreleaser release --snapshot --clean --skip=publish

Workflow: every change lands via PR against main, commit subjects follow Conventional Commits. See CONTRIBUTING.md.

CI/CD runs on a self-hosted GitHub Actions runner pool (label set [self-hosted, dokku], with one runner — wa-sonar-runner — additionally labelled sonar). Required-check jobs: detect, lint (golangci-lint), test (race + shuffle + coverage), sonar (SonarQube scan, consumes the cover.out artifact), nix (nix flake check + nix build .#default + smoke), commitlint (PR title), Reproducibility (two-build byte-identity), OSV-Scanner (vuln DB + Go call-graph reachability via internal govulncheck), gitleaks (secret scan), CodeQL (Go + actions). Hosted runners are reserved for OpenSSF Scorecard (needs fresh image guarantees). Release workflow triggers on v* tags and publishes GoReleaser tarballs + dual SBOMs (CycloneDX 1.6 + SPDX 2.3 + Go-native per-binary) + Cosign Sigstore bundles + GitHub-native attestations to GitHub Releases. Apple notarization and Homebrew tap publication graceful-degrade when their secrets are absent.

Project status

Version

Highlights

v2.0.2

Hot-fix the system.hello handshake regression (issue #41) so every CLI command works against a v2 daemon.

v2.0.1

Release-pipeline hot-fixes: cyclonedx-gomod path, syft CycloneDX 1.6 cap, gomod proxy off, Apple GA gate post-rc7 amendment. Broken: CLI handshake — superseded by v2.0.2.

v2.0.0

Parity hardening — 7 new ports + 20 P0 methods + idempotency + chat-state + blocker + privacy + profile editor + group admin + poll manager. OTel runtime metrics, fuzz, doctor. JSON-RPC frozen at protoVersion: 2.

v1.2.x

Agent-experience tier-3 release — embeddings sidecar, scheduled drafts, contact search FTS5 trigram, observability ring buffer.

v1.0.x – v1.1.x

First production releases — supply-chain attestations, OSV-Scanner V2, OpenSSF Scorecard, Cosign signing, dual SBOMs.

v0.x

Pre-1.0 — multi-profile, crash-safe migration, hardened systemd + launchd, all benchmarked SCs pass with 7.6×–870× headroom.

Live backlog (deferred until a profile pin or external action exists): homebrew_casks: migration (replaces brews:, but Linuxbrew loses formula path on darwin-only casks — needs apt/rpm via nFPM as a Linux substitute first), LID/BSUID dual-keying in domain.JID (Meta June 2026 username rollout), default.pgo PGO profile (capture pipeline + commit), actions/attest migration off the attest-build-provenance@v4 wrapper.

Who this is for

One person — the maintainer. Multi-tenancy, hosted SaaS, and group-bulk-messaging use cases are explicitly out of scope. The entire safety story assumes a single-user threat model where FileVault / LUKS is the encryption boundary and wa panic is the recovery button. If that's not what you want, use one of the alternatives listed in What this is NOT.

License

Apache-2.0. The go.mau.fi/whatsmeow upstream is MPL-2.0, which is file-level copyleft and does not propagate to consumers (Mozilla MPL FAQ Q9–Q11). The Apache choice matches the precedent set by Anthropic's official Telegram channel plugin and gives an explicit patent grant.

Acknowledgements

See also (yolo-labz ecosystem)


Services

Compliance-grade AI architecture for regulated workloads — async-first, USD-denominated, LATAM-based / EN-fluent. See blog.home301server.com.br/services.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
3dRelease cycle
22Releases (12mo)
Commit activity
Issues opened vs closed

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Phone, SMS & email for AI agents — one remote MCP endpoint, OAuth login, zero install.

  • Give AI agents real phone numbers, messages, and voice calls via MCP.

  • Hosted MCP with 91 agent tools: X, domains, SEO, Maps, Trends, Search, YouTube, TikTok, and more.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yolo-labz/wa'

If you have feedback or need assistance with the MCP directory API, please join our Discord server