mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-03 01:41:52 +00:00
feat: unified messaging TUI MVP — Synapse + 7 mautrix bridges + Textual TUI + MCP draft staging
One-command local stack: cp .env.example .env && make up. Idempotent bootstrap renders Synapse + bridge configs from pristine upstream examples, provisions databases, registrations, double puppeting, and the admin user. Textual TUI with bridge manager, in-terminal QR login, and drafts panel. MCP server exposes read tools and a human-gated draft send workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user