From asi
Access, search, analyze, and send messages across Beeper's unified networks using MCP API, beeper-cli, or SQLite/DuckDB archives. Useful for chat automation and cross-network analysis.
How this skill is triggered — by the user, by Claude, or both
Slash command
/asi:beeperThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Every output token must produce actionable value.**
Every output token must produce actionable value.
Access all messaging networks through three access tiers with increasing depth.
Tier 1: Desktop API (MCP + HTTP) — real-time chat; send text + attachments
Tier 2: beeper-cli (Auth) — paginated history, chat type metadata, contacts
Tier 3: SQLite→DuckDB (Archive) — full offline archive, cross-platform analytics
Need to SEND something?
└─ Text only → Tier 1 (MCP send_message)
└─ File/attachment → Tier 1 (Desktop API HTTP: upload + send)
Need chat type (DM vs group)?
└─ Yes → Tier 2 (beeper-cli has type: "single"|"group")
Need full history or cross-platform JOIN?
└─ Yes → Tier 3 (SQLite→DuckDB)
Need real-time / recent?
└─ Yes → Tier 1 (MCP) or Tier 2 (beeper-cli)
Need contact name resolution?
└─ MCP has senderName in messages
└─ beeper-cli has title field on chats
└─ SQLite has m.room.member displayname (most complete)
| Tool | Purpose |
|---|---|
search_chats | Search chats by title/network or participants |
get_chat | Get chat metadata (participants, last activity) |
list_messages | List messages in a chat (paged) |
search_messages | Search messages (literal word match, limit ≤ 20) |
send_message | Send a text message |
focus_app | Open Beeper Desktop, prefill draft text/attachment |
archive_chat | Archive/unarchive a chat |
set_chat_reminder | Set reminder for a chat |
mcp__beeper__search_chats query="contact name"
mcp__beeper__list_messages chatID="..."
mcp__beeper__send_message chatID="..." text="Hello!"
mcp__beeper__focus_app chatID="..." draftText="..." draftAttachmentPath="/path/to/file"
Search is LITERAL WORD MATCHING, not semantic. Use single keywords.
MCP send_message is text-only. For files:
POST /v1/assets/upload (multipart) → returns uploadIDPOST /v1/chats/{chatID}/messages with attachment.uploadIDskills/beeper/scripts/beeper_send_file.sh '<chat_id>' /path/to/file 'optional text'
Beeper/Matrix has TWO identifiers per user:
@username:beeper.com (permanent)Cross-reference list_messages to map senderID ↔ senderName.
# Auth pattern — secret never exposed to context
BEEPER_ACCESS_TOKEN=$(fnox get BEEPER_ACCESS_TOKEN --age-key-file ~/.age/key.txt) \
beeper-cli <command> -o json
# List chats (has type: "single" vs "group")
beeper-cli chats list -o json
# List messages from a chat
beeper-cli messages list -o json --chat-id "..."
# List connected accounts
beeper-cli accounts list -o json
# Filter by account (e.g. WhatsApp)
beeper-cli chats list -o json --account-ids whatsapp
| Account | Network | Identity |
|---|---|---|
| hungryserv | Matrix | @zigger:beeper.com |
| local-telegram | Telegram | @physetermacrocephalus |
| +14153141554 | ||
| (system) | iMessage | via macOS bridge |
--account-ids whatsapp| Database | Path | Contents |
|---|---|---|
| Beeper account.db | ~/Library/Application Support/BeeperTexts/account.db | Signal + Telegram via Matrix |
| Beeper index.db | ~/Library/Application Support/BeeperTexts/index.db | Full-text search index |
| iMessage chat.db | ~/Library/Messages/chat.db | All iMessage/SMS history |
INSTALL sqlite; LOAD sqlite;
ATTACH '~/Library/Application Support/BeeperTexts/account.db'
AS beeper (TYPE sqlite, READ_ONLY);
ATTACH '~/Library/Messages/chat.db'
AS imessage (TYPE sqlite, READ_ONLY);
WITH
beeper_dms AS (
SELECT le.room_id,
COUNT(*) FILTER (WHERE le.sender = '@zigger:beeper.com') AS my_msg_count,
COUNT(DISTINCT le.sender) AS sender_count,
CASE WHEN le.room_id LIKE '%.local-signal.%' THEN 'Signal'
WHEN le.room_id LIKE '%.local-telegram.%' THEN 'Telegram'
ELSE 'Other' END AS network
FROM beeper.local_events le WHERE le.type = 'm.room.message'
GROUP BY le.room_id
HAVING COUNT(*) FILTER (WHERE le.sender = '@zigger:beeper.com') >= 3
AND COUNT(DISTINCT le.sender) <= 2
),
beeper_names AS (
SELECT DISTINCT ON (le.room_id) le.room_id,
regexp_extract(CAST(le.content AS VARCHAR),
'displayname\\x22:\\x22([^\\]+)', 1) AS display_name
FROM beeper.local_events le
WHERE le.type = 'm.room.member'
AND le.sender <> '@zigger:beeper.com'
AND le.state_key <> '@zigger:beeper.com'
AND CAST(le.content AS VARCHAR) NOT LIKE '%bridge bot%'
ORDER BY le.room_id, le.event_ts DESC
),
beeper_final AS (
SELECT bd.room_id AS id,
COALESCE(NULLIF(bn.display_name, ''), 'unnamed') AS contact_name,
bd.my_msg_count, bd.network, 'beeper_db' AS source
FROM beeper_dms bd LEFT JOIN beeper_names bn ON bd.room_id = bn.room_id
),
imessage_dms AS (
SELECT c.chat_identifier AS id,
COALESCE(NULLIF(c.display_name, ''), c.chat_identifier) AS contact_name,
COUNT(*) AS my_msg_count, 'iMessage' AS network, 'imessage_db' AS source
FROM imessage.chat c
JOIN imessage.chat_message_join cmj ON c.ROWID = cmj.chat_id
JOIN imessage.message m ON cmj.message_id = m.ROWID
WHERE m.is_from_me = 1 AND c.style <> 43
GROUP BY c.ROWID, c.chat_identifier, c.display_name HAVING COUNT(*) >= 3
)
SELECT * FROM beeper_final UNION ALL SELECT * FROM imessage_dms
ORDER BY my_msg_count DESC;
Results materialized in ~/i.duckdb as dm_landscape:
beeper.local_events: room_id, event_id, sender, type, state_key, content BLOB, event_ts INT
\x22 instead of " — use regexp_extract not json_extract_stringimessage.chat: ROWID, chat_identifier, display_name, style (43=group)
imessage.message: ROWID, text, is_from_me, date, handle_id
Join via imessage.chat_message_join(chat_id, message_id).
@present SchMessagingWorld(FreeSchema) begin
Identity::Ob; Channel::Ob; Account::Ob; Contact::Ob; Conversation::Ob; Network::Ob
identity_account::Hom(Account, Identity)
account_channel::Hom(Account, Channel)
contact_account::Hom(Contact, Account)
conv_account::Hom(Conversation, Account)
contact_network::Hom(Contact, Network)
conv_network::Hom(Conversation, Network)
Email::AttrType; Name::AttrType; Trit::AttrType; Count::AttrType
contact_email::Attr(Contact, Email)
contact_name::Attr(Contact, Name)
channel_trit::Attr(Channel, Trit)
contact_count::Attr(Contact, Count)
end
OUTLOOK (−1) → [email protected] (academic, validator)
GMAIL ( 0) → greenteatree01@gmail (personal, coordinator)
BEEPER (+1) → @greenteatree01:beeper (multi-protocol, generator)
Σ = 0 ✓
CREATE TABLE IF NOT EXISTS beeper_conversation_branches (
branch_id VARCHAR PRIMARY KEY,
chat_id VARCHAR NOT NULL,
parent_branch_id VARCHAR,
topic VARCHAR NOT NULL,
first_message_id VARCHAR,
last_message_id VARCHAR,
status VARCHAR DEFAULT 'open', -- 'open', 'resolved', 'merged', 'stale'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
resolved_at TIMESTAMP
);
CREATE TABLE IF NOT EXISTS beeper_branch_transitions (
from_branch VARCHAR,
to_branch VARCHAR,
transition_type VARCHAR, -- 'fork', 'merge', 'abandon', 'resolve'
message_id VARCHAR,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (from_branch, to_branch, message_id)
);
Branch rules: Fork (one message → multiple topics), Merge (response addresses multiple), Resolve (explicit closure), Abandon (7 days inactive → stale).
For Signal-only access without Beeper bridge:
{
"signal": {
"command": "cargo",
"args": ["run", "--release", "--example", "signal-server-stdio"],
"cwd": "/Users/alice/signal-mcp",
"env": { "RUST_LOG": "signal_mcp=info" }
}
}
Capabilities: send/receive messages, list conversations, handle attachments.
Use read_mcp_resource with signal:// URIs.
| Trit | Role | Tier | Action |
|---|---|---|---|
| MINUS (−1) | Validator | SQLite→DuckDB | Verify data exists locally before fetching |
| ERGODIC (0) | Coordinator | beeper-cli | Metadata, routing, account selection |
| PLUS (+1) | Generator | MCP | Send messages, fetch fresh data |
NEVER pull full message history into context.
Context budget: 10,000 chars. Always set limit and dateAfter params.
{
"beeper": {
"command": "/bin/sh",
"args": ["-c", "BEEPER_ACCESS_TOKEN=$(fnox get BEEPER_ACCESS_TOKEN --age-key-file ~/.age/key.txt) exec npx -y @beeper/desktop-mcp"]
}
}
Requires: fnox, age key at ~/.age/key.txt, npx in PATH, Beeper Desktop running.
\x22 escapes — use regexp_extract<> not != (shell escaping)npx claudepluginhub plurigrid/asi --plugin asiSearches and browses local Beeper chat history (threads, messages, full-text search) via the beeper-cli tool. Read-only access to unified chat data from WhatsApp, Telegram, Signal, iMessage, Discord, etc.
Send and read messages across all channels. Routes based on arguments — whatsapp, email, slack, telegram, discord, notion, or natural language like "send [msg] to [contact]". WhatsApp via mcp__whatsapp__* (Baileys bridge).
Reads conversations and new messages from macOS Messages SQLite database, sends messages via AppleScript. For messaging automation and integration.