@domdhi/claude-code-tts 1.1.0 → 1.3.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/hooks/tts/daemon.py +6 -2
- package/.claude/hooks/tts/repeat.py +2 -1
- package/.claude/hooks/tts/stop.py +2 -1
- package/.claude/hooks/tts/task-hook.py +2 -1
- package/INSTALL.md +7 -5
- package/README.md +1 -1
- package/install.py +14 -12
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
claude-code-tts — TTS daemon
|
|
4
4
|
Persistent background process. Keeps model loaded. Serves speech via TCP.
|
|
5
5
|
|
|
6
|
-
Protocol (JSON lines over localhost
|
|
6
|
+
Protocol (JSON lines over localhost — port derived from install dir):
|
|
7
7
|
{"cmd": "speak", "text": "...", "voice": "af_heart", "speed": 1.0, "project": "repo-name"}
|
|
8
8
|
{"cmd": "stop"}
|
|
9
9
|
{"cmd": "ping"}
|
|
@@ -30,8 +30,12 @@ os.environ.setdefault('OMP_NUM_THREADS', '6')
|
|
|
30
30
|
os.environ.setdefault('ONNXRUNTIME_NUM_THREADS', '6')
|
|
31
31
|
|
|
32
32
|
HOST = '127.0.0.1'
|
|
33
|
-
PORT = 6254
|
|
34
33
|
DAEMON_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
34
|
+
|
|
35
|
+
# Port is derived from install location — each install gets its own daemon port,
|
|
36
|
+
# so multiple projects with local installs don't collide.
|
|
37
|
+
import hashlib as _hashlib
|
|
38
|
+
PORT = 49152 + (int(_hashlib.md5(DAEMON_DIR.encode()).hexdigest(), 16) % 16384)
|
|
35
39
|
PID_FILE = os.path.join(DAEMON_DIR, 'daemon.pid')
|
|
36
40
|
MODEL_PATH = os.path.join(DAEMON_DIR, 'models', 'kokoro-v1.0.onnx')
|
|
37
41
|
VOICES_PATH = os.path.join(DAEMON_DIR, 'models', 'voices-v1.0.bin')
|
|
@@ -20,7 +20,8 @@ VOICES_FILE = os.path.join(HOOK_DIR, 'voices.json')
|
|
|
20
20
|
DAEMON_SCRIPT = os.path.join(HOOK_DIR, 'daemon.py')
|
|
21
21
|
|
|
22
22
|
DAEMON_HOST = '127.0.0.1'
|
|
23
|
-
|
|
23
|
+
import hashlib as _hashlib
|
|
24
|
+
DAEMON_PORT = 49152 + (int(_hashlib.md5(HOOK_DIR.encode()).hexdigest(), 16) % 16384)
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def _start_daemon():
|
|
@@ -21,7 +21,8 @@ VOICES_FILE = os.path.join(HOOK_DIR, 'voices.json')
|
|
|
21
21
|
DAEMON_SCRIPT = os.path.join(HOOK_DIR, 'daemon.py')
|
|
22
22
|
|
|
23
23
|
DAEMON_HOST = '127.0.0.1'
|
|
24
|
-
|
|
24
|
+
import hashlib as _hashlib
|
|
25
|
+
DAEMON_PORT = 49152 + (int(_hashlib.md5(HOOK_DIR.encode()).hexdigest(), 16) % 16384)
|
|
25
26
|
|
|
26
27
|
READ_ALL_CHAIN = False # True = speak all assistant messages in chain, False = last only
|
|
27
28
|
|
|
@@ -19,7 +19,8 @@ VOICES_FILE = os.path.join(HOOK_DIR, 'voices.json')
|
|
|
19
19
|
DAEMON_SCRIPT = os.path.join(HOOK_DIR, 'daemon.py')
|
|
20
20
|
|
|
21
21
|
DAEMON_HOST = '127.0.0.1'
|
|
22
|
-
|
|
22
|
+
import hashlib as _hashlib
|
|
23
|
+
DAEMON_PORT = 49152 + (int(_hashlib.md5(HOOK_DIR.encode()).hexdigest(), 16) % 16384)
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def strip_markdown(text):
|
package/INSTALL.md
CHANGED
|
@@ -43,8 +43,8 @@ The installer automatically:
|
|
|
43
43
|
7. Patches `settings.json` / `settings.local.json` with hook entries (backs up original first)
|
|
44
44
|
|
|
45
45
|
**Local vs global:**
|
|
46
|
-
- Default (no flag):
|
|
47
|
-
- `--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. Each install gets its own daemon port, so multiple local-installed projects run independently without conflict.
|
|
47
|
+
- `--global`: everything goes into `~/.claude/` — hook scripts, commands, and `settings.json`. Recommended for personal use across all projects.
|
|
48
48
|
|
|
49
49
|
### Option B — installer script
|
|
50
50
|
|
|
@@ -290,7 +290,9 @@ Type in the Claude Code prompt:
|
|
|
290
290
|
|
|
291
291
|
## Daemon Protocol
|
|
292
292
|
|
|
293
|
-
The daemon runs on `localhost
|
|
293
|
+
The daemon runs on `localhost` on a port derived from the install directory (range 49152–65535). Each install location gets a stable, unique port — so multiple local-installed projects run independent daemons without conflict. Hook scripts and the daemon compute the same port, so they always find each other.
|
|
294
|
+
|
|
295
|
+
Accepts JSON lines:
|
|
294
296
|
|
|
295
297
|
| Command | Effect |
|
|
296
298
|
|---------|--------|
|
|
@@ -347,8 +349,8 @@ The daemon auto-restarts on the next Claude response.
|
|
|
347
349
|
- sounddevice doesn't always pull in cffi automatically
|
|
348
350
|
|
|
349
351
|
### Daemon keeps restarting / won't stay up
|
|
350
|
-
-
|
|
351
|
-
- Check
|
|
352
|
+
- Each install uses a unique port derived from its directory path — port conflicts between installs are not possible
|
|
353
|
+
- Check `daemon.log` in your hooks directory for errors
|
|
352
354
|
|
|
353
355
|
### Audio cuts off mid-sentence (kokoro fallback)
|
|
354
356
|
- kokoro-onnx has a 510-token (~1500 char) hard limit
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npx @domdhi/claude-code-tts
|
|
|
18
18
|
npx @domdhi/claude-code-tts --global
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
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.
|
|
22
22
|
|
|
23
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.
|
|
24
24
|
|
package/install.py
CHANGED
|
@@ -33,15 +33,16 @@ SOURCE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
33
33
|
HOOKS_SOURCE = os.path.join(SOURCE_DIR, '.claude', 'hooks', 'tts')
|
|
34
34
|
COMMANDS_SOURCE = os.path.join(SOURCE_DIR, '.claude', 'commands', 'voice')
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
_home_claude = os.path.join(os.path.expanduser('~'), '.claude')
|
|
37
|
+
_proj_claude = os.path.join(os.getcwd(), '.claude')
|
|
38
38
|
|
|
39
|
-
# Default: project-local
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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')
|
|
45
46
|
|
|
46
47
|
GLOBAL_SCOPE = False
|
|
47
48
|
|
|
@@ -294,7 +295,7 @@ def main():
|
|
|
294
295
|
parser = argparse.ArgumentParser(description='claude-code-tts installer')
|
|
295
296
|
parser.add_argument(
|
|
296
297
|
'--dir', metavar='PATH',
|
|
297
|
-
help='
|
|
298
|
+
help='Override hook install directory (for testing only)'
|
|
298
299
|
)
|
|
299
300
|
parser.add_argument(
|
|
300
301
|
'--global', dest='global_scope', action='store_true',
|
|
@@ -309,9 +310,10 @@ def main():
|
|
|
309
310
|
|
|
310
311
|
if args.global_scope:
|
|
311
312
|
GLOBAL_SCOPE = True
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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')
|
|
315
317
|
|
|
316
318
|
scope_label = 'global (~/.claude/)' if args.global_scope else 'project-local (.claude/)'
|
|
317
319
|
print(f'\nclaude-code-tts installer [{scope_label}]\n')
|