claude-can-speak 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # claude-can-speak
2
2
 
3
- **Now Claude Code talks back.** Speech-out for Claude Code: a companion to the
4
- built-in `/voice` speech-in. Turn `/voice` on and Claude can read its replies
5
- aloud through your speakers; turn it off and you are back to silent, text-only.
6
- Two ways to use it, a local neural voice, nothing sent to the cloud.
7
-
8
- - **Firehose mode** - a Stop hook speaks every finished reply while `/voice` is
9
- on. One switch (`/voice`) controls both directions: you talk to it, it talks
10
- back.
11
- - **Deliberate mode** - a `speak` skill lets Claude choose what to voice: a
12
- spoken "the build is done", a heads-up while you are looking away, a shoutout.
13
- Selective, on purpose, not a firehose.
3
+ **Let Claude decide what to say out loud.** Most "speak Claude Code aloud" tools
4
+ read *every* reply at you. claude-can-speak leads with the opposite: a Claude
5
+ Code **skill** that gives the model a deliberate "say this" capability, so Claude
6
+ voices only what is worth hearing, a spoken "the build is done and tests passed"
7
+ when you have stepped away, a heads-up that a deploy needs confirmation, a short
8
+ shoutout you asked for, while everything else stays text-only. Selective, on
9
+ purpose, model-controlled.
10
+
11
+ If you *do* want the firehose, it is one command away: a Stop hook that speaks
12
+ every finished reply, gated on the built-in `/voice` mode so one switch controls
13
+ both directions (you talk to it, it talks back). But the deliberate skill is the
14
+ point.
15
+
16
+ - **Deliberate mode (the headline)** - the `speak` skill lets Claude choose what
17
+ to voice. Install with `claude-can-speak install-skill`.
18
+ - **Firehose mode (optional)** - a Stop hook speaks every reply while `/voice` is
19
+ on. Install with `claude-can-speak install-hooks`.
14
20
 
15
21
  Speech is synthesised locally by [Kokoro](https://github.com/thewh1teagle/kokoro-onnx)
16
22
  (natural English, the default) or [Piper](https://github.com/OHF-Voice/piper1-gpl)
@@ -138,6 +144,20 @@ behalf. **By installing and using it you accept all risk.** You are responsible
138
144
  for complying with the licences of the bundled engines and the downloaded models
139
145
  (see [THIRD_PARTY.md](THIRD_PARTY.md)).
140
146
 
147
+ ## Related projects
148
+
149
+ Speaking Claude Code's replies aloud is a well-trodden idea, and several tools do
150
+ the firehose well: `claude-voice` (Kokoro plus karaoke word highlighting),
151
+ `claude-code-tts` (OpenAI or Kokoro auto-speak), `claude-voice-mcp` and
152
+ `soliloquy-tts` (MCP-based auto-speak). If all you want is "read every reply
153
+ aloud", any of those is a fine choice and lighter than this one (no Docker).
154
+
155
+ claude-can-speak is built around a different default: the deliberate `speak`
156
+ skill, so Claude voices only what is worth hearing rather than everything. It
157
+ also adds multilingual output (Piper for German, Turkish, and more), Docker
158
+ isolation so the engines never touch your host Python, and gating on the built-in
159
+ `/voice` switch. The firehose mode is included, but it is not the headline.
160
+
141
161
  ## Licence
142
162
 
143
163
  MIT - see [LICENSE](LICENSE). Author: Ramazan Yavuz. Part of the public,
@@ -9,7 +9,7 @@
9
9
  # any damage or loss arising from its use. By using it you accept all risk.
10
10
  set -uo pipefail
11
11
 
12
- VERSION="0.1.0"
12
+ VERSION="0.1.2"
13
13
 
14
14
  # Install layout: bundled scripts live in ../lib/claude-can-speak relative to
15
15
  # this CLI, whether installed via npm (into the global node_modules) or run from
@@ -27,6 +27,7 @@ LIBEXEC="$SELF/../lib/claude-can-speak"
27
27
  CCS_HOME="${CCS_HOME:-$HOME/.config/claude-can-speak}"
28
28
  CONFIG="$CCS_HOME/config.env"
29
29
  PIDFILE="$CCS_HOME/speaking.pid"
30
+ ENABLED_FLAG="$CCS_HOME/firehose.enabled"
30
31
  CONTAINER="${CCS_CONTAINER:-ccs-tts}"
31
32
  IMAGE="${CCS_IMAGE:-claude-can-speak:latest}"
32
33
  MODELS_DIR="${CCS_MODELS_DIR:-$HOME/.cache/claude-can-speak/models}"
@@ -64,10 +65,12 @@ USAGE
64
65
  claude-can-speak <command> [args]
65
66
 
66
67
  COMMANDS
67
- status Show gate state, container, config, and model cache.
68
+ setup One-shot install: Docker check, build, skill, hook.
69
+ on | off Turn the firehose (speak every reply) on or off. Default off.
70
+ status Show firehose state, container, config, and model cache.
68
71
  test [text] Speak a sample (or the given text) with the current voice.
69
72
  stop Interrupt any reply currently being spoken.
70
- say <text> Speak arbitrary text now (ignores the /voice gate).
73
+ say <text> Speak arbitrary text now (always speaks; used by the skill).
71
74
  start | up Start the persistent TTS container.
72
75
  stop-container Stop and remove the TTS container.
73
76
  voice <name> Set the default voice (e.g. af_heart, af_bella).
@@ -81,16 +84,19 @@ COMMANDS
81
84
  help | --help This text.
82
85
 
83
86
  TWO MODES
84
- Firehose : the Stop hook speaks every reply while /voice is on
85
- (claude-can-speak install-hooks).
86
- Deliberate: the 'speak' skill lets Claude choose what to voice
87
- (notifications, shoutouts) via 'claude-can-speak say'
88
- (claude-can-speak install-skill). Toggle with 'skill on|off'.
87
+ Deliberate (the headline): the 'speak' skill lets Claude choose what to
88
+ voice (notifications, shoutouts) via 'claude-can-speak say'.
89
+ Install with 'claude-can-speak install-skill'.
90
+ Firehose (optional): the Stop hook speaks every reply when the firehose is
91
+ on. Install the hook with 'claude-can-speak install-hooks', then
92
+ toggle with 'claude-can-speak on' / 'off'.
89
93
 
90
94
  GATING
91
- Speech-out only runs while /voice mode is on (voiceEnabled / voice.enabled
92
- in $SETTINGS_JSON). Toggle /voice in Claude Code to switch both speech-in
93
- and speech-out at once. Turn it off for full silence.
95
+ The firehose has its own explicit on/off switch ('claude-can-speak on|off',
96
+ default OFF), stored in ~/.config/claude-can-speak/firehose.enabled. It is
97
+ intentionally NOT tied to Claude Code's /voice, which is speech-IN dictation
98
+ and is a separate concern. The deliberate 'speak' skill always speaks when
99
+ invoked, regardless of the firehose switch.
94
100
 
95
101
  DISCLAIMER
96
102
  Provided AS IS, with NO WARRANTY. You accept all risk. See the project
@@ -179,12 +185,11 @@ cmd_test() {
179
185
 
180
186
  cmd_status() {
181
187
  echo "claude-can-speak $VERSION"
182
- printf 'voice gate : '
183
- if [ -f "$SETTINGS_JSON" ] && command -v jq >/dev/null 2>&1 \
184
- && [ "$(jq -r '(.voiceEnabled // .voice.enabled // false)|tostring' "$SETTINGS_JSON" 2>/dev/null)" = true ]; then
185
- echo "ON (/voice enabled)"
188
+ printf 'firehose : '
189
+ if [ -f "$ENABLED_FLAG" ]; then
190
+ echo "ON (replies spoken; 'claude-can-speak off' to silence)"
186
191
  else
187
- echo "off (/voice disabled) - replies will be silent"
192
+ echo "off (replies silent; 'claude-can-speak on' to enable)"
188
193
  fi
189
194
  printf 'engine/voice : %s / %s (%s)\n' "$ENGINE" "$VOICE" "$LANG"
190
195
  printf 'image : '; ensure_image && echo "$IMAGE present" || echo "$IMAGE MISSING (run: build)"
@@ -233,6 +238,41 @@ EOF
233
238
  fi
234
239
  }
235
240
 
241
+ cmd_on() {
242
+ mkdir -p "$CCS_HOME"
243
+ : > "$ENABLED_FLAG"
244
+ echo "firehose ON: replies will be spoken (needs the Stop hook; run install-hooks if you have not)."
245
+ }
246
+
247
+ cmd_off() {
248
+ rm -f "$ENABLED_FLAG" 2>/dev/null
249
+ # Also stop anything currently speaking.
250
+ cmd_stop >/dev/null 2>&1 || true
251
+ echo "firehose OFF: replies will not be spoken."
252
+ }
253
+
254
+ # One-shot setup: the happy path after `npm install -g`. Docker check, build the
255
+ # image, install the deliberate skill and the firehose hook. Idempotent.
256
+ cmd_setup() {
257
+ echo "claude-can-speak setup"
258
+ require_docker
259
+ echo "1/3 building the TTS container image (first time pulls deps, ~2 min) ..."
260
+ cmd_build
261
+ echo "2/3 installing the 'speak' skill ..."
262
+ cmd_install_skill
263
+ echo "3/3 installing the firehose Stop hook ..."
264
+ cmd_install_hooks
265
+ cat <<EOF
266
+
267
+ Setup complete.
268
+ - Deliberate mode: Claude can voice notifications via the 'speak' skill (active now).
269
+ - Firehose mode: turn it on with 'claude-can-speak on' (off by default).
270
+ Restart Claude Code once so it loads the new skill and hook, then try:
271
+ claude-can-speak on
272
+ claude-can-speak test
273
+ EOF
274
+ }
275
+
236
276
  cmd_install_skill() {
237
277
  # Locate the packaged skill (deb vs git checkout).
238
278
  local src
@@ -280,6 +320,9 @@ cmd_remove_hooks() {
280
320
  }
281
321
 
282
322
  case "${1:-help}" in
323
+ setup) cmd_setup ;;
324
+ on) cmd_on ;;
325
+ off) cmd_off ;;
283
326
  status) cmd_status ;;
284
327
  test) shift; cmd_test "$@" ;;
285
328
  say) shift; cmd_say "$@" ;;
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Printed once after `npm install -g claude-can-speak`. Deliberately does NO
3
+ // heavy or side-effecting work (no docker build, no editing ~/.claude): it only
4
+ // tells the user the single next command. The real setup is explicit and
5
+ // user-chosen, which keeps `npm install` fast, quiet, and unsurprising.
6
+ "use strict";
7
+
8
+ // Skip the banner in CI / non-interactive installs to avoid log noise.
9
+ if (process.env.CI || process.env.npm_config_loglevel === "silent") process.exit(0);
10
+
11
+ const L = [
12
+ "",
13
+ " claude-can-speak installed.",
14
+ "",
15
+ " One more step (needs Docker):",
16
+ " claude-can-speak setup",
17
+ "",
18
+ " That builds the local TTS container and installs the 'speak' skill",
19
+ " plus the optional firehose hook. Then restart Claude Code once.",
20
+ "",
21
+ " Deliberate mode (Claude voices notifications) is on after setup.",
22
+ " Firehose mode (speak every reply) is off by default; turn it on with:",
23
+ " claude-can-speak on",
24
+ "",
25
+ " Provided AS IS, no warranty. https://ra-yavuz.github.io/claude-can-speak/",
26
+ "",
27
+ ];
28
+ process.stdout.write(L.join("\n") + "\n");
@@ -17,7 +17,6 @@ set -uo pipefail
17
17
  # --- Resolve config -------------------------------------------------------
18
18
  CCS_HOME="${CCS_HOME:-$HOME/.config/claude-can-speak}"
19
19
  CCS_CONFIG="$CCS_HOME/config.env"
20
- SETTINGS_JSON="${CLAUDE_SETTINGS:-$HOME/.claude/settings.json}"
21
20
  CONTAINER="${CCS_CONTAINER:-ccs-tts}"
22
21
  IMAGE="${CCS_IMAGE:-claude-can-speak:latest}"
23
22
  MODELS_DIR="${CCS_MODELS_DIR:-$HOME/.cache/claude-can-speak/models}"
@@ -51,23 +50,13 @@ done
51
50
  # --- Read the Stop hook payload ------------------------------------------
52
51
  PAYLOAD="$(cat)"
53
52
 
54
- # --- Gate: only speak when /voice mode is on ------------------------------
55
- # Read voiceEnabled OR voice.enabled from settings.json. Absent/false = off.
56
- voice_on() {
57
- [ -f "$SETTINGS_JSON" ] || return 1
58
- if command -v jq >/dev/null 2>&1; then
59
- local v
60
- v="$(jq -r '(.voiceEnabled // .voice.enabled // false) | tostring' \
61
- "$SETTINGS_JSON" 2>/dev/null)"
62
- [ "$v" = "true" ]
63
- return
64
- fi
65
- # jq-less fallback: grep the two known keys.
66
- grep -Eq '"voiceEnabled"[[:space:]]*:[[:space:]]*true' "$SETTINGS_JSON" && return 0
67
- grep -Eq '"enabled"[[:space:]]*:[[:space:]]*true' "$SETTINGS_JSON" && return 0
68
- return 1
69
- }
70
- voice_on || { log "voice gate off; silent"; exit 0; }
53
+ # --- Gate: only speak when the firehose is explicitly ON ------------------
54
+ # claude-can-speak owns its own on/off state, decoupled from Claude Code's
55
+ # /voice (which is speech-IN dictation and is not reliably readable here).
56
+ # Default is OFF: the state file exists only when the user ran
57
+ # `claude-can-speak on`. This guarantees a real, predictable off-switch.
58
+ ENABLED_FLAG="${CCS_ENABLED_FLAG:-$CCS_HOME/firehose.enabled}"
59
+ [ -f "$ENABLED_FLAG" ] || { log "firehose off; silent"; exit 0; }
71
60
 
72
61
  # --- Extract the reply text ----------------------------------------------
73
62
  extract_text() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-can-speak",
3
- "version": "0.1.0",
4
- "description": "Speech-out for Claude Code: speak replies aloud (Stop-hook firehose) or let Claude voice deliberate notifications (skill). Local neural TTS via Kokoro/Piper in Docker. Gated on /voice mode.",
3
+ "version": "0.1.2",
4
+ "description": "Let Claude Code decide what to say out loud: a skill that voices deliberate notifications (not every reply), plus an optional speak-everything Stop hook. Local neural TTS via Kokoro/Piper in Docker, gated on /voice.",
5
5
  "keywords": [
6
6
  "claude",
7
7
  "claude-code",
@@ -28,8 +28,12 @@
28
28
  "bin": {
29
29
  "claude-can-speak": "bin/cli.js"
30
30
  },
31
+ "scripts": {
32
+ "postinstall": "node bin/postinstall.js"
33
+ },
31
34
  "files": [
32
35
  "bin/cli.js",
36
+ "bin/postinstall.js",
33
37
  "bin/claude-can-speak",
34
38
  "lib/claude-can-speak/",
35
39
  "container/",