mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-02 17:31:41 +00:00
150 lines
6.7 KiB
Markdown
150 lines
6.7 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
|
||
|
|
|
||
|
|
Open the TUI, press `b` for the bridge screen, pick a network — it opens a DM
|
||
|
|
with that bridge's bot. Send `help` to list commands and `login` to start the
|
||
|
|
login flow. QR codes sent by bots render in-terminal.
|
||
|
|
|
||
|
|
| network | bot | login flow |
|
||
|
|
|---|---|---|
|
||
|
|
| WhatsApp | `@whatsappbot` | `login qr` (scan with phone) or `login pairing-code` |
|
||
|
|
| Google Messages | `@gmessagesbot` | `login` → QR scan from the Messages app |
|
||
|
|
| Telegram | `@telegrambot` | `login` → phone number + code. **Requires `TELEGRAM_API_ID`/`TELEGRAM_API_HASH` in `.env`** (get them at [my.telegram.org](https://my.telegram.org)). Without them the bridge is skipped with a log line. |
|
||
|
|
| X/Twitter | `@twitterbot` | `login cookies` — paste browser cookies |
|
||
|
|
| LinkedIn | `@linkedinbot` | `login cookies` |
|
||
|
|
| Discord | `@discordbot` | `login qr` (scan with Discord app) or `login token` |
|
||
|
|
| Instagram | `@metabot` | `login cookies` (bridge runs in `instagram` mode) |
|
||
|
|
|
||
|
|
Cookie flows: the bot explains exactly which cookies to paste. 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).
|