@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.
@@ -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): 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 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, 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/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,25 @@ 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
+ 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(SOURCE_DIR, filename)
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 preserve existing customizations
95
- voices_src = os.path.join(SOURCE_DIR, 'voices.json')
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 see INSTALL.md for URLs.')
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 print_settings_snippet():
156
- tts_dir = INSTALL_DIR.replace('\\', '\\\\')
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
- 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\\"'
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
- 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):
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
- If settings.json already has a "hooks" key, merge these entries
211
- into your existing hooks object -- don't replace the whole file.
212
- -------------------------------------------------------------
249
+ if not changed:
250
+ ok('settings.json already up to date')
251
+ return
213
252
 
214
- {snippet}
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
- Settings file location: {settings_path}
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 to {INSTALL_DIR}
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 (after adding settings.json snippet):
226
- Run Claude Code, ask anything -- response will be read aloud.
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 Replay last response
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 {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)
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
- if args.dir:
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
- print('\nclaude-code-tts installer\n')
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
- print_settings_snippet()
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.0.0",
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
- "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