Files
vortex/README.md
T

150 lines
6.7 KiB
Markdown
Raw Normal View History

# 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).