Skip to main content
Glama
EtanHey
by EtanHey
README.md
# VoiceLayer

> Your AI agent can't hear you and respond to you. VoiceLayer gives it ears and a voice.

[![npm](https://img.shields.io/npm/v/voicelayer-mcp.svg)](https://www.npmjs.com/package/voicelayer-mcp)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![MCP](https://img.shields.io/badge/MCP-compatible-blue.svg)](https://modelcontextprotocol.io)
[![Tools](https://img.shields.io/badge/MCP%20tools-2-38BDF8.svg)](#the-two-tools)

**Voice I/O for AI coding assistants.** Press F5, speak to Claude Code, get on-device
transcription in under 1.5 seconds. Your AI speaks back. Works with any MCP client.

On a mouse with side buttons, button 5 (forward) works like F5 and button 4 (back) presses Return.

Select text in any app and press **Option+F5** to read it aloud; press it again to stop.
**Shift+F5** re-pastes the last transcript. Option+F5 is ignored during dictation or transcription.
Secure fields are never read. If a safe clipboard backup is unavailable, the shortcut asks you to retry and leaves the clipboard intact.

```
  You β”€β”€πŸŽ€β”€β”€> whisper.cpp ──> Claude Code ──> edge-tts β”€β”€πŸ”Šβ”€β”€> You
         STT (local)           MCP tools         TTS (free)
```

**Local-first. Free. Open-source.** No cloud APIs, no API keys, no data leaves your machine.

**[Website](https://voicelayer.etanheyman.com)** Β· **[Docs](https://etanhey.github.io/voicelayer/docs/)** Β· **[npm](https://www.npmjs.com/package/voicelayer-mcp)**

## Install

**macOS (recommended)** β€” the tap ships the CLI/MCP package and the notarized notch app:

```bash
brew tap etanhey/layers
brew install etanhey/layers/voicelayer         # CLI + MCP server
brew install --cask etanhey/layers/voicebar    # VoiceBar notch app (owns the mic)
voicelayer setup                               # one-time runtime setup
```

Install the cask too β€” the formula alone gives you the CLI/MCP but no notch UI.

**npm** β€” if you only want the MCP server:

```bash
bun add -g voicelayer-mcp        # or: npm i -g voicelayer-mcp
brew install sox socat && pip3 install edge-tts
brew install whisper-cpp         # optional, for local STT

# whisper model (~1.5 GB) β€” needed for local STT
mkdir -p ~/.cache/whisper && curl -L -o ~/.cache/whisper/ggml-large-v3-turbo.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
```

### Speak from the terminal

```bash
voicelayer speak "Hello"
echo "Text to read" | voicelayer speak
voicelayer speak - --markdown
voicelayer speak --file notes.md --profile long --wait
voicelayer speak --stop
```

Speech goes through the running VoiceLayer daemon, sharing Stop and the teleprompter.
With the daemon, the command returns after the first audio starts; `--wait` waits for completion.
`--profile auto` is the default; `short` and `long` select the corresponding profile.
Auto selects the saved long profile above `short_limit_chars` and the short profile at or below
the threshold (default: 400 characters). `--profile short|long` overrides that choice.
Markdown conversion defaults on for `.md` files; literal text and stdin are plain unless
`--markdown` is supplied. Long reads stream chunks without the MCP tool's text cap,
up to the shared 200,000-character safety limit.

If the daemon is unavailable, the CLI reports it on stderr and runs the same streaming
engine in the foreground, waiting for completion even without `--wait`. Ctrl-C stops
local synthesis/playback and waits for owned children to exit. Local fallback has no
teleprompter; `--stop` requires the daemon. Authentication, busy, synthesis and playback
failures return a nonzero exit code. Failures after an async daemon command has returned
are reported by the daemon;
use `--wait` when the final exit status matters. An older running daemon must be
updated/restarted to support this command.

Speech credentials resolve in this order: `VOICELAYER_DAEMON_SECRET_PATH`, isolated
`VOICELAYER_STATE_DIR`, an MCP socket override, `VOICELAYER_TTS_DAEMON_SECRET_FILE`,
`VOICELAYER_TTS_AUTH_TOKEN_FILE`, then `~/.voicelayer/daemon.secret`. Isolation paths take
precedence over inherited TTS aliases. Secret files must be regular, nonempty, non-symlink
files with mode 0600.

### Point your MCP client at the daemon

VoiceLayer runs as one persistent daemon on a Unix socket; every session connects through a
lightweight `socat` shim instead of spawning its own process. Add to `.mcp.json`:

```json
{
  "mcpServers": {
    "voicelayer": {
      "command": "socat",
      "args": ["STDIO", "UNIX-CONNECT:/tmp/voicelayer-mcp.sock"]
    }
  }
}
```

Or migrate every repo at once: `bash scripts/migrate-to-daemon.sh` (`--dry-run` to preview).
On macOS, grant your terminal microphone access (System Settings β†’ Privacy β†’ Microphone).

Updates, kill-switches, and cross-machine setup:
**[docs/install-and-update.md](docs/install-and-update.md)**.

## The two tools

| Tool | Behavior | Blocking |
|------|----------|:--------:|
| **`voice_speak`** | TTS with auto-mode (announce/brief/consult/think), replay, toggle | No |
| **`voice_ask`** | Speak a question, record the mic, transcribe the answer | Yes |

Both ship full MCP [ToolAnnotations](https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations).
Neither is destructive; both set `openWorldHint: false`.

Daemon speech profiles, env overrides, and socket controls: [docs/tts-settings.md](docs/tts-settings.md).

**How `voice_ask` works:** waits for any playing audio to finish β†’ speaks the question via
edge-tts β†’ records the mic and resamples to 16kHz β†’ Silero VAD detects when you stop talking β†’
whisper.cpp transcribes locally (~200-400ms on Apple Silicon) β†’ returns the text to your agent.

Full tool parameters: **[docs/tools-reference.md](docs/tools-reference.md)**.

## VoiceBar β€” the notch surface (macOS)

The canonical UI (SwiftUI + AppKit). It tucks around the MacBook camera housing instead of
floating over your work:

- **Liquid-Glass wings** flank the camera housing, with a graceful fallback on older macOS.
- **Teleprompter** with word-by-word karaoke highlighting as your agent speaks.
- **Idle-hover to summon** β€” collapsed it draws no pixels; hovering reveals the mic, History and
  Settings.
- **Settings** β€” General Β· Models Β· Dictionary Β· History. Open with ⌘, or right-click β†’ Settings…,
  or from a script: `open voicebar://settings/models`.
- **Morph animations** between idle ↔ recording ↔ speaking, Reduce-Motion aware.

## STT backends

| Backend | Type | Latency | Setup |
|---------|------|---------|-------|
| **whisper.cpp** | Local (default) | ~200-400ms | `brew install whisper-cpp` + model |
| **Wispr Flow** | Cloud (fallback) | ~500ms + network | Set `QA_VOICE_WISPR_KEY` |

Auto-detected. Override with `QA_VOICE_STT_BACKEND=whisper|wispr|auto`.

## Platform support

| Platform | TTS | STT | Recording | VoiceBar |
|----------|-----|-----|-----------|----------|
| **macOS** | edge-tts + afplay | whisper.cpp (CoreML) | sox | SwiftUI app |
| **Linux** | edge-tts + mpv/ffplay | whisper.cpp | sox | β€” |

## Part of Golems

| Server | What it does | Tools |
|--------|-------------|:-----:|
| **[BrainLayer](https://brainlayer.etanheyman.com)** | Persistent memory β€” knowledge graph + hybrid search | 12 |
| **[VoiceLayer](https://voicelayer.etanheyman.com)** | Voice I/O β€” local STT, neural TTS, notch VoiceBar, F5 recording | 2 |
| **[cmuxLayer](https://cmuxlayer.etanheyman.com)** | Terminal orchestration β€” spawn panes, read screens, coordinate agents | 22 |

Pair with BrainLayer to remember voice conversations across sessions.

## More

- **[Architecture](docs/architecture.md)** β€” why one daemon instead of a process per session
- **[Configuration](docs/configuration.md)** β€” environment variables and test commands
- **[Contributing](docs/contributing.md)**

## License

[Apache-2.0](LICENSE)