--- name: wacli description: "Read and send WhatsApp messages from the command line via wacli (steipete/wacli, whatsmeow-based). Use when the user asks to search WhatsApp messages, send a WhatsApp message, list chats/groups/contacts, look up a thread, or download WhatsApp media. Triggers on 'WhatsApp', 'wacli', 'send a wa message', 'find that WhatsApp thread', 'WA chats', 'who said X on WhatsApp'." --- # wacli — WhatsApp from the CLI `wacli` is a Go CLI built on the [whatsmeow](https://github.com/tulir/whatsmeow) library. It pairs to the user's WhatsApp account as a multi-device companion (like WhatsApp Web), syncs messages to a **local SQLite DB** at `~/.wacli/wacli.db`, and exposes commands to read, search, and send. Repo: https://github.com/steipete/wacli · install: `brew install steipete/tap/wacli`. ## Mental model — sync vs. read Two distinct phases: 1. **Sync** (`wacli sync` / `wacli auth --follow`) connects to WhatsApp servers and writes new messages to the local DB. Runs as a long-lived process. New messages only land in the DB while sync is running. 2. **Read** (`wacli messages list/search/show`, `wacli chats list`) queries the local DB. Fast, offline, no network. Always reads what sync has already written. So when the user asks "what did Alice send yesterday?" — the answer comes from the local DB. If the DB is stale, the user needs to run sync first. Use `wacli doctor` to check state. ## Critical gotchas - **Single-writer lock.** The store is locked while `wacli sync` (or any connecting command) holds it. Trying to send while sync is running fails with a lock error. Tell the user to stop sync (`pkill -f 'wacli sync'`) before sending, or run `wacli send ...` in a window where sync isn't running. `wacli doctor` shows lock state. - **History is shallow by default.** Initial sync only pulls recent messages from the user's primary device. Older history requires `wacli history backfill --chat --requests N`. Best-effort, may not return. - **JIDs, not phone numbers.** Most read commands take `--chat ` (e.g. `61400000000@s.whatsapp.net` for DMs, `@g.us` for groups). `wacli send text` accepts `--to` as either phone or JID. To find a JID: `wacli chats list --json` or `wacli contacts search "name" --json`. - **FTS5 may not be available.** `wacli doctor` shows `FTS5 false` on stock macOS SQLite — search falls back to LIKE, which is slower and less precise. Still usable for typical agent queries. - **Do not commit `~/.wacli/`.** It contains the session keypair and message DB. ## Setup (once) ```sh brew install steipete/tap/wacli wacli auth # shows QR; pair from phone → WhatsApp → Linked Devices ``` After pairing, kick off an initial sync that exits when idle: ```sh wacli sync --once --idle-exit 30s ``` Or leave a follower running in the background to keep the local DB hot: ```sh wacli sync --follow >/tmp/wacli-sync.log 2>&1 & ``` ## Always pass `--json` when an agent is consuming output Default output is human-formatted (columns, truncation). `--json` produces structured rows that pipe straight into `jq`. Use it everywhere the result feeds back to Claude. ```sh wacli messages search "magic tags" --limit 20 --json | jq '.[] | {chat, sender, ts, text}' ``` ## Read recipes ### Find the chat / contact JID ```sh # Browse chats sorted by recent activity wacli chats list --limit 30 --json # Substring match on a contact name wacli contacts search "Alice" --json # Show full contact record (aliases, tags, JID, push name) wacli contacts show --jid --json ``` ### Search messages ```sh # Global search across all chats wacli messages search "" --limit 50 --json # Constrain to a chat wacli messages search "" --chat --json # Constrain by sender (DMs include both sides; --from filters senders) wacli messages search "" --from --json # Time window (RFC3339 or YYYY-MM-DD; --before is exclusive) wacli messages search "" --after 2026-04-01 --before 2026-04-28 --json # Only media of a specific type wacli messages search "" --chat --type image --json ``` ### List a chat's recent messages ```sh wacli messages list --chat --limit 100 --json wacli messages list --chat --after 2026-04-25 --json ``` ### Get context around a message Once a search returns a hit, expand the surrounding thread: ```sh wacli messages context --chat --id --before 10 --after 10 --json ``` ### Pull older messages a primary device hasn't yet shared ```sh wacli history backfill --chat --count 50 --requests 3 --wait 60s ``` This asks the user's phone to send a history slab. Best-effort — phone must be online and recently active. ## Send recipes > Stop any running `wacli sync` before send commands, or they'll fail on the store lock. ### Text ```sh wacli send text --to --message "your text" # Phone form is auto-converted to JID; omit `+`. E.g. --to 61400000000 ``` ### File (image / video / audio / document) ```sh wacli send file --to --file /abs/path.png --caption "look at this" wacli send file --to --file /abs/notes.pdf --filename "Meeting notes.pdf" ``` `--mime` overrides detection if `wacli` mis-classifies an attachment. ## Group management ```sh wacli groups list --json # known groups (from local DB) wacli groups refresh # pull fresh group list from server wacli groups info --jid # participants, name, description wacli groups join --code wacli groups leave --jid wacli groups participants add --jid --participants , wacli groups participants remove --jid --participants wacli groups invite get --jid # current invite link ``` ## Media download Search returned a media message ID? Pull the binary: ```sh wacli media download --chat --id --output /tmp/media ``` Outputs to the configured media dir under the store by default. ## Diagnostics ```sh wacli doctor # store path, lock state, auth, FTS availability wacli doctor --connect # try a live connection (requires lock be free) wacli auth status # auth-only check wacli version ``` If `LOCKED true` and the user isn't expecting a sync running, kill it: ```sh pkill -f 'wacli sync' && wacli doctor ``` ## Common agent flows ### "Find that WhatsApp thread where we talked about X" ```sh wacli messages search "X" --limit 30 --json \ | jq '.[] | {chat, sender, ts, text}' ``` If a hit looks promising, fetch context: ```sh wacli messages context --chat --id --before 8 --after 8 --json ``` ### "Send Alice the link to Y" ```sh ALICE=$(wacli contacts search "Alice" --json | jq -r '.[0].jid') wacli send text --to "$ALICE" --message "Y: https://..." ``` ### "What's in the family group lately?" ```sh GROUP=$(wacli groups list --json | jq -r '.[] | select(.name=="Family") | .jid') wacli messages list --chat "$GROUP" --limit 50 --after $(date -v-7d +%Y-%m-%d) --json ``` ### Confirm sync is fresh before reporting ```sh wacli sync --once --idle-exit 15s # foreground catch-up, ~15s wacli messages list --chat --limit 5 --json ``` ## Output flags reference Every command supports: - `--json` — structured output (use this when piping to jq or returning to Claude) - `--store ` — alternate store dir (default `~/.wacli` or `$WACLI_STORE_DIR`) - `--timeout ` — bound non-sync commands (default 5m) ## What NOT to do - Don't run `wacli send ...` while a `wacli sync --follow` is up — store is locked. Stop sync first. - Don't paste a phone number with `+` or spaces to `--to` — strip to digits only (e.g. `61400000000`). - Don't try to send to a JID you fished out of message metadata that ends in `@lid` (linked device alias) — use the `@s.whatsapp.net` form. `wacli contacts show` returns the right one. - Don't expect old history to be there. Run `wacli history backfill` for a specific chat if the user asks about messages older than the sync window. - Don't commit `~/.wacli/` — it contains session keys.