@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.
@@ -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,7 @@
1
+ Enable TTS voice output by creating the enable file. Run the appropriate command for your OS silently using the Bash tool, then respond with only: "TTS enabled."
2
+
3
+ Mac/Linux:
4
+ touch ~/.claude/hooks/tts/on
5
+
6
+ Windows:
7
+ echo. > "%USERPROFILE%\.claude\hooks\tts\on"
@@ -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 — installer script (recommended)
24
+ ### Option A — npx (recommended)
25
25
 
26
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
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
- The installer:
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 files to `~/.claude/hooks/tts/`
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. Prints the `settings.json` snippet to add
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 B — manual
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
- # Create install dir
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 files
51
- cp daemon.py stop.py task-hook.py repeat.py voices.json ~/.claude/hooks/tts/
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.json snippet below manually.
79
+ Then add the settings snippet below manually.
58
80
 
59
81
  ---
60
82
 
61
83
  ## Claude Code Settings
62
84
 
63
- Add to `~/.claude/settings.json`. If you already have a `"hooks"` key, merge these entries — don't replace the whole object.
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` or `/stop` | Stop speech immediately, clear queue |
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 # kills all python processes
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 Python script, no build tools required
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 prints the `settings.json` snippet to add to Claude Code.
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/domdhi/claude-code-tts
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** (add to `voices.json`):
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** (add a `"projects"` section):
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** — any agent that begins its response with `[AgentName]:` gets routed to that voice:
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 # Mac/Linux
116
- echo. > %USERPROFILE%\.claude\hooks\tts\on # Windows
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 into ~/.claude/hooks/tts/ and prints the settings.json snippet.
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 handled separately to avoid clobbering customizations
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(SOURCE_DIR, filename)
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 preserve existing customizations
95
- voices_src = os.path.join(SOURCE_DIR, 'voices.json')
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 see INSTALL.md for URLs.')
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 print_settings_snippet():
156
- tts_dir = INSTALL_DIR.replace('\\', '\\\\')
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
- if sys.platform == 'win32':
159
- py = sys.executable.replace('\\', '\\\\')
160
- stop_cmd = f'{py} \\"{tts_dir}\\\\stop.py\\"'
161
- task_cmd = f'{py} \\"{tts_dir}\\\\task-hook.py\\"'
162
- repeat_cmd = f'{py} \\"{tts_dir}\\\\repeat.py\\"'
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
- py = sys.executable
165
- stop_cmd = f'{py} "{INSTALL_DIR}/stop.py"'
166
- task_cmd = f'{py} "{INSTALL_DIR}/task-hook.py"'
167
- repeat_cmd = f'{py} "{INSTALL_DIR}/repeat.py"'
168
-
169
- snippet = f'''{{
170
- "hooks": {{
171
- "Stop": [
172
- {{
173
- "hooks": [
174
- {{
175
- "type": "command",
176
- "command": "{stop_cmd}"
177
- }}
178
- ]
179
- }}
180
- ],
181
- "PostToolUse": [
182
- {{
183
- "matcher": "Task",
184
- "hooks": [
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
- If settings.json already has a "hooks" key, merge these entries
211
- into your existing hooks object -- don't replace the whole file.
212
- -------------------------------------------------------------
250
+ if not changed:
251
+ ok('settings.json already up to date')
252
+ return
213
253
 
214
- {snippet}
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
- Settings file location: {settings_path}
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 to {INSTALL_DIR}
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 (after adding settings.json snippet):
226
- Run Claude Code, ask anything -- response will be read aloud.
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 Replay last response
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 {os.path.join(INSTALL_DIR, 'on')}
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='Install hooks to PATH instead of ~/.claude/hooks/tts/'
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
- if args.dir:
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
- print('\nclaude-code-tts installer\n')
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
- print_settings_snippet()
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.0.0",
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
- "daemon.py",
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