beeper

Unified messaging via three access tiers — MCP (live API), beeper-cli (authenticated CLI), and direct SQLite→DuckDB (full archive). Search, analyze, and act across all networks. Subsumes beeper-mcp, messaging-world, and signal-messaging.

16 stars

Best use case

beeper is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Unified messaging via three access tiers — MCP (live API), beeper-cli (authenticated CLI), and direct SQLite→DuckDB (full archive). Search, analyze, and act across all networks. Subsumes beeper-mcp, messaging-world, and signal-messaging.

Teams using beeper should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/beeper/SKILL.md --create-dirs "https://raw.githubusercontent.com/plurigrid/asi/main/plugins/asi/skills/beeper/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/beeper/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How beeper Compares

Feature / AgentbeeperStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Unified messaging via three access tiers — MCP (live API), beeper-cli (authenticated CLI), and direct SQLite→DuckDB (full archive). Search, analyze, and act across all networks. Subsumes beeper-mcp, messaging-world, and signal-messaging.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

## CRITICAL: TOKENS PAY RENT

**Every output token must produce actionable value.**

1. **NO PASSIVE SUMMARIES** — extract ACTION ITEMS, DECISIONS, BLOCKERS → create artifacts
2. **NO AGREEMENT WITHOUT IMPLEMENTATION** — "I agree" must be followed by code/file/commit
3. **NO RHETORICAL QUESTIONS** — ask only when you cannot proceed without the answer
4. **Enforcement:** If output contains summary without artifact, STOP and create the artifact first.

---

# Beeper Unified Messaging

Access all messaging networks through three access tiers with increasing depth.

## Three Access Tiers

```
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
```

### Tier Selection Decision Tree

```
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)
```

---

## Tier 1: Desktop API (MCP + HTTP)

### MCP Tools

| 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 |

```bash
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.

### Send Attachments (Programmatic)

MCP `send_message` is text-only. For files:
1. `POST /v1/assets/upload` (multipart) → returns `uploadID`
2. `POST /v1/chats/{chatID}/messages` with `attachment.uploadID`

```bash
skills/beeper/scripts/beeper_send_file.sh '<chat_id>' /path/to/file 'optional text'
```

### User Identity

Beeper/Matrix has TWO identifiers per user:
- **Matrix userID**: `@username:beeper.com` (permanent)
- **Display name**: User-chosen (can differ)

Cross-reference `list_messages` to map `senderID` ↔ `senderName`.

---

## Tier 2: beeper-cli (Authenticated CLI)

```bash
# 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
```

### Connected Accounts

| Account | Network | Identity |
|---------|---------|----------|
| hungryserv | Matrix | @zigger:beeper.com |
| local-telegram | Telegram | @physetermacrocephalus |
| whatsapp | WhatsApp | +14153141554 |
| (system) | iMessage | via macOS bridge |

### Caveats
- **Pagination bug**: cursor can cycle — always deduplicate on chat ID
- **iMessage coverage**: ~22 recent chats; chat.db has 354 DMs
- **WhatsApp**: must use `--account-ids whatsapp`

---

## Tier 3: SQLite→DuckDB (Full Archive)

### Database Locations

| 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 |

### DuckDB Inline Attach

```sql
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);
```

### Unified DM Landscape Query

```sql
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;
```

### Persisted Table

Results materialized in `~/i.duckdb` as `dm_landscape`:
- Signal: 232 threads (8,569 msgs)
- iMessage: 119 threads (9,192 msgs)
- Telegram: 9 threads (156 msgs)
- Total: 360 threads, 17,917 messages

### Schema Reference

**beeper.local_events**: `room_id, event_id, sender, type, state_key, content BLOB, event_ts INT`
- Content BLOB uses `\x22` instead of `"` — use `regexp_extract` not `json_extract_string`

**imessage.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)`.

---

## ACSet Social Graph Schema

```julia
@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
```

### Channel Trit Assignment

```
OUTLOOK (−1)  →  y.shkel@utoronto.ca    (academic, validator)
GMAIL   ( 0)  →  greenteatree01@gmail   (personal, coordinator)
BEEPER  (+1)  →  @greenteatree01:beeper (multi-protocol, generator)
Σ = 0 ✓
```

### Network Topology (50 Active Chats)

- **Signal (18)**: ies, Meta-Org, Cognition Cosmos, Gay.jl, sloppies, Avalon, NYCies + DMs
- **Telegram (15)**: Frontier Tower community channels
- **WhatsApp (6)**: Stanford BJJ, family, salon
- **Discord (5)**: string-parsing-mines, 7-calendar-years + DMs
- **Matrix (3)**: Beeper Dev Community, Vivarium Public

---

## Conversation Branch Tracking

```sql
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).

---

## Signal via Rust MCP (Direct Channel)

For Signal-only access without Beeper bridge:

```json
{
  "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.

---

## GF(3) Triadic Access Pattern

| 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 |

---

## Resource-Aware Processing

**NEVER pull full message history into context.**

1. Check DuckDB first (Tier 3) — zero network cost
2. Use MCP for recent (Tier 1) — bounded by limit param
3. Use beeper-cli for metadata (Tier 2) — chat types, accounts

Context budget: 10,000 chars. Always set `limit` and `dateAfter` params.

---

## MCP Server Config

```json
{
  "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.

---

## Known Issues

- **beeper-cli pagination cycles**: cursor can loop — deduplicate on chat ID
- **Beeper BLOB encoding**: `\x22` escapes — use `regexp_extract`
- **WhatsApp not in account.db**: use beeper-cli for WhatsApp
- **iMessage SQL**: use `<>` not `!=` (shell escaping)

Related Skills

jepsen-testing

16
from plurigrid/asi

Jepsen-style correctness testing for distributed systems under faults (partitions, crashes, clock skew) using concurrent operation histories and formal checkers (linearizability/serializability and Elle-style anomalies). Use when designing, implementing, or running Jepsen tests, or interpreting histories/violations.

Deterministic Color Generation via Metadata Hashing

16
from plurigrid/asi

**Status**: ✅ Production Ready

cyton-dongle

16
from plurigrid/asi

Connect and stream from OpenBCI Cyton/Daisy via USB dongle, including first-time radio channel pairing

asi-transient-agenda

16
from plurigrid/asi

Org-agenda-like transient views for ASI skill orchestration via nbb/squint + Emacs hydra

Topological Superintelligence (TSI)

16
from plurigrid/asi

Compositional AI framework using GF(3) triadic balance and category-theoretic foundations.

zx-calculus

16
from plurigrid/asi

Coecke's ZX-calculus for quantum circuit reasoning via string diagrams with Z-spiders (green) and X-spiders (red)

zulip-cogen

16
from plurigrid/asi

Zulip Cogen Skill 🐸⚡

zls-integration

16
from plurigrid/asi

zls-integration skill

zig

16
from plurigrid/asi

zig skill

zig-syrup-bci

16
from plurigrid/asi

Multimodal BCI pipeline in Zig: DSI-24 EEG, fNIRS mBLL, eye tracking IVT, LSL sync, EDF read/write, GF(3) conservation

zig-programming

16
from plurigrid/asi

zig-programming skill

zeroth-bot

16
from plurigrid/asi

Zeroth Bot - 3D-printed open-source humanoid robot platform for sim-to-real and RL research. Affordable entry point for humanoid robotics.