# Vortex — unified messaging terminal client All your chat networks (WhatsApp, Telegram, Google Messages, X/Twitter, LinkedIn, Discord, Instagram) in one terminal UI, built on Matrix + mautrix bridges, with an MCP server that lets AI agents read your messages and stage drafts — but never send without a human. ``` ┌────────────┐ ┌─────────┐ ┌──────────────────────────────┐ │ Textual TUI│──▶│ Synapse │◀──│ mautrix bridges (7 networks) │ └─────┬──────┘ │ +Postgres│ └──────────────────────────────┘ │ drafts └────┬────┘ ▼ SQLite │ ┌────────────┐ │ │ MCP server │────────┘ read tools + draft staging for AI agents └────────────┘ ``` ## Quickstart ```sh cp .env.example .env # edit MATRIX_PASSWORD / POSTGRES_PASSWORD at minimum make up # builds + provisions + starts everything make tui # open the terminal client (already logged in) make verify # smoke-test the whole stack ``` That is the entire setup. Bootstrap renders the Synapse config, creates databases, generates bridge configs + appservice registrations, registers the admin user, and writes credentials to a shared volume. Re-running `make up` is a no-op. No YAML editing, no token copying. ## Make targets | target | what | |---|---| | `make up` | build + start the full stack, wait for healthy | | `make tui` | run the Textual TUI (interactive) | | `make verify` | smoke tests: health, appservice round-trip, MCP draft gating | | `make logs` | follow all logs | | `make down` | stop containers | | `make nuke` | stop and delete **all data** (volumes included) | ## Logging into bridges Press `b` in the TUI for the bridge screen. It shows each bridge's login state (via the bridge provisioning API). **Enter starts a guided login wizard**: 1. Pick a login method (the bridge advertises its flows: QR, pairing code, phone number, browser cookies, ...). 2. The wizard walks each step — QR codes render right in the terminal, text fields become prompts, and the wizard waits while you scan/confirm. 3. On success the bridge starts syncing your chats into the room list. The wizard talks to each bridge's `/_matrix/provision/v3` API, authenticated with your own Matrix access token — no extra secrets to configure. | network | typical flows | |---|---| | WhatsApp | QR scan or phone pairing code | | Google Messages | Google account / QR from the Messages app | | Telegram | phone number + code. **Requires `TELEGRAM_API_ID`/`TELEGRAM_API_HASH` in `.env`** ([my.telegram.org](https://my.telegram.org)); without them the bridge is skipped. | | X/Twitter | browser cookies (wizard prompts for each value) | | LinkedIn | browser cookies | | Discord | manual only — legacy bridge without the v3 provisioning API. Press `m` to open the bot DM and use `login qr` or `login token`. | | Instagram | browser cookies (bridge runs in `instagram` mode) | `m` on any bridge opens the bot DM for manual commands (`help`, `logout`, relay settings, ...). Passkey/WebAuthn flows aren't supported in a terminal — use the corresponding cookie or QR flow instead. Double puppeting is pre-configured for all bridges — your own messages sent from other devices appear as you. ## The TUI Three panes: room list grouped by network (with `[wa] [tg] [gm] [tw] [li] [dc] [ig] [mx]` labels and unread counts), message timeline, input bar. Keys: `q` quit · `b` bridge manager · `d` drafts panel · `tab` cycle focus · `enter` (in room list) open room. Type `/reply ` to reply to the last message. Media shows as download links. The drafts panel lists drafts staged by AI agents via MCP (live, 2s poll): `e` edit · `s` send · `x` discard. This is the human approval step. Native run (no Docker): `pip install -e ./tui`, then `MATRIX_HOMESERVER=http://localhost:8008 vortex-tui` (needs the shared volume mounted or `CREDENTIALS_FILE`/`DRAFTS_DB`/`STORE_DIR` pointed somewhere useful). ## MCP server for AI agents Runs at `http://localhost:8765/mcp` (streamable HTTP) and over stdio. Tools: `list_rooms`, `read_messages`, `search_messages`, `create_draft`, `list_drafts`, `update_draft`, `discard_draft`, `send_draft`. `create_draft` never sends. `send_draft` is refused while `MCP_ALLOW_SEND=false` (the default) — the agent is told a human must send from the TUI. Drafts appear in the TUI drafts panel live. Claude Code: ```sh claude mcp add --transport http vortex http://localhost:8765/mcp ``` Claude Desktop (`claude_desktop_config.json`), HTTP transport: ```json { "mcpServers": { "vortex": { "url": "http://localhost:8765/mcp" } } } ``` stdio transport (spawns inside the running container): ```json { "mcpServers": { "vortex": { "command": "docker", "args": ["compose", "-f", "/absolute/path/to/vortex/docker-compose.yml", "exec", "-i", "mcp", "vortex-mcp", "--stdio"] } } } ``` ## Environment variables See `.env.example` — every value is documented inline. Highlights: - `BRIDGES_ENABLED` — comma-separated bridge list; compose starts exactly these. After changing it, run `make down && make up`. - `REGISTRATION_SHARED_SECRET` / `DOUBLEPUPPET_SHARED_SECRET` — leave as `auto`; generated once and persisted in the data volume. - `MCP_ALLOW_SEND` — set `true` only if you want AI agents to send without you. ## How provisioning works `bootstrap` (one-shot, runs before Synapse) renders `homeserver.yaml` from `synapse/homeserver.template.yaml`, generates a signing key and secrets, creates one Postgres DB per bridge, patches each bridge's config from the pristine upstream example in `bridges/templates/` (homeserver address, appservice address, Postgres URI, admin permissions, double-puppet secret; Telegram API creds; Instagram mode), and writes matching `registration.yaml` files for Synapse *and* each bridge. `provision` (one-shot, after Synapse is healthy) registers the admin user via the shared-secret admin API and writes `credentials.json` to the shared volume; the TUI and MCP server log in from it with their own devices. Everything is keyed on "file already exists" — re-runs change nothing. ## Troubleshooting - **`The as_token was not accepted` in a bridge log** — registration drift; run `make down && make up` (bootstrap rewrites registrations from persisted tokens). If it persists: `make nuke && make up`. - **Telegram container prints "skipped"** — expected without `TELEGRAM_API_ID`/`TELEGRAM_API_HASH`. Set them, then `make down && make up`. - **Bridge bot doesn't answer** — `docker compose logs `; the bridge must show a successful websocket/appservice connection to Synapse. - **TUI can't log in** — check `docker compose logs provision`; delete `tui-store/session.json` in the shared volume to force a fresh login. - **Changed `MATRIX_SERVER_NAME` after first boot** — Synapse server names are permanent; `make nuke && make up`. - **Ports busy** — Synapse uses host `8008`, MCP uses `MCP_HTTP_PORT` (8765).