claude-can-speak 0.1.1 → 0.1.3

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
@@ -8,15 +8,22 @@ when you have stepped away, a heads-up that a deploy needs confirmation, a short
8
8
  shoutout you asked for, while everything else stays text-only. Selective, on
9
9
  purpose, model-controlled.
10
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.
11
+ If you *do* want the firehose, it is one switch away: a Stop hook that speaks
12
+ every finished reply, with its own explicit on/off (`claude-can-speak on` /
13
+ `off`, off by default). But the deliberate skill is the point.
15
14
 
16
15
  - **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`.
16
+ to voice. Active after `claude-can-speak setup`.
17
+ - **Firehose mode (optional)** - a Stop hook speaks every reply when you turn it
18
+ on with `claude-can-speak on` (off by default), or `/ccs on` from inside
19
+ Claude Code.
20
+
21
+ > **The toggle is a command, not a built-in slash setting.** claude-can-speak
22
+ > does not hook into Claude Code's `/voice`; if you type `/` in Claude Code
23
+ > before setup and find nothing, that is expected. Control the firehose from the
24
+ > terminal with `claude-can-speak on` / `off`, or from inside Claude Code with
25
+ > the `/ccs on` / `/ccs off` / `/ccs status` slash command that `setup`
26
+ > installs.
20
27
 
21
28
  Speech is synthesised locally by [Kokoro](https://github.com/thewh1teagle/kokoro-onnx)
22
29
  (natural English, the default) or [Piper](https://github.com/OHF-Voice/piper1-gpl)
@@ -37,40 +44,61 @@ network at speak time, no telemetry.
37
44
 
38
45
  ```sh
39
46
  npm install -g claude-can-speak
40
-
41
- # one-time: build the local TTS container image (needs Docker)
42
- claude-can-speak build
47
+ claude-can-speak setup # Docker check, build image, install skill + hook
43
48
  ```
44
49
 
50
+ `setup` is the one command that does everything (it needs Docker). Then
51
+ **restart Claude Code once** so it loads the new skill and hook.
52
+
45
53
  > If `npm install -g` fails with `EACCES` (a system-owned npm prefix like
46
54
  > `/usr`), either use a user-level prefix once:
47
55
  > `npm config set prefix ~/.npm-global && export PATH="$HOME/.npm-global/bin:$PATH"`
48
56
  > (add that `export` to your shell profile), or install with `sudo`.
49
57
 
50
- Then pick the mode(s) you want:
58
+ `setup` also installs a `/ccs` slash command into Claude Code, so the firehose
59
+ toggle is reachable from where you would instinctively look for it. **Restart
60
+ Claude Code once** after setup so it discovers the skill and the command.
61
+
62
+ After setup:
63
+
64
+ - **Deliberate mode** (the headline) is active: Claude can voice notifications
65
+ through the `speak` skill whenever it judges something worth hearing.
66
+ - **Firehose mode** (speak every reply) is **off by default**. Turn it on when
67
+ you want it, from the terminal or from inside Claude Code:
51
68
 
52
69
  ```sh
53
- claude-can-speak install-skill # deliberate mode: the 'speak' skill
54
- claude-can-speak install-hooks # firehose mode: speak every reply on /voice
70
+ claude-can-speak on # speak every reply
71
+ claude-can-speak off # back to silent (default)
72
+ ```
73
+
74
+ ```text
75
+ /ccs on # same, from inside Claude Code
76
+ /ccs off
77
+ /ccs status
55
78
  ```
56
79
 
57
80
  Models are downloaded on first use into `~/.cache/claude-can-speak/models`
58
81
  (nothing model-shaped is bundled in the package; see
59
82
  [THIRD_PARTY.md](THIRD_PARTY.md)).
60
83
 
61
- Then in Claude Code, toggle `/voice` on. Check everything with:
84
+ Check everything with:
62
85
 
63
86
  ```sh
64
87
  claude-can-speak status
65
88
  claude-can-speak test # speak a sample line
66
89
  ```
67
90
 
91
+ If you prefer to do the steps by hand instead of `setup`: `claude-can-speak
92
+ build`, then `claude-can-speak install-skill` and/or `claude-can-speak
93
+ install-hooks`.
94
+
68
95
  ## Usage
69
96
 
70
97
  ```sh
71
98
  claude-can-speak status # gate state, container, voice, model cache
72
99
  claude-can-speak test [text] # speak a sample (or your text)
73
- claude-can-speak say <text> # speak text now (ignores the /voice gate)
100
+ claude-can-speak on | off # firehose: speak every reply, or not (default off)
101
+ claude-can-speak say <text> # speak text now (always speaks; used by the skill)
74
102
  claude-can-speak stop # interrupt whatever is being spoken
75
103
  claude-can-speak voice <name> # set the default voice (e.g. af_heart)
76
104
  claude-can-speak engine kokoro|piper
@@ -102,7 +130,7 @@ claude-can-speak voice tr_TR-dfki-medium # Turkish
102
130
  ## How it works
103
131
 
104
132
  ```
105
- Claude Code reply ─▶ Stop hook (gated on /voice) ─▶ strip markdown & code
133
+ Claude Code reply ─▶ Stop hook (when firehose on) ─▶ strip markdown & code
106
134
  ─▶ docker exec synth (Kokoro/Piper)
107
135
  ─▶ play WAV on the host
108
136
  ```
@@ -118,8 +146,11 @@ Per-user config lives in `~/.config/claude-can-speak/config.env` (written by the
118
146
  `voice` / `engine` commands). Environment overrides: `CCS_IMAGE`,
119
147
  `CCS_CONTAINER`, `CCS_MODELS_DIR`, `CLAUDE_SETTINGS`.
120
148
 
121
- The `/voice` gate is read from `~/.claude/settings.json` (`voiceEnabled` or
122
- `voice.enabled`). The `speak` skill is toggled via `skillOverrides` there.
149
+ The firehose on/off state is a single file, `~/.config/claude-can-speak/firehose.enabled`
150
+ (present = on, absent = off), written by `claude-can-speak on` / `off`. It is
151
+ deliberately independent of Claude Code's `/voice`, which is speech-in dictation,
152
+ a separate concern. The `speak` skill is toggled via `skillOverrides` in
153
+ `~/.claude/settings.json`.
123
154
 
124
155
  ## Uninstall
125
156
 
@@ -154,9 +185,9 @@ aloud", any of those is a fine choice and lighter than this one (no Docker).
154
185
 
155
186
  claude-can-speak is built around a different default: the deliberate `speak`
156
187
  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.
188
+ also adds multilingual output (Piper for German, Turkish, and more) and Docker
189
+ isolation so the engines never touch your host Python. The firehose mode is
190
+ included, with its own explicit on/off switch, but it is not the headline.
160
191
 
161
192
  ## Licence
162
193
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # claude-can-speak: speech-out for Claude Code, gated on /voice mode.
2
+ # claude-can-speak: speech-out for Claude Code (deliberate skill + optional firehose).
3
3
  #
4
4
  # This CLI manages the TTS container and the playback lifecycle. The speaking
5
5
  # itself is driven by a Stop hook (tts-speak.sh); this command is for setup,
@@ -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.1"
12
+ VERSION="0.1.3"
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}"
@@ -58,16 +59,18 @@ require_docker() {
58
59
 
59
60
  cmd_help() {
60
61
  cat <<EOF
61
- claude-can-speak $VERSION - speech-out for Claude Code (gated on /voice)
62
+ claude-can-speak $VERSION - speech-out for Claude Code
62
63
 
63
64
  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).
@@ -76,21 +79,28 @@ COMMANDS
76
79
  install-hooks Register the Stop + interrupt hooks in settings.json.
77
80
  remove-hooks Remove this project's hooks from settings.json.
78
81
  install-skill Install the 'speak' skill into ~/.claude/skills.
82
+ install-command Install the '/ccs' slash command into ~/.claude/commands.
83
+ remove-command Remove the '/ccs' slash command.
79
84
  skill on|off Enable/disable the 'speak' skill (settings skillOverrides).
80
85
  build Build the TTS container image locally.
81
86
  help | --help This text.
82
87
 
83
88
  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'.
89
+ Deliberate (the headline): the 'speak' skill lets Claude choose what to
90
+ voice (notifications, shoutouts) via 'claude-can-speak say'.
91
+ Install with 'claude-can-speak install-skill'.
92
+ Firehose (optional): the Stop hook speaks every reply when the firehose is
93
+ on. Install the hook with 'claude-can-speak install-hooks', then
94
+ toggle with 'claude-can-speak on' / 'off' from the terminal, or
95
+ '/ccs on' / '/ccs off' from inside Claude Code (the /ccs slash
96
+ command is a convenience wrapper; run 'install-command' to add it).
89
97
 
90
98
  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.
99
+ The firehose has its own explicit on/off switch ('claude-can-speak on|off',
100
+ default OFF), stored in ~/.config/claude-can-speak/firehose.enabled. It is
101
+ intentionally NOT tied to Claude Code's /voice, which is speech-IN dictation
102
+ and is a separate concern. The deliberate 'speak' skill always speaks when
103
+ invoked, regardless of the firehose switch.
94
104
 
95
105
  DISCLAIMER
96
106
  Provided AS IS, with NO WARRANTY. You accept all risk. See the project
@@ -179,12 +189,11 @@ cmd_test() {
179
189
 
180
190
  cmd_status() {
181
191
  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)"
192
+ printf 'firehose : '
193
+ if [ -f "$ENABLED_FLAG" ]; then
194
+ echo "ON (replies spoken; 'claude-can-speak off' to silence)"
186
195
  else
187
- echo "off (/voice disabled) - replies will be silent"
196
+ echo "off (replies silent; 'claude-can-speak on' to enable)"
188
197
  fi
189
198
  printf 'engine/voice : %s / %s (%s)\n' "$ENGINE" "$VOICE" "$LANG"
190
199
  printf 'image : '; ensure_image && echo "$IMAGE present" || echo "$IMAGE MISSING (run: build)"
@@ -233,6 +242,45 @@ EOF
233
242
  fi
234
243
  }
235
244
 
245
+ cmd_on() {
246
+ mkdir -p "$CCS_HOME"
247
+ : > "$ENABLED_FLAG"
248
+ echo "firehose ON: replies will be spoken (needs the Stop hook; run install-hooks if you have not)."
249
+ }
250
+
251
+ cmd_off() {
252
+ rm -f "$ENABLED_FLAG" 2>/dev/null
253
+ # Also stop anything currently speaking.
254
+ cmd_stop >/dev/null 2>&1 || true
255
+ echo "firehose OFF: replies will not be spoken."
256
+ }
257
+
258
+ # One-shot setup: the happy path after `npm install -g`. Docker check, build the
259
+ # image, install the deliberate skill and the firehose hook. Idempotent.
260
+ cmd_setup() {
261
+ echo "claude-can-speak setup"
262
+ require_docker
263
+ echo "1/3 building the TTS container image (first time pulls deps, ~2 min) ..."
264
+ cmd_build
265
+ echo "2/4 installing the 'speak' skill ..."
266
+ cmd_install_skill
267
+ echo "3/4 installing the '/ccs' slash command ..."
268
+ cmd_install_command
269
+ echo "4/4 installing the firehose Stop hook ..."
270
+ cmd_install_hooks
271
+ cat <<EOF
272
+
273
+ Setup complete.
274
+ - Deliberate mode: Claude can voice notifications via the 'speak' skill (active now).
275
+ - Firehose mode: turn it on with 'claude-can-speak on' (off by default).
276
+ Restart Claude Code once so it loads the new skill, the /ccs command, and the
277
+ hook. After that you can toggle the firehose from inside Claude Code with
278
+ '/ccs on' and '/ccs off', or from the terminal:
279
+ claude-can-speak on
280
+ claude-can-speak test
281
+ EOF
282
+ }
283
+
236
284
  cmd_install_skill() {
237
285
  # Locate the packaged skill (deb vs git checkout).
238
286
  local src
@@ -247,6 +295,25 @@ cmd_install_skill() {
247
295
  echo "if ~/.claude/skills did not exist before, restart your session to discover it."
248
296
  }
249
297
 
298
+ cmd_install_command() {
299
+ # Locate the packaged slash command (deb vs git checkout).
300
+ local src
301
+ for cand in "/usr/share/claude-can-speak/commands/ccs.md" "$SELF/../commands/ccs.md"; do
302
+ [ -f "$cand" ] && { src="$cand"; break; }
303
+ done
304
+ [ -n "${src:-}" ] || die "packaged slash command not found"
305
+ local dest="$HOME/.claude/commands/ccs.md"
306
+ mkdir -p "$(dirname "$dest")"
307
+ cp "$src" "$dest"
308
+ echo "installed '/ccs' slash command -> $dest"
309
+ echo "restart Claude Code once so it discovers the new command, then use /ccs status."
310
+ }
311
+
312
+ cmd_remove_command() {
313
+ local dest="$HOME/.claude/commands/ccs.md"
314
+ if [ -f "$dest" ]; then rm -f "$dest"; echo "removed '/ccs' slash command"; else echo "'/ccs' not installed"; fi
315
+ }
316
+
250
317
  cmd_skill() {
251
318
  case "${1:-}" in
252
319
  on) _skill_override on; echo "'speak' skill enabled" ;;
@@ -280,6 +347,9 @@ cmd_remove_hooks() {
280
347
  }
281
348
 
282
349
  case "${1:-help}" in
350
+ setup) cmd_setup ;;
351
+ on) cmd_on ;;
352
+ off) cmd_off ;;
283
353
  status) cmd_status ;;
284
354
  test) shift; cmd_test "$@" ;;
285
355
  say) shift; cmd_say "$@" ;;
@@ -292,6 +362,8 @@ case "${1:-help}" in
292
362
  install-hooks) cmd_install_hooks ;;
293
363
  remove-hooks) cmd_remove_hooks ;;
294
364
  install-skill) cmd_install_skill ;;
365
+ install-command) cmd_install_command ;;
366
+ remove-command) cmd_remove_command ;;
295
367
  skill) shift; cmd_skill "${1:-}" ;;
296
368
  build) cmd_build ;;
297
369
  help|-h|--help) cmd_help ;;
@@ -0,0 +1,29 @@
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
+ " the '/ccs' slash command, and the optional firehose hook.",
20
+ " Then restart Claude Code once.",
21
+ "",
22
+ " Deliberate mode (Claude voices notifications) is on after setup.",
23
+ " Firehose mode (speak every reply) is off by default. Turn it on from the",
24
+ " terminal with 'claude-can-speak on', or inside Claude Code with '/ccs on'.",
25
+ "",
26
+ " Provided AS IS, no warranty. https://ra-yavuz.github.io/claude-can-speak/",
27
+ "",
28
+ ];
29
+ process.stdout.write(L.join("\n") + "\n");
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Control claude-can-speak (status / on / off)
3
+ argument-hint: [status|on|off]
4
+ allowed-tools: Bash(claude-can-speak:*)
5
+ ---
6
+
7
+ The user invoked the claude-can-speak control command with argument: "$ARGUMENTS"
8
+
9
+ Run the matching CLI command and report the result plainly. The argument is one
10
+ of `status` (default if empty), `on`, or `off`:
11
+
12
+ - empty or `status` -> show current state:
13
+ !`claude-can-speak status`
14
+ - `on` -> turn the speak-every-reply firehose on:
15
+ !`if [ "$ARGUMENTS" = "on" ]; then claude-can-speak on; fi`
16
+ - `off` -> turn the firehose off (replies go silent again):
17
+ !`if [ "$ARGUMENTS" = "off" ]; then claude-can-speak off; fi`
18
+
19
+ Note for the user if relevant: `claude-can-speak` is a terminal command too; this
20
+ slash command is just a convenience wrapper so the firehose toggle is reachable
21
+ from inside Claude Code. The firehose hook hot-reloads, so `on`/`off` take effect
22
+ on the next reply without restarting the session.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-can-speak",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
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",
@@ -28,12 +28,17 @@
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/",
36
40
  "skills/",
41
+ "commands/",
37
42
  "THIRD_PARTY.md",
38
43
  "LICENSE",
39
44
  "README.md"