Beeper — chat control across every connected network
Beeper Desktop exposes a local REST API on http://localhost:23373/v1/…. This skill
wraps it in a single stdlib-only Python CLI so any agent with a shell can read,
search, and send messages across every connected chat network (WhatsApp, Telegram,
Signal, iMessage, Instagram, Matrix, …).
The Beeper Desktop app must be running. If it is not, every command exits 2 with
Beeper Desktop is not running. Open the Beeper Desktop app and retry.
The CLI
<skill dir>/scripts/beeper <command> [args] [--json]
The primary install location is ~/.claude/skills/beeper-whatsapp/, so in practice:
~/.claude/skills/beeper-whatsapp/scripts/beeper accounts
--json (before or after the subcommand) prints raw API JSON instead of the
human-readable formatting — use it when you need to parse fields programmatically.
Always single-quote chatIDs and messageIDs in the shell. They look like
'!EXAMPLEchatid:ba_XXXXXXXXXXXX.local-whatsapp.localhost' — the leading !
triggers zsh/bash history expansion and the : and - confuse unquoted parsing.
Discover the user's accounts first
There is no fixed account list — every user's Beeper is connected to different
networks. Run beeper accounts once at the start of a session and use what it
returns for the rest of the conversation:
$B accounts # → accountIDs, network, phone/handle, display name
Then pass --account <accountID> (or the positional accountID where a command
requires it) to scope a command to one network. Omit it to search across everything.
AccountIDs are either a bare network name (telegram, whatsapp, signal) or a
network-prefixed local ID (local-whatsapp_ba_XXXXXXXXXXXX) — take them verbatim
from accounts output, never guess or hand-construct them.
Users often have several accounts on the same network — e.g. a work WhatsApp and
a personal WhatsApp, or two Telegram accounts. The same person then frequently has a
chat on both. When find-chat returns duplicates across accounts, check
lastActivity and ask the user which account and which chat they mean before
acting — especially before anything that writes.
Command reference
Read
| Command | What it does | Example |
|---|---|---|
accounts |
List connected accounts | beeper accounts |
chats |
Recent chats. --account, --limit N (1-200), --unread, --inbox primary|low-priority|archive, --cursor, --direction |
beeper chats --limit 10 --unread |
find-chat <query> |
Search chats by title; --scope participants matches member names instead; --account, --limit |
beeper find-chat "Alice" |
chat <chatID> |
Chat details + participant list (--max-participants N) |
beeper chat '!EXAMPLEchatid:ba_XXXX.local-whatsapp.localhost' |
messages <chatID> |
Recent messages, oldest→newest. --limit N auto-paginates (API pages are 20) |
beeper messages '!EXAMPLEchatid:…' --limit 30 |
search-messages <q> |
Full-text search. --chat, --account, --sender me|others|<id>, --after/--before ISO8601, --chat-type, --media any|image|video|link|file, --limit (max 20) |
beeper search-messages "invoice" --limit 10 |
search <q> |
Unified search: chats, group matches, and messages in one call | beeper search "Alice" |
contacts <accountID> <q> |
Search that account's address book | beeper contacts telegram "Bob" |
list-contacts <accountID> |
Paginated contact dump; --query, --limit, --cursor |
beeper list-contacts whatsapp --limit 50 |
download <mxcURL | chatID msgID> |
Fetch an attachment; --out <file|dir>, --index N for multi-attachment messages. Prints the saved path |
beeper download '!EXAMPLEchatid:…' 160518 --out ~/Desktop/pic.jpg |
info |
App/server version, MCP endpoint | beeper info |
Write — only on explicit user instruction
| Command | Example |
|---|---|
send <chatID> <text> (--reply-to <msgID>, --attach <file>) |
beeper send '!EXAMPLEchatid:…' "On my way" |
edit <chatID> <msgID> <newText> |
beeper edit '!EXAMPLEchatid:…' 177785 "corrected text" |
react <chatID> <msgID> <emoji> / unreact … |
beeper react '!EXAMPLEchatid:…' 177785 "👍" |
new-chat <accountID> <participantID…> (--mode start|create, --type, --title, --message) |
beeper new-chat whatsapp 15551234567 --message "hi" |
archive <chatID> / unarchive <chatID> |
beeper archive '!EXAMPLEchatid:…' |
remind <chatID> <ISO8601> / unremind <chatID> |
beeper remind '!EXAMPLEchatid:…' 2026-08-13T09:30:00 |
upload <file> |
beeper upload ~/Desktop/quote.pdf → uploadID (or just use send --attach) |
focus (--chatID, --messageID, --draft) |
beeper focus --chatID '!EXAMPLEchatid:…' --draft "typed but not sent" |
raw <METHOD> <path> [--data JSON] [--param k=v] |
beeper raw GET /v1/chats --param accountIDs=whatsapp |
Recipes
Reply to a person by name
B=~/.claude/skills/beeper-whatsapp/scripts/beeper
$B accounts # 0. learn the user's accounts (once per session)
$B find-chat "Alice" # 1. get the chatID (confirm which account!)
$B messages '<chatID>' --limit 15 # 2. read the thread for context
# 3. show the user the drafted reply and the chat title, then only if they approve:
$B send '<chatID>' "…"
Morning unread sweep
$B chats --unread --limit 30
# then for anything that matters:
$B messages '<chatID>' --limit 10
Find every mention of a topic across all accounts
$B search-messages "site visit" --limit 20
$B search-messages "payment" --account local-whatsapp_ba_XXXXXXXXXXXX --after 2026-08-01T00:00:00
Pull an attachment out of a chat
$B --json messages '<chatID>' --limit 20 | grep srcURL # or read the formatted output
$B download '<chatID>' <messageID> --out ~/Desktop/
Safety rules
- Never send, edit, react, archive, or set a reminder unless the user explicitly asked for that action in the current conversation. Reading and searching are free; anything that writes to a chat is not.
- Confirm the recipient before sending. State the chat title and which account it is on, and show the exact message text, then wait for approval. A wrong chatID sends a private message to the wrong person irreversibly.
- Never store, echo, forward, or summarize OTPs, PINs, passwords, or 2FA codes found in messages. Skip them silently. Do not write them to any note or file.
- Messages are personal data — do not bulk-export chats into files unless asked.
editonly works on messages the user sent; WhatsApp also enforces a time window.- If a command returns HTTP 403, the token lacks the
writescope — tell the user to re-authorize in Beeper Desktop rather than retrying.
Notes / API quirks
GET /v1/chatshas nolimit, so thechatscommand uses/v1/chats/search(which supportslimit,unreadOnly,inbox,accountIDs).GET /v1/chats/{id}/messageshas nolimiteither — it returns 20 newest-first per page; the CLI auto-paginates viaoldestCursorto satisfy--limit.search-messagesis capped at 20 results per call by the API; page with--cursor.- Attachment
srcURLs are usually already-cachedfile://paths;downloadcopies them directly and only calls the API formxc:///localmxc://URLs. - Message bodies contain light HTML (
<br>,<strong>,&). Formatted output flattens it;--jsonreturns the raw text unchanged. - Reminders take epoch milliseconds; the CLI converts ISO8601 for you (no timezone offset = your local time).
- Token lives at
<skill dir>/token(mode 600) — i.e.~/.claude/skills/beeper-whatsapp/tokenfor the standard install. Override with$BEEPER_ACCESS_TOKEN. Base URL override:$BEEPER_API_URL. - Exit codes:
0success,1API/HTTP error (message on stderr),2Beeper Desktop unreachable.