mcp-voice-hooks 1.0.21 → 1.0.25
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 +5 -6
- package/bin/cli.js +68 -26
- package/package.json +1 -1
- 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
@@ -4,7 +4,7 @@ Voice Mode for Claude Code allows you to have a continuous two-way conversation
|
|
4
4
|
|
5
5
|
It uses the new [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) to deliver voice input to Claude while it works.
|
6
6
|
|
7
|
-
This lets you speak continuously to Claude - interrupt, redirect, or provide feedback without stopping.
|
7
|
+
This lets you speak continuously to Claude - interrupt, redirect, or provide feedback without stopping what Claude is doing.
|
8
8
|
|
9
9
|
Optionally enable text-to-speech to have Claude speak back to you.
|
10
10
|
|
@@ -21,8 +21,7 @@ Installation is easy.
|
|
21
21
|
### 1. Install Claude Code
|
22
22
|
|
23
23
|
```bash
|
24
|
-
|
25
|
-
npm install -g @anthropic-ai/claude-code@">=1.0.45"
|
24
|
+
npm install -g @anthropic-ai/claude-code
|
26
25
|
```
|
27
26
|
|
28
27
|
### 2. Install Voice Mode
|
@@ -91,7 +90,7 @@ The hooks are automatically installed/updated when the MCP server starts. Howeve
|
|
91
90
|
npx mcp-voice-hooks install-hooks
|
92
91
|
```
|
93
92
|
|
94
|
-
This will configure your project's `.claude/settings.json` with the necessary hook commands.
|
93
|
+
This will configure your project's `.claude/settings.local.json` with the necessary hook commands.
|
95
94
|
|
96
95
|
## Uninstallation
|
97
96
|
|
@@ -109,7 +108,7 @@ npx mcp-voice-hooks uninstall
|
|
109
108
|
|
110
109
|
This will:
|
111
110
|
|
112
|
-
- Clean up voice hooks from your project's `.claude/settings.json`
|
111
|
+
- Clean up voice hooks from your project's `.claude/settings.local.json`
|
113
112
|
- Preserve any custom hooks you've added
|
114
113
|
|
115
114
|
## Development Mode
|
@@ -145,7 +144,7 @@ claude
|
|
145
144
|
|
146
145
|
#### Port Configuration
|
147
146
|
|
148
|
-
The default port is 5111. To use a different port, add to your project's `.claude/settings.json`:
|
147
|
+
The default port is 5111. To use a different port, add to your project's `.claude/settings.local.json`:
|
149
148
|
|
150
149
|
```json
|
151
150
|
{
|
package/bin/cli.js
CHANGED
@@ -15,7 +15,12 @@ async function main() {
|
|
15
15
|
const command = args[0];
|
16
16
|
|
17
17
|
try {
|
18
|
-
if (command === '
|
18
|
+
if (command === '--version' || command === '-v') {
|
19
|
+
// Read package.json to get version
|
20
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
21
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
22
|
+
console.log(packageJson.version);
|
23
|
+
} else if (command === 'install-hooks') {
|
19
24
|
console.log('🔧 Installing MCP Voice Hooks...');
|
20
25
|
|
21
26
|
// Configure Claude Code settings automatically
|
@@ -46,7 +51,9 @@ async function main() {
|
|
46
51
|
// Automatically configure Claude Code settings
|
47
52
|
async function configureClaudeCodeSettings() {
|
48
53
|
const claudeDir = path.join(process.cwd(), '.claude');
|
49
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
54
|
+
const settingsPath = path.join(claudeDir, 'settings.local.json');
|
55
|
+
// This was used in versions <= v1.0.21.
|
56
|
+
const oldSettingsPath = path.join(claudeDir, 'settings.json');
|
50
57
|
|
51
58
|
console.log('⚙️ Configuring project Claude Code settings...');
|
52
59
|
|
@@ -56,6 +63,32 @@ async function configureClaudeCodeSettings() {
|
|
56
63
|
console.log('✅ Created project .claude directory');
|
57
64
|
}
|
58
65
|
|
66
|
+
// Clean up old settings.json if it exists (for users upgrading from older versions)
|
67
|
+
if (fs.existsSync(oldSettingsPath)) {
|
68
|
+
try {
|
69
|
+
console.log('🔄 Found old settings.json, cleaning up voice hooks...');
|
70
|
+
const oldSettingsContent = fs.readFileSync(oldSettingsPath, 'utf8');
|
71
|
+
const oldSettings = JSON.parse(oldSettingsContent);
|
72
|
+
|
73
|
+
if (oldSettings.hooks) {
|
74
|
+
// Remove voice hooks from old settings
|
75
|
+
const cleanedHooks = removeVoiceHooks(oldSettings.hooks);
|
76
|
+
|
77
|
+
if (Object.keys(cleanedHooks).length === 0) {
|
78
|
+
delete oldSettings.hooks;
|
79
|
+
} else {
|
80
|
+
oldSettings.hooks = cleanedHooks;
|
81
|
+
}
|
82
|
+
|
83
|
+
// Write back cleaned settings
|
84
|
+
fs.writeFileSync(oldSettingsPath, JSON.stringify(oldSettings, null, 2));
|
85
|
+
console.log('✅ Cleaned up voice hooks from old settings.json');
|
86
|
+
}
|
87
|
+
} catch (error) {
|
88
|
+
console.log('⚠️ Could not clean up old settings.json:', error.message);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
59
92
|
// Read existing settings or create new
|
60
93
|
let settings = {};
|
61
94
|
if (fs.existsSync(settingsPath)) {
|
@@ -189,40 +222,49 @@ async function runMCPServer() {
|
|
189
222
|
|
190
223
|
// Uninstall MCP Voice Hooks
|
191
224
|
async function uninstall() {
|
192
|
-
const
|
225
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
226
|
+
const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
|
227
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
193
228
|
|
194
|
-
//
|
195
|
-
|
196
|
-
|
197
|
-
|
229
|
+
// Helper function to remove hooks from a settings file
|
230
|
+
async function removeHooksFromFile(filePath, fileName) {
|
231
|
+
if (fs.existsSync(filePath)) {
|
232
|
+
try {
|
233
|
+
console.log(`⚙️ Removing voice hooks from ${fileName}...`);
|
198
234
|
|
199
|
-
|
200
|
-
|
235
|
+
const settingsContent = fs.readFileSync(filePath, 'utf8');
|
236
|
+
const settings = JSON.parse(settingsContent);
|
201
237
|
|
202
|
-
|
203
|
-
|
204
|
-
|
238
|
+
if (settings.hooks) {
|
239
|
+
// Remove voice hooks
|
240
|
+
const cleanedHooks = removeVoiceHooks(settings.hooks);
|
205
241
|
|
206
|
-
|
207
|
-
|
208
|
-
|
242
|
+
if (Object.keys(cleanedHooks).length === 0) {
|
243
|
+
// If no hooks remain, remove the hooks property entirely
|
244
|
+
delete settings.hooks;
|
245
|
+
} else {
|
246
|
+
settings.hooks = cleanedHooks;
|
247
|
+
}
|
248
|
+
|
249
|
+
// Write updated settings
|
250
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2));
|
251
|
+
console.log(`✅ Removed voice hooks from ${fileName}`);
|
209
252
|
} else {
|
210
|
-
|
253
|
+
console.log(`ℹ️ No hooks found in ${fileName}`);
|
211
254
|
}
|
212
|
-
|
213
|
-
|
214
|
-
fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));
|
215
|
-
console.log('✅ Removed voice hooks from Claude settings');
|
216
|
-
} else {
|
217
|
-
console.log('ℹ️ No hooks found in Claude settings');
|
255
|
+
} catch (error) {
|
256
|
+
console.log(`⚠️ Could not update ${fileName}:`, error.message);
|
218
257
|
}
|
219
|
-
} catch (error) {
|
220
|
-
console.log('⚠️ Could not update Claude settings:', error.message);
|
221
258
|
}
|
222
|
-
} else {
|
223
|
-
console.log('ℹ️ No Claude settings file found in current project');
|
224
259
|
}
|
225
260
|
|
261
|
+
// Remove hooks from both settings.local.json and settings.json (for backwards compatibility)
|
262
|
+
await removeHooksFromFile(settingsLocalPath, 'settings.local.json');
|
263
|
+
await removeHooksFromFile(settingsPath, 'settings.json');
|
264
|
+
|
265
|
+
if (!fs.existsSync(settingsLocalPath) && !fs.existsSync(settingsPath)) {
|
266
|
+
console.log('ℹ️ No Claude settings files found in current project');
|
267
|
+
}
|
226
268
|
|
227
269
|
console.log('\n✅ Uninstallation complete!');
|
228
270
|
console.log('👋 MCP Voice Hooks has been removed.');
|
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
|