@domdhi/claude-code-tts 1.0.0 → 1.1.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/.claude/commands/voice/off.md +7 -0
- package/.claude/commands/voice/on.md +7 -0
- package/.claude/commands/voice/repeat.md +1 -0
- package/.claude/commands/voice/stop.md +1 -0
- package/INSTALL.md +44 -20
- package/LICENSE +21 -0
- package/README.md +26 -17
- package/install.py +148 -81
- package/package.json +4 -7
- /package/{daemon.py → .claude/hooks/tts/daemon.py} +0 -0
- /package/{repeat.py → .claude/hooks/tts/repeat.py} +0 -0
- /package/{stop.py → .claude/hooks/tts/stop.py} +0 -0
- /package/{task-hook.py → .claude/hooks/tts/task-hook.py} +0 -0
- /package/{voices.json → .claude/hooks/tts/voices.json} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Disable TTS voice output by removing the enable file. Run the appropriate command for your OS silently using the Bash tool, then respond with only: "TTS disabled."
|
|
2
|
+
|
|
3
|
+
Mac/Linux:
|
|
4
|
+
rm -f ~/.claude/hooks/tts/on
|
|
5
|
+
|
|
6
|
+
Windows:
|
|
7
|
+
del /F "%USERPROFILE%\.claude\hooks\tts\on" 2>nul
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/voice:repeat
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/voice:stop
|
package/INSTALL.md
CHANGED
|
@@ -21,46 +21,68 @@ Full reference for install options, voice configuration, hooks wiring, and troub
|
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
24
|
-
### Option A —
|
|
24
|
+
### Option A — npx (recommended)
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
# Install for current project only (commands + settings.local.json in ./.claude/)
|
|
28
|
+
npx @domdhi/claude-code-tts
|
|
29
|
+
|
|
30
|
+
# Install globally (commands + settings.json in ~/.claude/, all projects)
|
|
31
|
+
npx @domdhi/claude-code-tts --global
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Requires Node.js 16+ and Python 3.10+. No cloning required.
|
|
35
|
+
|
|
36
|
+
The installer automatically:
|
|
34
37
|
1. Checks Python version (3.10+ required)
|
|
35
|
-
2. Installs required packages
|
|
36
|
-
3. Copies hook
|
|
38
|
+
2. Installs required packages (`edge-tts`, `miniaudio`, `sounddevice`, `cffi`)
|
|
39
|
+
3. Copies hook scripts to `~/.claude/hooks/tts/`
|
|
37
40
|
4. Creates the `on` file (TTS enabled immediately)
|
|
38
41
|
5. Optionally installs kokoro-onnx offline fallback (~82MB)
|
|
39
|
-
6.
|
|
42
|
+
6. Installs `/voice:*` slash commands
|
|
43
|
+
7. Patches `settings.json` / `settings.local.json` with hook entries (backs up original first)
|
|
44
|
+
|
|
45
|
+
**Local vs global:**
|
|
46
|
+
- Default (no flag): commands go to `./.claude/commands/voice/`, hooks registered in `./.claude/settings.local.json` (gitignored — safe to commit the project)
|
|
47
|
+
- `--global`: commands go to `~/.claude/commands/voice/`, hooks registered in `~/.claude/settings.json`
|
|
48
|
+
|
|
49
|
+
### Option B — installer script
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/Domdhi/claude-code-tts
|
|
53
|
+
cd claude-code-tts
|
|
54
|
+
pip install edge-tts miniaudio sounddevice cffi
|
|
55
|
+
python install.py # project-local
|
|
56
|
+
python install.py --global # global (all projects)
|
|
57
|
+
```
|
|
40
58
|
|
|
41
|
-
### Option
|
|
59
|
+
### Option C — manual (Mac/Linux)
|
|
42
60
|
|
|
43
61
|
```bash
|
|
44
62
|
# Install packages
|
|
45
63
|
pip install edge-tts miniaudio sounddevice cffi
|
|
46
64
|
|
|
47
|
-
#
|
|
65
|
+
# Copy hook scripts
|
|
48
66
|
mkdir -p ~/.claude/hooks/tts
|
|
67
|
+
cp .claude/hooks/tts/daemon.py .claude/hooks/tts/stop.py \
|
|
68
|
+
.claude/hooks/tts/task-hook.py .claude/hooks/tts/repeat.py \
|
|
69
|
+
.claude/hooks/tts/voices.json ~/.claude/hooks/tts/
|
|
49
70
|
|
|
50
|
-
# Copy
|
|
51
|
-
|
|
71
|
+
# Copy slash commands (global)
|
|
72
|
+
mkdir -p ~/.claude/commands/voice
|
|
73
|
+
cp .claude/commands/voice/*.md ~/.claude/commands/voice/
|
|
52
74
|
|
|
53
75
|
# Enable TTS
|
|
54
76
|
touch ~/.claude/hooks/tts/on
|
|
55
77
|
```
|
|
56
78
|
|
|
57
|
-
Then add the settings
|
|
79
|
+
Then add the settings snippet below manually.
|
|
58
80
|
|
|
59
81
|
---
|
|
60
82
|
|
|
61
83
|
## Claude Code Settings
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
The installer patches this automatically. For manual setup, add to `~/.claude/settings.json` (global) or `.claude/settings.local.json` (project-local, gitignored). If you already have a `"hooks"` key, merge these entries — don't replace the whole object.
|
|
64
86
|
|
|
65
87
|
**Mac/Linux:**
|
|
66
88
|
```json
|
|
@@ -259,8 +281,10 @@ Type in the Claude Code prompt:
|
|
|
259
281
|
|
|
260
282
|
| Prompt | Effect |
|
|
261
283
|
|--------|--------|
|
|
262
|
-
| `/voice:stop`
|
|
263
|
-
| `/repeat` | Replay last spoken response |
|
|
284
|
+
| `/voice:stop` | Stop speech immediately, clear queue |
|
|
285
|
+
| `/voice:repeat` | Replay last spoken response |
|
|
286
|
+
| `/voice:on` | Re-enable TTS (creates `on` file) |
|
|
287
|
+
| `/voice:off` | Disable TTS (removes `on` file) |
|
|
264
288
|
|
|
265
289
|
---
|
|
266
290
|
|
|
@@ -294,10 +318,10 @@ After editing daemon.py, restart the daemon:
|
|
|
294
318
|
# Mac/Linux
|
|
295
319
|
pkill -f daemon.py
|
|
296
320
|
|
|
297
|
-
# Windows
|
|
298
|
-
taskkill /F /IM python.exe
|
|
321
|
+
# Windows (kills all Python processes — close other Python apps first)
|
|
322
|
+
taskkill /F /IM python.exe
|
|
299
323
|
```
|
|
300
|
-
The daemon auto-restarts on the next response.
|
|
324
|
+
The daemon auto-restarts on the next Claude response.
|
|
301
325
|
|
|
302
326
|
---
|
|
303
327
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Domdhi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,26 +4,31 @@ Neural TTS hook system for [Claude Code](https://claude.ai/code). Reads Claude's
|
|
|
4
4
|
|
|
5
5
|
**Engines:** Edge TTS (Microsoft neural voices, free, requires internet) with automatic offline fallback to kokoro-onnx.
|
|
6
6
|
**Platform:** Windows, macOS, Linux
|
|
7
|
-
**Install:** one
|
|
7
|
+
**Install:** one command, no build tools required
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
+
# Install for current project only
|
|
14
15
|
npx @domdhi/claude-code-tts
|
|
16
|
+
|
|
17
|
+
# Install globally (all projects)
|
|
18
|
+
npx @domdhi/claude-code-tts --global
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
That's it. The installer copies the hook files, enables TTS, optionally installs the offline fallback, and
|
|
21
|
+
That's it. The installer copies the hook files, enables TTS, optionally installs the offline fallback, and automatically patches `settings.json` and installs the slash commands.
|
|
18
22
|
|
|
19
23
|
**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
24
|
|
|
21
25
|
**Or install manually:**
|
|
22
26
|
```bash
|
|
23
|
-
git clone https://github.com/
|
|
27
|
+
git clone https://github.com/Domdhi/claude-code-tts
|
|
24
28
|
cd claude-code-tts
|
|
25
29
|
pip install edge-tts miniaudio sounddevice cffi
|
|
26
|
-
python install.py
|
|
30
|
+
python install.py # project-local
|
|
31
|
+
python install.py --global # all projects
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
---
|
|
@@ -36,7 +41,7 @@ Three Claude Code hooks work together:
|
|
|
36
41
|
|------|------|---------------|
|
|
37
42
|
| `Stop` | `stop.py` | After every Claude response — reads it aloud |
|
|
38
43
|
| `PostToolUse:Task` | `task-hook.py` | After a subagent finishes — reads its output |
|
|
39
|
-
| `UserPromptSubmit` | `repeat.py` | On `/repeat` or `/voice:stop` commands |
|
|
44
|
+
| `UserPromptSubmit` | `repeat.py` | On `/voice:repeat` or `/voice:stop` commands |
|
|
40
45
|
|
|
41
46
|
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
47
|
|
|
@@ -62,16 +67,15 @@ Edit `~/.claude/hooks/tts/voices.json` to customize voices per agent or per proj
|
|
|
62
67
|
| `am_liam` | RyanNeural | energetic male |
|
|
63
68
|
| `am_onyx` | ChristopherNeural | authoritative male |
|
|
64
69
|
|
|
65
|
-
**Per-agent voices**
|
|
70
|
+
**Per-agent voices** — `task-hook.py` reads `subagent_type` from the Task tool and looks up the agent by name:
|
|
66
71
|
```json
|
|
67
72
|
{
|
|
68
73
|
"default": {"voice": "af_heart", "speed": 1.0},
|
|
69
74
|
"general-purpose": {"voice": "am_michael", "speed": 1.0}
|
|
70
75
|
}
|
|
71
76
|
```
|
|
72
|
-
The `task-hook.py` reads `subagent_type` from the Task tool input to look up the agent's voice automatically.
|
|
73
77
|
|
|
74
|
-
**Per-project voices**
|
|
78
|
+
**Per-project voices** — add a `"projects"` section with keys matched against the project path:
|
|
75
79
|
```json
|
|
76
80
|
{
|
|
77
81
|
"projects": {
|
|
@@ -79,16 +83,16 @@ The `task-hook.py` reads `subagent_type` from the Task tool input to look up the
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
```
|
|
82
|
-
Project keys are matched as case-insensitive substrings of the encoded project path under `~/.claude/projects/`.
|
|
83
86
|
|
|
84
|
-
**Per-agent prefix** —
|
|
87
|
+
**Per-agent prefix** — add `Always begin your response with [AgentName]:` to an agent's system prompt, then add it to `voices.json`:
|
|
85
88
|
```json
|
|
86
89
|
{
|
|
87
90
|
"MyAgent": {"voice": "am_adam", "speed": 0.9}
|
|
88
91
|
}
|
|
89
92
|
```
|
|
93
|
+
The hook strips the `[AgentName]:` prefix before speaking.
|
|
90
94
|
|
|
91
|
-
See [INSTALL.md](INSTALL.md) for full configuration reference.
|
|
95
|
+
See [INSTALL.md](INSTALL.md) for the full configuration reference.
|
|
92
96
|
|
|
93
97
|
---
|
|
94
98
|
|
|
@@ -99,7 +103,9 @@ Type these in the Claude Code prompt:
|
|
|
99
103
|
| Command | Effect |
|
|
100
104
|
|---------|--------|
|
|
101
105
|
| `/voice:stop` | Stop speech immediately |
|
|
102
|
-
| `/repeat` | Replay last response |
|
|
106
|
+
| `/voice:repeat` | Replay last response |
|
|
107
|
+
| `/voice:on` | Re-enable TTS |
|
|
108
|
+
| `/voice:off` | Disable TTS |
|
|
103
109
|
|
|
104
110
|
---
|
|
105
111
|
|
|
@@ -111,9 +117,11 @@ TTS is controlled by the presence of an `on` file in the install directory:
|
|
|
111
117
|
# Disable
|
|
112
118
|
rm ~/.claude/hooks/tts/on
|
|
113
119
|
|
|
114
|
-
# Re-enable
|
|
115
|
-
touch ~/.claude/hooks/tts/on
|
|
116
|
-
|
|
120
|
+
# Re-enable (Mac/Linux)
|
|
121
|
+
touch ~/.claude/hooks/tts/on
|
|
122
|
+
|
|
123
|
+
# Re-enable (Windows cmd)
|
|
124
|
+
echo. > %USERPROFILE%\.claude\hooks\tts\on
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
---
|
|
@@ -121,13 +129,14 @@ echo. > %USERPROFILE%\.claude\hooks\tts\on # Windows
|
|
|
121
129
|
## Requirements
|
|
122
130
|
|
|
123
131
|
- Python 3.10+
|
|
132
|
+
- Node.js 16+ (for `npx` install only)
|
|
124
133
|
- Claude Code
|
|
125
134
|
- Internet connection (for Edge TTS primary engine)
|
|
126
|
-
- `edge-tts`, `miniaudio`, `sounddevice`, `cffi`
|
|
135
|
+
- `edge-tts`, `miniaudio`, `sounddevice`, `cffi` (installed automatically)
|
|
127
136
|
- Optional: `kokoro-onnx` + model files (~82MB) for offline fallback
|
|
128
137
|
|
|
129
138
|
---
|
|
130
139
|
|
|
131
140
|
## License
|
|
132
141
|
|
|
133
|
-
MIT
|
|
142
|
+
MIT — see [LICENSE](LICENSE)
|
package/install.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
claude-code-tts installer
|
|
4
|
-
Copies hook files
|
|
4
|
+
Copies hook files to ~/.claude/hooks/tts/, installs slash commands, and patches settings.json.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
|
+
import json
|
|
8
9
|
import os
|
|
9
10
|
import shutil
|
|
10
11
|
import subprocess
|
|
@@ -24,12 +25,25 @@ KOKORO_MODEL_URLS = [
|
|
|
24
25
|
),
|
|
25
26
|
]
|
|
26
27
|
|
|
27
|
-
# voices.json is excluded from HOOK_FILES
|
|
28
|
+
# voices.json is excluded from HOOK_FILES -- handled separately to avoid clobbering customizations
|
|
28
29
|
HOOK_FILES = ['daemon.py', 'stop.py', 'task-hook.py', 'repeat.py']
|
|
30
|
+
COMMAND_FILES = ['stop.md', 'repeat.md', 'on.md', 'off.md']
|
|
29
31
|
|
|
30
|
-
INSTALL_DIR = os.path.join(os.path.expanduser('~'), '.claude', 'hooks', 'tts')
|
|
31
|
-
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
32
32
|
SOURCE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
33
|
+
HOOKS_SOURCE = os.path.join(SOURCE_DIR, '.claude', 'hooks', 'tts')
|
|
34
|
+
COMMANDS_SOURCE = os.path.join(SOURCE_DIR, '.claude', 'commands', 'voice')
|
|
35
|
+
|
|
36
|
+
INSTALL_DIR = os.path.join(os.path.expanduser('~'), '.claude', 'hooks', 'tts')
|
|
37
|
+
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
38
|
+
|
|
39
|
+
# Default: project-local install (current directory's .claude/)
|
|
40
|
+
# Overridden to ~/.claude/ when --global is passed
|
|
41
|
+
_project_claude = os.path.join(os.getcwd(), '.claude')
|
|
42
|
+
COMMANDS_INSTALL_DIR = os.path.join(_project_claude, 'commands', 'voice')
|
|
43
|
+
# Use settings.local.json for local scope — gitignored, machine-specific (absolute Python paths)
|
|
44
|
+
SETTINGS_FILE = os.path.join(_project_claude, 'settings.local.json')
|
|
45
|
+
|
|
46
|
+
GLOBAL_SCOPE = False
|
|
33
47
|
|
|
34
48
|
|
|
35
49
|
def step(msg):
|
|
@@ -83,7 +97,7 @@ def create_dirs():
|
|
|
83
97
|
def copy_files():
|
|
84
98
|
step('Copying hook files...')
|
|
85
99
|
for filename in HOOK_FILES:
|
|
86
|
-
src = os.path.join(
|
|
100
|
+
src = os.path.join(HOOKS_SOURCE, filename)
|
|
87
101
|
dst = os.path.join(INSTALL_DIR, filename)
|
|
88
102
|
if not os.path.exists(src):
|
|
89
103
|
fail(f'Source file not found: {src}')
|
|
@@ -91,8 +105,8 @@ def copy_files():
|
|
|
91
105
|
shutil.copy2(src, dst)
|
|
92
106
|
ok(filename)
|
|
93
107
|
|
|
94
|
-
# voices.json: only copy on first install
|
|
95
|
-
voices_src = os.path.join(
|
|
108
|
+
# voices.json: only copy on first install -- preserve existing customizations
|
|
109
|
+
voices_src = os.path.join(HOOKS_SOURCE, 'voices.json')
|
|
96
110
|
voices_dst = os.path.join(INSTALL_DIR, 'voices.json')
|
|
97
111
|
if os.path.exists(voices_dst):
|
|
98
112
|
ok('voices.json (kept existing, not overwritten)')
|
|
@@ -101,6 +115,19 @@ def copy_files():
|
|
|
101
115
|
ok('voices.json')
|
|
102
116
|
|
|
103
117
|
|
|
118
|
+
def copy_commands():
|
|
119
|
+
step('Installing slash command files...')
|
|
120
|
+
os.makedirs(COMMANDS_INSTALL_DIR, exist_ok=True)
|
|
121
|
+
for filename in COMMAND_FILES:
|
|
122
|
+
src = os.path.join(COMMANDS_SOURCE, filename)
|
|
123
|
+
dst = os.path.join(COMMANDS_INSTALL_DIR, filename)
|
|
124
|
+
if not os.path.exists(src):
|
|
125
|
+
warn(f'Command file not found: {src} -- skipping')
|
|
126
|
+
continue
|
|
127
|
+
shutil.copy2(src, dst)
|
|
128
|
+
ok(filename)
|
|
129
|
+
|
|
130
|
+
|
|
104
131
|
def enable_tts():
|
|
105
132
|
step('Enabling TTS (creating on file)...')
|
|
106
133
|
on_file = os.path.join(INSTALL_DIR, 'on')
|
|
@@ -149,119 +176,159 @@ def offer_kokoro():
|
|
|
149
176
|
ok(filename)
|
|
150
177
|
except Exception as e:
|
|
151
178
|
fail(f'Model download failed: {e}')
|
|
152
|
-
print(' You can download manually
|
|
179
|
+
print(' You can download manually -- see INSTALL.md for URLs.')
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _hook_command(script_name):
|
|
183
|
+
"""Return the shell command string for a given hook script."""
|
|
184
|
+
path = os.path.join(INSTALL_DIR, script_name)
|
|
185
|
+
return f'{sys.executable} "{path}"'
|
|
153
186
|
|
|
154
187
|
|
|
155
|
-
def
|
|
156
|
-
|
|
188
|
+
def _has_command(hook_list, command):
|
|
189
|
+
"""Return True if command already appears in any entry in hook_list."""
|
|
190
|
+
for entry in hook_list:
|
|
191
|
+
for h in entry.get('hooks', []):
|
|
192
|
+
if h.get('command') == command:
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
157
196
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
197
|
+
def patch_settings_json():
|
|
198
|
+
step(f'Patching Claude Code settings: {SETTINGS_FILE}')
|
|
199
|
+
|
|
200
|
+
stop_cmd = _hook_command('stop.py')
|
|
201
|
+
task_cmd = _hook_command('task-hook.py')
|
|
202
|
+
repeat_cmd = _hook_command('repeat.py')
|
|
203
|
+
|
|
204
|
+
# Load existing settings (or start fresh)
|
|
205
|
+
settings = {}
|
|
206
|
+
if os.path.exists(SETTINGS_FILE):
|
|
207
|
+
try:
|
|
208
|
+
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
209
|
+
settings = json.load(f)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
warn(f'Could not parse settings.json: {e}')
|
|
212
|
+
warn('Skipping auto-patch. Add the hooks manually -- see INSTALL.md.')
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
hooks = settings.setdefault('hooks', {})
|
|
216
|
+
changed = False
|
|
217
|
+
|
|
218
|
+
# Stop hook
|
|
219
|
+
if not _has_command(hooks.get('Stop', []), stop_cmd):
|
|
220
|
+
hooks.setdefault('Stop', []).append({
|
|
221
|
+
'hooks': [{'type': 'command', 'command': stop_cmd}]
|
|
222
|
+
})
|
|
223
|
+
changed = True
|
|
224
|
+
ok('Added Stop hook')
|
|
163
225
|
else:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"type": "command",
|
|
187
|
-
"command": "{task_cmd}"
|
|
188
|
-
}}
|
|
189
|
-
]
|
|
190
|
-
}}
|
|
191
|
-
],
|
|
192
|
-
"UserPromptSubmit": [
|
|
193
|
-
{{
|
|
194
|
-
"hooks": [
|
|
195
|
-
{{
|
|
196
|
-
"type": "command",
|
|
197
|
-
"command": "{repeat_cmd}"
|
|
198
|
-
}}
|
|
199
|
-
]
|
|
200
|
-
}}
|
|
201
|
-
]
|
|
202
|
-
}}
|
|
203
|
-
}}'''
|
|
204
|
-
|
|
205
|
-
settings_path = os.path.join(os.path.expanduser('~'), '.claude', 'settings.json')
|
|
206
|
-
print(f"""
|
|
207
|
-
-------------------------------------------------------------
|
|
208
|
-
Add this to your Claude Code settings (~/.claude/settings.json):
|
|
226
|
+
ok('Stop hook already registered')
|
|
227
|
+
|
|
228
|
+
# PostToolUse:Task hook
|
|
229
|
+
if not _has_command(hooks.get('PostToolUse', []), task_cmd):
|
|
230
|
+
hooks.setdefault('PostToolUse', []).append({
|
|
231
|
+
'matcher': 'Task',
|
|
232
|
+
'hooks': [{'type': 'command', 'command': task_cmd}]
|
|
233
|
+
})
|
|
234
|
+
changed = True
|
|
235
|
+
ok('Added PostToolUse:Task hook')
|
|
236
|
+
else:
|
|
237
|
+
ok('PostToolUse:Task hook already registered')
|
|
238
|
+
|
|
239
|
+
# UserPromptSubmit hook
|
|
240
|
+
if not _has_command(hooks.get('UserPromptSubmit', []), repeat_cmd):
|
|
241
|
+
hooks.setdefault('UserPromptSubmit', []).append({
|
|
242
|
+
'hooks': [{'type': 'command', 'command': repeat_cmd}]
|
|
243
|
+
})
|
|
244
|
+
changed = True
|
|
245
|
+
ok('Added UserPromptSubmit hook')
|
|
246
|
+
else:
|
|
247
|
+
ok('UserPromptSubmit hook already registered')
|
|
209
248
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
249
|
+
if not changed:
|
|
250
|
+
ok('settings.json already up to date')
|
|
251
|
+
return
|
|
213
252
|
|
|
214
|
-
|
|
253
|
+
# Backup before writing
|
|
254
|
+
if os.path.exists(SETTINGS_FILE):
|
|
255
|
+
backup = SETTINGS_FILE + '.bak'
|
|
256
|
+
shutil.copy2(SETTINGS_FILE, backup)
|
|
257
|
+
ok(f'Backed up original to settings.json.bak')
|
|
215
258
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
259
|
+
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
|
|
260
|
+
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
261
|
+
json.dump(settings, f, indent=2)
|
|
262
|
+
ok(f'settings.json saved')
|
|
219
263
|
|
|
220
264
|
|
|
221
265
|
def print_success():
|
|
266
|
+
on_file = os.path.join(INSTALL_DIR, 'on')
|
|
267
|
+
scope_note = '(global)' if GLOBAL_SCOPE else '(project-local)'
|
|
222
268
|
print(f"""
|
|
223
|
-
DONE: claude-code-tts installed
|
|
269
|
+
DONE: claude-code-tts installed.
|
|
270
|
+
|
|
271
|
+
Hooks: {INSTALL_DIR}
|
|
272
|
+
Commands: {COMMANDS_INSTALL_DIR} {scope_note}
|
|
273
|
+
Settings: {SETTINGS_FILE} {scope_note}
|
|
224
274
|
|
|
225
|
-
Quick test
|
|
226
|
-
Run Claude Code
|
|
275
|
+
Quick test:
|
|
276
|
+
Run Claude Code and ask anything -- the response will be read aloud.
|
|
227
277
|
|
|
228
278
|
Commands (type in Claude Code prompt):
|
|
229
279
|
/voice:stop Stop speech immediately
|
|
230
|
-
/repeat
|
|
280
|
+
/voice:repeat Replay last response
|
|
281
|
+
/voice:on Re-enable TTS
|
|
282
|
+
/voice:off Disable TTS
|
|
231
283
|
|
|
232
284
|
To disable TTS:
|
|
233
|
-
Delete {
|
|
234
|
-
|
|
235
|
-
To re-enable:
|
|
236
|
-
touch {os.path.join(INSTALL_DIR, 'on')} (Mac/Linux)
|
|
237
|
-
echo. > {os.path.join(INSTALL_DIR, 'on').replace('/', chr(92))} (Windows)
|
|
285
|
+
Delete {on_file}
|
|
238
286
|
|
|
239
287
|
Full docs: INSTALL.md
|
|
240
288
|
""")
|
|
241
289
|
|
|
242
290
|
|
|
243
291
|
def main():
|
|
244
|
-
global INSTALL_DIR, MODELS_DIR
|
|
292
|
+
global INSTALL_DIR, MODELS_DIR, COMMANDS_INSTALL_DIR, SETTINGS_FILE, GLOBAL_SCOPE
|
|
245
293
|
|
|
246
294
|
parser = argparse.ArgumentParser(description='claude-code-tts installer')
|
|
247
295
|
parser.add_argument(
|
|
248
296
|
'--dir', metavar='PATH',
|
|
249
|
-
help='Install hooks to PATH instead of ~/.claude/hooks/tts/'
|
|
297
|
+
help='Install hooks to PATH instead of ~/.claude/hooks/tts/ (for testing)'
|
|
298
|
+
)
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
'--global', dest='global_scope', action='store_true',
|
|
301
|
+
help='Install commands and settings into ~/.claude/ (all projects) instead of ./.claude/ (current project)'
|
|
250
302
|
)
|
|
251
303
|
args = parser.parse_args()
|
|
252
304
|
|
|
253
|
-
|
|
305
|
+
testing = bool(args.dir)
|
|
306
|
+
if testing:
|
|
254
307
|
INSTALL_DIR = os.path.abspath(args.dir)
|
|
255
308
|
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
256
309
|
|
|
257
|
-
|
|
310
|
+
if args.global_scope:
|
|
311
|
+
GLOBAL_SCOPE = True
|
|
312
|
+
global_claude = os.path.join(os.path.expanduser('~'), '.claude')
|
|
313
|
+
COMMANDS_INSTALL_DIR = os.path.join(global_claude, 'commands', 'voice')
|
|
314
|
+
SETTINGS_FILE = os.path.join(global_claude, 'settings.json')
|
|
315
|
+
|
|
316
|
+
scope_label = 'global (~/.claude/)' if args.global_scope else 'project-local (.claude/)'
|
|
317
|
+
print(f'\nclaude-code-tts installer [{scope_label}]\n')
|
|
258
318
|
check_python()
|
|
259
319
|
install_packages()
|
|
260
320
|
create_dirs()
|
|
261
321
|
copy_files()
|
|
262
322
|
enable_tts()
|
|
263
323
|
offer_kokoro()
|
|
264
|
-
|
|
324
|
+
|
|
325
|
+
if testing:
|
|
326
|
+
step(f'Test install mode (--dir): skipping settings.json patch and command install.')
|
|
327
|
+
ok(f'Hook files installed to {INSTALL_DIR}')
|
|
328
|
+
else:
|
|
329
|
+
copy_commands()
|
|
330
|
+
patch_settings_json()
|
|
331
|
+
|
|
265
332
|
print_success()
|
|
266
333
|
|
|
267
334
|
|
package/package.json
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domdhi/claude-code-tts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Neural TTS hook system for Claude Code. Reads Claude's responses aloud as they finish.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-code-tts": "bin/install.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
|
-
"
|
|
11
|
-
"stop.py",
|
|
12
|
-
"task-hook.py",
|
|
13
|
-
"repeat.py",
|
|
14
|
-
"voices.json",
|
|
10
|
+
".claude/",
|
|
15
11
|
"install.py",
|
|
16
|
-
"INSTALL.md"
|
|
12
|
+
"INSTALL.md",
|
|
13
|
+
"LICENSE"
|
|
17
14
|
],
|
|
18
15
|
"keywords": [
|
|
19
16
|
"claude",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|