Skip to content

Latest commit

 

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claudio

Spoken summaries for Claude Code, marked inline by Claude itself.

Claudio is a small CLI + hook that makes Claude Code speak. Claude wraps a short summary of every answer in a <speak>...</speak> tag — claudio reads the transcript, extracts those blocks, and plays them through a TTS provider while you read the full reply.

You:    fix the failing test
Claude: [writes code, runs tests, all green]
        <speak>Tests pass, three fixes committed.</speak>
        ↑ this is what you hear, ~3 seconds of audio

Why

You read code faster than you read prose. Most of Claude's reply is detail you can skim. The one-line "what happened" is the part you'd actually want to know while looking somewhere else. Claudio plays that line in your ear while your eyes scan the diff.

Features

  • Two providers out of the box: ElevenLabs (neural, paid, premium voices) and macOS say (offline, free, native multi-language voices like Yelda for Turkish).
  • Streaming mode (live): plays the speak block between tool calls, not just at the end of the turn. You hear "looking at the index file..." while the tool runs.
  • Interactive TUI (claudio configure): switch provider, browse voices by language, search by name, preview the selected voice.
  • Stop on demand (claudio stop): kills any currently playing audio.
  • Sanitizer: strips markdown, backticks, HTML tags, code fences from the speak text before sending it to TTS. Your TTS won't say "less than slash speak greater than" out loud.
  • Race-resilient hook: retries the transcript reader and checks for late-arriving entries, so audio matches the message you actually see.
  • Session-scoped state: multiple Claude Code windows do not stomp on each other.
  • Silent failure: a broken TTS provider never blocks Claude. Errors go to the log, not to your face.

Install

You need Bun (>= 1.1) and macOS (the player and the system provider both call afplay / say).

git clone https://github.com/<you>/claudio
cd claudio
bun install
bun link              # registers `claudio` on your PATH
claudio install       # writes the hook into ~/.claude/settings.json

claudio install is idempotent. Re-run it any time; it merges into your existing settings.json and never touches hooks owned by other tools.

Pick a voice and provider

claudio configure

You will see your current settings, then a menu:

What now?
  > Change voice
    Change provider
    Change streaming mode (stop / live)
    Preview current voice
    Save and exit

Voices are pulled live from the active provider — say -v ? for the system provider, the /v1/voices endpoint for ElevenLabs. Long lists are filterable by language and substring.

To use ElevenLabs, put your key in your shell environment:

export ELEVENLABS_API_KEY=sk_...   # in ~/.zshrc or ~/.zshenv

Or, if you do not want it in env, edit ~/.claude/claudio/config.json directly and put the literal key in the apiKey field.

Streaming mode

stop  (default)  Audio plays after the turn finishes. Simple and predictable.
live             Audio also plays between tool calls. More responsive on
                 multi-step replies, but Claude's pre-tool text must include
                 a `<speak>` block for there to be anything to play.

live mode is opt-in. Toggle it in claudio configure under "Change streaming mode" — no reinstall needed, the hook reads the config on every fire.

Tell Claude when to speak

Add a CLAUDE.md block in your project (or in ~/.claude/CLAUDE.md for every session). A starter template is in this repo at CLAUDE.md — copy it as-is, or edit the tone and length to taste. The short version:

End every substantive response with one <speak>...</speak> block — one sentence, ≤ 15 words, in the user's language. If you are about to run tools, also write a short pre-tool sentence and wrap it in <speak> so the user hears what is about to happen while the tools run.

Commands

Command What it does
claudio install Add Stop + PostToolUse hooks to ~/.claude/settings.json, migrate config from legacy locations.
claudio configure Interactive TUI: provider, voice, streaming mode, preview.
claudio test "<text>" Synthesize and play one phrase through the current provider. Useful for sanity checks.
claudio stop Kill any currently playing claudio audio across all sessions.
claudio hook Internal — invoked by Claude Code's Stop / PostToolUse hooks; reads JSON payload on stdin.
claudio --version Print the version.

How it works

  1. Claude writes a reply that contains one or more <speak>...</speak> blocks.
  2. Claude Code fires a Stop hook (and, in live mode, PostToolUse hooks between tool calls). The hook runs claudio hook.
  3. claudio reads the session's transcript file at the path Claude Code gave it, finds the most recent assistant text entry, and extracts the speak blocks.
  4. Per-session state (~/.claude/claudio/state/<session_id>.json) remembers which message uuid was last spoken — the same message is not spoken twice even when both Stop and PostToolUse hooks see it.
  5. The text is sanitized, sent to the configured provider, played through afplay as a detached child, with a PID file so a later hook can interrupt it.

If the hook fails for any reason — network down, API quota exhausted, no audio device — Claude is never blocked. The failure is logged to ~/.claude/claudio/log and the next turn proceeds normally.

Platforms

Today: macOS only. The system provider (say) and the audio player (afplay) are macOS binaries. ElevenLabs works wherever Bun runs, but the player path does not.

Linux / Windows support is welcome as a contribution — the player is a single small module and the system provider abstracts over say. Drop-in replacements with mpg123 (Linux) or powershell -c (New-Object Media.SoundPlayer) (Windows) would not be a large change.

Configuration

~/.claude/claudio/config.json. Created by claudio install. Validated by a strict zod schema (typos in top-level keys are rejected).

{
  "enabled": true,
  "provider": "system",          // "elevenlabs" or "system"
  "streamingMode": "stop",       // "stop" or "live"
  "providers": {
    "elevenlabs": {
      "apiKey": "$ELEVENLABS_API_KEY",   // env interpolation supported
      "voiceId": "FGY2WhTYpPnrIDTdsKH5",
      "model": "eleven_turbo_v2_5"
    },
    "system": { "voice": "Yelda" }
  },
  "cache":  { "enabled": true, "maxSizeMb": 500 },
  "queue":  { "interruptOnNewPrompt": true },
  "filter": { "minLength": 3, "maxLength": 1000 }
}

Troubleshooting

I get no audio at all. Run claudio test "hello". If you hear nothing, the pipeline is broken before Claude Code is involved — check the audio output device and the provider. If you hear it, the hook side is the problem; look at ~/.claude/claudio/log.

The hook fires but no audio plays. Check the log. The most common cause is a provider error (401 from ElevenLabs, missing say voice on a fresh macOS). Switching provider to system is the quickest reset.

Audio cuts off mid-sentence. Most likely a PostToolUse fire is interrupting the previous audio. Either switch to stop mode, or wait a turn — the dedup will catch up.

The hook reads stale text. Already mitigated: the orchestrator does a stability check after finding a fresh transcript entry, so it does not commit to playing an intermediate chunk. If you still see this, your transcript flush is slower than 5 × 200 ms — open an issue with timing.

Development

bun test                  # 98 tests, ~3s
bun run lint              # biome
bunx tsc --noEmit         # strict typecheck
bun run src/cli/index.ts test "merhaba"

The codebase is small (~1.5k lines including tests). Modules are flat: audio, config, hook, providers, cli, util. No internal cycles.

License

MIT.

About

Hear what Claude Code did while you read what Claude Code wrote

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages