Standalone voice capture app — record in the browser, live transcript via ElevenLabs Scribe realtime, copy to clipboard, optional DeepSeek “Organize”, SQLite history.
| Diagram (SVG) | docs/q-capture.svg |
| Open in Excalidraw | Open diagram |
| Excalidraw source | docs/architecture.excalidraw |
Public gist (for #url=) |
gist.github.com/slubbles/eb39eac2dcd9b15c9b052b9bc66076f8 |
After editing docs/architecture.excalidraw, keep the shareable link working:
# from repo root (requires gh auth with gist scope)
gh gist edit eb39eac2dcd9b15c9b052b9bc66076f8 docs/architecture.excalidraw
# then commit/push the same file so repo + gist stay alignedHow it fits together
- The browser loads the Capture UI from this app and captures microphone audio.
- The app mints a short-lived, single-use ElevenLabs token (API key never goes to the browser).
- The browser streams audio directly to ElevenLabs over a WebSocket; partial/final text streams back into the UI.
- On stop: text is copied to the clipboard and saved to SQLite (
data/capture.db). - Optional Organize sends the text (not audio) to DeepSeek for cleanup only.
- In production, nginx terminates TLS and rate-limits paid routes; the app listens on
127.0.0.1only.
Scribe here is ElevenLabs’ speech-to-text product name (not this repo’s name). Realtime means live streaming STT over a WebSocket while you talk (partials appear as you speak), not “upload a finished file and wait.”
Your ElevenLabs account/API key must be allowed to call their realtime Scribe endpoints (plan/feature access on ElevenLabs’ side). Without that, token mint or the browser WebSocket will fail even if the key string is present.
- Free & open — no accounts, no profiles
- Anyone can record — shared SQLite history on the server
- Captures may be visible to anyone who opens
/history - Operator funds STT/LLM and caps spend (ElevenLabs console + nginx rate limits)
Do not treat this as private note storage.
- One-tap record / stop (push-to-talk style)
- Live partial + committed transcripts
- Autosave while recording (survives tab close mid-session)
- Clipboard copy on finish
- Organize button (optional DeepSeek cleanup)
- History page (
/history) - Production defaults: OpenAPI
/docsand/redocoff unlessENABLE_DOCS=1 - Server-side max text length on autosave / save / organize (default 100 000 chars)
- Python 3.11+
- ElevenLabs API key with Scribe realtime (streaming STT) enabled on the account
- Optional: DeepSeek API key for Organize
cd quick-capture
py -3 -m venv .venv
# Windows
.venv\Scripts\pip install -r requirements.txt
copy .env.example .env
# edit .env — set ELEVENLABS_API_KEY=
.venv\Scripts\uvicorn app.main:app --reload --host 127.0.0.1 --port 8010# macOS / Linux
.venv/bin/pip install -r requirements.txt
cp .env.example .env
.venv/bin/uvicorn app.main:app --reload --host 127.0.0.1 --port 8010Optional local docs: ENABLE_DOCS=1 then open /docs.
Open http://127.0.0.1:8010/ (HTTPS or localhost required for mic access in most browsers).
Health check: GET /health → {"ok":true,"db":true}
| Method | Path | Purpose |
|---|---|---|
| GET | / |
Capture UI |
| GET | /history |
Saved transcripts (shared) |
| GET | /capture/token |
Mint ElevenLabs single-use token |
| POST | /capture/start |
Create pending DB row |
| POST | /capture/autosave |
{ transcript_id, text } |
| POST | /capture/save |
Finalize { text, transcript_id? } |
| POST | /capture/organize |
{ text, transcript_id? } → cleaned text |
Oversized text on autosave/save/organize returns 413 with a clear error.
SQLite file: data/capture.db (gitignored). One shared database for the instance.
Artifacts live under deploy/. Prefer a clean reimage of a dedicated Contabo-style box. Full checklist: deploy/harden-checklist.md.
Reference production shape (this project’s public instance): https://capture.slubbles.com — DNS A record → VPS, nginx reverse-proxy, Let’s Encrypt, app on 127.0.0.1:8010 only. Never commit real API keys (only .env.example is tracked).
sudo useradd --system --home /opt/quick-capture --shell /usr/sbin/nologin capture
sudo mkdir -p /opt/quick-capture
sudo chown capture:capture /opt/quick-capture
sudo -u capture git clone <YOUR_REPO_URL> /opt/quick-capture
cd /opt/quick-capture
sudo -u capture python3 -m venv .venv
sudo -u capture .venv/bin/pip install -r requirements.txt
sudo -u capture cp .env.example .env
sudo -u capture nano .env # set ELEVENLABS_API_KEY= and optional DEEPSEEK_API_KEY=
sudo chmod 600 .env
sudo chown capture:capture .env
sudo -u capture mkdir -p datasudo cp deploy/quick-capture.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now quick-capture
sudo systemctl status quick-capture
# Must show 127.0.0.1:8010 — never public 0.0.0.0
ss -lntp | grep 8010Rate limits in deploy/nginx-quick-capture.conf (paid paths tight, general traffic looser):
| Path | Zone | Rate | Burst |
|---|---|---|---|
/capture/token |
capture_token_rl |
10 req/min | 3 |
/capture/organize |
capture_organize_rl |
6 req/min | 3 |
| everything else | qc_general |
10 req/s | 20 |
client_max_body_size 256k. No Basic Auth (open tool by design).
Recommended cert flow (HTTP first, then Let’s Encrypt):
# 1) Point DNS A record: capture.example.com -> your VPS IP
# 2) Install packages
sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx
# 3) Put limit_req_zone directives in http context (e.g. conf.d), then site:
# - either copy deploy/nginx-quick-capture.conf and replace CAPTURE_DOMAIN
# - or start with an HTTP-only server_name for ACME, then:
sudo certbot --nginx -d capture.example.com
sudo nginx -t && sudo systemctl reload nginxIf you paste the full sample file into sites-available, keep limit_req_zone lines once under /etc/nginx/conf.d/ (http context) if nginx complains about duplicate zones.
curl -sS https://capture.example.com/health
# Expect: {"ok":true,"db":true}
# /docs and /redoc should 404
# GET / should mention shared history / free & opencd /opt/quick-capture
sudo -u capture git pull
sudo systemctl restart quick-capture
curl -sS https://capture.example.com/healthAlso set a hard spend cap in the ElevenLabs (and DeepSeek) consoles.
# from repo root, after venv + pip install -r requirements.txt
.venv/bin/pip install -r requirements-dev.txt # pytest
.venv/bin/pytest -qOffline suite covers health, privacy copy, docs off, start→autosave→save→history, token-without-key, text size limits, and organize error mapping.
quick-capture/
app/
main.py # FastAPI routes, text limits, docs off by default
db.py # SQLite transcripts
elevenlabs_stt.py # token mint
organize.py # DeepSeek cleanup
deploy/
quick-capture.service
nginx-quick-capture.conf
harden-checklist.md
docs/
q-capture.svg # architecture diagram (README)
architecture.excalidraw
static/capture.js
templates/capture.html
templates/history.html
requirements.txt
requirements-dev.txt # pytest
.env.example
- Docker / compose
- File-upload archive pipeline
- Multi-user auth / private per-user history
Private.