Repository navigation
Development
┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Frontend │────▶│ Backend │────▶│ TS Server │
│ React SPA │ │ Express API │ │ WebQuery HTTP │
│ nginx :8080 │ │ Node :3001 │ │ SSH (events) │
└──────────────┘ └──────┬───────┘ └─────────────────┘
│
┌──────┴───────┐
│ SQLite │
│ (Prisma) │
└──────────────┘
│
┌──────┴───────┐
│ Sidecar │
│ Go/Pion │
│ WebRTC :9800│
└──────────────┘
Public: /widget/:token ──▶ SVG / PNG / JSON (no auth)
Four packages in a pnpm monorepo:
| Package | Description |
|---|---|
@ts6/common |
Shared types, constants, utilities — built to dist/ and imported by the backend and the frontend as a real package |
@ts6/backend |
Express API, WebQuery client, bot engine, voice bots, widgets |
@ts6/frontend |
React SPA with Vite, TailwindCSS, Radix UI primitives (shadcn-style) |
sidecar |
Go WebRTC media relay (Pion) for video streaming; not part of the pnpm workspace |
The backend proxies every TeamSpeak API call. The frontend never has direct access to API keys or server credentials — they are stored encrypted and never sent to the browser, which is also why credential fields in the UI appear blank when editing.
A server connection can use up to three independent links to a TeamSpeak server, and they are not all required: WebQuery (HTTP, for essentially every management operation), SSH ServerQuery (only for live events and the file transfer commands — see Getting Started) and the native TeamSpeak client protocol (only for the music bots, which join a channel as ordinary clients).
Frontend — React 19, Vite, TailwindCSS 4, Radix UI (shadcn-style components), TanStack Query + Table, Recharts, Zustand, i18next. The bot flow editor is built into the app and does not use a diagramming library.
Backend — Node.js, Express 5, Prisma 7 (SQLite through the better-sqlite3 adapter), JWT authentication, WebQuery HTTP client, SSH event listener
Voice/audio — a custom TS3 voice protocol client over UDP, Opus encoding, FFmpeg, yt-dlp
Video streaming — Go sidecar using Pion WebRTC v4, with RTCP Sender Reports for A/V sync
Requires Node.js 22.22 or newer (the Docker images use Node 24) and pnpm 9 or newer.
pnpm install
pnpm devThat starts backend and frontend in parallel — backend on :3001, frontend on :5173 via the Vite dev server, which forwards /api and /ws to the backend. It also runs @ts6/common in watch mode, which matters: the backend and the frontend read its compiled dist/, not its source, so a change to common only shows up once it has been built.
Copy .env.example to .env first; JWT_SECRET is the one that has to be set in production (see Configuration).
The checks that exist:
pnpm typecheck # tsc --noEmit across the TypeScript packages
cd packages/sidecar && go test ./... # the Go sidecar's testsThere is no JavaScript/TypeScript test suite; pnpm typecheck is the automated check for the backend, the frontend and common. Everything else is tried against a real TeamSpeak server.
Prisma with SQLite. The schema in packages/backend/prisma/schema.prisma is the single source of truth; there are no migration files, schema changes are pushed. On first run:
cd packages/backend
npx prisma db pushThe Docker images do this automatically on startup, so it is only needed when running directly from source.
Every user-visible change is recorded in the Changelog, in the order it shipped, usually including the reasoning behind it and anything that was ruled out along the way. It is the best starting point for understanding why something is built the way it is — several entries document TeamSpeak server behaviour that is either undocumented or documented incorrectly elsewhere.
Backend, frontend and sidecar each carry their own independent version number, bumped whenever that component changes.
v0.5, last edited 13.10.2026
Getting started
Using the app
Running it
When something is odd
Contributing