CainAble
Two-machine presenting from one Chrome session.
- Cain (presenter laptop): Chrome extension. Mirrors the current tab, captures call audio (Meet / Zoom web) and your mic, and sends the focused text box on a hotkey.
- Able (laptop on the big monitor): a web page served by the backend. Shows the mirrored tab, live captions, and Claude's streamed answers. The audience can ask questions here too. Answers appear only on Able.
Cain extension ──WebRTC video (via TURN)──▶ Able page
│ room WS (signaling, text box) ▲ room WS (transcripts, answers)
│ audio WS (16 kHz PCM, 2ch) │
└────────────▶ FastAPI backend ──────────┘
├─ Deepgram Nova-3 streaming STT (ch0 = call, ch1 = presenter mic)
└─ Claude Opus 5.5 (streaming, effort=low, screen frame attached)
Install
pip install cainable # or: uv tool install cainable / pipx install cainable
Requires Python 3.11+ and Chrome 116+.
Setup
1. Backend
mkdir cainable-run && cd cainable-run
cainable init # writes .env; fill in ANTHROPIC_API_KEY, DEEPGRAM_API_KEY, TURN creds, ACCESS_KEY
cainable serve # http://0.0.0.0:7444 (--port, --host, --env-file to change)
Both machines must reach it over HTTPS. Either deploy it (Fly.io, Render, a VM), or tunnel it:
cloudflared tunnel --url http://localhost:7444 gives you a public https://….trycloudflare.com URL.
TURN is needed over the internet. Without it, most home and corporate NATs block the video.
The simplest option is Cloudflare Realtime TURN (generous free tier): create a TURN key in the Cloudflare
dashboard and put the key ID and API token in .env. The backend hands out short-lived credentials.
2. Able (display machine)
Open https://<backend>/?key=<ACCESS_KEY> and press F for full screen. Set ACCESS_KEY in .env; if you leave it empty, the backend generates one and prints it at startup.
It shows a 6-digit room code. The code stays in the URL hash, so a reload rejoins the same room.
3. Cain (presenter machine)
- Run
cainable extension(copies the extension to./cainable-extension), thenchrome://extensions→ Developer mode → Load unpacked → select that folder. - Open the popup, enter the backend URL, room code and access key, then Connect.
- Go to the tab you want to show and press Alt+Shift+M (or Mirror this tab). Press it again on a different tab to switch. The video track is swapped with no reconnect.
- Optional:
- Alt+Shift+A on the Meet / Zoom web tab marks it as call audio. Without it, the mirrored tab's audio is transcribed.
- My mic → Turn on: the first time, it opens a tab to grant mic permission.
- Alt+Shift+S sends the focused text box (or the selected text) to Able, which answers it.
Rebind shortcuts at chrome://extensions/shortcuts.
How answers are triggered (all from Able)
| Trigger | Default |
|---|---|
| Text box sent from Cain (Alt+Shift+S) | always |
Question (…?) heard on the call |
on |
| Question heard on the presenter mic | off |
| Clicking a caption line | always |
| Typing in Able's ask box | always |
Each question carries the current mirrored frame (toggle in ⚙), the rolling transcript, the latest shared text, and the last 6 Q&A turns.
Latency and quality knobs
CLAUDE_EFFORT=low: fastest first token on Opus 5.5. Raise it for harder questions.CLAUDE_FAST_MODE=1: Opus fast mode, roughly 2.5x faster output at 2x the price (Claude API only).- Server-side refusal fallbacks (
fallbacks="default") are enabled, so a declined request is retried on a fallback model. - Video: VP9 preferred, 6 Mbps cap,
contentHint=detail,maintain-resolution. Slide text stays sharp at the cost of frame rate on bad links.
Development
git clone <repo> && cd CainAble
uv sync # installs the package in editable mode plus dev tools
uv run pytest
uv run cainable serve --reload
Layout: src/cainable/ holds the backend (app.py, stt.py, llm.py, ice.py, rooms.py),
the Able page (able/) and the Chrome extension (extension/).
Limitations
- Call audio works only for browser calls. Zoom / Teams desktop apps need a virtual audio device (e.g. BlackHole on macOS) selected as the mic.
- The text grab can't read canvas-rendered editors (Google Docs, Figma). Select the text instead, or use Able's ask box.
- Switching the mirrored tab needs the hotkey or popup. Chrome requires a user gesture per tab capture.
- Rooms live in memory. Restarting the backend creates new codes (Able picks a new one automatically).
Metadata
Release files for cainable 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| cainable-0.1.0.tar.gz | 29.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cainable-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.8 kB
Release files / cainable-0.1.0.tar.gz
| Download URL | cainable-0.1.0.tar.gz |
|---|---|
| Size | 29.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f66846ad87e5a16780384438378d2ed233bca1412fb725771cf024e15d127e2e
|
|
BLAKE2b-256 checksum How to use checksums |
25a69a41bcfd84a09a82198fb88a415801e0aa536844541dfe3eaba752cd8ef0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / cainable-0.1.0-py3-none-any.whl
| Download URL | cainable-0.1.0-py3-none-any.whl |
|---|---|
| Size | 35.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3c127c8f45a692d34999a3f15dfd5765dde4d95ea9a31963daac4ac711e785d1
|
|
BLAKE2b-256 checksum How to use checksums |
adc805c777a310d437a0d078475f76c0e3060dfff8848f5c43286241ec0181e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for cainable 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for cainable-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.