feat(tts): add voice support via macOS say#25
Conversation
Three hooks wire up TTS: SessionStart injects spoken-summary marker instructions, Stop reads <\!--TTS --> markers aloud (async, per sentence), UserPromptSubmit interrupts speech cooperatively at sentence boundaries. /tts-enable and /tts-disable skills toggle ~/.claude/toolkit/tts-on. Sandbox allowWrite extended to ~/.claude/toolkit for flag and token files.
| "autoAllowBashIfSandboxed": true, | ||
| "filesystem": { | ||
| "allowWrite": [ | ||
| "~/.claude/toolkit" |
There was a problem hiding this comment.
Required to write the files that control on/off
There was a problem hiding this comment.
Teeny, let's scope per skill:
| "~/.claude/toolkit" | |
| "~/.claude/toolkit/tts" |
| ] | ||
| } | ||
| ], | ||
| "SessionStart": [ |
There was a problem hiding this comment.
Injected on session start so TTS can be switched on/off without restart the session
| @@ -0,0 +1,167 @@ | |||
| #!/bin/bash | |||
There was a problem hiding this comment.
Summary of the logic:
- Checks if the flag file ~/.claude/toolkit/tts-on exists. If not, exits immediately, nothing happens
- Reads the transcript file path from the hook payload. If missing, falls back to finding the most recently modified transcript for the current project
- Does an initial scan of the transcript to find the index of the last user message
- Then polls only new bytes being appended to the transcript (up to 15 seconds)
- If a newer user message arrives during polling, exits (the response it was waiting for is no longer the latest)
- If it finds a marker in an assistant message that comes after the last user message, extracts the text and splits it into sentences
- If 15 seconds pass with no marker found, exits
- Writes a new unique token (process ID + timestamp) to the token file, claiming the "speaker" role
- Launches a detached background process that loops over each sentence:
- Before speaking each sentence, checks if it still owns the token. If not (interrupted by a new prompt or /tts-disable), stops
- Checks if 120 seconds haven't passed
- Pipes the sentence to macOS say
- The hook itself exits immediately. The background process speaks on its own
|
@tute, nudge nudge 😬 |
| # Always injected; whether the markers are spoken is gated by ~/.claude/toolkit/tts-on | ||
| # (see speak.sh). The marker is an HTML comment so it cannot break the markdown | ||
| # rendering of the response. | ||
|
|
There was a problem hiding this comment.
Should this script exit 0 if not ~/.claude/toolkit/tts-on? Like speak.sh does
| # The final assistant message is flushed to the transcript slightly after Stop | ||
| # fires, so we poll for a marker NEWER than the last user prompt. Requires | ||
| # "async": true in the hook registration or polling would stall every turn. | ||
|
|
There was a problem hiding this comment.
Let's add an early return (maybe logging) if TTS is ON but not on macOS.
| "autoAllowBashIfSandboxed": true, | ||
| "filesystem": { | ||
| "allowWrite": [ | ||
| "~/.claude/toolkit" |
There was a problem hiding this comment.
Teeny, let's scope per skill:
| "~/.claude/toolkit" | |
| "~/.claude/toolkit/tts" |
fedesapuppo
left a comment
There was a problem hiding this comment.
Well done Nacho!! Good idea. I personally prefer to read myself, rather than Claude talking to me. That said, this is still a nice-to-have in the toolkit and I would try it for long 'grilling' sessions. It would be great to include TTS, with Superwhisper (https://superwhisper.com), as a complement, so we can have a real conversation with Claude 🚀
Adds /stt-enable, /stt-disable, and a symmetric voice-mode pair on top of PR tute#25's TTS. Also fixes bin/install, which never symlinked .claude/hooks.
7c3fe8c to
12a71a7
Compare
bin/install never linked hooks, so speak.sh and the other TTS hooks from PR tute#25 were never actually reachable after install.
12a71a7 to
9faec78
Compare
|
no code additions needed to use STT, you just need to install Superwhisper and press the right command key |



Claude is talking!
Three hooks wire up TTS:
Controls:
/tts-enable
/tts-disable