dottie-talk
by stevederico
README.md
# dottie-talk
Local voice: STT + koko TTS (`:1314`) + HTTP façade (`:1320`).
Standalone. No gateway. No Dottie.app.
```bash
npm install
npm start
```
Bins ship in `bin/` (macOS). Models download on first boot (`~/.cache/parakeet.cpp`, `~/.cache/dottie-talk` or `~/.dottie`).
| Platform | STT | TTS |
|---|---|---|
| Apple Silicon (darwin) | `parakeet-server` `:1315` (bundled) | `koko` `:1314` |
| Omarchy / Linux | `voxtype` (PATH, else fetched AVX2 binary + `base.en`) | `koko` `:1314` (ELF fetched on first start as `bin/koko-linux-x86_64`) |
| Command | What |
|---|---|
| `npm start` | HTTP `:1320` (starts STT+TTS) |
| `npm run mcp` | MCP stdio |
| `npm run tts` | TTS only (`:1314`) |
| `dottie-talk speak "hi"` / `speak "hi"` | TTS → `speech.wav` (or `-o` / stdout) |
| `dottie-talk transcribe a.wav` / `transcribe a.wav` | STT → text on stdout |
| `dottie-talk start` | same as `npm start` |
| `dottie-talk health` | STT/TTS readiness JSON |
| `dottie-talk keys on` | enable talk-keys-style hotkeys (off by default) |
| `dottie-talk keys off` | disable hotkeys |
| `dottie-talk keys status` | enabled / armed / chords |
| `dottie-talk bar on` | Omarchy menubar icon (hidden until `start`) |
| `dottie-talk stop` | stop the HTTP server |
```bash
npx speak "hello" -o hello.wav
speak "hello" -o hello.wav # after dottie-talk bar on (~/.local/bin/speak)
npx transcribe hello.wav
```
Node ≥22. Override STT with `DOTTIE_STT=voxtype` or `DOTTIE_STT=parakeet`.
Linux: if `voxtype` is already on PATH, talk uses it. If not, first start downloads the x86_64 AVX2 binary into `~/.cache/dottie-talk/bin` and `base.en` (no sudo, no pacman). `npm start` skips the Darwin `bin/koko` and downloads the linux-bins ELF the same way. Streaming/multipart STT needs parakeet — buffered JSON `/v1/audio/transcriptions` works on both.
## MCP
`npm run mcp` (stdio). Audio stays on disk or goes to the speakers, so agent context never fills with base64.
| Tool | Args | Returns |
|---|---|---|
| `transcribe` | `path` (or `wavBase64` for apps) | text |
| `speak` | `text`, `voice?` | streams PCM to the speakers; starts in ~20 ms, not after the whole clip |
| `speak` | `out` | saves the file; `play: true` also plays it |
| `speak` | `returnAudio: true` | base64 too (old behavior, for apps) |
Streaming needs a player that reads stdin: `pw-cat`, `pacat`, `aplay`, or `ffplay`. The first one on PATH wins (`pw-cat` runs with `--raw`).
Add to Claude Code:
```bash
claude mcp add dottie-talk -s user -- node ~/Projects/dottie-talk/mcp.js
```
## Talk-back (Claude Code)
Read Claude's replies aloud through koko. `talk-back.js` runs as two Claude Code hooks, and `skills/` has the `/talk` and `/talk-toggle` skills.
| Command | What |
|---|---|
| `/talk` | speak this session's last reply, one time |
| `/talk on` / `off` / `toggle` | talk-back mode: read every reply aloud |
| `/talk-toggle` | same as `/talk toggle` |
| `/talk stop` | stop playback |
| `/talk status` | on or off |
Stop it: type anything (the next prompt cuts playback off), `/talk stop`, or `/talk off`. On Linux with `dottie-talk keys on`, Escape stops it too.
What is read: only the final reply. Never thinking, tool calls, or notes written between tool calls. Code blocks, tables, URLs, and markdown marks are dropped.
State: `~/.config/dottie-talk/talk-back/`. The saved last reply is per session (`last-<session>.txt`), so two windows never read each other's reply. The on/off mode is global.
### Setup
1. Start dottie-talk (`npm start`), so koko answers on `:1314`
2. Link the skills:
```bash
ln -s ~/Projects/dottie-talk/skills/talk ~/.claude/skills/talk
ln -s ~/Projects/dottie-talk/skills/talk-toggle ~/.claude/skills/talk-toggle
```
3. Add the hooks to `~/.claude/settings.json`:
```json
{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command", "command": "node ~/Projects/dottie-talk/talk-back.js hook", "timeout": 5 }] }],
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "node ~/Projects/dottie-talk/talk-back.js interrupt", "timeout": 5 }] }]
}
}
```
4. Restart Claude Code, then `/talk on`
The Stop hook saves every reply and speaks it only when the mode is on. It returns at once; playback runs in a detached child. The `interrupt` hook prints nothing, because `UserPromptSubmit` output goes into the model's context.
## Keys (Linux)
Talk-keys analog. **Off by default.** Enable, then keep `npm start` running so binds arm:
```bash
dottie-talk keys on
npm start
```
| Chord | Action | Omarchy |
|---|---|---|
| Alt+S | Speak selection if present, else clipboard. Second tap stops. | Steals app Alt+S menu accelerators **while armed** |
| Alt+D | Hold to dictate (voxtype push-to-talk) | Steals browser Alt+D address bar **while armed**; leaves Super+Ctrl+X / F9 alone |
| Escape | Stop speak | Only consumed while playback is active |
Config: `~/.config/dottie-talk/config.json`
```json
{
"keys": {
"enabled": false,
"speak": "ALT + S",
"stop": "ESCAPE",
"dictate": "ALT + D",
"voice": ""
}
}
```
`DOTTIE_TALK_KEYS=on` / `off` overrides `enabled`. Hotkeys no-op when the HTTP server is down. macOS: use [talk-keys](https://github.com/stevederico/talk-keys).
## Bar (Omarchy)
Menubar icon **only while `npm start` is running**. Click: keys toggle, speak selection, stop, quit. Middle-click quits.
```bash
dottie-talk bar on
dottie-talk start
```
`start` on Linux also installs the plugin (`sd.dottie-talk`) next to Dottie if the bar does not already have it.
## Related
- [talk-keys](https://github.com/stevederico/talk-keys) — macOS hotkeys for speak / dictate
- [dottie-desktop](https://github.com/stevederico/dottie-desktop) — desktop app
- [local-ai-cli](https://github.com/stevederico/local-ai-cli) — local AI CLI
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues