@domdhi/claude-code-tts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.md ADDED
@@ -0,0 +1,335 @@
1
+ # claude-code-tts — Installation Reference
2
+
3
+ Full reference for install options, voice configuration, hooks wiring, and troubleshooting.
4
+
5
+ ---
6
+
7
+ ## Stack
8
+
9
+ | Package | Role |
10
+ |---------|------|
11
+ | `edge-tts` | Primary TTS — Microsoft neural voices, free, cloud, ~0 RAM |
12
+ | `miniaudio` | Decodes edge-tts MP3 output to PCM for playback |
13
+ | `sounddevice` | Audio playback |
14
+ | `cffi` | sounddevice's C backend (not auto-installed by pip) |
15
+ | `kokoro-onnx` | Optional offline fallback — activates if edge-tts fails |
16
+ | `onnxruntime` | ONNX runtime (auto-installed with kokoro-onnx) |
17
+
18
+ **Engine priority:** edge-tts (primary) → kokoro-onnx (fallback, if installed)
19
+
20
+ ---
21
+
22
+ ## Install
23
+
24
+ ### Option A — installer script (recommended)
25
+
26
+ ```bash
27
+ git clone https://github.com/domdhi/claude-code-tts
28
+ cd claude-code-tts
29
+ pip install edge-tts miniaudio sounddevice cffi
30
+ python install.py
31
+ ```
32
+
33
+ The installer:
34
+ 1. Checks Python version (3.10+ required)
35
+ 2. Installs required packages
36
+ 3. Copies hook files to `~/.claude/hooks/tts/`
37
+ 4. Creates the `on` file (TTS enabled immediately)
38
+ 5. Optionally installs kokoro-onnx offline fallback (~82MB)
39
+ 6. Prints the `settings.json` snippet to add
40
+
41
+ ### Option B — manual
42
+
43
+ ```bash
44
+ # Install packages
45
+ pip install edge-tts miniaudio sounddevice cffi
46
+
47
+ # Create install dir
48
+ mkdir -p ~/.claude/hooks/tts
49
+
50
+ # Copy files
51
+ cp daemon.py stop.py task-hook.py repeat.py voices.json ~/.claude/hooks/tts/
52
+
53
+ # Enable TTS
54
+ touch ~/.claude/hooks/tts/on
55
+ ```
56
+
57
+ Then add the settings.json snippet below manually.
58
+
59
+ ---
60
+
61
+ ## Claude Code Settings
62
+
63
+ Add to `~/.claude/settings.json`. If you already have a `"hooks"` key, merge these entries — don't replace the whole object.
64
+
65
+ **Mac/Linux:**
66
+ ```json
67
+ {
68
+ "hooks": {
69
+ "Stop": [
70
+ {
71
+ "hooks": [
72
+ {
73
+ "type": "command",
74
+ "command": "python \"$HOME/.claude/hooks/tts/stop.py\""
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ "PostToolUse": [
80
+ {
81
+ "matcher": "Task",
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "python \"$HOME/.claude/hooks/tts/task-hook.py\""
86
+ }
87
+ ]
88
+ }
89
+ ],
90
+ "UserPromptSubmit": [
91
+ {
92
+ "hooks": [
93
+ {
94
+ "type": "command",
95
+ "command": "python \"$HOME/.claude/hooks/tts/repeat.py\""
96
+ }
97
+ ]
98
+ }
99
+ ]
100
+ }
101
+ }
102
+ ```
103
+
104
+ **Windows** (replace `C:\Users\YourName` with your actual home path):
105
+ ```json
106
+ {
107
+ "hooks": {
108
+ "Stop": [
109
+ {
110
+ "hooks": [
111
+ {
112
+ "type": "command",
113
+ "command": "python \"C:\\Users\\YourName\\.claude\\hooks\\tts\\stop.py\""
114
+ }
115
+ ]
116
+ }
117
+ ],
118
+ "PostToolUse": [
119
+ {
120
+ "matcher": "Task",
121
+ "hooks": [
122
+ {
123
+ "type": "command",
124
+ "command": "python \"C:\\Users\\YourName\\.claude\\hooks\\tts\\task-hook.py\""
125
+ }
126
+ ]
127
+ }
128
+ ],
129
+ "UserPromptSubmit": [
130
+ {
131
+ "hooks": [
132
+ {
133
+ "type": "command",
134
+ "command": "python \"C:\\Users\\YourName\\.claude\\hooks\\tts\\repeat.py\""
135
+ }
136
+ ]
137
+ }
138
+ ]
139
+ }
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Offline Fallback (kokoro-onnx)
146
+
147
+ kokoro-onnx is an optional local TTS engine. It activates automatically if edge-tts fails (no internet, rate limit, etc.).
148
+
149
+ ```bash
150
+ pip install kokoro-onnx
151
+
152
+ # Download model files (~82MB total)
153
+ # Mac/Linux:
154
+ mkdir -p ~/.claude/hooks/tts/models
155
+ curl -L "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx" \
156
+ -o ~/.claude/hooks/tts/models/kokoro-v1.0.onnx
157
+ curl -L "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin" \
158
+ -o ~/.claude/hooks/tts/models/voices-v1.0.bin
159
+
160
+ # Windows (PowerShell):
161
+ New-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\hooks\tts\models"
162
+ Invoke-WebRequest "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx" `
163
+ -OutFile "$env:USERPROFILE\.claude\hooks\tts\models\kokoro-v1.0.onnx"
164
+ Invoke-WebRequest "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin" `
165
+ -OutFile "$env:USERPROFILE\.claude\hooks\tts\models\voices-v1.0.bin"
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Voice Configuration
171
+
172
+ Edit `~/.claude/hooks/tts/voices.json`.
173
+
174
+ ### Available voices
175
+
176
+ | Key | Edge TTS voice | Style |
177
+ |-----|----------------|-------|
178
+ | `af_heart` | en-US-AriaNeural | warm, natural female (default) |
179
+ | `af_bella` | en-US-MichelleNeural | polished female |
180
+ | `af_sarah` | en-US-SaraNeural | professional female |
181
+ | `af_sky` | en-US-JennyNeural | friendly, conversational |
182
+ | `af_nova` | en-US-MonicaNeural | energetic female |
183
+ | `am_michael` | en-US-GuyNeural | natural, authoritative male |
184
+ | `am_adam` | en-US-DavisNeural | deep male |
185
+ | `am_echo` | en-US-TonyNeural | casual male |
186
+ | `am_eric` | en-US-EricNeural | confident male |
187
+ | `am_liam` | en-US-RyanNeural | young, energetic male |
188
+ | `am_onyx` | en-US-ChristopherNeural | deep, authoritative male |
189
+
190
+ ### Voice priority (highest → lowest)
191
+
192
+ 1. `[AgentName]:` prefix in response text → agent voice from `voices.json`
193
+ 2. Project key match → `voices.json` `"projects"` section
194
+ 3. `"default"` entry in `voices.json`
195
+
196
+ ### Per-agent voices (task-hook.py)
197
+
198
+ `task-hook.py` reads `subagent_type` from the Task tool input and looks up the agent by name:
199
+
200
+ ```json
201
+ {
202
+ "default": {"voice": "af_heart", "speed": 1.0},
203
+ "general-purpose": {"voice": "am_michael", "speed": 1.0},
204
+ "code-reviewer": {"voice": "am_onyx", "speed": 0.9}
205
+ }
206
+ ```
207
+
208
+ ### Per-agent prefix (stop.py)
209
+
210
+ Any agent that begins its response with `[AgentName]:` gets routed to that voice. Add to the agent's system prompt:
211
+ ```
212
+ Always begin your response with [AgentName]:
213
+ ```
214
+ Add to `voices.json`:
215
+ ```json
216
+ {
217
+ "MyAgent": {"voice": "am_adam", "speed": 0.9}
218
+ }
219
+ ```
220
+ The hook strips `[AgentName]:` before speaking.
221
+
222
+ ### Per-project voices
223
+
224
+ Add a `"projects"` section. Keys are matched as case-insensitive substrings of the encoded project path under `~/.claude/projects/`:
225
+
226
+ ```bash
227
+ ls ~/.claude/projects/ # shows encoded dir names like c--Users-me-Repos-MyProject
228
+ ```
229
+
230
+ ```json
231
+ {
232
+ "projects": {
233
+ "MyProject": {"voice": "am_onyx", "speed": 0.95},
234
+ "another-repo": {"voice": "af_sarah", "speed": 1.0}
235
+ }
236
+ }
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Enable / Disable
242
+
243
+ TTS is gated on the presence of `~/.claude/hooks/tts/on`:
244
+
245
+ ```bash
246
+ # Disable
247
+ rm ~/.claude/hooks/tts/on
248
+
249
+ # Re-enable
250
+ touch ~/.claude/hooks/tts/on # Mac/Linux
251
+ echo. > %USERPROFILE%\.claude\hooks\tts\on # Windows cmd
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Commands
257
+
258
+ Type in the Claude Code prompt:
259
+
260
+ | Prompt | Effect |
261
+ |--------|--------|
262
+ | `/voice:stop` or `/stop` | Stop speech immediately, clear queue |
263
+ | `/repeat` | Replay last spoken response |
264
+
265
+ ---
266
+
267
+ ## Daemon Protocol
268
+
269
+ The daemon runs on `localhost:6254` and accepts JSON lines:
270
+
271
+ | Command | Effect |
272
+ |---------|--------|
273
+ | `{"cmd": "speak", "text": "...", "voice": "af_heart", "speed": 1.0, "project": "repo"}` | Queue speech |
274
+ | `{"cmd": "stop"}` | Stop immediately, clear queue |
275
+ | `{"cmd": "ping"}` | Health check → `{"ok": true, "pid": N}` |
276
+ | `{"cmd": "quit"}` | Shut down daemon |
277
+
278
+ **Queue behavior:** at most one item per `project` key. New message from the same project replaces its queued slot. Messages from different projects line up. Omit `project` for single-project use.
279
+
280
+ ---
281
+
282
+ ## Performance Tuning
283
+
284
+ The daemon runs at below-normal process priority and limits ONNX threads by default. To adjust:
285
+
286
+ ```python
287
+ # Top of daemon.py, before any imports
288
+ os.environ.setdefault('OMP_NUM_THREADS', '4') # lower = less CPU spike
289
+ os.environ.setdefault('ONNXRUNTIME_NUM_THREADS', '4') # higher = faster synthesis
290
+ ```
291
+
292
+ After editing daemon.py, restart the daemon:
293
+ ```bash
294
+ # Mac/Linux
295
+ pkill -f daemon.py
296
+
297
+ # Windows
298
+ taskkill /F /IM python.exe # kills all python processes
299
+ ```
300
+ The daemon auto-restarts on the next response.
301
+
302
+ ---
303
+
304
+ ## Troubleshooting
305
+
306
+ ### No audio output
307
+ - Check that the `on` file exists: `ls ~/.claude/hooks/tts/on`
308
+ - Check that settings.json hooks are wired correctly
309
+ - Check `~/.claude/hooks/tts/daemon.log` for errors
310
+
311
+ ### edge-tts fails silently
312
+ - Requires internet access — check connectivity
313
+ - If offline, install kokoro-onnx fallback (see above)
314
+ - Check `~/.claude/hooks/tts/debug.log` for synthesis errors
315
+
316
+ ### kokoro-onnx not found at startup
317
+ - This is expected if you skipped the offline fallback install
318
+ - The daemon will log: `kokoro-onnx not installed — edge-tts only`
319
+ - Install it if you need offline support: `pip install kokoro-onnx` + download models
320
+
321
+ ### cffi not found / sounddevice import error
322
+ - Run: `pip install cffi`
323
+ - sounddevice doesn't always pull in cffi automatically
324
+
325
+ ### Daemon keeps restarting / won't stay up
326
+ - Check for port conflict: `lsof -i :6254` (Mac/Linux) or `netstat -ano | findstr 6254` (Windows)
327
+ - Check `~/.claude/hooks/tts/daemon.log`
328
+
329
+ ### Audio cuts off mid-sentence (kokoro fallback)
330
+ - kokoro-onnx has a 510-token (~1500 char) hard limit
331
+ - The daemon chunks text at sentence boundaries automatically — if you're hitting this, check `debug.log` for `IndexError`
332
+
333
+ ### Windows: DETACHED_PROCESS causes silence
334
+ - Do not add `DETACHED_PROCESS` to the subprocess flags — it breaks the Windows audio session
335
+ - `CREATE_NO_WINDOW` only is correct (already set in the hook files)
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # claude-code-tts
2
+
3
+ Neural TTS hook system for [Claude Code](https://claude.ai/code). Reads Claude's responses aloud as they finish.
4
+
5
+ **Engines:** Edge TTS (Microsoft neural voices, free, requires internet) with automatic offline fallback to kokoro-onnx.
6
+ **Platform:** Windows, macOS, Linux
7
+ **Install:** one Python script, no build tools required
8
+
9
+ ---
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npx @domdhi/claude-code-tts
15
+ ```
16
+
17
+ That's it. The installer copies the hook files, enables TTS, optionally installs the offline fallback, and prints the `settings.json` snippet to add to Claude Code.
18
+
19
+ **Requirements:** Node.js 16+ and Python 3.10+ must both be installed. The hooks run in Python — Node is only used for the install command.
20
+
21
+ **Or install manually:**
22
+ ```bash
23
+ git clone https://github.com/domdhi/claude-code-tts
24
+ cd claude-code-tts
25
+ pip install edge-tts miniaudio sounddevice cffi
26
+ python install.py
27
+ ```
28
+
29
+ ---
30
+
31
+ ## What It Does
32
+
33
+ Three Claude Code hooks work together:
34
+
35
+ | Hook | File | When it fires |
36
+ |------|------|---------------|
37
+ | `Stop` | `stop.py` | After every Claude response — reads it aloud |
38
+ | `PostToolUse:Task` | `task-hook.py` | After a subagent finishes — reads its output |
39
+ | `UserPromptSubmit` | `repeat.py` | On `/repeat` or `/voice:stop` commands |
40
+
41
+ A persistent daemon (`daemon.py`) keeps the TTS model loaded in the background. Hook files connect to it via TCP on `localhost:6254`, starting it automatically if needed.
42
+
43
+ ---
44
+
45
+ ## Voice Configuration
46
+
47
+ Edit `~/.claude/hooks/tts/voices.json` to customize voices per agent or per project.
48
+
49
+ **Available voices:**
50
+
51
+ | Key | Edge TTS | Style |
52
+ |-----|----------|-------|
53
+ | `af_heart` | AriaNeural | warm female (default) |
54
+ | `af_bella` | MichelleNeural | polished female |
55
+ | `af_sarah` | SaraNeural | professional female |
56
+ | `af_sky` | JennyNeural | friendly female |
57
+ | `af_nova` | MonicaNeural | energetic female |
58
+ | `am_michael` | GuyNeural | natural male |
59
+ | `am_adam` | DavisNeural | deep male |
60
+ | `am_echo` | TonyNeural | casual male |
61
+ | `am_eric` | EricNeural | confident male |
62
+ | `am_liam` | RyanNeural | energetic male |
63
+ | `am_onyx` | ChristopherNeural | authoritative male |
64
+
65
+ **Per-agent voices** (add to `voices.json`):
66
+ ```json
67
+ {
68
+ "default": {"voice": "af_heart", "speed": 1.0},
69
+ "general-purpose": {"voice": "am_michael", "speed": 1.0}
70
+ }
71
+ ```
72
+ The `task-hook.py` reads `subagent_type` from the Task tool input to look up the agent's voice automatically.
73
+
74
+ **Per-project voices** (add a `"projects"` section):
75
+ ```json
76
+ {
77
+ "projects": {
78
+ "my-project": {"voice": "am_onyx", "speed": 0.95}
79
+ }
80
+ }
81
+ ```
82
+ Project keys are matched as case-insensitive substrings of the encoded project path under `~/.claude/projects/`.
83
+
84
+ **Per-agent prefix** — any agent that begins its response with `[AgentName]:` gets routed to that voice:
85
+ ```json
86
+ {
87
+ "MyAgent": {"voice": "am_adam", "speed": 0.9}
88
+ }
89
+ ```
90
+
91
+ See [INSTALL.md](INSTALL.md) for full configuration reference.
92
+
93
+ ---
94
+
95
+ ## Commands
96
+
97
+ Type these in the Claude Code prompt:
98
+
99
+ | Command | Effect |
100
+ |---------|--------|
101
+ | `/voice:stop` | Stop speech immediately |
102
+ | `/repeat` | Replay last response |
103
+
104
+ ---
105
+
106
+ ## Enable / Disable
107
+
108
+ TTS is controlled by the presence of an `on` file in the install directory:
109
+
110
+ ```bash
111
+ # Disable
112
+ rm ~/.claude/hooks/tts/on
113
+
114
+ # Re-enable
115
+ touch ~/.claude/hooks/tts/on # Mac/Linux
116
+ echo. > %USERPROFILE%\.claude\hooks\tts\on # Windows
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Requirements
122
+
123
+ - Python 3.10+
124
+ - Claude Code
125
+ - Internet connection (for Edge TTS primary engine)
126
+ - `edge-tts`, `miniaudio`, `sounddevice`, `cffi`
127
+ - Optional: `kokoro-onnx` + model files (~82MB) for offline fallback
128
+
129
+ ---
130
+
131
+ ## License
132
+
133
+ MIT
package/bin/install.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { execFileSync } = require('child_process')
5
+ const path = require('path')
6
+
7
+ // Find Python — try python3 first on Mac/Linux, python first on Windows
8
+ const candidates = process.platform === 'win32'
9
+ ? ['python', 'python3']
10
+ : ['python3', 'python']
11
+
12
+ let python = null
13
+ for (const candidate of candidates) {
14
+ try {
15
+ execFileSync(candidate, ['--version'], { stdio: 'ignore' })
16
+ python = candidate
17
+ break
18
+ } catch {
19
+ // not found, try next
20
+ }
21
+ }
22
+
23
+ if (!python) {
24
+ console.error('Error: Python 3.10+ is required but was not found.')
25
+ console.error('Install Python from https://python.org and try again.')
26
+ process.exit(1)
27
+ }
28
+
29
+ const script = path.join(__dirname, '..', 'install.py')
30
+ const args = process.argv.slice(2) // pass through --dir and any other flags
31
+
32
+ try {
33
+ execFileSync(python, [script, ...args], { stdio: 'inherit' })
34
+ } catch (e) {
35
+ process.exit(e.status ?? 1)
36
+ }