Skip to content

Architecture

Components

┌─────────────────────────────┐     ┌──────────────────────┐     ┌────────────────────┐
│         Your Mac             │     │         Relay        │     │  Telegram Mini App  │
│                             │     │                      │     │                    │
│  Teleportus daemon          │     │                      │     │                    │
│  ├── screen capture         │ WS  │  ├── session router  │ WS  │  ├── WebSocket      │
│  ├── input controller       │────►│  ├── frame fanout    │────►│  ├── JPEG renderer  │
│  ├── audio capture / play   │◄────│  └── auth check      │◄────│  └── input events   │
│  ├── camera capture         │     │                      │     │                    │
│  ├── voice transcriber      │     └──────────────────────┘     └────────────────────┘
│  └── AI task runner         │
└─────────────────────────────┘

Daemon

The daemon is a Node.js/Bun process that runs on your Mac.

Screen capture — spawns a precompiled Swift binary that uses ScreenCaptureKit to grab a JPEG of the main display. The binary is compiled on first launch using the system swiftc compiler. Capture is skipped while no viewers are connected.

Input controller — dispatches mouse clicks, mouse moves, keyboard events, and scroll wheel events. Text input uses the clipboard + Cmd+V to support arbitrary Unicode without key code mapping.

Audio — two native Swift binaries capture system audio (output) and play microphone audio (input). Both are universal binaries (arm64 + x86_64).

Camera — a third Swift binary streams the default camera via AVFoundation. Camera frames are never forwarded to view-only share guests.

Voice transcription — audio from the Mini App is transcribed locally using Whisper, a small voice model built into the daemon that runs right on your Mac. No audio leaves the machine for transcription.

AI runner — receives natural-language instructions from the bot and executes them as multi-step actions using Claude. The AI has access to the screen capture, the input controller, and a shell runner.

Relay

The relay runs as a long-running HTTP/WebSocket server, deployable to any host that supports WebSockets.

WebSocket router — manages one room per SESSION_TOKEN. Both the daemon and the Mini App connect to the same room by token. The relay never interprets frames — it buffers the last frame and fans it out to connected viewers.

Authentication — Mini App connections present Telegram's signed login data. The relay verifies its signature to confirm the viewer is a real Telegram user. Share guests are verified separately by their share token.

Frame caching — the last frame per room is cached in memory so new viewers get an instant picture without waiting for the next capture tick.

Daemon staleness — if a daemon's WebSocket goes silent for 45 seconds, its slot is considered stale and the next registration takes over. This handles sleep/wake cycles gracefully.

Mini App

The Mini App is served as a Telegram Web App. It:

  • Opens a WebSocket to the relay using the session URL parameters
  • Renders incoming JPEG frames
  • Translates touch events to input messages (click, scroll, key, type)
  • Handles the screen/camera toggle by switching the visual source
  • Records microphone audio and sends it to the daemon via the relay

Data flow

capture tick (every 1/FPS seconds)
  daemon  →  JPEG binary frame  →  relay  →  broadcast to all room clients

user tap on Mini App
  Mini App  →  JSON input msg  →  relay  →  daemon  →  input dispatch

voice message
  Mini App  →  audio blob  →  relay  →  daemon  →  Whisper → text
  → same as typing the transcription

AI task (text to bot)
  Telegram bot  →  relay  →  daemon  →  AI task runner
  → screen caps + input actions as side effects

Security model

  • Every Mini App connection is verified by Telegram's signed login data — no anonymous access
  • Share guests get a separate token; they are forwarded screen frames only
  • Camera frames are never sent to share guests
  • The relay stores no frames on disk — only last-frame in memory, evicted when room is empty
  • Session tokens are randomly generated per-user; invalidated on /revoke