Manage chats, send messages, and control Beeper Desktop from the command line.
- Authentication - browser-based login with secure keyring storage
- Chat management - list, search, archive, and organize conversations
- Desktop control - focus Beeper window, navigate to chats, pre-fill drafts
- Messaging - send messages, search history, and view conversations
- Assets - upload files, resolve download paths, and stream media to stdout or disk
- Local-first - everything stays on your device via Beeper Desktop’s local API
- Multi-network support - WhatsApp, Signal, Telegram, Discord, Google Messages, Google Chat, Google Voice, Slack, X (Twitter DMs), LinkedIn, Instagram, Messenger (Facebook Messenger)
- Upcoming networks - LINE, Hinge, Snapchat (coming soon)
- Reminders - set and clear chat reminders
Currently supported chat networks:
- Signal
- Telegram
- Discord
- Google Messages
- Google Chat
- Google Voice
- Slack
- X (Twitter DMs)
- Messenger (Facebook Messenger)
brew install salmonumbrella/tap/beeper-cligo install github.com/salmonumbrella/beeper-cli/cmd/beeper@latestgit clone https://github.com/salmonumbrella/beeper-cli
cd beeper-cli
make build && make installOpen Beeper Desktop → Settings → Developers → Enable Local API
beeper auth loginThis opens your browser to authenticate with Beeper and securely stores the token in your system keyring.
Prefer to paste a token directly?
beeper auth add --token "<token>"beeper auth testbeeper accounts # List connected networks
beeper chats list # Show recent chats
beeper messages search "hi" # Search message history
beeper assets serve --url "mxc://example.org/id" --output ./asset.bin
beeper c ls # Short alias: chats list
beeper m s "hi" # Short alias: messages searchThe repo now ships a supported Go client at github.com/salmonumbrella/beeper-cli/pkg/beeper for the local Desktop API.
Install or import it from this module:
go get github.com/salmonumbrella/beeper-cliMinimal example:
package main
import (
"context"
"log"
"github.com/salmonumbrella/beeper-cli/pkg/beeper"
)
func main() {
client := beeper.NewClient("http://localhost:23373", "<token>")
accounts, err := client.ListAccounts(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("accounts=%d", len(accounts))
}Runnable SDK examples:
go run ./examples/fetch_accounts
go run ./examples/fetch_chats
go run ./examples/fetch_messages
go run ./examples/search
go run ./examples/send_messageThe examples read:
BEEPER_TEST_URLBEEPER_TEST_TOKENBEEPER_TEST_CHAT_IDfor message list/send examplesBEEPER_TEST_QUERYfor searchBEEPER_TEST_MESSAGE_TEXTfor sendBEEPER_TEST_RUN_SEND_EXAMPLE=1to opt into the mutating send example smoke
Beeper CLI is optimized for automation. The following features are designed to reduce agent friction:
beeper schema [command path]- JSON schema for commands/flagsbeeper schema --show-key-map- Include machine-readable light short-key mapbeeper examples- Canonical usage examplesbeeper agent guidelines- Store local agent instructionsbeeper agent smoke- Read-only smoke plan for key flowsbeeper agent state- Inspect/clear persisted handle state--handles/BEEPER_AGENT_HANDLES=1- Emit handle tokens like@chat:1and@msg:2beeper agent tools list|run- Run user-defined tool definitionsbeeper agent tools schema|validate- Tool definition schema + validation
The assets serve step in beeper agent smoke expects a seeded BEEPER_TEST_ASSET_URL when you want to exercise asset streaming manually.
Handle tokens persist to a local state file (see Environment Variables) so agents can reference @chat:1 later.
Message commands support --include-chat to bundle chat metadata in JSON output.
Default agent files live under the Beeper CLI config directory:
- Agent state:
agent-state.json(oragent-state.<profile>.json) - Agent trace:
agent-trace.jsonl
Run a local read-only MCP server over stdio:
beeper mcp serve --source db --local-onlyThe initial MCP surface is intentionally small:
list_conversationsread_messagessearch_messages
Privacy posture for the first release:
- local DB reads only by default
- no write-capable MCP tools
- tool inputs and outputs stay in memory for the lifetime of the process
Example MCP client configuration:
{
"command": "beeper",
"args": ["mcp", "serve", "--source", "db", "--local-only", "--max-results", "50"]
}Credentials are checked in this order:
BEEPER_TOKEN,BEEPER_API_TOKEN, orBEEPER_ACCESS_TOKEN--profile/BEEPER_PROFILE- Stored keychain token (
defaultor the only configured profile)
When present, Beeper CLI auto-loads a .env file from its config directory before resolving flag defaults. Override that location with BEEPER_ENV_FILE.
Filter commands by specific chat networks:
# Via flag
beeper chats list --account whatsapp
# Multiple networks
beeper messages search "invoice" --account whatsapp,telegramGet account IDs from beeper accounts, or set a default with BEEPER_ACCOUNT.
If you have multiple auth tokens saved, select one with --profile:
beeper --profile personal chats listYou can set a default profile with BEEPER_PROFILE.
Create shortcuts for frequently used chat IDs:
beeper alias add work "!abc123:beeper.local"
beeper alias add family "!xyz789:beeper.local"
beeper alias listUse aliases anywhere a chat name or ID is accepted:
beeper messages send --to work --text "Shipping now"
beeper focus --chat family --draft "On my way"Reserved keywords are also supported:
beeper messages send --to myself --text "Remember to follow up"BEEPER_TOKEN- API token to use directly from the environmentBEEPER_API_TOKEN- Alias forBEEPER_TOKENBEEPER_ACCESS_TOKEN- Alias forBEEPER_TOKENBEEPER_OUTPUT- Output format:agent(default),text,json,json-pretty, orjsonlBEEPER_COLOR- Color mode:auto(default),always, orneverNO_COLOR- Set to any value to disable colors (standard convention)BEEPER_PROFILE- Default auth profile nameBEEPER_ACCOUNT- Default account filterBEEPER_BASE_URL- Override API base URL (default:http://localhost:23373)BEEPER_URL- Alias forBEEPER_BASE_URLBEEPER_ENV_FILE- Override the auto-loaded dotenv file pathBEEPER_SOURCE- Backend source:api(default),db, orautoBEEPER_DB- Path to Beeperindex.db(implies--source db)BEEPER_AGENT_HANDLES- Emit handle tokens like@chat:1/@msg:2BEEPER_AGENT_STATE- Override the agent state file pathBEEPER_AGENT_TRACE- Enable trace logging (set to a path or truthy value)BEEPER_AGENT_TOOLS_DIR- Override the agent tools directory
For faster local search, you can use the DB backend which reads Beeper’s local SQLite database directly. This is read-only and does not support sending or modifying data.
- CGO-enabled Go toolchain
- SQLite3 library available on your system
# Use DB backend explicitly
beeper --source db messages search "invoice"
# Or point directly at the database (implies db backend)
beeper --db ~/Library/Application\ Support/BeeperTexts/index.db messages search "invoice"
# Include context around search hits
beeper --source db messages search "invoice" --context 2 --window 30m
# Auto mode (API first, DB fallback for search)
beeper --source auto messages search "invoice"
# Inspect all discovered local-store candidates
beeper db discoverYou can add extra discovery hints in the config dir at localstores.json:
{
"database_paths": [
"/path/to/index.db"
],
"max_results": 10
}Tokens are stored securely in your system's keychain:
- macOS: Keychain Access
- Linux: Secret Service (GNOME Keyring, KWallet)
- Windows: Credential Manager
If you prefer to bypass keychain lookups, place BEEPER_TOKEN=... in the auto-loaded dotenv file or export it in your shell.
BEEPER_API_TOKEN and BEEPER_ACCESS_TOKEN work as compatible aliases.
beeper auth login # Authenticate via browser (opens browser)
beeper auth add # Add a token (interactive or --token)
beeper auth add --token "<token>"
beeper auth add work # Add a token with a profile name
beeper auth status # Show auth source, token status, and config paths
beeper auth show # Alias for auth status
beeper auth list # List configured tokens
beeper auth remove <name> # Remove a token
beeper auth test # Test current env or keychain token
beeper auth test work # Test a named tokenbeeper alias list
beeper alias add <name> <chat-id>
beeper alias show <name>
beeper alias remove <name>beeper accounts # List connected chat networksbeeper chats list # List recent chats
beeper chats list --unread # Only unread chats
beeper chats list --all # List ALL chats (pagination via search)
beeper chats list --all --include-muted # Include muted chats
beeper chats list --pinned # Only pinned chats
beeper chats list --muted # Only muted chats
beeper chats list --archived # Only archived chats
beeper chats list --inbox primary # Filter by inbox
beeper chats list --inbox archive # Show archived chats
beeper chats list --account whatsapp # Filter by network
beeper chats list --type dm # Filter by chat type (dm or group)
beeper chats list --cursor <cursor> # Paginate (recent or all)
beeper chats list --direction before # Paginate direction
beeper --source db chats list # List chats via local DB (read-only)
beeper chats get <chat-id> # Get chat details
beeper chats search <query> # Search for chats by name
beeper chats search <query> --limit 50 # Limit search results
beeper chats archive <chat-id> # Archive a chat
beeper chats archive --chat "John" # Archive by name
beeper chats archive --unarchive <chat-id> # Unarchive
beeper chats archive-read # Archive all read chats
beeper chats archive-read --dry-run # Preview what would be archived
beeper chats archive-read --yes # Skip confirmation promptbeeper messages list <chat-id> # List messages in a chat
beeper messages list --chat "John" # List by chat name
beeper messages list <chat-id> --limit 50 # Limit results
beeper messages list --unread # Only unread messages
beeper messages list --cursor <cursor> # Paginate from cursor
beeper messages list --direction before # Paginate direction
beeper messages search <query> # Search all messages
beeper messages search "invoice" --account telegram
beeper messages search "invoice" --after 2024-01-01
beeper messages search "invoice" --after "2h ago"
beeper messages search "invoice" --chat <chat-id>
beeper messages search "invoice" --limit 50
beeper messages send <chat-id> --text "Hello!"
beeper messages send --to "John" --text "Meeting at 3pm"
beeper messages send --to "John" --stdin
beeper messages send --to "John" --file ./message.txt
beeper messages send --to "John" --reply-to <message-id>
beeper messages tail <chat-id> # Follow new messages
beeper messages tail --chat "John" # Follow by chat name
beeper messages tail --interval 2s # Polling interval
beeper messages tail --limit 20 # Show last N before following
beeper messages delete <message-id> --chat "John"
beeper messages delete <message-id> --chat-id <chat-id> --yes
beeper messages read --chat "John"
beeper messages read --chat-id <chat-id>
beeper messages react <message-id> --chat "John" --emoji "👍"
beeper messages react <message-id> --chat-id <chat-id> --emoji "👍"
beeper messages get <message-id> --chat "John" --context 3
beeper messages get <message-id> --chat-id <chat-id>beeper events tail # Stream live Desktop API events
beeper events tail --type message.upserted # Filter by event type
beeper events tail --chat "Team" # Resolve a chat name, then filter server-side
beeper events tail --raw # Show raw websocket envelopes
beeper events tail --timeout 10s -o jsonl # Exit after a bounded live captureThese are shortcuts for common flows (same flags/behavior as the canonical commands):
beeper send|snd ... # = beeper messages send ...
beeper tail|tl ... # = beeper messages tail ...
beeper search|s ... # = beeper messages search ...
beeper reply|r ... # reply to a message (infers chat via handle state / DB)
beeper open|o ... # = beeper focus ... (with extra copy/paste affordances)
beeper unread|ib # = beeper inboxFor token savings, common resource and subcommand aliases are available:
beeper c ls # chats list
beeper c s "team" # chats search
beeper m ls --chat "Team" # messages list
beeper m g "$msg123" --chat "Team" # messages get
beeper ct s "alex" # contacts searchDB backend search options:
beeper --source db messages search "invoice" --days 7
beeper --source db messages search "invoice" --context 2 --window 30m
beeper --source db messages search "invoice" --message-format plain
beeper --source db messages search "invoice" --media image,videobeeper contacts list # List DM contacts
beeper contacts list --limit 50 # Limit results
beeper contacts search <query> # Search contacts by name
beeper contacts search <query> --limit 50 # Limit search resultsBoth contacts search and chats search use fuzzy matching with smart ranking:
- Fuzzy matching - tolerates typos and approximate spelling
beeper contacts search "damnznd" # Finds "Damnzand" beeper chats search "boxing" # Finds "Boxing Club" etc.
- Multi-term search - multiple words use AND logic (all terms must match)
beeper contacts search "john signal" # Matches contacts named John on Signal
- Field-weighted scoring - exact name matches rank higher than participant matches
- Activity sorting - when scores are tied, most recently active results appear first
- Group sender hints - when no DM contacts match, matching participants in group chats are shown on stderr
beeper contacts search "alex" # stderr: hint: "Alex" found as sender in group "Team Chat"
beeper inbox # Show unread messages
beeper inbox --limit 10 # Limit chats shown
beeper inbox --dm # Only DM conversations
beeper inbox --since 1h # Messages since duration
beeper inbox --watch --interval 3s # Stream new messagesbeeper reminders set <chat-id> --at "2024-12-25 10:00"
beeper reminders set --chat "John" --at "2024-12-25 10:00"
beeper reminders set --chat "John" --at "30m"
beeper reminders clear <chat-id>beeper agent guidelines # Show current agent guidelines
beeper agent guidelines --init # Create a starter guidelines file
beeper agent guidelines --path # Print the guidelines file pathGuidelines are stored in your config directory. When using --profile, a profile-specific guidelines file is used.
beeper focus # Bring Beeper to foreground
beeper focus --chat "John" # Open specific chat
beeper focus --chat-id <chat-id> # Open specific chat by ID
beeper focus --chat "John" --draft "Hi!" # Open with pre-filled draft
beeper focus --chat "John" --message <id> # Jump to a message
beeper focus --chat "John" --attachment /path/to/filebeeper version # Print version info
beeper version --check # Check for updatesbeeper status # Show concise CLI health
beeper db discover # List discovered local stores
beeper db info # Inspect local DB + bridge discovery
beeper doctor # Run diagnosticsbeeper completion [bash|zsh|fish|powershell]Human-readable tables with colors and formatting:
$ beeper chats list
ID NAME NETWORK UNREAD LAST ACTIVITY
!abc123... John Smith WhatsApp 3:42 PM
!def456... Team Chat Telegram 5 2:15 PM
!ghi789... Mom iMessage 2 YesterdayCompact, ID-embedded output for automation:
$ beeper chats list -o agent
[chat:!abc123] Team Chat (slack) - 2 unread [group]
[chat:!def456] Alice (whatsapp)
$ beeper messages list --chat "Team Chat" -o agent
[chat:!abc123] Team Chat
10:23 Alex: Standup in 5 [id:$msg1]
10:25 → You: On it [id:$msg2]Machine-readable output:
$ beeper chats list -o json
{
"items": [
{
"id": "!abc123...",
"title": "John Smith",
"network": "WhatsApp",
"unreadCount": 0,
"lastActivity": "2024-12-25T15:42:00Z"
}
],
"total": 1,
"hasMore": false
}Data goes to stdout, errors and progress to stderr for clean piping.
When using -o json, -o jsonl, or -o json-pretty, errors are also emitted as a single JSON object on stderr.
If you want only the array for piping to tools like jq, use --results-only:
beeper chats list -o json --results-only | jq '.[0]'Readable JSON formatting:
beeper chats list -o json-prettyCustomize text output with Go templates (applies per item for list commands):
beeper chats list --format '{{.Title}}\t{{.Network}}'Streaming commands emit one JSON object per line when -o json or -o jsonl is set.
For list outputs, -o jsonl emits one object per line:
# Follow a chat and stream JSON events
beeper messages tail --chat "Team" -o jsonl | jq -c .
# Stream Desktop API websocket events as JSON lines
beeper events tail --type message.upserted -o jsonl | jq -c .
# Watch inbox updates as JSON lines
beeper inbox --watch -o jsonl
# List chats as JSON lines
beeper chats list -o jsonlSample event payloads:
{"chatID":"!abc123:beeper.com","chatTitle":"Team","network":"Slack","chatType":"group","message":"Standup in 5","sender":"Alex","timestamp":"2025-01-12T10:15:00Z"}{"id":"$event:1","chatID":"!abc123:beeper.com","accountID":"acct1","senderID":"@alex:beeper.com","sender":"Alex","senderName":"Alex","text":"Hello","timestamp":"2025-01-12T10:15:00Z","isMe":false}Fields by stream:
beeper inbox --watch -o jsonchatID,chatTitle,network,chatType,message,sender,timestamp
beeper messages tail -o jsonid,chatID,accountID,senderID,sender,senderName,text,timestamp,isMe,attachments,reactions,replyTo
beeper events tail -o jsontype,seq,timestamp,chatID,ids,entries
beeper messages tail still polls chat history on an interval. beeper events tail uses the Desktop API websocket directly, supports --type, --chat, --raw, and reconnects after transient disconnects.
Streaming filters:
# Print only message text from a live stream
beeper messages tail --chat "Team" -o json | jq -r '.text'
# Print event type + chat ID from the websocket stream
beeper events tail -o json | jq -r '"\(.type) \(.chatID)"'
# Print inbox sender + message
beeper inbox --watch -o json | jq -r '"\(.sender): \(.message)"'List output schema (JSON):
beeper chats list -o jsonitems[]with chat fields (id,title,network,unreadCount,lastActivity,type,accountID,isArchived,isMuted,isPinned,preview)total,hasMore,cursor,newestCursor,oldestCursor
beeper messages list -o jsonitems[]with message fields (id,chatID,accountID,senderID,sender,senderName,text,timestamp,isMe,attachments,reactions,replyTo)
beeper messages search -o jsonmessages[]with message fields,chats{}lookup,hasMore,cursor
beeper inbox -o jsonchatID,chatTitle,network,chatType,message,sender,timestamp
beeper contacts list -o jsonid,title,network,lastActivity
beeper contacts search -o jsonitems[]with chat fields (DMs),hasMore,cursor
beeper accounts -o jsonid,network,user
beeper reminders set/clear -o jsonstatus,chat_id,reminder_at(forset)
beeper focus -o jsonstatus,chat_id,draft
# Preview first
beeper chats archive-read --dry-run
# Then execute
beeper chats archive-readbeeper messages send --to "John" --text "Running 5 mins late!"beeper messages tail --chat "Team" --interval 2sbeeper events tail --type message.upserted --timeout 10s -o jsonlbeeper messages search "pdf" --account whatsapp -o json | jq '.messages[]'beeper messages search "invoice" --after "yesterday"beeper --source db messages search "invoice" --media filebeeper alias add work "!abc123:beeper.local"
beeper messages send --to work --text "Shipped"
beeper messages send --to myself --text "Follow up tomorrow"beeper focus --chat "Team" --draft "$(pbpaste)"beeper messages send --to "Project" --exact --text "Shipping now"
beeper chats archive --chat "Family" --first
beeper reminders set --chat "Alex" --dm --at "2024-12-25 10:00"Use -o json for scripting and --dry-run to preview changes:
# Count unread chats
beeper chats list -o json | jq '[.items[] | select(.unreadCount > 0)] | length'
# Get all Telegram chat IDs
beeper chats list --account telegram -o json --query '.items[].id'
# Notify on unread count
unread=$(beeper chats list -o json | jq '[.items[] | select(.unreadCount > 0)] | length')
[ "$unread" -gt 10 ] && osascript -e "display notification \"$unread unread\" with title \"Beeper\""Enable verbose output for troubleshooting:
beeper --debug chats list
# Shows: api request method=GET url=http://localhost:9988/...
# Shows: api response status=200 content_length=1234Filter JSON output with JQ expressions:
# Get only unread chats
beeper chats list -o json --query '.items[] | select(.unreadCount > 0)'
# Extract chat IDs
beeper chats list -o json --query '.items[].id'
# Get chats from specific network
beeper chats list -o json --query '.items[] | select(.network == "Telegram")'All commands support these flags:
--account <id>- Filter by account/network ID--profile <name>- Auth profile name (frombeeper auth list)--base-url <url>- Override API base URL-o, --output <format>- Output format:agent,text,json,json-pretty, orjsonl(default: agent)--color <mode>- Color mode:auto,always, ornever(default: auto)--debug- Enable debug output (shows API requests/responses)--query <expr>- JQ filter expression for JSON output--results-only- For JSON output, return only the results array (strip envelope)--compact-json,--cj- Prefer compact JSON in JSON modes (forces single-line output forjson-pretty)--format <template>- Go template for text output (applies per item for lists)--source <api|db|auto>- Backend source (db is read-only)--db <path>- Path to Beeper index.db (implies db backend)--help- Show help for any command--version- Show version information
Commands that support local --light (--li) auto-switch to -o json when output was not explicitly set, and default --compact-json to true unless --compact-json/--cj was explicitly provided.
In light mode, JSON payloads use short field keys to reduce token usage. Key map:
- chats:
id,ti(title),nw(network),ty(type),ur(unread),ts(last activity),pv(preview) - messages:
id,ch(chat id),sn(sender),tx(text),ts(timestamp),me(is me) - chats messages: same key map as messages (uses MessageLight)
- inbox:
ch(chat id),ti(chat title),sn(sender),tx(message),ts(timestamp)
For programmatic discovery, run:
beeper schema --show-key-map -o json --query '.lightKeyMap'The Beeper Desktop local API does not currently expose endpoints for:
- Muting, pinning, or renaming chats
The CLI now exposes real-time websocket events via beeper events tail, but beeper messages tail remains a polling command because the Desktop API does not currently expose a chat-scoped "follow messages" endpoint with the same payload shape.
Some endpoints have server-side limits:
| Endpoint | Max --limit |
Notes |
|---|---|---|
messages search |
20 | Use --cursor to paginate for more results |
chats search |
20 | Use --cursor to paginate for more results |
contacts search |
20 | Use --cursor to paginate for more results |
The DB backend (--source db) does not have these limits.
Chat IDs start with ! which has special meaning in bash/zsh (history expansion). Prefer single quotes around chat IDs:
# Correct
beeper messages list '!abc123:beeper.local'
beeper chats get '!abc123:beeper.local'
# Also correct (escaped)
beeper messages list "\\!abc123:beeper.local"
beeper chats get "\\!abc123:beeper.local"
# Wrong - may fail or behave unexpectedly
beeper messages list "!abc123:beeper.local"
beeper messages list !abc123:beeper.localIn addition to raw IDs, most commands accept identifiers copied directly from output:
- Chat tokens:
[chat:!abc123] ...orchat:!abc123 - Message tokens:
... [id:$msg123] ...,id:$msg123, or↩$msg123 - Handle refs (when enabled):
@chat:1,@msg:2(also works as[@chat:1]/[@msg:2])
When piping to jq, avoid != which can trigger history expansion. Use jq's truthy check instead:
# Correct - jq treats null as falsy
beeper messages list "!chat:id" -o json | jq '.items[] | select(.text)'
# Problematic in some shells
beeper messages list "!chat:id" -o json | jq '.items[] | select(.text != null)'Generate shell completions for your preferred shell:
# macOS (Homebrew):
beeper completion bash > $(brew --prefix)/etc/bash_completion.d/beeper
# Linux:
beeper completion bash > /etc/bash_completion.d/beeper
# Or source directly:
source <(beeper completion bash)beeper completion zsh > "${fpath[1]}/_beeper"beeper completion fish > ~/.config/fish/completions/beeper.fishbeeper completion powershell | Out-String | Invoke-ExpressionAfter cloning, install git hooks:
make setupThis installs lefthook pre-commit and pre-push hooks for linting and testing.
go test ./pkg/beeper ./examples/...
go test ./internal/cmd ./internal/service -run 'TestLive' -v
go test ./examples -run 'TestLive' -v
beeper auth status
beeper doctor
beeper status
beeper db discover
beeper db info --no-bridge
beeper events tail --type message.upserted --timeout 5s --raw
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | beeper mcp serve --source db --local-only
beeper alias add work "!abc123:beeper.local"
beeper alias list
beeper messages send --to myself --text "ping"
beeper messages search "invoice" --after "2h ago"
beeper --source db messages search "invoice" --media imageThe public Go SDK smoke test is env-gated and skipped by default:
BEEPER_TEST_URL=http://localhost:23373 \
BEEPER_TEST_TOKEN=... \
go test ./pkg/beeper -run 'TestLive' -vTestLiveListAccounts and TestLiveListChats only need the URL/token. TestLiveListMessages also reads BEEPER_TEST_CHAT_ID, and TestLiveSearchMessages reads BEEPER_TEST_QUERY.
There is also a manual GitHub Actions path in .github/workflows/ci.yml via workflow_dispatch with run_live_sdk=true, and it requires BEEPER_TEST_CHAT_ID and BEEPER_TEST_QUERY so the full SDK smoke runs instead of partially skipping.
The CLI now has env-gated subprocess smoke coverage for the shipped command surfaces:
BEEPER_TEST_URL=http://localhost:23373 \
BEEPER_TEST_TOKEN=... \
go test ./internal/cmd ./internal/service -run 'TestLive' -vThat smoke suite exercises:
beeper auth status -o jsonbeeper status -o jsonbeeper db discover -o jsonbeeper db info --no-bridge -o jsonbeeper events tail --timeout 2s --raw -o jsonlbeeper mcp serve --source db --local-onlybeeper assets serve --output ...
The assets serve smoke uploads a tiny temporary text asset first, then verifies the CLI can stream it back to disk.
That same run also includes the low-level live websocket event stream smoke from ./internal/service.
There is also a workflow_dispatch path in .github/workflows/ci.yml with run_live_cli=true.
The runnable SDK example programs are also covered by env-gated smoke tests:
BEEPER_TEST_URL=http://localhost:23373 \
BEEPER_TEST_TOKEN=... \
BEEPER_TEST_CHAT_ID=... \
BEEPER_TEST_QUERY=... \
go test ./examples -run 'TestLive' -vThat covers fetch_accounts, fetch_chats, fetch_messages, and search. send_message is intentionally opt-in because it mutates state:
BEEPER_TEST_URL=http://localhost:23373 \
BEEPER_TEST_TOKEN=... \
BEEPER_TEST_CHAT_ID=... \
BEEPER_TEST_MESSAGE_TEXT="ping" \
BEEPER_TEST_RUN_SEND_EXAMPLE=1 \
go test ./examples -run 'TestLiveSendMessageExample' -vThere is also a workflow_dispatch path in .github/workflows/ci.yml with run_live_examples=true.
That path also requires BEEPER_TEST_CHAT_ID and BEEPER_TEST_QUERY so fetch_messages and search do not silently skip.
The websocket integration test is opt-in and requires a live Desktop API token:
BEEPER_TEST_URL=http://localhost:23373 \
BEEPER_TEST_TOKEN=... \
go test ./internal/service -run 'TestLiveEventStream' -vMIT