What makes bpr different

A thin adapter over the official Beeper SDK, shaped end to end around how an LLM agent actually reads, decides, and acts.

# Short stable ids — raw Matrix ids never escape

Every chat, message, account, and contact is shown as a short prefixed handle (CHT-, MSG-, ACC-, USR-). The opaque Beeper/Matrix id is accepted as input and stored behind the handle, but it is never displayed — so a multi-line room id can't blow up an agent's context, and the same handle round-trips into the next command unchanged.

$ bpr chats get CHT-7
CHT-7 Design Team slack · 5 people · 12 unread
latest: MSG-4a "Latest invoice attached →" 2d ago

⇄ Adaptive output — table on a TTY, JSON when piped

bpr detects whether stdout is a terminal. A human at a prompt gets a dense, aligned table; the moment output is piped or redirected it becomes clean JSON, with no flag to remember. The pipe itself is the signal — though --json forces it either way.

$ bpr chats list
ID NAME NETWORK UNREAD
CHT-7 Design Team slack 12
CHT-3 Jane Designer whatsapp 2
 
$ bpr chats list | jq length
2

⌕ Compact query syntax — filters mixed with words

search and count take literal search words alongside compact filters: from:me, has:media|image|video|link|file, is:group|single, and after:/before: accepting a date, an RFC3339 time, or a relative offset like 7d, 2w, or 12h. Results are ranked by relevance, so the most useful match lands first.

$ bpr messages search "dinner from:me has:image after:7d"
@1 MSG-8f CHT-3 you "dinner pics 🍝" 3d ago 🖼
@2 MSG-2c CHT-7 you "dinner spot tonight?" 6d ago
2 messages · ranked by relevance
 
$ bpr count messages "is:group has:link"
47

! Structured errors — code, hint, exit code

Failures aren't a stack trace. Each carries a stable code, a human message, an actionable hint, and a category — and maps to a deterministic exit code, so a script or agent can branch on the failure instead of scraping text.

$ bpr send CHT-99 "hi"
error: no chat matches CHT-99
code: chat_not_found
hint: run `bpr chats list` to see valid ids
$ echo $?
4

◆ The prime contract — one command to orient

bpr prime emits the full agent-orientation document: every command, the id shapes, the output rules, the query grammar, and the error model. Pipe it as JSON straight into a system prompt, and the agent knows the whole surface before its first real call.

$ bpr prime --json | jq '.commands | keys'
[
"chats", "messages", "count", "send",
"open", "pick", "watch", "takeout",
"accounts", "contacts", "doctor", "auth"
]

◷ Watch without a daemon — pull-style updates

Subscribe to a chat, and watch check returns only the messages newer than your last check before advancing a watermark stored in your config. There is no background process: bpr stays strictly one-shot, so a cron job or an agent loop owns the cadence.

$ bpr watch add CHT-7
watching Design Team — watermark set to now
$ bpr watch check
CHT-7 2 new
MSG-6d Jane "ready for review?" just now
MSG-6e Jane "ping when you can" just now

How it compares

CapabilitybprRaw Beeper SDKGeneric chat CLI
Stable short idsBuilt in (CHT-/MSG-…)Raw Matrix idsVaries
JSON on pipe, table on TTYAutomaticYou write the codeFlag, if any
Compact query filtersfrom:/has:/is:/after:Rarely
Structured errors + exit codescode · hint · categoryGo errorsText on stderr
Agent orientation contractbpr prime
Daemonless watchPull-style watermarkBuild it yourselfLong-running

Install

go install github.com/doublej/bpr@latest

Read the source on GitHub →