Skip to content
MrPeppersDevPublic

About

Local, offline text-to-speech for Claude Code on macOS — Kokoro-82M wrapper with F13 hotkey

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Claudio

Local, offline highlighted-text-to-speech for macOS. Select text in any app, press F13, hear it. Synthesis runs entirely against a local Kokoro-82M model — no API keys, no network.

  • Fully offline — no API keys, no network. Once installed, everything runs against weights on disk.
  • ~660 MB RAM when the server is running; manually stoppable from the menu bar to release.
  • Synth in 0.7–1.6 s warm, 0.4 s cold-start for the model.

What's in this repo

This repo is a wrapper around upstream Kokoro. It ships ~25 KB of augmentation code — the model, the ONNX runtime, and the inference library are installed separately from their upstream sources:

  • pip install kokoro-onnx → upstream inference library (Apache 2.0)
  • download-model.sh → fetches the ONNX weights + voice embeddings from thewh1teagle/kokoro-onnx releases

What Claudio adds on top:

  • Shared text preprocessing — markdown-to-speech (strip code blocks, flatten links, normalize headings/bullets) + a pronunciation dictionary (API → ay pee eye, kubectl → kube control, etc.)
  • Split-speed synthesis — model runs at 1.5× and afplay's phase vocoder covers the remaining 1.33× to hit effective 2.0× without muddied phonemes
  • Menu bar UI + F13 hotkey (Hammerspoon)
  • App-agnostic selection capture — three fallbacks in order: Accessibility API (works in most native apps, Safari, Notes, Mail), simulated Cmd+C + pasteboard diff (works anywhere Cmd+C does), copy-on-select pasteboard scan (iTerm2, Terminal.app, Alacritty, kitty, WezTerm, Ghostty)

Layout

.
├── play-last.sh               # top-level orchestrator; owns the job lock
├── preprocess.py              # markdown + pronunciation + sentence split
├── pronunciations.txt         # FROM|TO dictionary for tech jargon
├── common-phrases.txt         # stock utterances for cache pre-warm
├── kokoro-tts.sh              # backend wrapper (HTTP → server → afplay)
├── kokoro-server.sh           # start/stop/status for the local Python server
├── warm-cache.sh              # pre-synthesize common-phrases.txt into cache
├── kokoro/
│   ├── server.py              # stdlib http.server wrapping kokoro-onnx
│   ├── requirements.txt
│   ├── download-model.sh      # fetches .onnx + voices .bin from upstream
│   ├── kokoro-v1.0.onnx       # (gitignored, 310 MB, pulled at install)
│   └── voices-v1.0.bin        # (gitignored, 27 MB, pulled at install)
├── hammerspoon/
│   └── claudio.lua            # menubar dropdown, F13 hotkey, selection capture
└── install.sh                 # bootstrap for a new machine

Install

git clone <repo> ~/.claude/claudio
cd ~/.claude/claudio
./install.sh

Install does:

  1. Symlinks hammerspoon/claudio.lua to ~/.hammerspoon/claudio.lua.
  2. Creates a Python venv (prefers 3.12; accepts anything ≥ 3.10) and installs kokoro-onnx, soundfile, scipy. Override the interpreter with CLAUDIO_PYTHON=/path/to/python3 if auto-detection picks wrong.
  3. Downloads Kokoro v1.0 model weights + voices (~340 MB one-time).
  4. Pre-warms the sentence cache with every line in common-phrases.txt (~15 s; skipped non-fatally on failure).
  5. Prints follow-up instructions — reload Hammerspoon and add the require("claudio") / F13 binding snippet below to your init.lua.

Then add to your Hammerspoon init.lua:

local claudio = require("claudio")
claudio.start()
hs.hotkey.bind({}, "F13", function() claudio.toggle() end)

Usage

  • F13 — highlight text in any app, then press F13 to read it aloud. Press F13 again to stop. With no selection, F13 shows a reminder.
  • Menu bar icon — dropdown with Play/Stop and manual server stop. ▸ idle, ⟳ synth, ▶ playing.

Env vars

  • KOKORO_VOICE — default af_bella. See VOICES.md for the full list. Accepts a weighted blend too: af_bella:70,am_michael:30. Weights are normalized — 1,1 is a 50/50 mix. Embeddings are averaged server-side.
  • KOKORO_SPEED — target effective speed. Default 2.0.
  • KOKORO_SYNTH_CAP — split point between synth and afplay. Default 1.5 (synth at 1.5×, afplay covers the remaining multiplier). Lower values push more work to afplay's phase vocoder; higher push it to the model.
  • KOKORO_LANG — override auto-detected phoneme language (en-us / en-gb).
  • KOKORO_URL — server base URL. Default http://127.0.0.1:8880.
  • KOKORO_EARCON — WAV/AIFF played at structural boundaries (where a code block was stripped). Default /System/Library/Sounds/Pop.aiff.
  • KOKORO_CACHE_DIR — per-sentence WAV cache. Default ~/.claude/claudio/cache.
  • KOKORO_CACHE_MAX_MB — cache cap, mtime-LRU pruned. Default 200.
  • KOKORO_NO_PLAY — synth-to-cache and skip playback (used by warm-cache.sh).
  • KOKORO_EQ_GAIN_DB / KOKORO_EQ_FREQ / KOKORO_EQ_Q — server-side peaking EQ. Default 3.0 / 2500 / 1.0. A small presence bump helps consonant intelligibility survive the 1.33× afplay time-stretch. Set KOKORO_EQ_GAIN_DB=0 to disable. All three participate in the cache key, so toggling them auto-invalidates stale entries.
  • CLAUDIO_STATE_DIR — where runtime state (lock, logs, cache) lives. Default ~/.claude/claudio. Shell scripts derive their own location from $BASH_SOURCE, so the repo can be cloned anywhere; only state has a conventional default.
  • CLAUDIO_DIR — Lua-side override pointing at the repo (for Hammerspoon to find play-last.sh / kokoro-server.sh). Default ~/.claude/claudio. Set it in a LaunchAgent or before launching Hammerspoon from a shell if the repo lives elsewhere.

Pronunciation dictionary

Edit pronunciations.txt. Format: FROM|TO, one per line, # for comments. Matching is case-sensitive and word-boundary respecting. Longer/more-specific entries should come first (plurals before singulars). Changes take effect on the next invocation — no reload needed.

Cache pre-warming

./warm-cache.sh synthesizes every line in common-phrases.txt into the sentence cache with playback disabled. Stock utterances ("Let me check that.", "Done.") then hit on first real use instead of paying a cold synth. Run it after changing KOKORO_VOICE to warm the new voice's cache (cache keys include voice, so each voice needs its own warm). install.sh runs it automatically at the end of a fresh install.

Resource footprint

  • RAM: ~660 MB RSS (model held resident in the server process)
  • Disk: ~310 MB model + 27 MB voices + ~270 MB venv
  • CPU: brief single-core spike during synth, idle otherwise
  • Latency: 0.4 s cold start (first synth after boot), 0.7–1.6 s warm synth

Architecture notes

  • Lock model: play-last.sh owns job.pid for its entire run (synth + playback). A second invocation while mid-synth kills the whole process tree — that's how the menu bar toggle-stop works.
  • Lazy server start: play-last.sh calls kokoro-server.sh start before each synth. Idempotent: ~0.05 s no-op when already healthy, up to ~8 s on cold boot.
  • Selection capture (claudio.lua): Accessibility API → simulated Cmd+C + pasteboard diff → copy-on-select terminal pasteboard scan. Three paths in order of preference; the terminal fallback is gated to a small whitelist (iTerm2, Terminal.app, Alacritty, kitty, WezTerm, Ghostty) so stale clipboards in non-terminal apps don't get replayed.

License

Claudio is licensed under the Apache License 2.0.

Upstream attribution

Claudio is a wrapper — the TTS capability comes entirely from upstream work that is fetched at install time, not vendored here:

  • kokoro-onnx (Apache-2.0) — ONNX inference wrapper.
  • Kokoro-82M — the model weights and voice embeddings, published under the terms described in its model card. Review those terms before redistributing the downloaded weights; Claudio only fetches them to the user's machine, never ships them.

About

Local, offline text-to-speech for Claude Code on macOS — Kokoro-82M wrapper with F13 hotkey

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages