mcp-voice-hooks 1.0.8 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/pre-speak-hook.sh +1 -1
- package/.claude/hooks/pre-tool-hook.sh +1 -1
- package/.claude/hooks/pre-wait-hook.sh +1 -1
- package/.claude/hooks/stop-hook.sh +1 -1
- package/CLAUDE.local.md +3 -10
- package/README.md +48 -71
- package/dist/unified-server.js +147 -95
- package/dist/unified-server.js.map +1 -1
- package/package.json +1 -1
- package/public/app.js +451 -45
- package/public/index.html +255 -61
package/public/index.html
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html lang="en">
|
3
|
+
|
3
4
|
<head>
|
4
5
|
<meta charset="UTF-8">
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
-
<title>Voice
|
7
|
+
<title>Voice Mode for Claude Code</title>
|
7
8
|
<style>
|
8
9
|
body {
|
9
10
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
@@ -12,34 +13,34 @@
|
|
12
13
|
padding: 20px;
|
13
14
|
background-color: #f5f5f5;
|
14
15
|
}
|
15
|
-
|
16
|
+
|
16
17
|
.container {
|
17
18
|
background: white;
|
18
19
|
border-radius: 12px;
|
19
20
|
padding: 24px;
|
20
|
-
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
21
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
21
22
|
}
|
22
|
-
|
23
|
+
|
23
24
|
h1 {
|
24
25
|
color: #333;
|
25
26
|
margin-bottom: 8px;
|
26
27
|
}
|
27
|
-
|
28
|
+
|
28
29
|
.subtitle {
|
29
30
|
color: #666;
|
30
31
|
margin-bottom: 24px;
|
31
32
|
}
|
32
|
-
|
33
|
+
|
33
34
|
.input-section {
|
34
35
|
margin-bottom: 24px;
|
35
36
|
}
|
36
|
-
|
37
|
+
|
37
38
|
.input-group {
|
38
39
|
display: flex;
|
39
40
|
gap: 12px;
|
40
41
|
margin-bottom: 16px;
|
41
42
|
}
|
42
|
-
|
43
|
+
|
43
44
|
#utteranceInput {
|
44
45
|
flex: 1;
|
45
46
|
padding: 12px;
|
@@ -47,12 +48,12 @@
|
|
47
48
|
border-radius: 8px;
|
48
49
|
font-size: 16px;
|
49
50
|
}
|
50
|
-
|
51
|
+
|
51
52
|
#utteranceInput:focus {
|
52
53
|
outline: none;
|
53
54
|
border-color: #007AFF;
|
54
55
|
}
|
55
|
-
|
56
|
+
|
56
57
|
#sendBtn {
|
57
58
|
background: #007AFF;
|
58
59
|
color: white;
|
@@ -62,22 +63,22 @@
|
|
62
63
|
font-size: 16px;
|
63
64
|
cursor: pointer;
|
64
65
|
}
|
65
|
-
|
66
|
+
|
66
67
|
#sendBtn:hover {
|
67
68
|
background: #0056CC;
|
68
69
|
}
|
69
|
-
|
70
|
+
|
70
71
|
#sendBtn:disabled {
|
71
72
|
background: #ccc;
|
72
73
|
cursor: not-allowed;
|
73
74
|
}
|
74
|
-
|
75
|
+
|
75
76
|
.status {
|
76
77
|
display: flex;
|
77
78
|
gap: 24px;
|
78
79
|
margin-bottom: 24px;
|
79
80
|
}
|
80
|
-
|
81
|
+
|
81
82
|
.status-item {
|
82
83
|
padding: 12px;
|
83
84
|
background: #f8f9fa;
|
@@ -85,30 +86,30 @@
|
|
85
86
|
text-align: center;
|
86
87
|
flex: 1;
|
87
88
|
}
|
88
|
-
|
89
|
+
|
89
90
|
.status-number {
|
90
91
|
font-size: 24px;
|
91
92
|
font-weight: bold;
|
92
93
|
color: #007AFF;
|
93
94
|
}
|
94
|
-
|
95
|
+
|
95
96
|
.status-label {
|
96
97
|
font-size: 14px;
|
97
98
|
color: #666;
|
98
99
|
}
|
99
|
-
|
100
|
+
|
100
101
|
.utterances-section h3 {
|
101
102
|
color: #333;
|
102
103
|
margin-bottom: 16px;
|
103
104
|
}
|
104
|
-
|
105
|
+
|
105
106
|
.utterances-list {
|
106
107
|
max-height: 400px;
|
107
108
|
overflow-y: auto;
|
108
109
|
border: 1px solid #ddd;
|
109
110
|
border-radius: 8px;
|
110
111
|
}
|
111
|
-
|
112
|
+
|
112
113
|
.utterance-item {
|
113
114
|
padding: 12px 16px;
|
114
115
|
border-bottom: 1px solid #eee;
|
@@ -116,22 +117,22 @@
|
|
116
117
|
justify-content: space-between;
|
117
118
|
align-items: center;
|
118
119
|
}
|
119
|
-
|
120
|
+
|
120
121
|
.utterance-item:last-child {
|
121
122
|
border-bottom: none;
|
122
123
|
}
|
123
|
-
|
124
|
+
|
124
125
|
.utterance-text {
|
125
126
|
flex: 1;
|
126
127
|
margin-right: 12px;
|
127
128
|
}
|
128
|
-
|
129
|
+
|
129
130
|
.utterance-meta {
|
130
131
|
font-size: 12px;
|
131
132
|
color: #666;
|
132
133
|
text-align: right;
|
133
134
|
}
|
134
|
-
|
135
|
+
|
135
136
|
.utterance-status {
|
136
137
|
display: inline-block;
|
137
138
|
padding: 2px 8px;
|
@@ -140,17 +141,17 @@
|
|
140
141
|
font-weight: bold;
|
141
142
|
margin-top: 4px;
|
142
143
|
}
|
143
|
-
|
144
|
+
|
144
145
|
.status-pending {
|
145
146
|
background: #FFF3CD;
|
146
147
|
color: #856404;
|
147
148
|
}
|
148
|
-
|
149
|
+
|
149
150
|
.status-delivered {
|
150
151
|
background: #D1ECF1;
|
151
152
|
color: #0C5460;
|
152
153
|
}
|
153
|
-
|
154
|
+
|
154
155
|
.refresh-btn {
|
155
156
|
background: #6C757D;
|
156
157
|
color: white;
|
@@ -161,25 +162,25 @@
|
|
161
162
|
cursor: pointer;
|
162
163
|
margin-left: 12px;
|
163
164
|
}
|
164
|
-
|
165
|
+
|
165
166
|
.refresh-btn:hover {
|
166
167
|
background: #545B62;
|
167
168
|
}
|
168
|
-
|
169
|
+
|
169
170
|
.empty-state {
|
170
171
|
text-align: center;
|
171
172
|
color: #666;
|
172
173
|
padding: 40px 20px;
|
173
174
|
font-style: italic;
|
174
175
|
}
|
175
|
-
|
176
|
+
|
176
177
|
.voice-controls {
|
177
178
|
display: flex;
|
178
179
|
gap: 12px;
|
179
180
|
align-items: center;
|
180
181
|
margin-bottom: 16px;
|
181
182
|
}
|
182
|
-
|
183
|
+
|
183
184
|
#listenBtn {
|
184
185
|
background: #28A745;
|
185
186
|
color: white;
|
@@ -192,24 +193,24 @@
|
|
192
193
|
align-items: center;
|
193
194
|
gap: 8px;
|
194
195
|
}
|
195
|
-
|
196
|
+
|
196
197
|
#listenBtn:hover {
|
197
198
|
background: #218838;
|
198
199
|
}
|
199
|
-
|
200
|
+
|
200
201
|
#listenBtn.listening {
|
201
202
|
background: #DC3545;
|
202
203
|
}
|
203
|
-
|
204
|
+
|
204
205
|
#listenBtn.listening:hover {
|
205
206
|
background: #C82333;
|
206
207
|
}
|
207
|
-
|
208
|
+
|
208
209
|
#listenBtn:disabled {
|
209
210
|
background: #ccc;
|
210
211
|
cursor: not-allowed;
|
211
212
|
}
|
212
|
-
|
213
|
+
|
213
214
|
.listening-indicator {
|
214
215
|
display: none;
|
215
216
|
align-items: center;
|
@@ -217,11 +218,11 @@
|
|
217
218
|
color: #DC3545;
|
218
219
|
font-weight: 500;
|
219
220
|
}
|
220
|
-
|
221
|
+
|
221
222
|
.listening-indicator.active {
|
222
223
|
display: flex;
|
223
224
|
}
|
224
|
-
|
225
|
+
|
225
226
|
.listening-dot {
|
226
227
|
width: 8px;
|
227
228
|
height: 8px;
|
@@ -229,13 +230,21 @@
|
|
229
230
|
border-radius: 50%;
|
230
231
|
animation: pulse 1.5s infinite;
|
231
232
|
}
|
232
|
-
|
233
|
+
|
233
234
|
@keyframes pulse {
|
234
|
-
0% {
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
235
|
+
0% {
|
236
|
+
opacity: 1;
|
237
|
+
}
|
238
|
+
|
239
|
+
50% {
|
240
|
+
opacity: 0.3;
|
241
|
+
}
|
242
|
+
|
243
|
+
100% {
|
244
|
+
opacity: 1;
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
239
248
|
.interim-text {
|
240
249
|
display: none;
|
241
250
|
padding: 12px;
|
@@ -246,28 +255,213 @@
|
|
246
255
|
font-style: italic;
|
247
256
|
color: #6C757D;
|
248
257
|
}
|
249
|
-
|
258
|
+
|
250
259
|
.interim-text.active {
|
251
260
|
display: block;
|
252
261
|
}
|
253
|
-
|
262
|
+
|
254
263
|
.mic-icon {
|
255
264
|
width: 16px;
|
256
265
|
height: 16px;
|
257
266
|
fill: currentColor;
|
258
267
|
}
|
268
|
+
|
269
|
+
.tts-settings {
|
270
|
+
background: #f8f9fa;
|
271
|
+
padding: 12px;
|
272
|
+
border-radius: 8px;
|
273
|
+
margin-bottom: 24px;
|
274
|
+
}
|
275
|
+
|
276
|
+
.tts-settings h4 {
|
277
|
+
margin-top: 0;
|
278
|
+
margin-bottom: 0;
|
279
|
+
color: #333;
|
280
|
+
}
|
281
|
+
|
282
|
+
.tts-controls {
|
283
|
+
display: flex;
|
284
|
+
gap: 16px;
|
285
|
+
align-items: center;
|
286
|
+
flex-wrap: wrap;
|
287
|
+
}
|
288
|
+
|
289
|
+
.tts-control {
|
290
|
+
display: flex;
|
291
|
+
align-items: center;
|
292
|
+
gap: 8px;
|
293
|
+
}
|
294
|
+
|
295
|
+
.tts-control label {
|
296
|
+
font-size: 14px;
|
297
|
+
color: #666;
|
298
|
+
}
|
299
|
+
|
300
|
+
.tts-control select,
|
301
|
+
.tts-control input {
|
302
|
+
padding: 6px;
|
303
|
+
border: 1px solid #ddd;
|
304
|
+
border-radius: 4px;
|
305
|
+
font-size: 14px;
|
306
|
+
}
|
307
|
+
|
308
|
+
.tts-test-btn {
|
309
|
+
background: #6C757D;
|
310
|
+
color: white;
|
311
|
+
border: none;
|
312
|
+
padding: 6px 12px;
|
313
|
+
border-radius: 4px;
|
314
|
+
font-size: 14px;
|
315
|
+
cursor: pointer;
|
316
|
+
}
|
317
|
+
|
318
|
+
.tts-test-btn:hover {
|
319
|
+
background: #545B62;
|
320
|
+
}
|
321
|
+
|
322
|
+
.rate-warning,
|
323
|
+
.system-voice-info {
|
324
|
+
font-size: 12px;
|
325
|
+
padding: 8px 12px;
|
326
|
+
border-radius: 6px;
|
327
|
+
margin: 8px 0;
|
328
|
+
display: flex;
|
329
|
+
align-items: center;
|
330
|
+
gap: 8px;
|
331
|
+
}
|
332
|
+
|
333
|
+
.rate-warning {
|
334
|
+
background: #fff3cd;
|
335
|
+
color: #856404;
|
336
|
+
border: 1px solid #ffeaa7;
|
337
|
+
}
|
338
|
+
|
339
|
+
.system-voice-info {
|
340
|
+
background: #d1ecf1;
|
341
|
+
color: #0c5460;
|
342
|
+
border: 1px solid #bee5eb;
|
343
|
+
}
|
344
|
+
|
345
|
+
.system-voice-info a {
|
346
|
+
color: #0c5460;
|
347
|
+
font-weight: bold;
|
348
|
+
}
|
349
|
+
|
350
|
+
.warning-icon,
|
351
|
+
.info-icon {
|
352
|
+
font-size: 16px;
|
353
|
+
}
|
354
|
+
|
355
|
+
.info-message {
|
356
|
+
background: #d1ecf1;
|
357
|
+
border: 1px solid #bee5eb;
|
358
|
+
border-radius: 8px;
|
359
|
+
margin-bottom: 16px;
|
360
|
+
}
|
361
|
+
|
362
|
+
.info-message .empty-state {
|
363
|
+
color: #0c5460;
|
364
|
+
padding: 20px;
|
365
|
+
font-style: normal;
|
366
|
+
}
|
367
|
+
|
368
|
+
/* Toggle switch styles */
|
369
|
+
.switch {
|
370
|
+
position: relative;
|
371
|
+
display: inline-block;
|
372
|
+
width: 50px;
|
373
|
+
height: 26px;
|
374
|
+
}
|
375
|
+
|
376
|
+
.switch input {
|
377
|
+
opacity: 0;
|
378
|
+
width: 0;
|
379
|
+
height: 0;
|
380
|
+
}
|
381
|
+
|
382
|
+
.slider {
|
383
|
+
position: absolute;
|
384
|
+
cursor: pointer;
|
385
|
+
top: 0;
|
386
|
+
left: 0;
|
387
|
+
right: 0;
|
388
|
+
bottom: 0;
|
389
|
+
background-color: #ccc;
|
390
|
+
transition: .3s;
|
391
|
+
border-radius: 26px;
|
392
|
+
}
|
393
|
+
|
394
|
+
.slider:before {
|
395
|
+
position: absolute;
|
396
|
+
content: "";
|
397
|
+
height: 18px;
|
398
|
+
width: 18px;
|
399
|
+
left: 4px;
|
400
|
+
bottom: 4px;
|
401
|
+
background-color: white;
|
402
|
+
transition: .3s;
|
403
|
+
border-radius: 50%;
|
404
|
+
}
|
405
|
+
|
406
|
+
input:checked+.slider {
|
407
|
+
background-color: #007AFF;
|
408
|
+
}
|
409
|
+
|
410
|
+
input:checked+.slider:before {
|
411
|
+
transform: translateX(24px);
|
412
|
+
}
|
259
413
|
</style>
|
260
414
|
</head>
|
415
|
+
|
261
416
|
<body>
|
262
417
|
<div class="container">
|
263
|
-
<h1>Voice
|
264
|
-
<p class="subtitle">Speak or type
|
265
|
-
|
418
|
+
<h1>Voice Mode for Claude Code</h1>
|
419
|
+
<p class="subtitle">Speak or type to enqueue messages for Claude</p>
|
420
|
+
|
421
|
+
<div class="tts-settings">
|
422
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
423
|
+
<h4>Allow Claude to speak back to you</h4>
|
424
|
+
<label class="switch">
|
425
|
+
<input type="checkbox" id="voiceResponsesToggle">
|
426
|
+
<span class="slider"></span>
|
427
|
+
</label>
|
428
|
+
</div>
|
429
|
+
<div class="tts-controls" id="voiceOptions" style="display: none;">
|
430
|
+
<div class="tts-control" style="width: 100%;">
|
431
|
+
<label for="voiceSelect">Voice:</label>
|
432
|
+
<select id="voiceSelect">
|
433
|
+
<option value="system">Mac System Voice</option>
|
434
|
+
<optgroup label="Browser Voices (Cloud)" id="cloudVoicesGroup">
|
435
|
+
</optgroup>
|
436
|
+
<optgroup label="Browser Voices (Local)" id="localVoicesGroup">
|
437
|
+
</optgroup>
|
438
|
+
</select>
|
439
|
+
</div>
|
440
|
+
<div id="systemVoiceInfo" class="system-voice-info" style="display: none;">
|
441
|
+
<span class="info-icon">ℹ️</span>
|
442
|
+
<span class="info-text">You can download high quality voices in your Mac settings app. See the <a
|
443
|
+
href="https://github.com/johnmatthewtennant/mcp-voice-hooks?tab=readme-ov-file#voice-responses"
|
444
|
+
target="_blank">README</a></span>
|
445
|
+
</div>
|
446
|
+
<div id="rateWarning" class="rate-warning" style="display: none;">
|
447
|
+
<span class="warning-icon">⚠️</span>
|
448
|
+
<span class="warning-text">Google voices may not respond well to rate adjustments</span>
|
449
|
+
</div>
|
450
|
+
<div class="tts-control" style="width: 100%;">
|
451
|
+
<label for="speechRate">Speaking Rate:</label>
|
452
|
+
<input type="range" id="speechRate" min="0.5" max="5" step="0.1" value="1">
|
453
|
+
<input type="number" id="speechRateInput" min="0.5" max="5" step="0.1" value="1">
|
454
|
+
</div>
|
455
|
+
<button class="tts-test-btn" id="testTTSBtn">Test Voice</button>
|
456
|
+
</div>
|
457
|
+
</div>
|
458
|
+
|
266
459
|
<div class="input-section">
|
267
460
|
<div class="voice-controls">
|
268
461
|
<button id="listenBtn">
|
269
462
|
<svg class="mic-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
270
|
-
<path
|
463
|
+
<path
|
464
|
+
d="M12 1C10.34 1 9 2.34 9 4V12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12V4C15 2.34 13.66 1 12 1ZM19 12C19 15.53 16.39 18.44 13 18.93V22H11V18.93C7.61 18.44 5 15.53 5 12H7C7 14.76 9.24 17 12 17C14.76 17 17 14.76 17 12H19Z" />
|
271
465
|
</svg>
|
272
466
|
<span id="listenBtnText">Start Listening</span>
|
273
467
|
</button>
|
@@ -276,20 +470,15 @@
|
|
276
470
|
<span>Listening...</span>
|
277
471
|
</div>
|
278
472
|
</div>
|
279
|
-
|
473
|
+
|
280
474
|
<div class="interim-text" id="interimText"></div>
|
281
|
-
|
475
|
+
|
282
476
|
<div class="input-group">
|
283
|
-
<input
|
284
|
-
type="text"
|
285
|
-
id="utteranceInput"
|
286
|
-
placeholder="Type your utterance here..."
|
287
|
-
autofocus
|
288
|
-
>
|
477
|
+
<input type="text" id="utteranceInput" placeholder="Type your utterance here..." autofocus>
|
289
478
|
<button id="sendBtn">Send</button>
|
290
479
|
</div>
|
291
480
|
</div>
|
292
|
-
|
481
|
+
|
293
482
|
<div class="status">
|
294
483
|
<div class="status-item">
|
295
484
|
<div class="status-number" id="totalCount">0</div>
|
@@ -304,19 +493,24 @@
|
|
304
493
|
<div class="status-label">Delivered</div>
|
305
494
|
</div>
|
306
495
|
</div>
|
307
|
-
|
496
|
+
|
308
497
|
<div class="utterances-section">
|
309
498
|
<h3>
|
310
499
|
Recent Utterances
|
311
500
|
<button class="refresh-btn" id="refreshBtn">Refresh</button>
|
312
|
-
<button class="refresh-btn" id="clearAllBtn" style="background: #DC3545; margin-left: 8px;">Clear
|
501
|
+
<button class="refresh-btn" id="clearAllBtn" style="background: #DC3545; margin-left: 8px;">Clear
|
502
|
+
All</button>
|
313
503
|
</h3>
|
504
|
+
<div class="info-message" id="infoMessage" style="display: none;">
|
505
|
+
<div class="empty-state">You need to send one message in the Claude code CLI to start voice interaction</div>
|
506
|
+
</div>
|
314
507
|
<div class="utterances-list" id="utterancesList">
|
315
508
|
<div class="empty-state">No utterances yet. Type something above to get started!</div>
|
316
509
|
</div>
|
317
510
|
</div>
|
318
511
|
</div>
|
319
|
-
|
512
|
+
|
320
513
|
<script src="app.js"></script>
|
321
514
|
</body>
|
515
|
+
|
322
516
|
</html>
|