agentleFS
jaskirat1616jaskirat1616/grok-skills/skills/comms-whatsapp/SKILL.md

comms-whatsapp

Skill22 starsChanged 2 months agoMITView on GitHub

Read and send WhatsApp messages with wacli. Use to search messages, list chats, groups, or contacts, fetch threads or media, or send WhatsApp messages.

  • Installs packages

We split this skill into the rules it gives an agent

15 rules. About 0 are common, 3 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    After pairing, kick off an initial sync that exits when idle:

    line 36
  2. Very specific:

    Check first: Run wacli doctor / wacli auth status before pairing.

    line 50
  3. Very specific:

    Linux store: Default store is ~/.local/state/wacli (XDG).

    line 52
  4. Very specific:

    Install on Linux if missing: Prefer the GitHub release binary (openclaw/wacli, linux_amd64) onto ~/.local/bin/wacli.

    line 54
  5. Very specific:

    Default output is human-formatted (columns, truncation, senders shown as bare @lid numbers).

    line 58
  6. Very specific:

    Every --json response is an envelope: {"success":bool, "data":…, "error":…}.

    line 60
  7. Distinctive:

    Search returned a media message ID?

    line 181
  8. Very specific:

    If LOCKED true and the user isn't expecting a sync running, kill it:

    line 198
  9. Distinctive:

    If a hit looks promising, fetch context:

    line 213
  10. Very specific:

    Don't run wacli send ... while a wacli sync --follow is up — store is locked.

    line 250
  11. Very specific:

    Don't paste a phone number with + or spaces to --to — strip to digits only (e.g.

    line 251
  12. Very specific:

    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.

    line 252
  13. Very specific:

    Don't attribute a quote from the sender JID, the human output, or DisplayText/Snippet — only FromMe (direction) + the message's own Text …

    line 253
  14. Very specific:

    Don't expect old history to be there.

    line 254
  15. Very specific:

    Don't commit ~/.wacli/ — it contains session keys.

    line 255

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

wacli — WhatsApp from the CLI

wacli is a Go CLI built on the 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 <jid> --requests N. Best-effort, may not return.
  • JIDs, not phone numbers. Most read commands take --chat <jid> (e.g. 61400000000@s.whatsapp.net for DMs, <id>@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)

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:

wacli sync --once --idle-exit 30s

Or leave a follower running in the background to keep the local DB hot:

wacli sync --follow >/tmp/wacli-sync.log 2>&1 &

Shared computer / already paired

  • Check first. Run wacli doctor / wacli auth status before pairing. If AUTHENTICATED true, do not run wacli auth again — that creates another linked device.
  • Shared machine. The binary and store live on the shared home directory. Any agent on that computer uses the same wacli session. No second install, no second QR.
  • Linux store. Default store is ~/.local/state/wacli (XDG). macOS is ~/.wacli. Binary is often ~/.local/bin/wacli.
  • One lock. wacli sync / wacli auth holds a single-writer lock for everyone. Do not leave wacli sync --follow running. Reads (messages search/list, chats list) are fine when unlocked. Stop sync before send.
  • Install on Linux if missing. Prefer the GitHub release binary (openclaw/wacli, linux_amd64) onto ~/.local/bin/wacli. Homebrew tap openclaw/tap/wacli also works where brew exists.

Always pass --json when an agent is consuming output

Default output is human-formatted (columns, truncation, senders shown as bare @lid numbers). --json produces structured rows that pipe straight into jq. Use it everywhere the result feeds back to Claude — and never attribute a quote from the human output (see "Attribution" below).

Every --json response is an envelope: {"success":bool, "data":…, "error":…}. The payload is under .data:

  • messages list/search → .data.messages[], each with PascalCase keys: ChatJID, ChatName, SenderJID, Timestamp, FromMe, Text, DisplayText, MediaType, Snippet.
  • chats list → .data[] with JID, Kind, Name, LastMessageTS.
  • contacts search → .data[] with JID, Phone, Name, Alias, Tags.
wacli messages search "magic tags" --limit 20 --json \
  | jq '.data.messages[] | {ChatName, FromMe, Timestamp, Text}'

Attribution — who sent each message (read before quoting anyone)

Getting "who said X" wrong is the most common and most damaging wacli mistake. Two traps cause it; both are avoided by the same two rules.

  1. FromMe is the only reliable speaker signal — never infer the sender from the JID or the human output. In a DM both sides come back. Only the account owner is reliably identifiable, via the boolean FromMe. The other party's SenderJID is a raw @lid (e.g. 191624378347690@lid), not a name, and the same person appears under device variants (…@lid, …:19@lid). The human output prints your messages as me and theirs as the bare @lid. Rule: attribute every quote from FromMe — true = the account owner (you), false = the other party. Resolve a @lid to a name with wacli contacts search/show only for labelling, never for deciding direction.
  2. Text is the message's own words; DisplayText/Snippet bundle quoted-reply context — so a search can pin a phrase on the wrong author. When B replies to A, B's DisplayText/Snippet contains A's quoted line, so a substring search matches both A's original and B's reply — making it look like B said A's words. Rule: when deciding who said a phrase, match the Text field only — never DisplayText or Snippet.

Correct attribution recipe — a clean transcript labelled by FromMe, using each message's own Text:

wacli messages list --chat <jid> --limit 200 --json \
  | jq -r '.data.messages[] | select(.Text != "")
           | (if .FromMe then "ME  " else "THEM" end) + " | " + .Timestamp + " | " + .Text'

Build any "who said what" answer from this — not from the human output, not from DisplayText. If a phrase isn't in anyone's own Text (only in quoted replies), it means nobody in the window said it as their own message — don't attribute it, and history backfill if it predates the sync window.

Read recipes

Find the chat / contact JID

# 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 <jid> --json

Search messages

# Global search across all chats
wacli messages search "<query>" --limit 50 --json

# Constrain to a chat
wacli messages search "<query>" --chat <jid> --json

# Constrain by sender (DMs include both sides; --from filters senders)
wacli messages search "<query>" --from <jid> --json

# Time window (RFC3339 or YYYY-MM-DD; --before is exclusive)
wacli messages search "<query>" --after 2026-04-01 --before 2026-04-28 --json

# Only media of a specific type
wacli messages search "" --chat <jid> --type image --json

List a chat's recent messages

wacli messages list --chat <jid> --limit 100 --json
wacli messages list --chat <jid> --after 2026-04-25 --json

Get context around a message

Once a search returns a hit, expand the surrounding thread:

wacli messages context --chat <jid> --id <message-id> --before 10 --after 10 --json

Pull older messages a primary device hasn't yet shared

wacli history backfill --chat <jid> --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

wacli send text --to <phone-or-jid> --message "your text"
# Phone form is auto-converted to JID; omit `+`. E.g. --to 61400000000

File (image / video / audio / document)

wacli send file --to <jid> --file /abs/path.png --caption "look at this"
wacli send file --to <jid> --file /abs/notes.pdf --filename "Meeting notes.pdf"

--mime overrides detection if wacli mis-classifies an attachment.

Group management

wacli groups list --json                              # known groups (from local DB)
wacli groups refresh                                  # pull fresh group list from server
wacli groups info --jid <group-jid>                   # participants, name, description
wacli groups join --code <invite-code>
wacli groups leave --jid <group-jid>
wacli groups participants add --jid <group> --participants <jid>,<jid>
wacli groups participants remove --jid <group> --participants <jid>
wacli groups invite get --jid <group-jid>             # current invite link

Media download

Search returned a media message ID? Pull the binary:

wacli media download --chat <jid> --id <message-id> --output /tmp/media

Outputs to the configured media dir under the store by default.

Diagnostics

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:

pkill -f 'wacli sync' && wacli doctor

Common agent flows

"Find that WhatsApp thread where we talked about X"

wacli messages search "X" --limit 30 --json \
  | jq '.data.messages[] | {ChatName, FromMe, Timestamp, Text}'

If a hit looks promising, fetch context:

wacli messages context --chat <jid> --id <message-id> --before 8 --after 8 --json

"Send Alice the link to Y"

ALICE=$(wacli contacts search "Alice" --json | jq -r '.data[0].JID')
wacli send text --to "$ALICE" --message "Y: https://..."

"What's in the family group lately?"

GROUP=$(wacli groups list --json | jq -r '.data[] | 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

wacli sync --once --idle-exit 15s   # foreground catch-up, ~15s
wacli messages list --chat <jid> --limit 5 --json

Output flags reference

Every command supports:

  • --json — structured output (use this when piping to jq or returning to Claude)
  • --store <dir> — alternate store dir (default ~/.wacli or $WACLI_STORE_DIR)
  • --timeout <duration> — 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 attribute a quote from the sender JID, the human output, or DisplayText/Snippet — only FromMe (direction) + the message's own Text (content) are reliable. See "Attribution" above.
  • 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.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.