@domdhi/claude-code-tts 1.0.0 → 1.2.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 +150 -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): everything goes into `./.claude/` — hook scripts, commands, and `settings.local.json` (gitignored). Good for shipping TTS config alongside a project. Note: the local daemon runs on `localhost:6254`; if you have two local-installed projects open at once, they'll share the port (the first one wins).
|
|
47
|
+
- `--global`: everything goes into `~/.claude/` — hook scripts, commands, and `settings.json`. Recommended for personal use across all projects.
|
|
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
|
|
21
|
+
That's it. The installer copies the hook files into `.claude/hooks/tts/`, enables TTS, optionally installs the offline fallback, installs slash commands, and patches `settings.local.json` — all in the current project directory. Use `--global` to install once for all projects.
|
|
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,26 @@ 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
|
+
_home_claude = os.path.join(os.path.expanduser('~'), '.claude')
|
|
37
|
+
_proj_claude = os.path.join(os.getcwd(), '.claude')
|
|
38
|
+
|
|
39
|
+
# Default: project-local — all files go into ./.claude/ (current project)
|
|
40
|
+
# --global overrides all of these to ~/.claude/
|
|
41
|
+
INSTALL_DIR = os.path.join(_proj_claude, 'hooks', 'tts')
|
|
42
|
+
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
43
|
+
COMMANDS_INSTALL_DIR = os.path.join(_proj_claude, 'commands', 'voice')
|
|
44
|
+
# settings.local.json is gitignored — safe for machine-specific absolute paths
|
|
45
|
+
SETTINGS_FILE = os.path.join(_proj_claude, 'settings.local.json')
|
|
46
|
+
|
|
47
|
+
GLOBAL_SCOPE = False
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
def step(msg):
|
|
@@ -83,7 +98,7 @@ def create_dirs():
|
|
|
83
98
|
def copy_files():
|
|
84
99
|
step('Copying hook files...')
|
|
85
100
|
for filename in HOOK_FILES:
|
|
86
|
-
src = os.path.join(
|
|
101
|
+
src = os.path.join(HOOKS_SOURCE, filename)
|
|
87
102
|
dst = os.path.join(INSTALL_DIR, filename)
|
|
88
103
|
if not os.path.exists(src):
|
|
89
104
|
fail(f'Source file not found: {src}')
|
|
@@ -91,8 +106,8 @@ def copy_files():
|
|
|
91
106
|
shutil.copy2(src, dst)
|
|
92
107
|
ok(filename)
|
|
93
108
|
|
|
94
|
-
# voices.json: only copy on first install
|
|
95
|
-
voices_src = os.path.join(
|
|
109
|
+
# voices.json: only copy on first install -- preserve existing customizations
|
|
110
|
+
voices_src = os.path.join(HOOKS_SOURCE, 'voices.json')
|
|
96
111
|
voices_dst = os.path.join(INSTALL_DIR, 'voices.json')
|
|
97
112
|
if os.path.exists(voices_dst):
|
|
98
113
|
ok('voices.json (kept existing, not overwritten)')
|
|
@@ -101,6 +116,19 @@ def copy_files():
|
|
|
101
116
|
ok('voices.json')
|
|
102
117
|
|
|
103
118
|
|
|
119
|
+
def copy_commands():
|
|
120
|
+
step('Installing slash command files...')
|
|
121
|
+
os.makedirs(COMMANDS_INSTALL_DIR, exist_ok=True)
|
|
122
|
+
for filename in COMMAND_FILES:
|
|
123
|
+
src = os.path.join(COMMANDS_SOURCE, filename)
|
|
124
|
+
dst = os.path.join(COMMANDS_INSTALL_DIR, filename)
|
|
125
|
+
if not os.path.exists(src):
|
|
126
|
+
warn(f'Command file not found: {src} -- skipping')
|
|
127
|
+
continue
|
|
128
|
+
shutil.copy2(src, dst)
|
|
129
|
+
ok(filename)
|
|
130
|
+
|
|
131
|
+
|
|
104
132
|
def enable_tts():
|
|
105
133
|
step('Enabling TTS (creating on file)...')
|
|
106
134
|
on_file = os.path.join(INSTALL_DIR, 'on')
|
|
@@ -149,119 +177,160 @@ def offer_kokoro():
|
|
|
149
177
|
ok(filename)
|
|
150
178
|
except Exception as e:
|
|
151
179
|
fail(f'Model download failed: {e}')
|
|
152
|
-
print(' You can download manually
|
|
180
|
+
print(' You can download manually -- see INSTALL.md for URLs.')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _hook_command(script_name):
|
|
184
|
+
"""Return the shell command string for a given hook script."""
|
|
185
|
+
path = os.path.join(INSTALL_DIR, script_name)
|
|
186
|
+
return f'{sys.executable} "{path}"'
|
|
153
187
|
|
|
154
188
|
|
|
155
|
-
def
|
|
156
|
-
|
|
189
|
+
def _has_command(hook_list, command):
|
|
190
|
+
"""Return True if command already appears in any entry in hook_list."""
|
|
191
|
+
for entry in hook_list:
|
|
192
|
+
for h in entry.get('hooks', []):
|
|
193
|
+
if h.get('command') == command:
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
157
197
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
def patch_settings_json():
|
|
199
|
+
step(f'Patching Claude Code settings: {SETTINGS_FILE}')
|
|
200
|
+
|
|
201
|
+
stop_cmd = _hook_command('stop.py')
|
|
202
|
+
task_cmd = _hook_command('task-hook.py')
|
|
203
|
+
repeat_cmd = _hook_command('repeat.py')
|
|
204
|
+
|
|
205
|
+
# Load existing settings (or start fresh)
|
|
206
|
+
settings = {}
|
|
207
|
+
if os.path.exists(SETTINGS_FILE):
|
|
208
|
+
try:
|
|
209
|
+
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
210
|
+
settings = json.load(f)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
warn(f'Could not parse settings.json: {e}')
|
|
213
|
+
warn('Skipping auto-patch. Add the hooks manually -- see INSTALL.md.')
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
hooks = settings.setdefault('hooks', {})
|
|
217
|
+
changed = False
|
|
218
|
+
|
|
219
|
+
# Stop hook
|
|
220
|
+
if not _has_command(hooks.get('Stop', []), stop_cmd):
|
|
221
|
+
hooks.setdefault('Stop', []).append({
|
|
222
|
+
'hooks': [{'type': 'command', 'command': stop_cmd}]
|
|
223
|
+
})
|
|
224
|
+
changed = True
|
|
225
|
+
ok('Added Stop hook')
|
|
163
226
|
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):
|
|
227
|
+
ok('Stop hook already registered')
|
|
228
|
+
|
|
229
|
+
# PostToolUse:Task hook
|
|
230
|
+
if not _has_command(hooks.get('PostToolUse', []), task_cmd):
|
|
231
|
+
hooks.setdefault('PostToolUse', []).append({
|
|
232
|
+
'matcher': 'Task',
|
|
233
|
+
'hooks': [{'type': 'command', 'command': task_cmd}]
|
|
234
|
+
})
|
|
235
|
+
changed = True
|
|
236
|
+
ok('Added PostToolUse:Task hook')
|
|
237
|
+
else:
|
|
238
|
+
ok('PostToolUse:Task hook already registered')
|
|
239
|
+
|
|
240
|
+
# UserPromptSubmit hook
|
|
241
|
+
if not _has_command(hooks.get('UserPromptSubmit', []), repeat_cmd):
|
|
242
|
+
hooks.setdefault('UserPromptSubmit', []).append({
|
|
243
|
+
'hooks': [{'type': 'command', 'command': repeat_cmd}]
|
|
244
|
+
})
|
|
245
|
+
changed = True
|
|
246
|
+
ok('Added UserPromptSubmit hook')
|
|
247
|
+
else:
|
|
248
|
+
ok('UserPromptSubmit hook already registered')
|
|
209
249
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
250
|
+
if not changed:
|
|
251
|
+
ok('settings.json already up to date')
|
|
252
|
+
return
|
|
213
253
|
|
|
214
|
-
|
|
254
|
+
# Backup before writing
|
|
255
|
+
if os.path.exists(SETTINGS_FILE):
|
|
256
|
+
backup = SETTINGS_FILE + '.bak'
|
|
257
|
+
shutil.copy2(SETTINGS_FILE, backup)
|
|
258
|
+
ok(f'Backed up original to settings.json.bak')
|
|
215
259
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
260
|
+
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
|
|
261
|
+
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
262
|
+
json.dump(settings, f, indent=2)
|
|
263
|
+
ok(f'settings.json saved')
|
|
219
264
|
|
|
220
265
|
|
|
221
266
|
def print_success():
|
|
267
|
+
on_file = os.path.join(INSTALL_DIR, 'on')
|
|
268
|
+
scope_note = '(global)' if GLOBAL_SCOPE else '(project-local)'
|
|
222
269
|
print(f"""
|
|
223
|
-
DONE: claude-code-tts installed
|
|
270
|
+
DONE: claude-code-tts installed.
|
|
271
|
+
|
|
272
|
+
Hooks: {INSTALL_DIR}
|
|
273
|
+
Commands: {COMMANDS_INSTALL_DIR} {scope_note}
|
|
274
|
+
Settings: {SETTINGS_FILE} {scope_note}
|
|
224
275
|
|
|
225
|
-
Quick test
|
|
226
|
-
Run Claude Code
|
|
276
|
+
Quick test:
|
|
277
|
+
Run Claude Code and ask anything -- the response will be read aloud.
|
|
227
278
|
|
|
228
279
|
Commands (type in Claude Code prompt):
|
|
229
280
|
/voice:stop Stop speech immediately
|
|
230
|
-
/repeat
|
|
281
|
+
/voice:repeat Replay last response
|
|
282
|
+
/voice:on Re-enable TTS
|
|
283
|
+
/voice:off Disable TTS
|
|
231
284
|
|
|
232
285
|
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)
|
|
286
|
+
Delete {on_file}
|
|
238
287
|
|
|
239
288
|
Full docs: INSTALL.md
|
|
240
289
|
""")
|
|
241
290
|
|
|
242
291
|
|
|
243
292
|
def main():
|
|
244
|
-
global INSTALL_DIR, MODELS_DIR
|
|
293
|
+
global INSTALL_DIR, MODELS_DIR, COMMANDS_INSTALL_DIR, SETTINGS_FILE, GLOBAL_SCOPE
|
|
245
294
|
|
|
246
295
|
parser = argparse.ArgumentParser(description='claude-code-tts installer')
|
|
247
296
|
parser.add_argument(
|
|
248
297
|
'--dir', metavar='PATH',
|
|
249
|
-
help='
|
|
298
|
+
help='Override hook install directory (for testing only)'
|
|
299
|
+
)
|
|
300
|
+
parser.add_argument(
|
|
301
|
+
'--global', dest='global_scope', action='store_true',
|
|
302
|
+
help='Install commands and settings into ~/.claude/ (all projects) instead of ./.claude/ (current project)'
|
|
250
303
|
)
|
|
251
304
|
args = parser.parse_args()
|
|
252
305
|
|
|
253
|
-
|
|
306
|
+
testing = bool(args.dir)
|
|
307
|
+
if testing:
|
|
254
308
|
INSTALL_DIR = os.path.abspath(args.dir)
|
|
255
309
|
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
256
310
|
|
|
257
|
-
|
|
311
|
+
if args.global_scope:
|
|
312
|
+
GLOBAL_SCOPE = True
|
|
313
|
+
INSTALL_DIR = os.path.join(_home_claude, 'hooks', 'tts')
|
|
314
|
+
MODELS_DIR = os.path.join(INSTALL_DIR, 'models')
|
|
315
|
+
COMMANDS_INSTALL_DIR = os.path.join(_home_claude, 'commands', 'voice')
|
|
316
|
+
SETTINGS_FILE = os.path.join(_home_claude, 'settings.json')
|
|
317
|
+
|
|
318
|
+
scope_label = 'global (~/.claude/)' if args.global_scope else 'project-local (.claude/)'
|
|
319
|
+
print(f'\nclaude-code-tts installer [{scope_label}]\n')
|
|
258
320
|
check_python()
|
|
259
321
|
install_packages()
|
|
260
322
|
create_dirs()
|
|
261
323
|
copy_files()
|
|
262
324
|
enable_tts()
|
|
263
325
|
offer_kokoro()
|
|
264
|
-
|
|
326
|
+
|
|
327
|
+
if testing:
|
|
328
|
+
step(f'Test install mode (--dir): skipping settings.json patch and command install.')
|
|
329
|
+
ok(f'Hook files installed to {INSTALL_DIR}')
|
|
330
|
+
else:
|
|
331
|
+
copy_commands()
|
|
332
|
+
patch_settings_json()
|
|
333
|
+
|
|
265
334
|
print_success()
|
|
266
335
|
|
|
267
336
|
|
package/package.json
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domdhi/claude-code-tts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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
|