Give Claude Code a voice. Hear what it did, not what it wrote.
A Stop hook that synthesizes Claude's response into a short, human summary and speaks it aloud via ElevenLabs TTS. Like a colleague telling you "he's fixed the login bug" while you keep coding.
Quickstart · How it works · Usage · Troubleshooting
Claude Code writes walls of text. You already have eyes for that. What you don't have is someone telling you what just happened while you keep coding.
Before: Claude responds. You stop what you're doing. You read 40 lines. You go back to coding.
After: Claude responds. You hear "Fixed the login bug and updated the tests". Your eyes never leave the code.
Mamerto doesn't read Claude's output — it synthesizes it. Claude itself distills the response into 1-2 sentences, the way a human would summarize it out loud. Then ElevenLabs speaks it.
Claude responds
|
v
Stop hook fires --> narrator.py
|
|-- 1. Extract final assistant text from the transcript
|-- 2. Strip markdown (code blocks, links, headers...)
|-- 3. Synthesize via claude -p --bare (1-2 sentence human summary)
'-- 4. Send to ElevenLabs TTS --> play audio
The synthesis step calls claude -p --bare to distill the response into what a person would actually say:
- 40-line explanation becomes "Refactored the auth service, pulled token validation into its own module"
- Long debugging session becomes "Found the bug — circular import between user service and auth middleware"
- Code generation becomes "Built the payment component with all three variants you asked for"
Zero extra API cost — it runs through your existing Claude Code plan.
5 minutes. No dependencies. Just Python and Claude Code.
git clone https://github.com/jlcases/mamerto.git
cd mamerto
chmod +x narrator.py narratorCreate a .env file with your ElevenLabs credentials:
ELEVENLABS_API_KEY=your_api_key_here
ELEVENLABS_VOICE_ID=your_voice_id_hereGet your API key at elevenlabs.io. Find the voice ID in the URL when you select a voice in the dashboard.
Add this to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/mamerto/narrator.py"
}
]
}
]
}
}If you already have other settings in that file, just merge the
hookssection.
The skill lets you toggle narration with /narrator on and /narrator off inside Claude Code.
mkdir -p ~/.claude/skills/narrator
cat > ~/.claude/skills/narrator/SKILL.md << 'SKILL'
---
name: narrator
description: Toggle voice narration on or off. Use when the user says "narrator on", "narrator off", "turn narration on/off", or any request to enable/disable voice narration.
---
# Narrator Toggle
Run the toggle script and report the result:
| Intent | Command |
| ------- | ---------------------------------- |
| Enable | `/path/to/mamerto/narrator on` |
| Disable | `/path/to/mamerto/narrator off` |
| Toggle | `/path/to/mamerto/narrator toggle` |
| Status | `/path/to/mamerto/narrator status` |
Reply with one line confirming the new state.
SKILLReplace /path/to/mamerto with your actual path.
You: /narrator on
Claude: Narrator: ON
You: explain how the auth middleware works
Claude: [text response + you hear a spoken summary]
You: /narrator off
Claude: Narrator: OFF
Built entirely with Claude Code primitives — no framework, no external orchestrator:
| File | What it does |
|---|---|
narrator.py |
Stop hook — extracts text, synthesizes via Claude, sends to TTS |
narrator |
Bash toggle — creates/removes the flag file ~/.claude/narrator_enabled |
.env |
ElevenLabs credentials (gitignored) |
The synthesis step calls claude -p, which could trigger the Stop hook again. A lock file (/tmp/narrator_humanizing.lock) prevents the loop: the hook checks for it on startup and exits immediately if present.
If claude -p fails (timeout, CLI not found), narration falls back to the cleaned text directly.
- macOS — uses
afplayfor audio playback - Claude Code CLI — installed and authenticated
- ElevenLabs account — free tier works fine
- Python 3.10+ — zero pip dependencies, stdlib only
tail -20 /path/to/mamerto/narrator.log| Symptom | Fix |
|---|---|
| No audio after enabling | Check .env has valid ELEVENLABS_API_KEY and ELEVENLABS_VOICE_ID |
| Hook not firing | Verify path in settings.json is absolute and narrator.py is executable |
| Narration says unrelated things | Make sure you have the latest narrator.py with --bare in the synthesis call |
| Slow narration | Normal — synthesis adds ~3s. If too slow, it falls back to raw text |
MIT
"I don't just read code... I narrate brilliance." — Mamerto
