Files
Prad NukalaandClaude Fable 5 760d9db800 feat: guided bridge login wizard in TUI via bridgev2 provisioning API
Bootstrap now enables each bridge's provisioning API (persisted shared
secret, matrix-token auth) and publishes /shared/bridges.json. The TUI
bridge screen shows per-bridge login state and launches a step-driven
wizard: flow picker, sequential input prompts (phone/cookies/password),
in-terminal QR rendering with long-poll wait, cancel support. Discord
(legacy bridge, no v3 API) keeps the bot-DM path via 'm'. verify.sh
gains a provisioning API smoke check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 12:36:26 -04:00

7.2 KiB

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

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); 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 <text> 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:

claude mcp add --transport http vortex http://localhost:8765/mcp

Claude Desktop (claude_desktop_config.json), HTTP transport:

{ "mcpServers": { "vortex": { "url": "http://localhost:8765/mcp" } } }

stdio transport (spawns inside the running container):

{ "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 answerdocker compose logs <bridge>; 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).