Metadata-Version: 2.5
Name: cainable
Version: 0.1.0
Summary: Mirror a Chrome tab, call audio and text boxes to a second screen with live captions and Claude-powered answers.
Author-email: Abhijit <itabhijitb@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: chrome-extension,claude,presentation,screen-mirroring,speech-to-text,webrtc
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video :: Display
Classifier: Topic :: Office/Business
Requires-Python: >=3.11
Requires-Dist: anthropic>=1.15.0
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.27
Requires-Dist: python-dotenv>=1.0
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: websockets>=14.0
Description-Content-Type: text/markdown

# 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

```bash
pip install cainable        # or: uv tool install cainable / pipx install cainable
```
Requires Python 3.11+ and Chrome 116+.

## Setup

### 1. Backend
```bash
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)
1. Run `cainable extension` (copies the extension to `./cainable-extension`), then
   `chrome://extensions` → Developer mode → **Load unpacked** → select that folder.
2. Open the popup, enter the backend URL, room code and access key, then **Connect**.
3. 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.
4. 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
```bash
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).
