roadrunner (rr)
Use rr when the user explicitly wants to operate Beeper Desktop via the local API (send, search, list chats/messages, reminders, focus).
Prefer --agent for agent use (forces JSON, envelope, no-input, readonly).
Safety
- •Default to read-only commands unless the user explicitly requests a mutation in this turn.
- •Require explicit recipient (chat ID) and message text before sending.
- •Confirm or ask a clarifying question if the chat ID is ambiguous.
- •Never paste raw rr command output (JSON dumps, chat lists, etc.) into outgoing messages. Treat tool output as private; summarize or extract only what the user needs.
- •Use
--agentfor safe agent defaults:rr --agent --enable-commands=chats,messages,status chats list - •Use
--readonlyto block writes:rr --readonly chats list --json - •Use
--enable-commandsto allowlist:rr --enable-commands=chats,messages chats list --json - •Use
--envelopefor structured errors:rr --json --envelope chats get "!chatid" - •Envelope errors may include
error.hintwith next-step guidance for safe retries. - •Never request, paste, or store raw auth tokens in chat. If auth is missing, ask the user to configure it locally.
- •If sending message text through a shell, avoid interpolation/expansion (e.g.
$100/monthor!). Prefer--stdin <<'EOF' ... EOFfor safe literals.
Setup (once)
- •
rr auth set --stdin(recommended; token saved to~/.config/beeper/config.json) - •
rr auth status --check - •
rr doctor
Common commands
- •List accounts:
rr accounts list --json - •Capabilities:
rr capabilities --json - •Describe command/flags:
rr describe messages send --json - •Connect metadata:
rr connect info --json - •Live websocket events (experimental):
rr events tail --all --stop-after 30s --json - •List contacts:
rr contacts list "<account-id>" --json - •Search contacts:
rr contacts search "<account-id>" "Alice" --json - •Search contacts (flag):
rr contacts search "Alice" --account-id="<account-id>" --json - •Resolve contact:
rr contacts resolve "<account-id>" "Alice" --json - •Resolve contact (flag):
rr contacts resolve "Alice" --account-id="<account-id>" --json - •List chats:
rr chats list --json - •List chats (JSON Lines):
rr chats list --jsonl - •Search chats:
rr chats search "John" --json - •Search chats (filters):
rr chats search --inbox=primary --unread-only --json - •Search chats (activity):
rr chats search --last-activity-after="2024-07-01T00:00:00Z" --json - •Search by participant name:
rr chats search "Jamie" --scope=participants --json - •Resolve chat:
rr chats resolve "Jamie" --json - •Get chat:
rr chats get "!chatid:beeper.com" --json - •Get chat (bounded participants):
rr chats get "!chatid:beeper.com" --max-participant-count=50 --json - •Start/resolve DM from merged contact hints:
rr chats start "<account-id>" --email "alice@example.com" --full-name "Alice" --json - •Default account for commands:
rr --account="imessage:+123" chats list --json - •List messages:
rr messages list "!chatid:beeper.com" --json - •List messages (all pages):
rr messages list "!chatid:beeper.com" --all --max-items=1000 --json - •List messages (download media):
rr messages list "!chatid:beeper.com" --download-media --download-dir ./media --json - •Search messages:
rr messages search "dinner" --json - •Search messages (JSON Lines):
rr messages search "dinner" --jsonl - •Search messages (all pages):
rr messages search "dinner" --all --max-items=1000 --json - •Search messages (filters):
rr messages search --sender=me --date-after="2024-07-01T00:00:00Z" --media-types=image --json - •Add/remove reaction:
rr messages react "!chatid:beeper.com" "<message-id>" "👍" --json/rr messages unreact "!chatid:beeper.com" "<message-id>" "👍" --json - •Tail messages (polling):
rr messages tail "!chatid:beeper.com" --interval 2s --stop-after 30s --json - •Wait for message:
rr messages wait --chat-id="!chatid:beeper.com" --contains "deploy" --wait-timeout 2m --json - •Message context:
rr messages context "!chatid:beeper.com" "<sortKey>" --before 5 --after 2 --json - •Draft message (pre-fill without sending):
rr focus --chat-id="!chatid:beeper.com" --draft-text="Hello!" - •Draft message from file:
rr focus --chat-id="!chatid:beeper.com" --draft-text-file ./draft.txt - •Draft with attachment:
rr focus --chat-id="!chatid:beeper.com" --draft-attachment="/path/to/file.jpg" - •Download attachment:
rr assets download "mxc://example.org/abc123" --dest "./attachment.jpg" - •Stream attachment bytes:
rr assets serve "mxc://example.org/abc123" --dest "./attachment.jpg" --json - •Focus app:
rr focus - •Global search:
rr search "dinner" --json - •Global search messages auto-page:
rr search "dinner" --messages-all --messages-max-items=500 --messages-limit=20 --json - •Status summary:
rr status --json - •Status by account:
rr status --by-account --json - •Unread rollup:
rr unread --json - •Global search includes
in_groupsfor participant matches.
Mutations (explicit user request only)
- •Message send:
rr messages send "!chatid:beeper.com" "Hello!" - •Message edit:
rr messages edit "!chatid:beeper.com" "<message-id>" "Updated text" - •Message react/unreact:
rr messages react "!chatid:beeper.com" "<message-id>" "👍"/rr messages unreact "!chatid:beeper.com" "<message-id>" "👍" - •Upload + send file:
rr messages send-file "!chatid:beeper.com" ./photo.jpg "See attached" - •Create chat:
rr chats create "<account-id>" --participant "<user-id>" - •Start chat from merged contact hints:
rr chats start "<account-id>" --email "alice@example.com" --full-name "Alice" - •Archive/unarchive:
rr chats archive "!chatid:beeper.com"/rr chats archive "!chatid:beeper.com" --unarchive - •Reminder mutations:
rr reminders set "!chatid:beeper.com" "2h"/rr reminders clear "!chatid:beeper.com" - •Asset uploads:
rr assets upload ./photo.jpg/rr assets upload-base64 --content-file ./photo.b64 - •For retries on non-idempotent writes, use
--request-idand prefer--dedupe-window. - •Use
--dry-runto validate mutating requests without API write side effects.
Pagination
- •Auto-page chats list/search:
rr chats list --all --max-items=1000 --json/rr chats search "alice" --all --max-items=1000 --json - •Auto-page messages list/search:
rr messages list "!chatid:beeper.com" --all --max-items=1000 --json/rr messages search "deploy" --all --max-items=1000 --json - •Chats:
rr chats list --cursor="<oldestCursor>" --direction=before --json - •Messages list:
rr messages list "!chatid:beeper.com" --cursor="<sortKey>" --direction=before --json - •Messages search (max 20):
rr messages search "project" --limit=20 --json - •Messages search page:
rr messages search "project" --cursor="<cursor>" --direction=before --json - •Global search message paging (max 20):
rr search "dinner" --messages-limit=20 --json - •Global search message page:
rr search "dinner" --messages-cursor="<cursor>" --messages-direction=before --json
Notes
- •Requires Beeper Desktop running; token from app settings.
- •Token is stored in
~/.config/beeper/config.jsonviarr auth set(recommended).BEEPER_TOKENoverrides the config file. - •
BEEPER_ACCOUNTsets the default account ID (aliases supported). - •
rr auth status --checkprefers OAuth introspection (/oauth/introspect) when available and falls back to account-list validation on older builds. - •Message search is literal word match (not semantic).
- •
rr contacts resolveis strict and fails on ambiguous names; resolve by ID aftercontacts searchwhen needed. - •If a DM title shows your own Matrix ID, use
--scope=participantsto find by name. - •JSON output includes
display_namefor single chats (derived from participants). - •Message JSON includes
message_type,linked_message_id,is_sender,is_unread,attachments, andreactions. - •
downloaded_attachmentsis only populated when--download-mediais used. - •
rr messages sendreturnspending_message_id(temporary ID). - •Account
networkmay be missing in newer API builds;rrfalls back to"unknown"in summaries/search output. - •
rr assets servewrites raw bytes to stdout unless--destis provided. - •
--chatdoes exact matching and fails on ambiguous matches. - •Attachment overrides require
--attachment-upload-id; set--attachment-widthand--attachment-heighttogether. - •
--allhas a safety cap (default 500 items, max 5000); use--max-itemsto tune it. - •Prefer
--jsonor--jsonl(and--no-input) for automation. - •
--jsonlemits one JSON object per line and is supported on high-volume list/search commands. - •
--dry-run/BEEPER_DRY_RUNvalidates mutating command inputs and prints preview output without sending write API requests. - •
BEEPER_URLoverrides API base URL;BEEPER_TIMEOUTsets timeout in seconds. - •JSON/Plain output goes to stdout; errors/hints go to stderr.
- •Destructive commands prompt unless
--force;--no-input/BEEPER_NO_INPUTfails without--force. - •Use
--fail-if-emptyon list/search commands to exit with code 1 if no results. - •Use
--fieldswith--plainto select columns (comma-separated). - •In bash/zsh,
!triggers history expansion. Prefer single quotes, or disable history expansion (set +Hin bash,setopt NO_HIST_EXPANDin zsh). - •
rr version --jsonreturnsfeaturesarray for capability discovery. - •
rr capabilities --jsonreturns full CLI capability metadata. - •
rr events taildepends on experimental/v1/wssupport in Beeper Desktop; fall back torr messages tailwhen unavailable. - •Envelope error codes:
AUTH_ERROR,NOT_FOUND,VALIDATION_ERROR,CONNECTION_ERROR,INTERNAL_ERROR. - •Retry policy: retry
CONNECTION_ERRORwith backoff; do not blind-retryAUTH_ERROR/VALIDATION_ERROR; refresh IDs before retryingNOT_FOUND. - •Non-idempotent writes:
messages send,messages send-file,chats create,chats start,assets upload,assets upload-base64. - •Use
--request-id/BEEPER_REQUEST_IDto tag envelope metadata for cross-retry attempt tracing. - •Use
--dedupe-window/BEEPER_DEDUPE_WINDOWto block duplicate non-idempotent writes with repeated request IDs. - •Local smoke check:
make test-agent-smoke.