mcp-voice-hooks 1.0.6 → 1.0.12

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,3 @@
1
+ #!/bin/bash
2
+ PORT="${MCP_VOICE_HOOKS_PORT:-5111}"
3
+ curl -s -X POST http://localhost:${PORT}/api/hooks/pre-speak || echo '{"decision": "approve", "reason": "voice-hooks unavailable"}'
@@ -1,33 +1,3 @@
1
1
  #!/bin/bash
2
-
3
- # Pre-Tool Hook - Checks for pending utterances before allowing tool execution
4
- # Forces Claude to use dequeue_utterances tool if there are pending utterances
5
-
6
- # Get port from environment variable or use default
7
2
  PORT="${MCP_VOICE_HOOKS_PORT:-5111}"
8
-
9
- # Check has-pending-utterances endpoint
10
- response=$(curl -s http://localhost:${PORT}/api/has-pending-utterances 2>/dev/null)
11
-
12
- if [ $? -ne 0 ]; then
13
- # Server not available, allow tool execution
14
- echo '{"decision": "approve"}'
15
- exit 0
16
- fi
17
-
18
- # Extract pending status
19
- hasPending=$(echo "$response" | jq -r '.hasPending')
20
- pendingCount=$(echo "$response" | jq -r '.pendingCount')
21
-
22
- if [ "$hasPending" = "true" ]; then
23
- # There are pending utterances, block tool execution
24
- cat <<EOF
25
- {
26
- "decision": "block",
27
- "reason": "There are $pendingCount pending voice utterances. Please use dequeue_utterances to process them first."
28
- }
29
- EOF
30
- else
31
- # No pending utterances, allow tool execution
32
- echo '{"decision": "approve"}'
33
- fi
3
+ curl -s -X POST http://localhost:${PORT}/api/hooks/pre-tool || echo '{"decision": "approve", "reason": "voice-hooks unavailable"}'
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ PORT="${MCP_VOICE_HOOKS_PORT:-5111}"
3
+ curl -s -X POST http://localhost:${PORT}/api/hooks/pre-wait || echo '{"decision": "approve", "reason": "voice-hooks unavailable"}'
@@ -1,32 +1,3 @@
1
1
  #!/bin/bash
2
-
3
- # Stop Hook - Intelligently decides whether to wait for voice input
4
- # Checks if there have been any utterances since the last timeout
5
-
6
- # Get port from environment variable or use default
7
2
  PORT="${MCP_VOICE_HOOKS_PORT:-5111}"
8
-
9
- # Check should-wait endpoint
10
- response=$(curl -s http://localhost:${PORT}/api/should-wait 2>/dev/null)
11
-
12
- if [ $? -ne 0 ]; then
13
- # Server not available, allow stop
14
- echo '{"decision": "approve"}'
15
- exit 0
16
- fi
17
-
18
- # Extract shouldWait boolean
19
- shouldWait=$(echo "$response" | jq -r '.shouldWait')
20
-
21
- if [ "$shouldWait" = "true" ]; then
22
- # There have been utterances since last timeout, block and ask to wait
23
- cat <<EOF
24
- {
25
- "decision": "block",
26
- "reason": "Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input"
27
- }
28
- EOF
29
- else
30
- # No utterances since last timeout, allow stop
31
- echo '{"decision": "approve", "reason": "No utterances since last timeout"}'
32
- fi
3
+ curl -s -X POST http://localhost:${PORT}/api/hooks/stop || echo '{"decision": "approve", "reason": "voice-hooks unavailable"}'
package/CLAUDE.local.md CHANGED
@@ -4,17 +4,10 @@
4
4
  # 1. Build the project first
5
5
  npm run build
6
6
 
7
- # 2. Update roadmap.md with version info and stage it
8
- git add roadmap.md
7
+ # 2. Bump version (patch, minor, or major) - creates a commit and tag
8
+ HUSKY=0 npm version patch --registry https://registry.npmjs.org/
9
9
 
10
- # 3. Bump version (patch, minor, or major) - creates a commit and tag
11
- npm version patch --registry https://registry.npmjs.org/
12
-
13
- # Alternative: Bump version without creating a commit/tag
14
- # npm version patch --no-git-tag-version
15
- # Then manually commit the changes
16
-
17
- # 4. Publish to npm (this creates the .tgz file automatically)
10
+ # 3. Publish to npm (this creates the .tgz file automatically)
18
11
  npm publish --registry https://registry.npmjs.org/
19
12
 
20
13
  # Note: It can take 1-5 minutes for the package to be available globally
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Real-time voice interaction for Claude Code. Speak naturally while Claude works - interrupt, redirect, or provide continuous feedback without stopping.
4
4
 
5
+ Optionally enable text-to-speech to have Claude speak back to you.
6
+
7
+ Mac only for now.
8
+
5
9
  ## Demo
6
10
 
7
11
  [![Voice Hooks Demo](https://img.youtube.com/vi/KpkxvJ65gbM/0.jpg)](https://youtu.be/KpkxvJ65gbM)
@@ -15,11 +19,11 @@ mcp-voice-hooks enables continuous voice conversations with AI assistants by:
15
19
  - Using hooks to ensure Claude checks for voice input before tool use and before stopping
16
20
  - Allowing natural interruptions like "No, stop that" or "Wait, try something else"
17
21
 
18
- ## Features
22
+ ## Browser Compatibility
19
23
 
20
- - 🎤 **Real-time Voice Capture**: Browser-based speech recognition with automatic segmentation
21
- - 🔄 **Continuous Interaction**: Keep talking while Claude works - no need to stop between commands
22
- - 🪝 **Smart Hook System**: Pre-tool and stop hooks ensure Claude always checks for your input
24
+ - **Chrome**: Full support for speech recognition and text-to-speech
25
+ - **Safari**: Full support for speech recognition and text-to-speech
26
+ - **Edge**: Speech recognition not working on Apple Silicon (language-not-supported error)
23
27
 
24
28
  ## Installation in Your Own Project
25
29
 
@@ -52,21 +56,41 @@ mcp-voice-hooks enables continuous voice conversations with AI assistants by:
52
56
  claude
53
57
  ```
54
58
 
55
- 3. **Open the voice interface** at <http://localhost:5111> and start speaking!
59
+ **Important**: After the first-time installation, you will need to restart Claude for the hooks to take effect. This is because the hooks are automatically installed when the MCP server starts for the first time.
56
60
 
57
- The hooks are automatically installed when the MCP server starts. You need to send one text message to Claude to trigger the voice hooks.
61
+ 3. **Open the voice interface** at <http://localhost:5111> and start speaking!
58
62
 
59
- **Note**: After the first-time installation, you may need to restart Claude for the hooks to take effect.
63
+ You need to send one text message to Claude to trigger the voice hooks.
60
64
 
61
- The default port is 5111. To use a different port, add to your project's `.claude/settings.json`:
65
+ ## Voice responses
62
66
 
63
- ```json
64
- {
65
- "env": {
66
- "MCP_VOICE_HOOKS_PORT": "8080"
67
- }
68
- }
69
- ```
67
+ There are two options for voice responses:
68
+
69
+ 1. Browser Text-to-Speech (Cloud)
70
+ 2. Browser Text-to-Speech (Local)
71
+ 3. Mac System Voice
72
+
73
+ ### Selecting and downloading high quality System Voices (Mac only)
74
+
75
+ When "Mac System Voice" is selected, the system uses macOS's built-in `say` command.
76
+
77
+ Configure the system voice in `System Settings > Accessibility > Spoken Content > System Voice`
78
+
79
+ I recommend using a Siri voice, as they are much higher quality.
80
+
81
+ Click the info icon next to the system voice dropdown. Search for "Siri" to find the highest quality voices. You'll have to trigger a download of the voice.
82
+
83
+ It may take a while to download.
84
+
85
+ Once it's downloaded, you can select it in the system voice dropdown.
86
+
87
+ Test it with the bash command:
88
+
89
+ ```bash
90
+ say "Hi, this is your mac system voice"
91
+ ```
92
+
93
+ You can also download other high quality voices in the same way. Other voices will show up in the browser voice dropdown, but for Siri voices you need to set the system voice and select Mac System Voice in the browser voice dropdown.
70
94
 
71
95
  ## Manual Hook Installation
72
96
 
@@ -100,6 +124,10 @@ This will:
100
124
  - Clean up voice hooks from your project's `.claude/settings.json`
101
125
  - Preserve any custom hooks you've added
102
126
 
127
+ ## Known Limitations
128
+
129
+ - **Intermittent Stop Hook Execution**: Claude Code's Stop hooks are not triggered consistently. Sometimes the assistant can end responses without the Stop hook being executed. I believe this is an issue with Claude Code's hook system, not with mcp-voice-hooks. When working correctly, the Stop hook should prevent the assistant from stopping without first checking for voice input.
130
+
103
131
  ## Development Mode
104
132
 
105
133
  If you're developing mcp-voice-hooks itself:
@@ -152,65 +180,14 @@ and then configure claude to use the mcp proxy like so:
152
180
  }
153
181
  ```
154
182
 
155
- ## Voice responses (Mac only)
183
+ ### Port Configuration
156
184
 
157
- Add the post tool hook to your claude settings:
185
+ The default port is 5111. To use a different port, add to your project's `.claude/settings.json`:
158
186
 
159
- ```json
160
- {
187
+ ```json
161
188
  {
162
- "hooks": {
163
- "PostToolUse": [
164
- {
165
- "matcher": "^mcp__voice-hooks__",
166
- "hooks": [
167
- {
168
- "type": "command",
169
- "command": "./.claude/hooks/post-tool-voice-hook.sh"
170
- }
171
- ]
172
- }
173
- ]
174
- },
175
189
  "env": {
176
- "VOICE_RESPONSES_ENABLED": "true"
190
+ "MCP_VOICE_HOOKS_PORT": "8080"
177
191
  }
178
192
  }
179
- }
180
- ```
181
-
182
- ### Configuration
183
-
184
- Voice responses are disabled by default. To enable them:
185
-
186
- Add to your Claude Code settings JSON:
187
-
188
- ```json
189
- {
190
- "env": {
191
- "VOICE_RESPONSES_ENABLED": "true"
192
- }
193
- }
194
- ```
195
-
196
- To disable voice responses, set the value to `false` or remove the setting entirely.
197
-
198
- ### High quality voice responses
199
-
200
- These voice responses are spoken by your Mac's system voice.
201
-
202
- Configure in `System Settings > Accessibility > Spoken Content > System Voice`
203
-
204
- I recommend using a Siri voice, as they are much higher quality.
205
-
206
- Click the info icon next to the system voice dropdown. Search for "Siri" to find the highest quality voices. You'll have to trigger a download of the voice.
207
-
208
- It may take a while to download.
209
-
210
- Once it's downloaded, you can select it in the system voice dropdown.
211
-
212
- Test it with the bash command:
213
-
214
- ```bash
215
- say "Hi, this is your mac system voice"
216
- ```
193
+ ```
package/bin/cli.js CHANGED
@@ -163,15 +163,22 @@ async function configureClaudeCodeSettings() {
163
163
  "command": "sh ~/.mcp-voice-hooks/hooks/pre-tool-hook.sh"
164
164
  }
165
165
  ]
166
- }
167
- ],
168
- "PostToolUse": [
166
+ },
167
+ {
168
+ "matcher": "^mcp__voice-hooks__speak$",
169
+ "hooks": [
170
+ {
171
+ "type": "command",
172
+ "command": "sh ~/.mcp-voice-hooks/hooks/pre-speak-hook.sh"
173
+ }
174
+ ]
175
+ },
169
176
  {
170
- "matcher": "^mcp__voice-hooks__",
177
+ "matcher": "^mcp__voice-hooks__wait_for_utterance$",
171
178
  "hooks": [
172
179
  {
173
180
  "type": "command",
174
- "command": "sh ~/.mcp-voice-hooks/hooks/post-tool-voice-hook.sh"
181
+ "command": "sh ~/.mcp-voice-hooks/hooks/pre-wait-hook.sh"
175
182
  }
176
183
  ]
177
184
  }
@@ -199,49 +206,19 @@ async function configureClaudeCodeSettings() {
199
206
  async function ensureHooksInstalled() {
200
207
  const userDir = path.join(os.homedir(), '.mcp-voice-hooks');
201
208
  const hooksDir = path.join(userDir, 'hooks');
202
- const packageHooksDir = path.join(__dirname, '..', '.claude', 'hooks');
203
209
 
204
210
  try {
205
- // Only create directories if they don't exist
206
- if (!fs.existsSync(hooksDir)) {
207
- fs.mkdirSync(hooksDir, { recursive: true });
208
- console.log('🔧 Installing hooks for first time...');
209
- }
211
+ console.log('🔄 Updating hooks to latest version...');
210
212
 
211
- // Check if hooks need updating
212
- let needsUpdate = false;
213
- if (fs.existsSync(packageHooksDir)) {
214
- const hookFiles = fs.readdirSync(packageHooksDir).filter(file => file.endsWith('.sh'));
215
-
216
- for (const hookFile of hookFiles) {
217
- const sourcePath = path.join(packageHooksDir, hookFile);
218
- const destPath = path.join(hooksDir, hookFile);
219
-
220
- // Check if hook doesn't exist or is different
221
- if (!fs.existsSync(destPath)) {
222
- needsUpdate = true;
223
- break;
224
- }
225
-
226
- const sourceContent = fs.readFileSync(sourcePath, 'utf8');
227
- const destContent = fs.readFileSync(destPath, 'utf8');
228
-
229
- if (sourceContent !== destContent) {
230
- needsUpdate = true;
231
- break;
232
- }
233
- }
213
+ // Always remove and recreate the hooks directory to ensure clean state
214
+ if (fs.existsSync(hooksDir)) {
215
+ fs.rmSync(hooksDir, { recursive: true, force: true });
234
216
  }
235
217
 
236
- if (needsUpdate) {
237
- console.log('🔄 Updating hooks to latest version...');
238
- await ensureUserDirectorySetup();
239
- await configureClaudeCodeSettings();
240
- console.log('✅ Hooks and settings updated');
241
- } else {
242
- // Even if hooks are up to date, ensure settings are correct
243
- await configureClaudeCodeSettings();
244
- }
218
+ // Recreate with latest hooks
219
+ await ensureUserDirectorySetup();
220
+ await configureClaudeCodeSettings();
221
+ console.log('✅ Hooks and settings updated');
245
222
  } catch (error) {
246
223
  // Silently continue if hooks can't be updated
247
224
  console.warn('⚠️ Could not auto-update hooks:', error.message);