Skip to main content
Glama
hexicgrind

elo-stt-passive

by hexicgrind
README.md
# Elo STT Passive v1.0

Global **push-to-talk speech-to-text** for Windows 11.

Hold **Right Ctrl** to talk (works while the app is minimized or unfocused). On release, audio is sent to [Fish Audio](https://fish.audio) STT, then typed into whichever window has focus—including fullscreen games. Optionally run as an **MCP server** so Claude Desktop, Claude Code, OpenClaw, and similar tools can read your STT history.

## Features

| Feature | Details |
|--------|---------|
| Global hotkey | Right Ctrl hold-to-talk; Right Shift cancels |
| Rolling buffer | Configurable pre/post roll (default **0.5 s**) so words are not clipped |
| Speech gate | Skips the API when only ambient noise is present (distinct soft cue) |
| Audio prep | Normalizes *quiet* speech, then boosts all speech (~**+30%** default, dB-aware) |
| Game-friendly typing | `SendInput` Unicode **key-down / key-up** with configurable delay & hold |
| Parallel jobs | Start a new hold while a previous clip is still processing |
| Soft cues | Distinct low-volume sounds for error, cancel, and no-speech |
| API key safety | Reads `FISH_AUDIO_API_KEY` from the environment only — **never saved to disk** |
| MCP | Query recent results, time windows, ranges, search; timestamps on hold start/end |
| Config | All knobs in `config.ini` next to the program |

## Requirements

- Windows 11 (designed for; other Windows versions may work)
- Python **3.10+**
- A [Fish Audio API key](https://fish.audio/app/api-keys)

## Install

```powershell
cd "C:\Projects\Elo STT Passive\repo"
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```

### Set the API key (Windows 11)

1. Press **Win**, search for **environment variables**, open **Edit the system environment variables**.
2. **Environment Variables…** → under **User variables** → **New…**
3. Name: `FISH_AUDIO_API_KEY`  
   Value: your key from https://fish.audio/app/api-keys  
4. OK out of all dialogs. **Restart** the terminal (and this app) so the variable is visible.

Session-only (PowerShell, not permanent):

```powershell
$env:FISH_AUDIO_API_KEY = "your_key_here"
```

## Run

**Desktop** (Notepad and other unelevated apps):

```powershell
python elo_stt_passive.py
```

**In some fullscreen games**, Windows hides keyboard state from other programs. If Right Ctrl does nothing while the game is focused, start elevated (optional):

```powershell
python elo_stt_passive.py --elevate
```

| Action | Result |
|--------|--------|
| Hold **Right Ctrl** | Record (includes buffer before the press) |
| Release **Right Ctrl** | Transcribe → type into the focused app |
| **Right Shift** while holding Right Ctrl | Cancel; soft cancel sound |
| **Ctrl+C** in the console | Quit |

## Configuration

Edit `config.ini` in this folder and save. Most settings — including **capture mode** — apply immediately; you do not need to restart the program. A fully commented template is in `config.example.ini`. Defaults and limits are also documented in the header of `elo_stt_passive.py`.

Useful keys:

- `[audio] capture_mode` — `microphone` (default), `system` (what you hear), or `both` (switch live)
- `[recording] buffer_seconds` — pre/post roll (default `0.5`)
- `[audio_processing] speech_boost_percent` — default `30` (×1.30 ≈ **+2.28 dB**)
- `[keystrokes] inter_key_delay` / `key_hold_duration`
- `[stt] timeout_seconds` — default `8`
- `[speech_detection] *` — ambient / speech gate

## MCP server

Start capture **and** expose tools to MCP clients:

```powershell
python elo_stt_passive.py --mcp
```

### Claude Desktop (example)

Add to your Claude Desktop MCP config (path may vary):

```json
{
  "mcpServers": {
    "elo-stt-passive": {
      "command": "C:\\Projects\\Elo STT Passive\\repo\\.venv\\Scripts\\python.exe",
      "args": [
        "C:\\Projects\\Elo STT Passive\\repo\\elo_stt_passive.py",
        "--mcp",
        "--skip-api-key-check"
      ]
    }
  }
}
```

Set `FISH_AUDIO_API_KEY` in your user environment rather than putting it in the JSON file.

### Tools

| Tool | Purpose |
|------|---------|
| `get_recent_stt_results` | Last N transcripts |
| `get_latest_stt_result` | Single newest result |
| `get_stt_results_past_seconds` | Everything in the last N seconds |
| `get_stt_results_in_range` | Overlap with Unix timestamp range |
| `get_stt_results_since_iso` | Since an ISO-8601 time |
| `search_stt_results` | Substring search |
| `get_stt_result_by_id` | Lookup by id |
| `get_stt_stats` / `get_elo_stt_status` | Counts and runtime status |
| `clear_stt_history` | Wipe in-memory history |

Each success record includes **`hold_start_utc` / `hold_end_utc`** (and Unix equivalents) with millisecond precision, plus the transcript text.

Optional SSE transport for a long-running windowed app:

```powershell
python elo_stt_passive.py --mcp-sse
```

## Tests

```powershell
python -m pytest tests -q
```

## Project layout

```
elo_stt_passive.py     # Entry point + documented defaults
config.ini             # Your settings
config.example.ini     # Template
elo_stt/               # Modular package
tests/
```

## Privacy & security

- The Fish Audio API key is read from the environment **only when needed**.
- This program **does not** write API keys to `config.ini` or any other file.
- Audio is sent to Fish Audio over HTTPS for transcription; review their terms for data handling.
- STT history is **in-memory only** (lost when the process exits), sized by `mcp.max_history`.

## License

MIT. See LICENSE. Fish Audio, Claude, and other trademarks belong to their respective owners.