mcp-voice-hooks 1.0.14 → 1.0.15

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/dist/index.js CHANGED
@@ -39,7 +39,6 @@ import express from "express";
39
39
  import cors from "cors";
40
40
  import path from "path";
41
41
  import { fileURLToPath } from "url";
42
- import { exec } from "child_process";
43
42
  var __filename = fileURLToPath(import.meta.url);
44
43
  var __dirname = path.dirname(__filename);
45
44
  var HttpServer = class {
@@ -103,32 +102,10 @@ var HttpServer = class {
103
102
  res.sendFile(path.join(__dirname, "..", "public", "index.html"));
104
103
  });
105
104
  }
106
- openBrowser(url) {
107
- const platform = process.platform;
108
- let command;
109
- if (platform === "darwin") {
110
- command = `open ${url}`;
111
- } else if (platform === "win32") {
112
- command = `start ${url}`;
113
- } else {
114
- command = `xdg-open ${url}`;
115
- }
116
- exec(command, (error) => {
117
- if (error) {
118
- console.error(`Failed to open browser: ${error.message}`);
119
- } else {
120
- console.log(`Opened ${url} in default browser`);
121
- }
122
- });
123
- }
124
105
  start() {
125
106
  return new Promise((resolve) => {
126
107
  this.app.listen(this.port, () => {
127
- const serverUrl = `http://localhost:${this.port}`;
128
- console.log(`HTTP Server running on ${serverUrl}`);
129
- if (process.env.MCP_VOICE_HOOKS_NO_BROWSER !== "true") {
130
- this.openBrowser(serverUrl);
131
- }
108
+ console.log(`HTTP Server running on http://localhost:${this.port}`);
132
109
  resolve();
133
110
  });
134
111
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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 { exec } from 'child_process';\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 = 5111) {\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 private openBrowser(url: string) {\n const platform = process.platform;\n let command: string;\n \n if (platform === 'darwin') {\n command = `open ${url}`;\n } else if (platform === 'win32') {\n command = `start ${url}`;\n } else {\n command = `xdg-open ${url}`;\n }\n \n exec(command, (error) => {\n if (error) {\n console.error(`Failed to open browser: ${error.message}`);\n } else {\n console.log(`Opened ${url} in default browser`);\n }\n });\n }\n\n start(): Promise<void> {\n return new Promise((resolve) => {\n this.app.listen(this.port, () => {\n const serverUrl = `http://localhost:${this.port}`;\n console.log(`HTTP Server running on ${serverUrl}`);\n if (process.env.MCP_VOICE_HOOKS_NO_BROWSER !== 'true') {\n this.openBrowser(serverUrl);\n }\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 port = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\n const httpServer = new HttpServer(utteranceQueue, port);\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:${port}`);\n console.log('- MCP server: Ready for stdio connection');\n}\n\nmain().catch(console.error);"],"mappings":"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","names":[]}
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 = 5111) {\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 port = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\n const httpServer = new HttpServer(utteranceQueue, port);\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:${port}`);\n console.log('- MCP server: Ready for stdio connection');\n}\n\nmain().catch(console.error);"],"mappings":"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","names":[]}
@@ -398,32 +398,10 @@ app.post("/api/speak-system", async (req, res) => {
398
398
  app.get("/", (_req, res) => {
399
399
  res.sendFile(path.join(__dirname, "..", "public", "index.html"));
400
400
  });
401
- function openBrowser(url) {
402
- const platform = process.platform;
403
- let command;
404
- if (platform === "darwin") {
405
- command = `open ${url}`;
406
- } else if (platform === "win32") {
407
- command = `start ${url}`;
408
- } else {
409
- command = `xdg-open ${url}`;
410
- }
411
- exec(command, (error) => {
412
- if (error) {
413
- console.error(`[Browser] Failed to open browser: ${error.message}`);
414
- } else {
415
- console.log(`[Browser] Opened ${url} in default browser`);
416
- }
417
- });
418
- }
419
401
  var HTTP_PORT = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;
420
402
  app.listen(HTTP_PORT, () => {
421
- const serverUrl = `http://localhost:${HTTP_PORT}`;
422
- console.log(`[HTTP] Server listening on ${serverUrl}`);
403
+ console.log(`[HTTP] Server listening on http://localhost:${HTTP_PORT}`);
423
404
  console.log(`[Mode] Running in ${IS_MCP_MANAGED ? "MCP-managed" : "standalone"} mode`);
424
- if (process.env.MCP_VOICE_HOOKS_NO_BROWSER !== "true") {
425
- openBrowser(serverUrl);
426
- }
427
405
  });
428
406
  function getVoiceResponseReminder() {
429
407
  const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;
@@ -1 +1 @@
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 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' | 'responded';\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();\n// TODO: Uncomment these when Claude Code 1.0.45 is released and we reinstate speak-before-stop requirement\n// let lastToolUseTimestamp: Date | null = null;\n// let lastSpeakTimestamp: Date | null = null;\n\n// Voice preferences (controlled by browser)\nlet voicePreferences = {\n voiceResponsesEnabled: false,\n voiceInputActive: false\n};\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 // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot dequeue utterances when voice input is disabled.'\n });\n return;\n }\n\n const pendingUtterances = queue.utterances\n .filter(u => u.status === 'pending')\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());\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 // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot wait for utterances when voice input is disabled.'\n });\n return;\n }\n\n const secondsToWait = WAIT_TIMEOUT_SECONDS;\n const maxWaitMs = secondsToWait * 1000;\n const startTime = Date.now();\n\n debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);\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 );\n\n if (pendingUtterances.length > 0) {\n // Found utterances\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\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: maxWaitMs,\n });\n});\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// Unified action validation endpoint\napp.post('/api/validate-action', (req: Request, res: Response) => {\n const { action } = req.body;\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n\n if (!action || !['tool-use', 'stop'].includes(action)) {\n res.status(400).json({ error: 'Invalid action. Must be \"tool-use\" or \"stop\"' });\n return;\n }\n\n // Only check for pending utterances if voice input is active\n if (voicePreferences.voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'dequeue_utterances',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n });\n return;\n }\n }\n\n // Check for delivered but unresponded utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'speak',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n });\n return;\n }\n }\n\n // For stop action, check if we should wait (only if voice input is active)\n if (action === 'stop' && voicePreferences.voiceInputActive) {\n if (queue.utterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'wait_for_utterance',\n reason: 'Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n });\n return;\n }\n }\n\n // All checks passed - action is allowed\n res.json({\n allowed: true\n });\n});\n\n// Unified hook handler\nfunction handleHookRequest(attemptedAction: 'tool' | 'speak' | 'wait' | 'stop'): { decision: 'approve' | 'block', reason?: string } {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n const voiceInputActive = voicePreferences.voiceInputActive;\n\n // 1. Check for pending utterances (only if voice input is active)\n if (voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n // Allow dequeue to proceed (dequeue doesn't go through hooks)\n return {\n decision: 'block',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n };\n }\n }\n\n // 2. Check for delivered utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n // Only allow speak to proceed\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n return {\n decision: 'block',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n };\n }\n }\n\n // 3. Handle tool action\n if (attemptedAction === 'tool') {\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastToolUseTimestamp = new Date();\n return { decision: 'approve' };\n }\n\n // 4. Handle wait for utterance\n if (attemptedAction === 'wait') {\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before waiting for utterances.'\n };\n }\n */\n return { decision: 'approve' };\n }\n\n // 5. Handle speak\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n\n // 6. Handle stop\n if (attemptedAction === 'stop') {\n // Check if must speak after tool use\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before proceeding.'\n };\n }\n */\n\n // Check if should wait for utterances (only if voice input is active)\n if (voiceInputActive) {\n return {\n decision: 'block',\n reason: 'Assistant tried to end its response, but voice input is active. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n };\n }\n\n return {\n decision: 'approve',\n reason: 'No utterances since last timeout'\n };\n }\n\n // Default to approve (shouldn't reach here)\n return { decision: 'approve' };\n}\n\n// Dedicated hook endpoints that return in Claude's expected format\napp.post('/api/hooks/pre-tool', (_req: Request, res: Response) => {\n const result = handleHookRequest('tool');\n res.json(result);\n});\n\napp.post('/api/hooks/stop', (_req: Request, res: Response) => {\n const result = handleHookRequest('stop');\n res.json(result);\n});\n\n// Pre-speak hook endpoint\napp.post('/api/hooks/pre-speak', (_req: Request, res: Response) => {\n const result = handleHookRequest('speak');\n res.json(result);\n});\n\n// Pre-wait hook endpoint\napp.post('/api/hooks/pre-wait', (_req: Request, res: Response) => {\n const result = handleHookRequest('wait');\n res.json(result);\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\n// Server-Sent Events for TTS notifications\nconst ttsClients = new Set<Response>();\n\napp.get('/api/tts-events', (_req: Request, res: Response) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial connection message\n res.write('data: {\"type\":\"connected\"}\\n\\n');\n\n // Add client to set\n ttsClients.add(res);\n\n // Remove client on disconnect\n res.on('close', () => {\n ttsClients.delete(res);\n });\n});\n\n// Helper function to notify all connected TTS clients\nfunction notifyTTSClients(text: string) {\n const message = JSON.stringify({ type: 'speak', text });\n ttsClients.forEach(client => {\n client.write(`data: ${message}\\n\\n`);\n });\n}\n\n// API for voice preferences\napp.post('/api/voice-preferences', (req: Request, res: Response) => {\n const { voiceResponsesEnabled } = req.body;\n\n // Update preferences\n voicePreferences.voiceResponsesEnabled = !!voiceResponsesEnabled;\n\n debugLog(`[Preferences] Updated: voiceResponses=${voicePreferences.voiceResponsesEnabled}`);\n\n res.json({\n success: true,\n preferences: voicePreferences\n });\n});\n\n// API for voice input state\napp.post('/api/voice-input-state', (req: Request, res: Response) => {\n const { active } = req.body;\n\n // Update voice input state\n voicePreferences.voiceInputActive = !!active;\n\n debugLog(`[Voice Input] ${voicePreferences.voiceInputActive ? 'Started' : 'Stopped'} listening`);\n\n res.json({\n success: true,\n voiceInputActive: voicePreferences.voiceInputActive\n });\n});\n\n// API for text-to-speech\napp.post('/api/speak', async (req: Request, res: Response) => {\n const { text } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n // Check if voice responses are enabled\n if (!voicePreferences.voiceResponsesEnabled) {\n debugLog(`[Speak] Voice responses disabled, returning error`);\n res.status(400).json({\n error: 'Voice responses are disabled',\n message: 'Cannot speak when voice responses are disabled'\n });\n return;\n }\n\n try {\n // Always notify browser clients - they decide how to speak\n notifyTTSClients(text);\n debugLog(`[Speak] Sent text to browser for TTS: \"${text}\"`);\n\n // Note: The browser will decide whether to use system voice or browser voice\n\n // Mark all delivered utterances as responded\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n deliveredUtterances.forEach(u => {\n u.status = 'responded';\n debugLog(`[Queue] marked as responded: \"${u.text}\"\t[id: ${u.id}]`);\n });\n\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastSpeakTimestamp = new Date();\n\n res.json({\n success: true,\n message: 'Text spoken successfully',\n respondedCount: deliveredUtterances.length\n });\n } catch (error) {\n debugLog(`[Speak] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\n// API for system text-to-speech (always uses Mac say command)\napp.post('/api/speak-system', async (req: Request, res: Response) => {\n const { text, rate = 150 } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n try {\n // Execute text-to-speech using macOS say command\n // Note: Mac say command doesn't support volume control\n await execAsync(`say -r ${rate} \"${text.replace(/\"/g, '\\\\\"')}\"`);\n debugLog(`[Speak System] Spoke text using macOS say: \"${text}\" (rate: ${rate})`);\n\n res.json({\n success: true,\n message: 'Text spoken successfully via system voice'\n });\n } catch (error) {\n debugLog(`[Speak System] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text via system voice',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\napp.get('/', (_req: Request, res: Response) => {\n res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));\n});\n\n// Function to open browser cross-platform\nfunction openBrowser(url: string) {\n const platform = process.platform;\n let command: string;\n \n if (platform === 'darwin') {\n command = `open ${url}`;\n } else if (platform === 'win32') {\n command = `start ${url}`;\n } else {\n command = `xdg-open ${url}`;\n }\n \n exec(command, (error) => {\n if (error) {\n console.error(`[Browser] Failed to open browser: ${error.message}`);\n } else {\n console.log(`[Browser] Opened ${url} in default browser`);\n }\n });\n}\n\n// Start HTTP server\nconst HTTP_PORT = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\napp.listen(HTTP_PORT, () => {\n const serverUrl = `http://localhost:${HTTP_PORT}`;\n console.log(`[HTTP] Server listening on ${serverUrl}`);\n console.log(`[Mode] Running in ${IS_MCP_MANAGED ? 'MCP-managed' : 'standalone'} mode`);\n \n // Open browser automatically unless disabled\n if (process.env.MCP_VOICE_HOOKS_NO_BROWSER !== 'true') {\n openBrowser(serverUrl);\n }\n});\n\n// Helper function to get voice response reminder\nfunction getVoiceResponseReminder(): string {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n return voiceResponsesEnabled\n ? '\\n\\nThe user has enabled voice responses, so use the \\'speak\\' tool to respond to the user\\'s voice input before proceeding.'\n : '';\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 },\n },\n {\n name: 'wait_for_utterance',\n description: 'Wait for an utterance to be available or until timeout',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'speak',\n description: 'Speak text using text-to-speech and mark delivered utterances as responded',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The text to speak',\n },\n },\n required: ['text'],\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 response = await fetch(`http://localhost:${HTTP_PORT}/api/dequeue-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to dequeue utterances'}`,\n },\n ],\n };\n }\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 }${getVoiceResponseReminder()}`,\n },\n ],\n };\n }\n\n if (name === 'wait_for_utterance') {\n debugLog(`[MCP] Calling wait_for_utterance`);\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/wait-for-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to wait for utterances'}`,\n },\n ],\n };\n }\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}${getVoiceResponseReminder()}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: data.message || `No utterances found. Timed out.`,\n },\n ],\n };\n }\n }\n\n if (name === 'speak') {\n const text = args?.text as string;\n\n if (!text || !text.trim()) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: Text is required for speak tool',\n },\n ],\n isError: true,\n };\n }\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/speak`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n\n const data = await response.json() as any;\n\n if (response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: '', // Return empty string for success\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: `Error speaking text: ${data.error || 'Unknown error'}`,\n },\n ],\n isError: true,\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":[]}
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 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' | 'responded';\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();\n// TODO: Uncomment these when Claude Code 1.0.45 is released and we reinstate speak-before-stop requirement\n// let lastToolUseTimestamp: Date | null = null;\n// let lastSpeakTimestamp: Date | null = null;\n\n// Voice preferences (controlled by browser)\nlet voicePreferences = {\n voiceResponsesEnabled: false,\n voiceInputActive: false\n};\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 // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot dequeue utterances when voice input is disabled.'\n });\n return;\n }\n\n const pendingUtterances = queue.utterances\n .filter(u => u.status === 'pending')\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());\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 // Check if voice input is active\n if (!voicePreferences.voiceInputActive) {\n res.status(400).json({\n success: false,\n error: 'Voice input is not active. Cannot wait for utterances when voice input is disabled.'\n });\n return;\n }\n\n const secondsToWait = WAIT_TIMEOUT_SECONDS;\n const maxWaitMs = secondsToWait * 1000;\n const startTime = Date.now();\n\n debugLog(`[Server] Starting wait_for_utterance (${secondsToWait}s)`);\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 );\n\n if (pendingUtterances.length > 0) {\n // Found utterances\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\n res.json({\n success: true,\n utterances: [],\n message: `No utterances found after waiting ${secondsToWait} seconds.`,\n waitTime: maxWaitMs,\n });\n});\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// Unified action validation endpoint\napp.post('/api/validate-action', (req: Request, res: Response) => {\n const { action } = req.body;\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n\n if (!action || !['tool-use', 'stop'].includes(action)) {\n res.status(400).json({ error: 'Invalid action. Must be \"tool-use\" or \"stop\"' });\n return;\n }\n\n // Only check for pending utterances if voice input is active\n if (voicePreferences.voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'dequeue_utterances',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n });\n return;\n }\n }\n\n // Check for delivered but unresponded utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'speak',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n });\n return;\n }\n }\n\n // For stop action, check if we should wait (only if voice input is active)\n if (action === 'stop' && voicePreferences.voiceInputActive) {\n if (queue.utterances.length > 0) {\n res.json({\n allowed: false,\n requiredAction: 'wait_for_utterance',\n reason: 'Assistant tried to end its response. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n });\n return;\n }\n }\n\n // All checks passed - action is allowed\n res.json({\n allowed: true\n });\n});\n\n// Unified hook handler\nfunction handleHookRequest(attemptedAction: 'tool' | 'speak' | 'wait' | 'stop'): { decision: 'approve' | 'block', reason?: string } {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n const voiceInputActive = voicePreferences.voiceInputActive;\n\n // 1. Check for pending utterances (only if voice input is active)\n if (voiceInputActive) {\n const pendingUtterances = queue.utterances.filter(u => u.status === 'pending');\n if (pendingUtterances.length > 0) {\n // Allow dequeue to proceed (dequeue doesn't go through hooks)\n return {\n decision: 'block',\n reason: `${pendingUtterances.length} pending utterance(s) must be dequeued first. Please use dequeue_utterances to process them.`\n };\n }\n }\n\n // 2. Check for delivered utterances (when voice enabled)\n if (voiceResponsesEnabled) {\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n if (deliveredUtterances.length > 0) {\n // Only allow speak to proceed\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n return {\n decision: 'block',\n reason: `${deliveredUtterances.length} delivered utterance(s) require voice response. Please use the speak tool to respond before proceeding.`\n };\n }\n }\n\n // 3. Handle tool action\n if (attemptedAction === 'tool') {\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastToolUseTimestamp = new Date();\n return { decision: 'approve' };\n }\n\n // 4. Handle wait for utterance\n if (attemptedAction === 'wait') {\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before waiting for utterances.'\n };\n }\n */\n return { decision: 'approve' };\n }\n\n // 5. Handle speak\n if (attemptedAction === 'speak') {\n return { decision: 'approve' };\n }\n\n // 6. Handle stop\n if (attemptedAction === 'stop') {\n // Check if must speak after tool use\n // TEMPORARILY COMMENTED OUT - TODO: Remove comment to re-enable speak requirement when 1.0.45 is released\n /*\n if (voiceResponsesEnabled && lastToolUseTimestamp &&\n (!lastSpeakTimestamp || lastSpeakTimestamp < lastToolUseTimestamp)) {\n return {\n decision: 'block',\n reason: 'Assistant must speak after using tools. Please use the speak tool to respond before proceeding.'\n };\n }\n */\n\n // Check if should wait for utterances (only if voice input is active)\n if (voiceInputActive) {\n return {\n decision: 'block',\n reason: 'Assistant tried to end its response, but voice input is active. Stopping is not allowed without first checking for voice input. Assistant should now use wait_for_utterance to check for voice input'\n };\n }\n\n return {\n decision: 'approve',\n reason: 'No utterances since last timeout'\n };\n }\n\n // Default to approve (shouldn't reach here)\n return { decision: 'approve' };\n}\n\n// Dedicated hook endpoints that return in Claude's expected format\napp.post('/api/hooks/pre-tool', (_req: Request, res: Response) => {\n const result = handleHookRequest('tool');\n res.json(result);\n});\n\napp.post('/api/hooks/stop', (_req: Request, res: Response) => {\n const result = handleHookRequest('stop');\n res.json(result);\n});\n\n// Pre-speak hook endpoint\napp.post('/api/hooks/pre-speak', (_req: Request, res: Response) => {\n const result = handleHookRequest('speak');\n res.json(result);\n});\n\n// Pre-wait hook endpoint\napp.post('/api/hooks/pre-wait', (_req: Request, res: Response) => {\n const result = handleHookRequest('wait');\n res.json(result);\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\n// Server-Sent Events for TTS notifications\nconst ttsClients = new Set<Response>();\n\napp.get('/api/tts-events', (_req: Request, res: Response) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial connection message\n res.write('data: {\"type\":\"connected\"}\\n\\n');\n\n // Add client to set\n ttsClients.add(res);\n\n // Remove client on disconnect\n res.on('close', () => {\n ttsClients.delete(res);\n });\n});\n\n// Helper function to notify all connected TTS clients\nfunction notifyTTSClients(text: string) {\n const message = JSON.stringify({ type: 'speak', text });\n ttsClients.forEach(client => {\n client.write(`data: ${message}\\n\\n`);\n });\n}\n\n// API for voice preferences\napp.post('/api/voice-preferences', (req: Request, res: Response) => {\n const { voiceResponsesEnabled } = req.body;\n\n // Update preferences\n voicePreferences.voiceResponsesEnabled = !!voiceResponsesEnabled;\n\n debugLog(`[Preferences] Updated: voiceResponses=${voicePreferences.voiceResponsesEnabled}`);\n\n res.json({\n success: true,\n preferences: voicePreferences\n });\n});\n\n// API for voice input state\napp.post('/api/voice-input-state', (req: Request, res: Response) => {\n const { active } = req.body;\n\n // Update voice input state\n voicePreferences.voiceInputActive = !!active;\n\n debugLog(`[Voice Input] ${voicePreferences.voiceInputActive ? 'Started' : 'Stopped'} listening`);\n\n res.json({\n success: true,\n voiceInputActive: voicePreferences.voiceInputActive\n });\n});\n\n// API for text-to-speech\napp.post('/api/speak', async (req: Request, res: Response) => {\n const { text } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n // Check if voice responses are enabled\n if (!voicePreferences.voiceResponsesEnabled) {\n debugLog(`[Speak] Voice responses disabled, returning error`);\n res.status(400).json({\n error: 'Voice responses are disabled',\n message: 'Cannot speak when voice responses are disabled'\n });\n return;\n }\n\n try {\n // Always notify browser clients - they decide how to speak\n notifyTTSClients(text);\n debugLog(`[Speak] Sent text to browser for TTS: \"${text}\"`);\n\n // Note: The browser will decide whether to use system voice or browser voice\n\n // Mark all delivered utterances as responded\n const deliveredUtterances = queue.utterances.filter(u => u.status === 'delivered');\n deliveredUtterances.forEach(u => {\n u.status = 'responded';\n debugLog(`[Queue] marked as responded: \"${u.text}\"\t[id: ${u.id}]`);\n });\n\n // TODO: Uncomment when Claude Code 1.0.45 is released\n // lastSpeakTimestamp = new Date();\n\n res.json({\n success: true,\n message: 'Text spoken successfully',\n respondedCount: deliveredUtterances.length\n });\n } catch (error) {\n debugLog(`[Speak] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text',\n details: error instanceof Error ? error.message : String(error)\n });\n }\n});\n\n// API for system text-to-speech (always uses Mac say command)\napp.post('/api/speak-system', async (req: Request, res: Response) => {\n const { text, rate = 150 } = req.body;\n\n if (!text || !text.trim()) {\n res.status(400).json({ error: 'Text is required' });\n return;\n }\n\n try {\n // Execute text-to-speech using macOS say command\n // Note: Mac say command doesn't support volume control\n await execAsync(`say -r ${rate} \"${text.replace(/\"/g, '\\\\\"')}\"`);\n debugLog(`[Speak System] Spoke text using macOS say: \"${text}\" (rate: ${rate})`);\n\n res.json({\n success: true,\n message: 'Text spoken successfully via system voice'\n });\n } catch (error) {\n debugLog(`[Speak System] Failed to speak text: ${error}`);\n res.status(500).json({\n error: 'Failed to speak text via system voice',\n details: error instanceof Error ? error.message : String(error)\n });\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 = process.env.MCP_VOICE_HOOKS_PORT ? parseInt(process.env.MCP_VOICE_HOOKS_PORT) : 5111;\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// Helper function to get voice response reminder\nfunction getVoiceResponseReminder(): string {\n const voiceResponsesEnabled = voicePreferences.voiceResponsesEnabled;\n return voiceResponsesEnabled\n ? '\\n\\nThe user has enabled voice responses, so use the \\'speak\\' tool to respond to the user\\'s voice input before proceeding.'\n : '';\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 },\n },\n {\n name: 'wait_for_utterance',\n description: 'Wait for an utterance to be available or until timeout',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'speak',\n description: 'Speak text using text-to-speech and mark delivered utterances as responded',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The text to speak',\n },\n },\n required: ['text'],\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 response = await fetch(`http://localhost:${HTTP_PORT}/api/dequeue-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to dequeue utterances'}`,\n },\n ],\n };\n }\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 }${getVoiceResponseReminder()}`,\n },\n ],\n };\n }\n\n if (name === 'wait_for_utterance') {\n debugLog(`[MCP] Calling wait_for_utterance`);\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/wait-for-utterances`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n const data = await response.json() as any;\n\n // Check if the request was successful\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${data.error || 'Failed to wait for utterances'}`,\n },\n ],\n };\n }\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}${getVoiceResponseReminder()}`,\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: data.message || `No utterances found. Timed out.`,\n },\n ],\n };\n }\n }\n\n if (name === 'speak') {\n const text = args?.text as string;\n\n if (!text || !text.trim()) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: Text is required for speak tool',\n },\n ],\n isError: true,\n };\n }\n\n const response = await fetch(`http://localhost:${HTTP_PORT}/api/speak`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n\n const data = await response.json() as any;\n\n if (response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: '', // Return empty string for success\n },\n ],\n };\n } else {\n return {\n content: [\n {\n type: 'text',\n text: `Error speaking text: ${data.error || 'Unknown error'}`,\n },\n ],\n isError: true,\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-voice-hooks",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "bin": {