mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-02 17:31:41 +00:00
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>
160 lines
7.2 KiB
Markdown
160 lines
7.2 KiB
Markdown
# 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 <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:
|
|
|
|
```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 <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).
|