chat.spboucher.ai
Author: Simon-Pierre Boucher Contact: contact@spboucher.ai Production: https://chat.spboucher.ai
A private, universal chat interface over the entire OpenRouter ecosystem — one login, one server-side API key, 400+ models behind a single catalog, full conversation persistence with branching and per-message model attribution. Designed mobile-first as an installable PWA: a personal ChatGPT/Claude-class product, not an API demo.
Highlights
- One gateway, hundreds of models — the catalog is synced dynamically from OpenRouter into SQLite (never hand-maintained); removed models are marked
Unavailable, historical conversations stay intact forever. - Real streaming — normalized SSE event protocol (
generation.start,content.delta,reasoning.delta,usage, …) with 15 s keep-alive pings that survive ngrok and mobile radio sleep. Stop truly aborts the upstream OpenRouter stream. - Branching & regeneration — regenerating never destroys an answer; siblings live side by side with ‹ 1/2 › navigation, so the same prompt can be compared across models.
- Generation state machine —
queued → starting → streaming → completed | cancelled | failed, persisted per generation; a dropped mobile connection resyncs from the server. - Usage & cost tracking — prompt/completion/reasoning/cached tokens and reported cost per generation, with a Today / 7d / 30d / All-time dashboard broken down by model.
- Instrument Panel design — ink/graphite surfaces, one teal accent for "alive", one amber accent for "cost", mono type for model IDs and meters, and the signature Model Rail cartridge attached to the composer (provider glyph · mono model ID · live context meter · price-per-1M chip).
- Security posture for a public tunnel — argon2id single-user auth, DB-backed sessions, aggressive login rate limiting,
Secure/HttpOnly/SameSitecookies, CSP + security headers, zero unauthenticated API surface.
Architecture
Browser (phone or desktop, PWA)
│ POST /api/chat (SSE)
▼
Next.js 16 backend — node m4m64a (Apple Silicon, macOS)
│ src/lib/openrouter/ (single client module, normalized errors/streaming)
▼
OpenRouter API ──► Anthropic · OpenAI · Google · Meta · Qwen · DeepSeek · Mistral · …- Runtime: single Node.js process (API + UI), port 3000, exposed via ngrok at
chat.spboucher.ai - Database: SQLite (better-sqlite3, WAL) — conversations, message tree, generations, usage, model catalog cache, sessions
- Supervision: three launchd agents (
app,ngrok, nightlybackupat 03:30 with 14-day retention)
Local development
cp .env.example .env # fill in OPENROUTER_API_KEY
npm install
npm run set-password <username> [password] # generates a strong one if omitted
npm run devDeployment (m4m64a)
rsync -az --delete --exclude node_modules --exclude .next --exclude data --exclude .env \
~/Desktop/chat-spboucher/ M4M64a:apps/chat.spboucher.ai/
ssh M4M64a '~/apps/chat.spboucher.ai/ops/deploy.sh'| launchd agent | Role |
|---|---|
ai.spboucher.chat.app |
npm run start on port 3000, KeepAlive |
ai.spboucher.chat.ngrok |
ngrok http --url=chat.spboucher.ai 3000, KeepAlive |
ai.spboucher.chat.backup |
nightly SQLite backup 03:30, 14-day retention |
API surface
All routes require a session cookie except POST /api/auth/login.
| Endpoint | Purpose |
|---|---|
POST /api/chat |
send a message or regenerate ({regenerateOf}) — returns a normalized SSE stream |
POST /api/generations/:id/cancel |
abort the upstream OpenRouter generation |
GET /api/models · POST /api/models/sync |
cached catalog · manual refresh |
POST /api/models/prefs |
favorites / pinned models |
GET/PATCH/DELETE /api/conversations[/:id] |
conversation CRUD, branch leaf selection |
GET /api/usage?period=today|7d|30d|all |
usage & cost dashboard data |
License
Private software — © Simon-Pierre Boucher. All rights reserved.
Simon-Pierre Boucher · contact@spboucher.ai · chat.spboucher.ai