mcp-voice-hooks 1.0.12 → 1.0.13
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 +22 -24
- package/bin/cli.js +10 -100
- package/dist/hook-merger.d.ts +1 -1
- package/dist/hook-merger.js +1 -1
- package/dist/hook-merger.js.map +1 -1
- package/package.json +1 -1
- package/.claude/hooks/pre-speak-hook.sh +0 -3
- package/.claude/hooks/pre-tool-hook.sh +0 -3
- package/.claude/hooks/pre-wait-hook.sh +0 -3
- package/.claude/hooks/stop-hook.sh +0 -3
- package/CLAUDE.local.md +0 -18
- package/test-npx-clean/mcp-voice-hooks-1.0.1.tgz +0 -0
- package/test-npx-clean/package/dist/chunk-IYGM5COW.js +0 -12
- package/test-npx-clean/package/dist/chunk-IYGM5COW.js.map +0 -1
- package/test-npx-clean/package/dist/index.d.ts +0 -2
- package/test-npx-clean/package/dist/index.js +0 -125
- package/test-npx-clean/package/dist/index.js.map +0 -1
- package/test-npx-clean/package/dist/unified-server.d.ts +0 -1
- package/test-npx-clean/package/dist/unified-server.js +0 -352
- package/test-npx-clean/package/dist/unified-server.js.map +0 -1
- package/test-npx-clean/package/mcp-voice-hooks-1.0.0.tgz +0 -0
- package/test-npx-clean/package/mcp-voice-hooks-1.0.1.tgz +0 -0
package/README.md
CHANGED
@@ -21,8 +21,8 @@ mcp-voice-hooks enables continuous voice conversations with AI assistants by:
|
|
21
21
|
|
22
22
|
## Browser Compatibility
|
23
23
|
|
24
|
-
- ✅ **Chrome**: Full support for speech recognition and text-to-speech
|
25
|
-
-
|
24
|
+
- ✅ **Chrome**: Full support for speech recognition, browser text-to-speech, and system text-to-speech
|
25
|
+
- ⚠️ **Safari**: Full support for speech recognition, but only system text-to-speech is supported
|
26
26
|
- ❌ **Edge**: Speech recognition not working on Apple Silicon (language-not-supported error)
|
27
27
|
|
28
28
|
## Installation in Your Own Project
|
@@ -66,23 +66,18 @@ mcp-voice-hooks enables continuous voice conversations with AI assistants by:
|
|
66
66
|
|
67
67
|
There are two options for voice responses:
|
68
68
|
|
69
|
-
1. Browser Text-to-Speech
|
70
|
-
2.
|
71
|
-
3. Mac System Voice
|
69
|
+
1. Browser Text-to-Speech
|
70
|
+
2. System Text-to-Speech
|
72
71
|
|
73
72
|
### Selecting and downloading high quality System Voices (Mac only)
|
74
73
|
|
75
|
-
|
74
|
+
Mac has built-in text to speech, but high quality voices are not available by default.
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
I recommend using a Siri voice, as they are much higher quality.
|
76
|
+
You can download high quality voices from the system voice menu: `System Settings > Accessibility > Spoken Content > System Voice`
|
80
77
|
|
81
78
|
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
79
|
|
83
|
-
|
84
|
-
|
85
|
-
Once it's downloaded, you can select it in the system voice dropdown.
|
80
|
+
Once it's downloaded, you can select it in the system voice menu.
|
86
81
|
|
87
82
|
Test it with the bash command:
|
88
83
|
|
@@ -90,7 +85,13 @@ Test it with the bash command:
|
|
90
85
|
say "Hi, this is your mac system voice"
|
91
86
|
```
|
92
87
|
|
93
|
-
|
88
|
+
For Siri voices you need to set your system voice and select "Mac System Voice" in the voice-hooks browser interface.
|
89
|
+
|
90
|
+
Other downloaded voices will show up in the voice dropdown in the voice-hooks browser interface.
|
91
|
+
|
92
|
+
### Selecting and downloading high quality Browser Voices
|
93
|
+
|
94
|
+
|
94
95
|
|
95
96
|
## Manual Hook Installation
|
96
97
|
|
@@ -100,33 +101,30 @@ The hooks are automatically installed/updated when the MCP server starts. Howeve
|
|
100
101
|
npx mcp-voice-hooks install-hooks
|
101
102
|
```
|
102
103
|
|
103
|
-
This will
|
104
|
-
|
105
|
-
- Clean up any existing `~/.mcp-voice-hooks` directory contents
|
106
|
-
- Install/update hook scripts to `~/.mcp-voice-hooks/hooks/`
|
107
|
-
- Configure your project's `.claude/settings.json`
|
104
|
+
This will configure your project's `.claude/settings.json` with the necessary hook commands.
|
108
105
|
|
109
106
|
## Uninstallation
|
110
107
|
|
111
108
|
To completely remove MCP Voice Hooks:
|
112
109
|
|
113
110
|
```bash
|
114
|
-
# Remove
|
115
|
-
npx mcp-voice-hooks uninstall
|
116
|
-
|
117
|
-
# Also remove from Claude MCP servers
|
111
|
+
# Remove from Claude MCP servers
|
118
112
|
claude mcp remove voice-hooks
|
119
113
|
```
|
120
114
|
|
115
|
+
```bash
|
116
|
+
# Also remove hooks and settings
|
117
|
+
npx mcp-voice-hooks uninstall
|
118
|
+
```
|
119
|
+
|
121
120
|
This will:
|
122
121
|
|
123
|
-
- Remove the `~/.mcp-voice-hooks` directory
|
124
122
|
- Clean up voice hooks from your project's `.claude/settings.json`
|
125
123
|
- Preserve any custom hooks you've added
|
126
124
|
|
127
125
|
## Known Limitations
|
128
126
|
|
129
|
-
- **Intermittent Stop Hook Execution**: Claude Code's Stop hooks are not triggered
|
127
|
+
- **Intermittent Stop Hook Execution**: Claude Code's Stop hooks are not triggered if the agent stops immediately after a tool call. This results in the assistant occasionally stopping without checking for voice input. This will be fixed in 1.0.45. [github issue](https://github.com/anthropics/claude-code/issues/3113#issuecomment-3047324928)
|
130
128
|
|
131
129
|
## Development Mode
|
132
130
|
|
package/bin/cli.js
CHANGED
@@ -2,10 +2,9 @@
|
|
2
2
|
|
3
3
|
import fs from 'fs';
|
4
4
|
import path from 'path';
|
5
|
-
import os from 'os';
|
6
5
|
import { spawn } from 'child_process';
|
7
6
|
import { fileURLToPath } from 'url';
|
8
|
-
import { replaceVoiceHooks, areHooksEqual } from '../dist/hook-merger.js';
|
7
|
+
import { replaceVoiceHooks, areHooksEqual, removeVoiceHooks } from '../dist/hook-merger.js';
|
9
8
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
11
10
|
const __dirname = path.dirname(__filename);
|
@@ -19,10 +18,7 @@ async function main() {
|
|
19
18
|
if (command === 'install-hooks') {
|
20
19
|
console.log('🔧 Installing MCP Voice Hooks...');
|
21
20
|
|
22
|
-
//
|
23
|
-
await ensureUserDirectorySetup();
|
24
|
-
|
25
|
-
// Step 2: Configure Claude Code settings automatically
|
21
|
+
// Configure Claude Code settings automatically
|
26
22
|
await configureClaudeCodeSettings();
|
27
23
|
|
28
24
|
console.log('\n✅ Installation complete!');
|
@@ -46,74 +42,6 @@ async function main() {
|
|
46
42
|
}
|
47
43
|
}
|
48
44
|
|
49
|
-
// Ensure ~/.mcp-voice-hooks/hooks/ directory exists and contains latest hook files
|
50
|
-
async function ensureUserDirectorySetup() {
|
51
|
-
const userDir = path.join(os.homedir(), '.mcp-voice-hooks');
|
52
|
-
const hooksDir = path.join(userDir, 'hooks');
|
53
|
-
|
54
|
-
console.log('📁 Setting up user directory:', userDir);
|
55
|
-
|
56
|
-
// Clean up existing directory contents (except README.md if it exists)
|
57
|
-
if (fs.existsSync(userDir)) {
|
58
|
-
console.log('🧹 Cleaning up existing directory...');
|
59
|
-
|
60
|
-
// Remove hooks directory if it exists
|
61
|
-
if (fs.existsSync(hooksDir)) {
|
62
|
-
fs.rmSync(hooksDir, { recursive: true, force: true });
|
63
|
-
console.log('✅ Cleaned up old hooks');
|
64
|
-
}
|
65
|
-
|
66
|
-
// Remove any other files
|
67
|
-
const files = fs.readdirSync(userDir);
|
68
|
-
for (const file of files) {
|
69
|
-
const filePath = path.join(userDir, file);
|
70
|
-
const stat = fs.statSync(filePath);
|
71
|
-
if (stat.isDirectory()) {
|
72
|
-
fs.rmSync(filePath, { recursive: true, force: true });
|
73
|
-
} else {
|
74
|
-
fs.unlinkSync(filePath);
|
75
|
-
}
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
// Create directories
|
80
|
-
if (!fs.existsSync(userDir)) {
|
81
|
-
fs.mkdirSync(userDir, { recursive: true });
|
82
|
-
console.log('✅ Created user directory');
|
83
|
-
}
|
84
|
-
|
85
|
-
if (!fs.existsSync(hooksDir)) {
|
86
|
-
fs.mkdirSync(hooksDir, { recursive: true });
|
87
|
-
console.log('✅ Created hooks directory');
|
88
|
-
}
|
89
|
-
|
90
|
-
// Copy/update hook files from the package's .claude/hooks/ to user directory
|
91
|
-
const packageHooksDir = path.join(__dirname, '..', '.claude', 'hooks');
|
92
|
-
|
93
|
-
if (fs.existsSync(packageHooksDir)) {
|
94
|
-
const hookFiles = fs.readdirSync(packageHooksDir).filter(file => file.endsWith('.sh'));
|
95
|
-
|
96
|
-
for (const hookFile of hookFiles) {
|
97
|
-
const sourcePath = path.join(packageHooksDir, hookFile);
|
98
|
-
const destPath = path.join(hooksDir, hookFile);
|
99
|
-
|
100
|
-
// Copy hook file
|
101
|
-
fs.copyFileSync(sourcePath, destPath);
|
102
|
-
console.log(`✅ Updated hook: ${hookFile}`);
|
103
|
-
}
|
104
|
-
} else {
|
105
|
-
console.log('⚠️ Package hooks directory not found, skipping hook installation');
|
106
|
-
}
|
107
|
-
|
108
|
-
// Copy README.md from project root to user directory
|
109
|
-
const projectReadmePath = path.join(__dirname, '..', 'README.md');
|
110
|
-
const userReadmePath = path.join(userDir, 'README.md');
|
111
|
-
|
112
|
-
if (fs.existsSync(projectReadmePath)) {
|
113
|
-
fs.copyFileSync(projectReadmePath, userReadmePath);
|
114
|
-
console.log('✅ Copied README.md');
|
115
|
-
}
|
116
|
-
}
|
117
45
|
|
118
46
|
// Automatically configure Claude Code settings
|
119
47
|
async function configureClaudeCodeSettings() {
|
@@ -141,7 +69,7 @@ async function configureClaudeCodeSettings() {
|
|
141
69
|
}
|
142
70
|
}
|
143
71
|
|
144
|
-
// Add hook configuration
|
72
|
+
// Add hook configuration with inline commands
|
145
73
|
const hookConfig = {
|
146
74
|
"Stop": [
|
147
75
|
{
|
@@ -149,7 +77,7 @@ async function configureClaudeCodeSettings() {
|
|
149
77
|
"hooks": [
|
150
78
|
{
|
151
79
|
"type": "command",
|
152
|
-
"command": "
|
80
|
+
"command": "curl -s -X POST \"http://localhost:${MCP_VOICE_HOOKS_PORT:-5111}/api/hooks/stop\" || echo '{\"decision\": \"approve\", \"reason\": \"voice-hooks unavailable\"}'"
|
153
81
|
}
|
154
82
|
]
|
155
83
|
}
|
@@ -160,7 +88,7 @@ async function configureClaudeCodeSettings() {
|
|
160
88
|
"hooks": [
|
161
89
|
{
|
162
90
|
"type": "command",
|
163
|
-
"command": "
|
91
|
+
"command": "curl -s -X POST \"http://localhost:${MCP_VOICE_HOOKS_PORT:-5111}/api/hooks/pre-tool\" || echo '{\"decision\": \"approve\", \"reason\": \"voice-hooks unavailable\"}'"
|
164
92
|
}
|
165
93
|
]
|
166
94
|
},
|
@@ -169,7 +97,7 @@ async function configureClaudeCodeSettings() {
|
|
169
97
|
"hooks": [
|
170
98
|
{
|
171
99
|
"type": "command",
|
172
|
-
"command": "
|
100
|
+
"command": "curl -s -X POST \"http://localhost:${MCP_VOICE_HOOKS_PORT:-5111}/api/hooks/pre-speak\" || echo '{\"decision\": \"approve\", \"reason\": \"voice-hooks unavailable\"}'"
|
173
101
|
}
|
174
102
|
]
|
175
103
|
},
|
@@ -178,7 +106,7 @@ async function configureClaudeCodeSettings() {
|
|
178
106
|
"hooks": [
|
179
107
|
{
|
180
108
|
"type": "command",
|
181
|
-
"command": "
|
109
|
+
"command": "curl -s -X POST \"http://localhost:${MCP_VOICE_HOOKS_PORT:-5111}/api/hooks/pre-wait\" || echo '{\"decision\": \"approve\", \"reason\": \"voice-hooks unavailable\"}'"
|
182
110
|
}
|
183
111
|
]
|
184
112
|
}
|
@@ -204,19 +132,10 @@ async function configureClaudeCodeSettings() {
|
|
204
132
|
|
205
133
|
// Silent hook installation check - runs on every startup
|
206
134
|
async function ensureHooksInstalled() {
|
207
|
-
const userDir = path.join(os.homedir(), '.mcp-voice-hooks');
|
208
|
-
const hooksDir = path.join(userDir, 'hooks');
|
209
|
-
|
210
135
|
try {
|
211
136
|
console.log('🔄 Updating hooks to latest version...');
|
212
137
|
|
213
|
-
//
|
214
|
-
if (fs.existsSync(hooksDir)) {
|
215
|
-
fs.rmSync(hooksDir, { recursive: true, force: true });
|
216
|
-
}
|
217
|
-
|
218
|
-
// Recreate with latest hooks
|
219
|
-
await ensureUserDirectorySetup();
|
138
|
+
// Update hooks configuration in settings.json
|
220
139
|
await configureClaudeCodeSettings();
|
221
140
|
console.log('✅ Hooks and settings updated');
|
222
141
|
} catch (error) {
|
@@ -259,19 +178,9 @@ async function runMCPServer() {
|
|
259
178
|
|
260
179
|
// Uninstall MCP Voice Hooks
|
261
180
|
async function uninstall() {
|
262
|
-
const userDir = path.join(os.homedir(), '.mcp-voice-hooks');
|
263
181
|
const claudeSettingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
264
182
|
|
265
|
-
//
|
266
|
-
if (fs.existsSync(userDir)) {
|
267
|
-
console.log('📁 Removing user directory:', userDir);
|
268
|
-
fs.rmSync(userDir, { recursive: true, force: true });
|
269
|
-
console.log('✅ Removed ~/.mcp-voice-hooks');
|
270
|
-
} else {
|
271
|
-
console.log('ℹ️ ~/.mcp-voice-hooks directory not found');
|
272
|
-
}
|
273
|
-
|
274
|
-
// Step 2: Remove voice hooks from Claude settings
|
183
|
+
// Remove voice hooks from Claude settings
|
275
184
|
if (fs.existsSync(claudeSettingsPath)) {
|
276
185
|
try {
|
277
186
|
console.log('⚙️ Removing voice hooks from Claude settings...');
|
@@ -303,6 +212,7 @@ async function uninstall() {
|
|
303
212
|
console.log('ℹ️ No Claude settings file found in current project');
|
304
213
|
}
|
305
214
|
|
215
|
+
|
306
216
|
console.log('\n✅ Uninstallation complete!');
|
307
217
|
console.log('👋 MCP Voice Hooks has been removed.');
|
308
218
|
}
|
package/dist/hook-merger.d.ts
CHANGED
@@ -10,7 +10,7 @@ interface HookSettings {
|
|
10
10
|
[hookType: string]: HookConfig[];
|
11
11
|
}
|
12
12
|
/**
|
13
|
-
* Removes any existing voice hooks that
|
13
|
+
* Removes any existing voice hooks that use MCP_VOICE_HOOKS_PORT
|
14
14
|
* @param hooks - The current hooks configuration
|
15
15
|
* @returns The hooks with voice hooks removed
|
16
16
|
*/
|
package/dist/hook-merger.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
// src/hook-merger.ts
|
2
2
|
function removeVoiceHooks(hooks = {}) {
|
3
3
|
const cleaned = {};
|
4
|
-
const voiceHookPattern =
|
4
|
+
const voiceHookPattern = /MCP_VOICE_HOOKS_PORT/;
|
5
5
|
for (const [hookType, hookArray] of Object.entries(hooks)) {
|
6
6
|
cleaned[hookType] = hookArray.filter((hookConfig) => {
|
7
7
|
return !hookConfig.hooks.some((hook) => voiceHookPattern.test(hook.command));
|
package/dist/hook-merger.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/hook-merger.ts"],"sourcesContent":["interface Hook {\n type: string;\n command: string;\n}\n\ninterface HookConfig {\n matcher: string;\n hooks: Hook[];\n}\n\nexport interface HookSettings {\n [hookType: string]: HookConfig[];\n}\n\n/**\n * Removes any existing voice hooks that
|
1
|
+
{"version":3,"sources":["../src/hook-merger.ts"],"sourcesContent":["interface Hook {\n type: string;\n command: string;\n}\n\ninterface HookConfig {\n matcher: string;\n hooks: Hook[];\n}\n\nexport interface HookSettings {\n [hookType: string]: HookConfig[];\n}\n\n/**\n * Removes any existing voice hooks that use MCP_VOICE_HOOKS_PORT\n * @param hooks - The current hooks configuration\n * @returns The hooks with voice hooks removed\n */\nexport function removeVoiceHooks(hooks: HookSettings = {}): HookSettings {\n const cleaned: HookSettings = {};\n // Pattern to match voice hooks by the unique environment variable\n const voiceHookPattern = /MCP_VOICE_HOOKS_PORT/;\n \n for (const [hookType, hookArray] of Object.entries(hooks)) {\n cleaned[hookType] = hookArray.filter(hookConfig => {\n // Keep this hook config only if none of its commands match our pattern\n return !hookConfig.hooks.some(hook => voiceHookPattern.test(hook.command));\n });\n \n // Remove empty arrays\n if (cleaned[hookType].length === 0) {\n delete cleaned[hookType];\n }\n }\n \n return cleaned;\n}\n\n/**\n * Replaces voice hooks - removes any existing ones and adds new ones\n * @param existingHooks - The current hooks configuration\n * @param voiceHooks - The voice hooks to add\n * @returns The updated hooks configuration\n */\nexport function replaceVoiceHooks(existingHooks: HookSettings = {}, voiceHooks: HookSettings): HookSettings {\n // First, remove any existing voice hooks\n const cleaned = removeVoiceHooks(existingHooks);\n \n // Then merge in the new voice hooks\n const result: HookSettings = JSON.parse(JSON.stringify(cleaned)); // Deep clone\n \n for (const [hookType, hookArray] of Object.entries(voiceHooks)) {\n if (!result[hookType]) {\n result[hookType] = hookArray;\n } else {\n result[hookType].push(...hookArray);\n }\n }\n \n return result;\n}\n\n/**\n * Checks if two hook settings are semantically equal (ignoring order)\n * @param hooks1 - First hooks configuration\n * @param hooks2 - Second hooks configuration\n * @returns True if they contain the same hooks regardless of order\n */\nexport function areHooksEqual(hooks1: HookSettings = {}, hooks2: HookSettings = {}): boolean {\n const types1 = Object.keys(hooks1).sort();\n const types2 = Object.keys(hooks2).sort();\n \n // Different hook types\n if (types1.join(',') !== types2.join(',')) {\n return false;\n }\n \n // Check each hook type\n for (const hookType of types1) {\n const configs1 = hooks1[hookType];\n const configs2 = hooks2[hookType];\n \n if (configs1.length !== configs2.length) {\n return false;\n }\n \n // Create normalized strings for comparison\n const normalized1 = configs1\n .map(config => JSON.stringify(config))\n .sort();\n const normalized2 = configs2\n .map(config => JSON.stringify(config))\n .sort();\n \n // Compare sorted arrays\n for (let i = 0; i < normalized1.length; i++) {\n if (normalized1[i] !== normalized2[i]) {\n return false;\n }\n }\n }\n \n return true;\n}"],"mappings":";AAmBO,SAAS,iBAAiB,QAAsB,CAAC,GAAiB;AACvE,QAAM,UAAwB,CAAC;AAE/B,QAAM,mBAAmB;AAEzB,aAAW,CAAC,UAAU,SAAS,KAAK,OAAO,QAAQ,KAAK,GAAG;AACzD,YAAQ,QAAQ,IAAI,UAAU,OAAO,gBAAc;AAEjD,aAAO,CAAC,WAAW,MAAM,KAAK,UAAQ,iBAAiB,KAAK,KAAK,OAAO,CAAC;AAAA,IAC3E,CAAC;AAGD,QAAI,QAAQ,QAAQ,EAAE,WAAW,GAAG;AAClC,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,kBAAkB,gBAA8B,CAAC,GAAG,YAAwC;AAE1G,QAAM,UAAU,iBAAiB,aAAa;AAG9C,QAAM,SAAuB,KAAK,MAAM,KAAK,UAAU,OAAO,CAAC;AAE/D,aAAW,CAAC,UAAU,SAAS,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC9D,QAAI,CAAC,OAAO,QAAQ,GAAG;AACrB,aAAO,QAAQ,IAAI;AAAA,IACrB,OAAO;AACL,aAAO,QAAQ,EAAE,KAAK,GAAG,SAAS;AAAA,IACpC;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,cAAc,SAAuB,CAAC,GAAG,SAAuB,CAAC,GAAY;AAC3F,QAAM,SAAS,OAAO,KAAK,MAAM,EAAE,KAAK;AACxC,QAAM,SAAS,OAAO,KAAK,MAAM,EAAE,KAAK;AAGxC,MAAI,OAAO,KAAK,GAAG,MAAM,OAAO,KAAK,GAAG,GAAG;AACzC,WAAO;AAAA,EACT;AAGA,aAAW,YAAY,QAAQ;AAC7B,UAAM,WAAW,OAAO,QAAQ;AAChC,UAAM,WAAW,OAAO,QAAQ;AAEhC,QAAI,SAAS,WAAW,SAAS,QAAQ;AACvC,aAAO;AAAA,IACT;AAGA,UAAM,cAAc,SACjB,IAAI,YAAU,KAAK,UAAU,MAAM,CAAC,EACpC,KAAK;AACR,UAAM,cAAc,SACjB,IAAI,YAAU,KAAK,UAAU,MAAM,CAAC,EACpC,KAAK;AAGR,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAI,YAAY,CAAC,MAAM,YAAY,CAAC,GAAG;AACrC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
package/CLAUDE.local.md
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# To publish to npm
|
2
|
-
|
3
|
-
```bash
|
4
|
-
# 1. Build the project first
|
5
|
-
npm run build
|
6
|
-
|
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
|
-
|
10
|
-
# 3. Publish to npm (this creates the .tgz file automatically)
|
11
|
-
npm publish --registry https://registry.npmjs.org/
|
12
|
-
|
13
|
-
# Note: It can take 1-5 minutes for the package to be available globally
|
14
|
-
# Check availability at: https://www.npmjs.com/package/mcp-voice-hooks
|
15
|
-
|
16
|
-
# Optional: Create .tgz file without publishing
|
17
|
-
npm pack
|
18
|
-
```
|
Binary file
|
@@ -1,12 +0,0 @@
|
|
1
|
-
// src/debug.ts
|
2
|
-
var DEBUG = process.env.DEBUG === "true" || process.env.VOICE_HOOKS_DEBUG === "true";
|
3
|
-
function debugLog(...args) {
|
4
|
-
if (DEBUG) {
|
5
|
-
console.log(...args);
|
6
|
-
}
|
7
|
-
}
|
8
|
-
|
9
|
-
export {
|
10
|
-
debugLog
|
11
|
-
};
|
12
|
-
//# sourceMappingURL=chunk-IYGM5COW.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"sources":["../src/debug.ts"],"sourcesContent":["const DEBUG = process.env.DEBUG === 'true' || process.env.VOICE_HOOKS_DEBUG === 'true';\n\nexport function debugLog(...args: any[]): void {\n if (DEBUG) {\n console.log(...args);\n }\n}"],"mappings":";AAAA,IAAM,QAAQ,QAAQ,IAAI,UAAU,UAAU,QAAQ,IAAI,sBAAsB;AAEzE,SAAS,YAAY,MAAmB;AAC7C,MAAI,OAAO;AACT,YAAQ,IAAI,GAAG,IAAI;AAAA,EACrB;AACF;","names":[]}
|
@@ -1,125 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
debugLog
|
3
|
-
} from "./chunk-IYGM5COW.js";
|
4
|
-
|
5
|
-
// src/utterance-queue.ts
|
6
|
-
import { randomUUID } from "crypto";
|
7
|
-
var InMemoryUtteranceQueue = class {
|
8
|
-
utterances = [];
|
9
|
-
add(text, timestamp) {
|
10
|
-
const utterance = {
|
11
|
-
id: randomUUID(),
|
12
|
-
text: text.trim(),
|
13
|
-
timestamp: timestamp || /* @__PURE__ */ new Date(),
|
14
|
-
status: "pending"
|
15
|
-
};
|
16
|
-
this.utterances.push(utterance);
|
17
|
-
debugLog(`[Queue] queued: "${utterance.text}" [id: ${utterance.id}]`);
|
18
|
-
return utterance;
|
19
|
-
}
|
20
|
-
getRecent(limit = 10) {
|
21
|
-
return this.utterances.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit);
|
22
|
-
}
|
23
|
-
markDelivered(id) {
|
24
|
-
const utterance = this.utterances.find((u) => u.id === id);
|
25
|
-
if (utterance) {
|
26
|
-
utterance.status = "delivered";
|
27
|
-
debugLog(`[Queue] delivered: "${utterance.text}" [id: ${id}]`);
|
28
|
-
}
|
29
|
-
}
|
30
|
-
clear() {
|
31
|
-
const count = this.utterances.length;
|
32
|
-
this.utterances = [];
|
33
|
-
debugLog(`[Queue] Cleared ${count} utterances`);
|
34
|
-
}
|
35
|
-
};
|
36
|
-
|
37
|
-
// src/http-server.ts
|
38
|
-
import express from "express";
|
39
|
-
import cors from "cors";
|
40
|
-
import path from "path";
|
41
|
-
import { fileURLToPath } from "url";
|
42
|
-
var __filename = fileURLToPath(import.meta.url);
|
43
|
-
var __dirname = path.dirname(__filename);
|
44
|
-
var HttpServer = class {
|
45
|
-
app;
|
46
|
-
utteranceQueue;
|
47
|
-
port;
|
48
|
-
constructor(utteranceQueue, port = 3e3) {
|
49
|
-
this.utteranceQueue = utteranceQueue;
|
50
|
-
this.port = port;
|
51
|
-
this.app = express();
|
52
|
-
this.setupMiddleware();
|
53
|
-
this.setupRoutes();
|
54
|
-
}
|
55
|
-
setupMiddleware() {
|
56
|
-
this.app.use(cors());
|
57
|
-
this.app.use(express.json());
|
58
|
-
this.app.use(express.static(path.join(__dirname, "..", "public")));
|
59
|
-
}
|
60
|
-
setupRoutes() {
|
61
|
-
this.app.post("/api/potential-utterances", (req, res) => {
|
62
|
-
const { text, timestamp } = req.body;
|
63
|
-
if (!text || !text.trim()) {
|
64
|
-
res.status(400).json({ error: "Text is required" });
|
65
|
-
return;
|
66
|
-
}
|
67
|
-
const parsedTimestamp = timestamp ? new Date(timestamp) : void 0;
|
68
|
-
const utterance = this.utteranceQueue.add(text, parsedTimestamp);
|
69
|
-
res.json({
|
70
|
-
success: true,
|
71
|
-
utterance: {
|
72
|
-
id: utterance.id,
|
73
|
-
text: utterance.text,
|
74
|
-
timestamp: utterance.timestamp,
|
75
|
-
status: utterance.status
|
76
|
-
}
|
77
|
-
});
|
78
|
-
});
|
79
|
-
this.app.get("/api/utterances", (req, res) => {
|
80
|
-
const limit = parseInt(req.query.limit) || 10;
|
81
|
-
const utterances = this.utteranceQueue.getRecent(limit);
|
82
|
-
res.json({
|
83
|
-
utterances: utterances.map((u) => ({
|
84
|
-
id: u.id,
|
85
|
-
text: u.text,
|
86
|
-
timestamp: u.timestamp,
|
87
|
-
status: u.status
|
88
|
-
}))
|
89
|
-
});
|
90
|
-
});
|
91
|
-
this.app.get("/api/utterances/status", (req, res) => {
|
92
|
-
const total = this.utteranceQueue.utterances.length;
|
93
|
-
const pending = this.utteranceQueue.utterances.filter((u) => u.status === "pending").length;
|
94
|
-
const delivered = this.utteranceQueue.utterances.filter((u) => u.status === "delivered").length;
|
95
|
-
res.json({
|
96
|
-
total,
|
97
|
-
pending,
|
98
|
-
delivered
|
99
|
-
});
|
100
|
-
});
|
101
|
-
this.app.get("/", (req, res) => {
|
102
|
-
res.sendFile(path.join(__dirname, "..", "public", "index.html"));
|
103
|
-
});
|
104
|
-
}
|
105
|
-
start() {
|
106
|
-
return new Promise((resolve) => {
|
107
|
-
this.app.listen(this.port, () => {
|
108
|
-
console.log(`HTTP Server running on http://localhost:${this.port}`);
|
109
|
-
resolve();
|
110
|
-
});
|
111
|
-
});
|
112
|
-
}
|
113
|
-
};
|
114
|
-
|
115
|
-
// src/index.ts
|
116
|
-
async function main() {
|
117
|
-
const utteranceQueue = new InMemoryUtteranceQueue();
|
118
|
-
const httpServer = new HttpServer(utteranceQueue);
|
119
|
-
await httpServer.start();
|
120
|
-
console.log("Voice Hooks servers ready!");
|
121
|
-
console.log("- HTTP server: http://localhost:3000");
|
122
|
-
console.log("- MCP server: Ready for stdio connection");
|
123
|
-
}
|
124
|
-
main().catch(console.error);
|
125
|
-
//# sourceMappingURL=index.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"sources":["../src/utterance-queue.ts","../src/http-server.ts","../src/index.ts"],"sourcesContent":["import { Utterance, UtteranceQueue } from './types.js';\nimport { randomUUID } from 'crypto';\nimport { debugLog } from './debug.js';\n\nexport class InMemoryUtteranceQueue implements UtteranceQueue {\n public utterances: Utterance[] = [];\n\n add(text: string, timestamp?: Date): Utterance {\n const utterance: Utterance = {\n id: randomUUID(),\n text: text.trim(),\n timestamp: timestamp || new Date(),\n status: 'pending'\n };\n \n this.utterances.push(utterance);\n debugLog(`[Queue] queued:\t\"${utterance.text}\"\t[id: ${utterance.id}]`);\n return utterance;\n }\n\n getRecent(limit: number = 10): Utterance[] {\n return this.utterances\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n }\n\n markDelivered(id: string): void {\n const utterance = this.utterances.find(u => u.id === id);\n if (utterance) {\n utterance.status = 'delivered';\n debugLog(`[Queue] delivered:\t\"${utterance.text}\"\t[id: ${id}]`);\n }\n }\n\n clear(): void {\n const count = this.utterances.length;\n this.utterances = [];\n debugLog(`[Queue] Cleared ${count} utterances`);\n }\n}","import express from 'express';\nimport cors from 'cors';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { InMemoryUtteranceQueue } from './utterance-queue.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport class HttpServer {\n private app: express.Application;\n private utteranceQueue: InMemoryUtteranceQueue;\n private port: number;\n\n constructor(utteranceQueue: InMemoryUtteranceQueue, port: number = 3000) {\n this.utteranceQueue = utteranceQueue;\n this.port = port;\n this.app = express();\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware() {\n this.app.use(cors());\n this.app.use(express.json());\n this.app.use(express.static(path.join(__dirname, '..', 'public')));\n }\n\n private setupRoutes() {\n // API Routes\n this.app.post('/api/potential-utterances', (req: express.Request, res: express.Response) => {\n const { text, timestamp } = req.body;\n \n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n const parsedTimestamp = timestamp ? new Date(timestamp) : undefined;\n const utterance = this.utteranceQueue.add(text, parsedTimestamp);\n res.json({\n success: true,\n utterance: {\n id: utterance.id,\n text: utterance.text,\n timestamp: utterance.timestamp,\n status: utterance.status,\n },\n });\n });\n\n this.app.get('/api/utterances', (req: express.Request, res: express.Response) => {\n const limit = parseInt(req.query.limit as string) || 10;\n const utterances = this.utteranceQueue.getRecent(limit);\n \n res.json({\n utterances: utterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: u.status,\n })),\n });\n });\n\n this.app.get('/api/utterances/status', (req: express.Request, res: express.Response) => {\n const total = this.utteranceQueue.utterances.length;\n const pending = this.utteranceQueue.utterances.filter(u => u.status === 'pending').length;\n const delivered = this.utteranceQueue.utterances.filter(u => u.status === 'delivered').length;\n\n res.json({\n total,\n pending,\n delivered,\n });\n });\n\n // Serve the browser client\n this.app.get('/', (req: express.Request, res: express.Response) => {\n res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));\n });\n }\n\n start(): Promise<void> {\n return new Promise((resolve) => {\n this.app.listen(this.port, () => {\n console.log(`HTTP Server running on http://localhost:${this.port}`);\n resolve();\n });\n });\n }\n}","import { InMemoryUtteranceQueue } from './utterance-queue.js';\nimport { HttpServer } from './http-server.js';\n\nasync function main() {\n // Shared utterance queue between HTTP and MCP servers\n const utteranceQueue = new InMemoryUtteranceQueue();\n \n // Start HTTP server for browser client\n const httpServer = new HttpServer(utteranceQueue);\n await httpServer.start();\n \n // Note: MCP server runs separately via `npm run mcp` command\n \n console.log('Voice Hooks servers ready!');\n console.log('- HTTP server: http://localhost:3000');\n console.log('- MCP server: Ready for stdio connection');\n}\n\nmain().catch(console.error);"],"mappings":"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","names":[]}
|
@@ -1 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
@@ -1,352 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
import {
|
3
|
-
debugLog
|
4
|
-
} from "./chunk-IYGM5COW.js";
|
5
|
-
|
6
|
-
// src/unified-server.ts
|
7
|
-
import express from "express";
|
8
|
-
import cors from "cors";
|
9
|
-
import path from "path";
|
10
|
-
import { fileURLToPath } from "url";
|
11
|
-
import { randomUUID } from "crypto";
|
12
|
-
import { exec } from "child_process";
|
13
|
-
import { promisify } from "util";
|
14
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
15
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
16
|
-
import {
|
17
|
-
CallToolRequestSchema,
|
18
|
-
ListToolsRequestSchema
|
19
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
20
|
-
var __filename = fileURLToPath(import.meta.url);
|
21
|
-
var __dirname = path.dirname(__filename);
|
22
|
-
var DEFAULT_WAIT_TIMEOUT_SECONDS = 30;
|
23
|
-
var MIN_WAIT_TIMEOUT_SECONDS = 30;
|
24
|
-
var MAX_WAIT_TIMEOUT_SECONDS = 60;
|
25
|
-
var execAsync = promisify(exec);
|
26
|
-
async function playNotificationSound() {
|
27
|
-
try {
|
28
|
-
await execAsync("afplay /System/Library/Sounds/Funk.aiff");
|
29
|
-
debugLog("[Sound] Played notification sound");
|
30
|
-
} catch (error) {
|
31
|
-
debugLog(`[Sound] Failed to play sound: ${error}`);
|
32
|
-
}
|
33
|
-
}
|
34
|
-
var UtteranceQueue = class {
|
35
|
-
utterances = [];
|
36
|
-
add(text, timestamp) {
|
37
|
-
const utterance = {
|
38
|
-
id: randomUUID(),
|
39
|
-
text: text.trim(),
|
40
|
-
timestamp: timestamp || /* @__PURE__ */ new Date(),
|
41
|
-
status: "pending"
|
42
|
-
};
|
43
|
-
this.utterances.push(utterance);
|
44
|
-
debugLog(`[Queue] queued: "${utterance.text}" [id: ${utterance.id}]`);
|
45
|
-
return utterance;
|
46
|
-
}
|
47
|
-
getRecent(limit = 10) {
|
48
|
-
return this.utterances.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit);
|
49
|
-
}
|
50
|
-
markDelivered(id) {
|
51
|
-
const utterance = this.utterances.find((u) => u.id === id);
|
52
|
-
if (utterance) {
|
53
|
-
utterance.status = "delivered";
|
54
|
-
debugLog(`[Queue] delivered: "${utterance.text}" [id: ${id}]`);
|
55
|
-
}
|
56
|
-
}
|
57
|
-
clear() {
|
58
|
-
const count = this.utterances.length;
|
59
|
-
this.utterances = [];
|
60
|
-
debugLog(`[Queue] Cleared ${count} utterances`);
|
61
|
-
}
|
62
|
-
};
|
63
|
-
var IS_MCP_MANAGED = process.argv.includes("--mcp-managed");
|
64
|
-
var queue = new UtteranceQueue();
|
65
|
-
var lastTimeoutTimestamp = null;
|
66
|
-
var app = express();
|
67
|
-
app.use(cors());
|
68
|
-
app.use(express.json());
|
69
|
-
app.use(express.static(path.join(__dirname, "..", "public")));
|
70
|
-
app.post("/api/potential-utterances", (req, res) => {
|
71
|
-
const { text, timestamp } = req.body;
|
72
|
-
if (!text || !text.trim()) {
|
73
|
-
res.status(400).json({ error: "Text is required" });
|
74
|
-
return;
|
75
|
-
}
|
76
|
-
const parsedTimestamp = timestamp ? new Date(timestamp) : void 0;
|
77
|
-
const utterance = queue.add(text, parsedTimestamp);
|
78
|
-
res.json({
|
79
|
-
success: true,
|
80
|
-
utterance: {
|
81
|
-
id: utterance.id,
|
82
|
-
text: utterance.text,
|
83
|
-
timestamp: utterance.timestamp,
|
84
|
-
status: utterance.status
|
85
|
-
}
|
86
|
-
});
|
87
|
-
});
|
88
|
-
app.get("/api/utterances", (req, res) => {
|
89
|
-
const limit = parseInt(req.query.limit) || 10;
|
90
|
-
const utterances = queue.getRecent(limit);
|
91
|
-
res.json({
|
92
|
-
utterances: utterances.map((u) => ({
|
93
|
-
id: u.id,
|
94
|
-
text: u.text,
|
95
|
-
timestamp: u.timestamp,
|
96
|
-
status: u.status
|
97
|
-
}))
|
98
|
-
});
|
99
|
-
});
|
100
|
-
app.get("/api/utterances/status", (req, res) => {
|
101
|
-
const total = queue.utterances.length;
|
102
|
-
const pending = queue.utterances.filter((u) => u.status === "pending").length;
|
103
|
-
const delivered = queue.utterances.filter((u) => u.status === "delivered").length;
|
104
|
-
res.json({
|
105
|
-
total,
|
106
|
-
pending,
|
107
|
-
delivered
|
108
|
-
});
|
109
|
-
});
|
110
|
-
app.post("/api/dequeue-utterances", (req, res) => {
|
111
|
-
const { limit = 10 } = req.body;
|
112
|
-
const pendingUtterances = queue.utterances.filter((u) => u.status === "pending").sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit);
|
113
|
-
pendingUtterances.forEach((u) => {
|
114
|
-
queue.markDelivered(u.id);
|
115
|
-
});
|
116
|
-
res.json({
|
117
|
-
success: true,
|
118
|
-
utterances: pendingUtterances.map((u) => ({
|
119
|
-
text: u.text,
|
120
|
-
timestamp: u.timestamp
|
121
|
-
}))
|
122
|
-
});
|
123
|
-
});
|
124
|
-
app.post("/api/wait-for-utterances", async (req, res) => {
|
125
|
-
const { seconds_to_wait = DEFAULT_WAIT_TIMEOUT_SECONDS } = req.body;
|
126
|
-
const secondsToWait = Math.max(
|
127
|
-
MIN_WAIT_TIMEOUT_SECONDS,
|
128
|
-
Math.min(MAX_WAIT_TIMEOUT_SECONDS, seconds_to_wait)
|
129
|
-
);
|
130
|
-
const maxWaitMs = secondsToWait * 1e3;
|
131
|
-
const startTime = Date.now();
|
132
|
-
debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);
|
133
|
-
if (lastTimeoutTimestamp) {
|
134
|
-
const hasNewUtterances = queue.utterances.some(
|
135
|
-
(u) => u.timestamp > lastTimeoutTimestamp
|
136
|
-
);
|
137
|
-
if (!hasNewUtterances) {
|
138
|
-
debugLog("[Server] No new utterances since last timeout, returning immediately");
|
139
|
-
res.json({
|
140
|
-
success: true,
|
141
|
-
utterances: [],
|
142
|
-
message: `No utterances found after waiting ${secondsToWait} seconds.`,
|
143
|
-
waitTime: 0
|
144
|
-
});
|
145
|
-
return;
|
146
|
-
}
|
147
|
-
}
|
148
|
-
let firstTime = true;
|
149
|
-
while (Date.now() - startTime < maxWaitMs) {
|
150
|
-
const pendingUtterances = queue.utterances.filter(
|
151
|
-
(u) => u.status === "pending" && (!lastTimeoutTimestamp || u.timestamp > lastTimeoutTimestamp)
|
152
|
-
);
|
153
|
-
if (pendingUtterances.length > 0) {
|
154
|
-
lastTimeoutTimestamp = null;
|
155
|
-
const sortedUtterances = pendingUtterances.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
156
|
-
sortedUtterances.forEach((u) => {
|
157
|
-
queue.markDelivered(u.id);
|
158
|
-
});
|
159
|
-
res.json({
|
160
|
-
success: true,
|
161
|
-
utterances: sortedUtterances.map((u) => ({
|
162
|
-
id: u.id,
|
163
|
-
text: u.text,
|
164
|
-
timestamp: u.timestamp,
|
165
|
-
status: "delivered"
|
166
|
-
// They are now delivered
|
167
|
-
})),
|
168
|
-
count: pendingUtterances.length,
|
169
|
-
waitTime: Date.now() - startTime
|
170
|
-
});
|
171
|
-
return;
|
172
|
-
}
|
173
|
-
if (firstTime) {
|
174
|
-
firstTime = false;
|
175
|
-
await playNotificationSound();
|
176
|
-
}
|
177
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
178
|
-
}
|
179
|
-
lastTimeoutTimestamp = /* @__PURE__ */ new Date();
|
180
|
-
res.json({
|
181
|
-
success: true,
|
182
|
-
utterances: [],
|
183
|
-
message: `No utterances found after waiting ${secondsToWait} seconds.`,
|
184
|
-
waitTime: maxWaitMs
|
185
|
-
});
|
186
|
-
});
|
187
|
-
app.get("/api/should-wait", (req, res) => {
|
188
|
-
const shouldWait = !lastTimeoutTimestamp || queue.utterances.some((u) => u.timestamp > lastTimeoutTimestamp);
|
189
|
-
res.json({ shouldWait });
|
190
|
-
});
|
191
|
-
app.get("/api/has-pending-utterances", (req, res) => {
|
192
|
-
const pendingCount = queue.utterances.filter((u) => u.status === "pending").length;
|
193
|
-
const hasPending = pendingCount > 0;
|
194
|
-
res.json({
|
195
|
-
hasPending,
|
196
|
-
pendingCount
|
197
|
-
});
|
198
|
-
});
|
199
|
-
app.delete("/api/utterances", (req, res) => {
|
200
|
-
const clearedCount = queue.utterances.length;
|
201
|
-
queue.clear();
|
202
|
-
res.json({
|
203
|
-
success: true,
|
204
|
-
message: `Cleared ${clearedCount} utterances`,
|
205
|
-
clearedCount
|
206
|
-
});
|
207
|
-
});
|
208
|
-
app.get("/", (req, res) => {
|
209
|
-
res.sendFile(path.join(__dirname, "..", "public", "index.html"));
|
210
|
-
});
|
211
|
-
var HTTP_PORT = 3e3;
|
212
|
-
app.listen(HTTP_PORT, () => {
|
213
|
-
console.log(`[HTTP] Server listening on http://localhost:${HTTP_PORT}`);
|
214
|
-
console.log(`[Mode] Running in ${IS_MCP_MANAGED ? "MCP-managed" : "standalone"} mode`);
|
215
|
-
});
|
216
|
-
if (IS_MCP_MANAGED) {
|
217
|
-
console.log("[MCP] Initializing MCP server...");
|
218
|
-
const mcpServer = new Server(
|
219
|
-
{
|
220
|
-
name: "voice-hooks",
|
221
|
-
version: "1.0.0"
|
222
|
-
},
|
223
|
-
{
|
224
|
-
capabilities: {
|
225
|
-
tools: {}
|
226
|
-
}
|
227
|
-
}
|
228
|
-
);
|
229
|
-
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
230
|
-
return {
|
231
|
-
tools: [
|
232
|
-
{
|
233
|
-
name: "dequeue_utterances",
|
234
|
-
description: "Dequeue pending utterances and mark them as delivered",
|
235
|
-
inputSchema: {
|
236
|
-
type: "object",
|
237
|
-
properties: {
|
238
|
-
limit: {
|
239
|
-
type: "number",
|
240
|
-
description: "Maximum number of utterances to dequeue (default: 10)",
|
241
|
-
default: 10
|
242
|
-
}
|
243
|
-
}
|
244
|
-
}
|
245
|
-
},
|
246
|
-
{
|
247
|
-
name: "wait_for_utterance",
|
248
|
-
description: "Wait for an utterance to be available or until timeout. Returns immediately if no utterances since last timeout.",
|
249
|
-
inputSchema: {
|
250
|
-
type: "object",
|
251
|
-
properties: {
|
252
|
-
seconds_to_wait: {
|
253
|
-
type: "number",
|
254
|
-
description: `Maximum seconds to wait for an utterance (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}, min: ${MIN_WAIT_TIMEOUT_SECONDS}, max: ${MAX_WAIT_TIMEOUT_SECONDS})`,
|
255
|
-
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
256
|
-
minimum: MIN_WAIT_TIMEOUT_SECONDS,
|
257
|
-
maximum: MAX_WAIT_TIMEOUT_SECONDS
|
258
|
-
}
|
259
|
-
}
|
260
|
-
}
|
261
|
-
}
|
262
|
-
]
|
263
|
-
};
|
264
|
-
});
|
265
|
-
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
266
|
-
const { name, arguments: args } = request.params;
|
267
|
-
try {
|
268
|
-
if (name === "dequeue_utterances") {
|
269
|
-
const limit = args?.limit ?? 10;
|
270
|
-
const response = await fetch("http://localhost:3000/api/dequeue-utterances", {
|
271
|
-
method: "POST",
|
272
|
-
headers: { "Content-Type": "application/json" },
|
273
|
-
body: JSON.stringify({ limit })
|
274
|
-
});
|
275
|
-
const data = await response.json();
|
276
|
-
if (data.utterances.length === 0) {
|
277
|
-
return {
|
278
|
-
content: [
|
279
|
-
{
|
280
|
-
type: "text",
|
281
|
-
text: "No recent utterances found."
|
282
|
-
}
|
283
|
-
]
|
284
|
-
};
|
285
|
-
}
|
286
|
-
return {
|
287
|
-
content: [
|
288
|
-
{
|
289
|
-
type: "text",
|
290
|
-
text: `Dequeued ${data.utterances.length} utterance(s):
|
291
|
-
|
292
|
-
${data.utterances.reverse().map((u) => `"${u.text}" [time: ${new Date(u.timestamp).toISOString()}]`).join("\n")}`
|
293
|
-
}
|
294
|
-
]
|
295
|
-
};
|
296
|
-
}
|
297
|
-
if (name === "wait_for_utterance") {
|
298
|
-
const requestedSeconds = args?.seconds_to_wait ?? DEFAULT_WAIT_TIMEOUT_SECONDS;
|
299
|
-
const secondsToWait = Math.max(
|
300
|
-
MIN_WAIT_TIMEOUT_SECONDS,
|
301
|
-
Math.min(MAX_WAIT_TIMEOUT_SECONDS, requestedSeconds)
|
302
|
-
);
|
303
|
-
debugLog(`[MCP] Calling wait_for_utterance with ${secondsToWait}s timeout`);
|
304
|
-
const response = await fetch("http://localhost:3000/api/wait-for-utterances", {
|
305
|
-
method: "POST",
|
306
|
-
headers: { "Content-Type": "application/json" },
|
307
|
-
body: JSON.stringify({ seconds_to_wait: secondsToWait })
|
308
|
-
});
|
309
|
-
const data = await response.json();
|
310
|
-
if (data.utterances && data.utterances.length > 0) {
|
311
|
-
const utteranceTexts = data.utterances.map((u) => `[${u.timestamp}] "${u.text}"`).join("\n");
|
312
|
-
return {
|
313
|
-
content: [
|
314
|
-
{
|
315
|
-
type: "text",
|
316
|
-
text: `Found ${data.count} utterance(s):
|
317
|
-
|
318
|
-
${utteranceTexts}`
|
319
|
-
}
|
320
|
-
]
|
321
|
-
};
|
322
|
-
} else {
|
323
|
-
return {
|
324
|
-
content: [
|
325
|
-
{
|
326
|
-
type: "text",
|
327
|
-
text: data.message || `No utterances found after waiting ${secondsToWait} seconds.`
|
328
|
-
}
|
329
|
-
]
|
330
|
-
};
|
331
|
-
}
|
332
|
-
}
|
333
|
-
throw new Error(`Unknown tool: ${name}`);
|
334
|
-
} catch (error) {
|
335
|
-
return {
|
336
|
-
content: [
|
337
|
-
{
|
338
|
-
type: "text",
|
339
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
340
|
-
}
|
341
|
-
],
|
342
|
-
isError: true
|
343
|
-
};
|
344
|
-
}
|
345
|
-
});
|
346
|
-
const transport = new StdioServerTransport();
|
347
|
-
mcpServer.connect(transport);
|
348
|
-
console.log("[MCP] Server connected via stdio");
|
349
|
-
} else {
|
350
|
-
console.log("[MCP] Skipping MCP server initialization (not in MCP-managed mode)");
|
351
|
-
}
|
352
|
-
//# sourceMappingURL=unified-server.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"sources":["../src/unified-server.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport express from 'express';\nimport type { Request, Response } from 'express';\nimport cors from 'cors';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { randomUUID } from 'crypto';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { debugLog } from './debug.ts';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Constants\nconst DEFAULT_WAIT_TIMEOUT_SECONDS = 30;\nconst MIN_WAIT_TIMEOUT_SECONDS = 30;\nconst MAX_WAIT_TIMEOUT_SECONDS = 60;\n\n// Promisified exec for async/await\nconst execAsync = promisify(exec);\n\n// Function to play a sound notification\nasync function playNotificationSound() {\n try {\n // Use macOS system sound\n await execAsync('afplay /System/Library/Sounds/Funk.aiff');\n debugLog('[Sound] Played notification sound');\n } catch (error) {\n debugLog(`[Sound] Failed to play sound: ${error}`);\n // Don't throw - sound is not critical\n }\n}\n\n// Shared utterance queue\ninterface Utterance {\n id: string;\n text: string;\n timestamp: Date;\n status: 'pending' | 'delivered';\n}\n\nclass UtteranceQueue {\n utterances: Utterance[] = [];\n\n add(text: string, timestamp?: Date): Utterance {\n const utterance: Utterance = {\n id: randomUUID(),\n text: text.trim(),\n timestamp: timestamp || new Date(),\n status: 'pending'\n };\n\n this.utterances.push(utterance);\n debugLog(`[Queue] queued: \"${utterance.text}\"\t[id: ${utterance.id}]`);\n return utterance;\n }\n\n getRecent(limit: number = 10): Utterance[] {\n return this.utterances\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n }\n\n markDelivered(id: string): void {\n const utterance = this.utterances.find(u => u.id === id);\n if (utterance) {\n utterance.status = 'delivered';\n debugLog(`[Queue] delivered: \"${utterance.text}\"\t[id: ${id}]`);\n }\n }\n\n clear(): void {\n const count = this.utterances.length;\n this.utterances = [];\n debugLog(`[Queue] Cleared ${count} utterances`);\n }\n}\n\n// Determine if we're running in MCP-managed mode\nconst IS_MCP_MANAGED = process.argv.includes('--mcp-managed');\n\n// Global state\nconst queue = new UtteranceQueue();\nlet lastTimeoutTimestamp: Date | null = null;\n\n// HTTP Server Setup (always created)\nconst app = express();\napp.use(cors());\napp.use(express.json());\napp.use(express.static(path.join(__dirname, '..', 'public')));\n\n// API Routes\napp.post('/api/potential-utterances', (req: Request, res: Response) => {\n const { text, timestamp } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n const parsedTimestamp = timestamp ? new Date(timestamp) : undefined;\n const utterance = queue.add(text, parsedTimestamp);\n res.json({\n success: true,\n utterance: {\n id: utterance.id,\n text: utterance.text,\n timestamp: utterance.timestamp,\n status: utterance.status,\n },\n });\n});\n\napp.get('/api/utterances', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 10;\n const utterances = queue.getRecent(limit);\n\n res.json({\n utterances: utterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: u.status,\n })),\n });\n});\n\napp.get('/api/utterances/status', (req: Request, res: Response) => {\n const total = queue.utterances.length;\n const pending = queue.utterances.filter(u => u.status === 'pending').length;\n const delivered = queue.utterances.filter(u => u.status === 'delivered').length;\n\n res.json({\n total,\n pending,\n delivered,\n });\n});\n\n// MCP server integration\napp.post('/api/dequeue-utterances', (req: Request, res: Response) => {\n const { limit = 10 } = req.body;\n const pendingUtterances = queue.utterances\n .filter(u => u.status === 'pending')\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, limit);\n\n // Mark as delivered\n pendingUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: pendingUtterances.map(u => ({\n text: u.text,\n timestamp: u.timestamp,\n })),\n });\n});\n\n// Wait for utterance endpoint\napp.post('/api/wait-for-utterances', async (req: Request, res: Response) => {\n const { seconds_to_wait = DEFAULT_WAIT_TIMEOUT_SECONDS } = req.body;\n const secondsToWait = Math.max(\n MIN_WAIT_TIMEOUT_SECONDS,\n Math.min(MAX_WAIT_TIMEOUT_SECONDS, seconds_to_wait)\n );\n const maxWaitMs = secondsToWait * 1000;\n const startTime = Date.now();\n\n debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);\n\n // Check if we should return immediately\n if (lastTimeoutTimestamp) {\n const hasNewUtterances = queue.utterances.some(\n u => u.timestamp > lastTimeoutTimestamp!\n );\n if (!hasNewUtterances) {\n debugLog('[Server] No new utterances since last timeout, returning immediately');\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: 0,\n });\n return;\n }\n }\n\n let firstTime = true;\n\n // Poll for utterances\n while (Date.now() - startTime < maxWaitMs) {\n const pendingUtterances = queue.utterances.filter(\n u => u.status === 'pending' &&\n (!lastTimeoutTimestamp || u.timestamp > lastTimeoutTimestamp)\n );\n\n if (pendingUtterances.length > 0) {\n // Found utterances - clear lastTimeoutTimestamp\n lastTimeoutTimestamp = null;\n\n // Sort by timestamp (oldest first)\n const sortedUtterances = pendingUtterances\n .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());\n\n // Mark utterances as delivered\n sortedUtterances.forEach(u => {\n queue.markDelivered(u.id);\n });\n\n res.json({\n success: true,\n utterances: sortedUtterances.map(u => ({\n id: u.id,\n text: u.text,\n timestamp: u.timestamp,\n status: 'delivered', // They are now delivered\n })),\n count: pendingUtterances.length,\n waitTime: Date.now() - startTime,\n });\n return;\n }\n\n if (firstTime) {\n firstTime = false;\n // Play notification sound since we're about to start waiting\n await playNotificationSound();\n }\n\n // Wait 100ms before checking again\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n\n // Timeout reached - no utterances found\n lastTimeoutTimestamp = new Date();\n\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: maxWaitMs,\n });\n});\n\n// API for the stop hook to check if it should wait\napp.get('/api/should-wait', (req: Request, res: Response) => {\n const shouldWait = !lastTimeoutTimestamp ||\n queue.utterances.some(u => u.timestamp > lastTimeoutTimestamp!);\n\n res.json({ shouldWait });\n});\n\n// API for pre-tool hook to check for pending utterances\napp.get('/api/has-pending-utterances', (req: Request, res: Response) => {\n const pendingCount = queue.utterances.filter(u => u.status === 'pending').length;\n const hasPending = pendingCount > 0;\n\n res.json({\n hasPending,\n pendingCount\n });\n});\n\n// API to clear all utterances\napp.delete('/api/utterances', (req: Request, res: Response) => {\n const clearedCount = queue.utterances.length;\n queue.clear();\n\n res.json({\n success: true,\n message: `Cleared ${clearedCount} utterances`,\n clearedCount\n });\n});\n\napp.get('/', (req: Request, res: Response) => {\n res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));\n});\n\n// Start HTTP server\nconst HTTP_PORT = 3000;\napp.listen(HTTP_PORT, () => {\n console.log(`[HTTP] Server listening on http://localhost:${HTTP_PORT}`);\n console.log(`[Mode] Running in ${IS_MCP_MANAGED ? 'MCP-managed' : 'standalone'} mode`);\n});\n\n// MCP Server Setup (only if MCP-managed)\nif (IS_MCP_MANAGED) {\n console.log('[MCP] Initializing MCP server...');\n\n const mcpServer = new Server(\n {\n name: 'voice-hooks',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Tool handlers\n mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [\n {\n name: 'dequeue_utterances',\n description: 'Dequeue pending utterances and mark them as delivered',\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'number',\n description: 'Maximum number of utterances to dequeue (default: 10)',\n default: 10,\n },\n },\n },\n },\n {\n name: 'wait_for_utterance',\n description: 'Wait for an utterance to be available or until timeout. Returns immediately if no utterances since last timeout.',\n inputSchema: {\n type: 'object',\n properties: {\n seconds_to_wait: {\n type: 'number',\n description: `Maximum seconds to wait for an utterance (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}, min: ${MIN_WAIT_TIMEOUT_SECONDS}, max: ${MAX_WAIT_TIMEOUT_SECONDS})`,\n default: DEFAULT_WAIT_TIMEOUT_SECONDS,\n minimum: MIN_WAIT_TIMEOUT_SECONDS,\n maximum: MAX_WAIT_TIMEOUT_SECONDS,\n },\n },\n },\n },\n ],\n };\n });\n\n mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n if (name === 'dequeue_utterances') {\n const limit = (args?.limit as number) ?? 10;\n const response = await fetch('http://localhost:3000/api/dequeue-utterances', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ limit }),\n });\n\n const data = await response.json() as any;\n\n if (data.utterances.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No recent utterances found.',\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Dequeued ${data.utterances.length} utterance(s):\\n\\n${data.utterances.reverse().map((u: any) => `\"${u.text}\"\\t[time: ${new Date(u.timestamp).toISOString()}]`).join('\\n')\n }`,\n },\n ],\n };\n }\n\n if (name === 'wait_for_utterance') {\n const requestedSeconds = (args?.seconds_to_wait as number) ?? DEFAULT_WAIT_TIMEOUT_SECONDS;\n const secondsToWait = Math.max(\n MIN_WAIT_TIMEOUT_SECONDS,\n Math.min(MAX_WAIT_TIMEOUT_SECONDS, requestedSeconds)\n );\n debugLog(`[MCP] Calling wait_for_utterance with ${secondsToWait}s timeout`);\n\n const response = await fetch('http://localhost:3000/api/wait-for-utterances', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ seconds_to_wait: secondsToWait }),\n });\n\n const data = await response.json() as any;\n\n if (data.utterances && data.utterances.length > 0) {\n const utteranceTexts = data.utterances\n .map((u: any) => `[${u.timestamp}] \"${u.text}\"`)\n .join('\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Found ${data.count} utterance(s):\\n\\n${utteranceTexts}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: data.message || `No utterances found after waiting ${secondsToWait} seconds.`,\n },\n ],\n };\n }\n }\n\n throw new Error(`Unknown tool: ${name}`);\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n });\n\n // Connect via stdio\n const transport = new StdioServerTransport();\n mcpServer.connect(transport);\n console.log('[MCP] Server connected via stdio');\n} else {\n console.log('[MCP] Skipping MCP server initialization (not in MCP-managed mode)');\n}"],"mappings":"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","names":[]}
|
Binary file
|
Binary file
|