voice-mode 2.27.0__py3-none-any.whl → 2.28.1__py3-none-any.whl

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.
Files changed (79) hide show
  1. voice_mode/__version__.py +1 -1
  2. voice_mode/cli.py +152 -37
  3. voice_mode/cli_commands/exchanges.py +6 -0
  4. voice_mode/frontend/.next/BUILD_ID +1 -1
  5. voice_mode/frontend/.next/app-build-manifest.json +5 -5
  6. voice_mode/frontend/.next/build-manifest.json +3 -3
  7. voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -1
  8. voice_mode/frontend/.next/next-server.js.nft.json +1 -1
  9. voice_mode/frontend/.next/prerender-manifest.json +1 -1
  10. voice_mode/frontend/.next/required-server-files.json +1 -1
  11. voice_mode/frontend/.next/server/app/_not-found/page.js +1 -1
  12. voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. voice_mode/frontend/.next/server/app/_not-found.html +1 -1
  14. voice_mode/frontend/.next/server/app/_not-found.rsc +1 -1
  15. voice_mode/frontend/.next/server/app/api/connection-details/route.js +2 -2
  16. voice_mode/frontend/.next/server/app/favicon.ico/route.js +2 -2
  17. voice_mode/frontend/.next/server/app/index.html +1 -1
  18. voice_mode/frontend/.next/server/app/index.rsc +2 -2
  19. voice_mode/frontend/.next/server/app/page.js +2 -2
  20. voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -1
  21. voice_mode/frontend/.next/server/chunks/994.js +1 -1
  22. voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -1
  23. voice_mode/frontend/.next/server/next-font-manifest.js +1 -1
  24. voice_mode/frontend/.next/server/next-font-manifest.json +1 -1
  25. voice_mode/frontend/.next/server/pages/404.html +1 -1
  26. voice_mode/frontend/.next/server/pages/500.html +1 -1
  27. voice_mode/frontend/.next/server/server-reference-manifest.json +1 -1
  28. voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -1
  29. voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +5 -5
  30. voice_mode/frontend/.next/standalone/.next/build-manifest.json +3 -3
  31. voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -1
  32. voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -1
  33. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  34. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  35. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -1
  36. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  37. voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +2 -2
  38. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +2 -2
  39. voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -1
  40. voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +2 -2
  41. voice_mode/frontend/.next/standalone/.next/server/app/page.js +2 -2
  42. voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  43. voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +1 -1
  44. voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  45. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -1
  46. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -1
  47. voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -1
  48. voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -1
  49. voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  50. voice_mode/frontend/.next/standalone/server.js +1 -1
  51. voice_mode/frontend/.next/static/chunks/app/{layout-08be62ed6e344292.js → layout-2a1721553cbe58e4.js} +1 -1
  52. voice_mode/frontend/.next/static/chunks/app/page-fe35d9da20297c85.js +1 -0
  53. voice_mode/frontend/.next/static/chunks/{main-app-413f77c1f2c53e3f.js → main-app-c17195caa4e269d6.js} +1 -1
  54. voice_mode/frontend/.next/trace +43 -43
  55. voice_mode/frontend/.next/types/app/api/connection-details/route.ts +1 -1
  56. voice_mode/frontend/.next/types/app/layout.ts +1 -1
  57. voice_mode/frontend/.next/types/app/page.ts +1 -1
  58. voice_mode/frontend/package-lock.json +6 -6
  59. voice_mode/tools/converse.py +44 -24
  60. voice_mode/tools/service.py +30 -3
  61. voice_mode/tools/services/kokoro/install.py +1 -1
  62. voice_mode/tools/services/whisper/__init__.py +15 -5
  63. voice_mode/tools/services/whisper/install.py +41 -9
  64. voice_mode/tools/services/whisper/list_models.py +14 -14
  65. voice_mode/tools/services/whisper/model_active.py +54 -0
  66. voice_mode/tools/services/whisper/model_benchmark.py +159 -0
  67. voice_mode/tools/services/whisper/{download_model.py → model_install.py} +72 -11
  68. voice_mode/tools/services/whisper/model_remove.py +36 -0
  69. voice_mode/tools/services/whisper/models.py +225 -26
  70. voice_mode/utils/services/whisper_helpers.py +206 -19
  71. voice_mode/utils/services/whisper_version.py +138 -0
  72. {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/METADATA +5 -1
  73. {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/RECORD +77 -74
  74. voice_mode/frontend/.next/static/chunks/app/page-80fc72669f25298f.js +0 -1
  75. voice_mode/tools/services/whisper/list_models_tool.py +0 -65
  76. /voice_mode/frontend/.next/static/{wQ5pxzPmwjlzdUfJwSjMg → LhJalgfazyY_l3L_v0_Kw}/_buildManifest.js +0 -0
  77. /voice_mode/frontend/.next/static/{wQ5pxzPmwjlzdUfJwSjMg → LhJalgfazyY_l3L_v0_Kw}/_ssgManifest.js +0 -0
  78. {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/WHEEL +0 -0
  79. {voice_mode-2.27.0.dist-info → voice_mode-2.28.1.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,11 @@
1
1
  """Helper functions for whisper service management."""
2
2
 
3
3
  import os
4
+ import re
4
5
  import subprocess
5
6
  import platform
6
7
  import logging
8
+ import shutil
7
9
  from pathlib import Path
8
10
  from typing import Optional, List, Dict, Union
9
11
 
@@ -108,7 +110,6 @@ async def download_whisper_model(
108
110
  break
109
111
 
110
112
  if original_script:
111
- import shutil
112
113
  shutil.copy2(original_script, download_script)
113
114
  os.chmod(download_script, 0o755)
114
115
  else:
@@ -116,7 +117,6 @@ async def download_whisper_model(
116
117
  # (happens during install when models_dir is install_dir/models)
117
118
  parent_script = models_dir.parent / "models" / "download-ggml-model.sh"
118
119
  if parent_script.exists() and parent_script != download_script:
119
- import shutil
120
120
  shutil.copy2(parent_script, download_script)
121
121
  os.chmod(download_script, 0o755)
122
122
  else:
@@ -146,16 +146,52 @@ async def download_whisper_model(
146
146
 
147
147
  # Check for Core ML support on Apple Silicon
148
148
  if platform.system() == "Darwin" and platform.machine() == "arm64":
149
+ # Check if Core ML dependencies are needed
150
+ requirements_file = Path(models_dir) / "requirements-coreml.txt"
151
+ if requirements_file.exists() and shutil.which("uv"):
152
+ # Try to check if torch is available
153
+ try:
154
+ subprocess.run(
155
+ ["uv", "run", "python", "-c", "import torch"],
156
+ capture_output=True,
157
+ check=True,
158
+ timeout=5
159
+ )
160
+ torch_available = True
161
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
162
+ torch_available = False
163
+
164
+ if not torch_available:
165
+ logger.info("Installing Core ML dependencies for optimal performance...")
166
+ try:
167
+ subprocess.run(
168
+ ["uv", "pip", "install", "-r", str(requirements_file)],
169
+ capture_output=True,
170
+ check=True,
171
+ timeout=120
172
+ )
173
+ logger.info("Core ML dependencies installed successfully")
174
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
175
+ logger.info("Could not install Core ML dependencies automatically. Whisper will still work with Metal acceleration.")
176
+
149
177
  core_ml_result = await convert_to_coreml(model, models_dir)
150
178
  if core_ml_result["success"]:
151
179
  logger.info(f"Core ML conversion completed for {model}")
152
180
  else:
153
- logger.warning(f"Core ML conversion failed: {core_ml_result.get('error')}")
181
+ # Log appropriate level based on error category
182
+ error_category = core_ml_result.get('error_category', 'unknown')
183
+ if error_category in ['missing_pytorch', 'missing_coremltools', 'missing_whisper', 'missing_ane_transformers', 'missing_module']:
184
+ logger.info(f"Core ML conversion skipped - {core_ml_result.get('error', 'Missing dependencies')}. Whisper will use Metal acceleration.")
185
+ else:
186
+ logger.warning(f"Core ML conversion failed ({error_category}): {core_ml_result.get('error', 'Unknown error')}")
154
187
 
188
+ # Always include Core ML status in response
155
189
  return {
156
190
  "success": True,
157
191
  "path": str(model_path),
158
- "message": f"Model {model} downloaded successfully"
192
+ "message": f"Model {model} downloaded successfully",
193
+ "core_ml_status": core_ml_result,
194
+ "acceleration": "coreml" if core_ml_result.get("success") else "metal"
159
195
  }
160
196
 
161
197
  except subprocess.CalledProcessError as e:
@@ -200,26 +236,82 @@ async def convert_to_coreml(
200
236
  }
201
237
 
202
238
  # Find the Core ML conversion script
203
- whisper_dir = Path.home() / ".voicemode" / "whisper.cpp"
204
- convert_script = whisper_dir / "models" / "generate-coreml-model.sh"
239
+ # Try new location first, then fall back to old location
240
+ whisper_dir = Path.home() / ".voicemode" / "services" / "whisper"
241
+ if not whisper_dir.exists():
242
+ whisper_dir = Path.home() / ".voicemode" / "whisper.cpp"
243
+
244
+ # Use the uv wrapper script if it exists, otherwise fallback to original
245
+ convert_script = whisper_dir / "models" / "generate-coreml-model-uv.sh"
246
+ if not convert_script.exists():
247
+ convert_script = whisper_dir / "models" / "generate-coreml-model.sh"
205
248
 
206
249
  if not convert_script.exists():
207
250
  return {
208
251
  "success": False,
209
- "error": "Core ML conversion script not found"
252
+ "error": f"Core ML conversion script not found at {convert_script}"
210
253
  }
211
254
 
212
255
  logger.info(f"Converting {model} to Core ML format...")
213
256
 
214
257
  try:
215
- # Run conversion script
216
- result = subprocess.run(
217
- ["bash", str(convert_script), model],
218
- cwd=str(models_dir),
219
- capture_output=True,
220
- text=True,
221
- check=True
222
- )
258
+ # Check if we should use uv for Python dependencies
259
+ # Try to find the voicemode project root for uv
260
+ voicemode_root = None
261
+ current = Path(__file__).parent
262
+ while current != current.parent:
263
+ if (current / "pyproject.toml").exists():
264
+ with open(current / "pyproject.toml") as f:
265
+ content = f.read()
266
+ if 'name = "voice-mode"' in content or 'name = "voicemode"' in content:
267
+ voicemode_root = current
268
+ break
269
+ current = current.parent
270
+
271
+ # If we found voicemode root and uv is available, use it
272
+ if voicemode_root and shutil.which("uv"):
273
+ # Run the Python script directly with uv instead of using the bash wrapper
274
+ logger.info("Using uv for Core ML conversion with Python dependencies")
275
+ # Run from the whisper models directory
276
+ script_path = whisper_dir / "models" / "convert-whisper-to-coreml.py"
277
+ result = subprocess.run(
278
+ ["uv", "run", "--project", str(voicemode_root), "python",
279
+ str(script_path),
280
+ "--model", model, "--encoder-only", "True", "--optimize-ane", "True"],
281
+ cwd=str(whisper_dir / "models"),
282
+ capture_output=True,
283
+ text=True,
284
+ check=True
285
+ )
286
+
287
+ # Now compile the mlpackage to mlmodelc using coremlc
288
+ mlpackage_path = models_dir / f"coreml-encoder-{model}.mlpackage"
289
+ if mlpackage_path.exists():
290
+ logger.info(f"Compiling Core ML model with coremlc...")
291
+ compile_result = subprocess.run(
292
+ ["xcrun", "coremlc", "compile", str(mlpackage_path), str(models_dir)],
293
+ capture_output=True,
294
+ text=True,
295
+ check=True
296
+ )
297
+
298
+ # Move the compiled model to the correct name
299
+ compiled_path = models_dir / f"coreml-encoder-{model}.mlmodelc"
300
+ if compiled_path.exists():
301
+ shutil.rmtree(coreml_path, ignore_errors=True)
302
+ shutil.move(str(compiled_path), str(coreml_path))
303
+ else:
304
+ # Fallback to original bash script
305
+ logger.info("Using standard Python for Core ML conversion")
306
+ # Run from the whisper models directory where the script is located
307
+ script_dir = convert_script.parent
308
+ result = subprocess.run(
309
+ ["bash", str(convert_script), model],
310
+ cwd=str(script_dir),
311
+ capture_output=True,
312
+ text=True,
313
+ check=True
314
+ )
223
315
 
224
316
  if coreml_path.exists():
225
317
  return {
@@ -234,16 +326,111 @@ async def convert_to_coreml(
234
326
  }
235
327
 
236
328
  except subprocess.CalledProcessError as e:
237
- logger.error(f"Core ML conversion failed: {e.stderr}")
329
+ error_text = e.stderr if e.stderr else ""
330
+ stdout_text = e.stdout if e.stdout else ""
331
+ # Combine both for error detection since Python errors can appear in either
332
+ combined_output = error_text + stdout_text
333
+
334
+ # Enhanced error detection with specific categories
335
+ error_details = {
336
+ "success": False,
337
+ "error_type": "subprocess_error",
338
+ "return_code": e.returncode,
339
+ "command": " ".join(e.cmd) if hasattr(e, 'cmd') else "conversion script",
340
+ }
341
+
342
+ # Detect specific missing dependencies
343
+ if "ModuleNotFoundError" in combined_output:
344
+ if "torch" in combined_output:
345
+ error_details.update({
346
+ "error_category": "missing_pytorch",
347
+ "error": "PyTorch not installed - required for Core ML conversion",
348
+ "install_command": "uv pip install torch",
349
+ "manual_install": "pip install torch",
350
+ "package_size": "~2.5GB"
351
+ })
352
+ elif "coremltools" in combined_output:
353
+ error_details.update({
354
+ "error_category": "missing_coremltools",
355
+ "error": "CoreMLTools not installed",
356
+ "install_command": "uv pip install coremltools",
357
+ "manual_install": "pip install coremltools",
358
+ "package_size": "~50MB"
359
+ })
360
+ elif "whisper" in combined_output:
361
+ error_details.update({
362
+ "error_category": "missing_whisper",
363
+ "error": "OpenAI Whisper package not installed",
364
+ "install_command": "uv pip install openai-whisper",
365
+ "manual_install": "pip install openai-whisper",
366
+ "package_size": "~100MB"
367
+ })
368
+ elif "ane_transformers" in combined_output:
369
+ error_details.update({
370
+ "error_category": "missing_ane_transformers",
371
+ "error": "ANE Transformers not installed for Apple Neural Engine optimization",
372
+ "install_command": "uv pip install ane_transformers",
373
+ "manual_install": "pip install ane_transformers",
374
+ "package_size": "~10MB"
375
+ })
376
+ else:
377
+ # Generic module not found
378
+ module_match = re.search(r"No module named '([^']+)'", combined_output)
379
+ module_name = module_match.group(1) if module_match else "unknown"
380
+ error_details.update({
381
+ "error_category": "missing_module",
382
+ "error": f"Python module '{module_name}' not installed",
383
+ "install_command": f"uv pip install {module_name}",
384
+ "manual_install": f"pip install {module_name}"
385
+ })
386
+ elif "xcrun: error" in combined_output and "coremlc" in combined_output:
387
+ error_details.update({
388
+ "error_category": "missing_coremlc",
389
+ "error": "Core ML compiler (coremlc) not found - requires full Xcode installation",
390
+ "install_command": "Install Xcode from Mac App Store",
391
+ "note": "Command Line Tools alone are insufficient. Full Xcode provides coremlc for Core ML compilation.",
392
+ "alternative": "Models will work with Metal acceleration without Core ML compilation"
393
+ })
394
+ elif "xcrun: error" in combined_output:
395
+ error_details.update({
396
+ "error_category": "missing_xcode_tools",
397
+ "error": "Xcode Command Line Tools not installed or xcrun not available",
398
+ "install_command": "xcode-select --install",
399
+ "note": "Requires Xcode Command Line Tools"
400
+ })
401
+ elif "timeout" in combined_output.lower():
402
+ error_details.update({
403
+ "error_category": "conversion_timeout",
404
+ "error": "Core ML conversion timed out",
405
+ "suggestion": "Try with a smaller model or increase timeout"
406
+ })
407
+ else:
408
+ # Generic conversion failure
409
+ error_details.update({
410
+ "error_category": "conversion_failure",
411
+ "error": f"Core ML conversion failed",
412
+ "stderr": error_text[:500] if error_text else None, # Truncate long errors
413
+ "stdout": stdout_text[:500] if stdout_text else None
414
+ })
415
+
416
+ logger.error(f"Core ML conversion failed - Category: {error_details.get('error_category', 'unknown')}, Error: {error_text[:200]}")
417
+ return error_details
418
+
419
+ except subprocess.TimeoutExpired as e:
420
+ logger.error(f"Core ML conversion timed out after {e.timeout} seconds")
238
421
  return {
239
422
  "success": False,
240
- "error": f"Conversion failed: {e.stderr}"
423
+ "error_category": "timeout",
424
+ "error": f"Core ML conversion timed out after {e.timeout} seconds",
425
+ "suggestion": "Model conversion is taking too long. Try again or use a smaller model."
241
426
  }
242
427
  except Exception as e:
243
- logger.error(f"Error during Core ML conversion: {e}")
428
+ logger.error(f"Unexpected error during Core ML conversion: {e}")
244
429
  return {
245
430
  "success": False,
246
- "error": str(e)
431
+ "error_category": "unexpected_error",
432
+ "error": str(e),
433
+ "error_type": type(e).__name__
247
434
  }
248
435
 
249
436
 
@@ -0,0 +1,138 @@
1
+ """Helper functions to get whisper.cpp version and capabilities."""
2
+
3
+ import subprocess
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Dict, Any, Optional
7
+
8
+
9
+ def get_whisper_version_info() -> Dict[str, Any]:
10
+ """Get version and capability information for whisper.cpp.
11
+
12
+ Returns:
13
+ Dict containing version, commit hash, Core ML support status, etc.
14
+ """
15
+ info = {
16
+ "version": None,
17
+ "commit": None,
18
+ "coreml_supported": False,
19
+ "metal_supported": False,
20
+ "cuda_supported": False,
21
+ "build_type": None
22
+ }
23
+
24
+ # Find whisper-cli binary
25
+ whisper_dir = Path.home() / ".voicemode" / "services" / "whisper"
26
+ whisper_cli = whisper_dir / "build" / "bin" / "whisper-cli"
27
+
28
+ # Fallback to legacy location
29
+ if not whisper_cli.exists():
30
+ whisper_cli = whisper_dir / "main"
31
+
32
+ if not whisper_cli.exists():
33
+ return info
34
+
35
+ try:
36
+ # Get version from git if available
37
+ if (whisper_dir / ".git").exists():
38
+ result = subprocess.run(
39
+ ["git", "describe", "--tags", "--always"],
40
+ cwd=whisper_dir,
41
+ capture_output=True,
42
+ text=True,
43
+ timeout=5
44
+ )
45
+ if result.returncode == 0:
46
+ info["version"] = result.stdout.strip()
47
+
48
+ # Get commit hash
49
+ result = subprocess.run(
50
+ ["git", "rev-parse", "--short", "HEAD"],
51
+ cwd=whisper_dir,
52
+ capture_output=True,
53
+ text=True,
54
+ timeout=5
55
+ )
56
+ if result.returncode == 0:
57
+ info["commit"] = result.stdout.strip()
58
+
59
+ # Run whisper-cli to check capabilities
60
+ # Use a non-existent file to make it fail quickly but still show system info
61
+ result = subprocess.run(
62
+ [str(whisper_cli), "-h"],
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=5
66
+ )
67
+
68
+ if result.returncode == 0:
69
+ output = result.stdout + result.stderr
70
+
71
+ # Check for Core ML support in help text or by running with dummy input
72
+ # Try running with minimal command to get system info
73
+ test_result = subprocess.run(
74
+ [str(whisper_cli), "--help"],
75
+ capture_output=True,
76
+ text=True,
77
+ timeout=5
78
+ )
79
+
80
+ # The system_info line shows what's compiled in
81
+ # We need to actually run it to see the capabilities
82
+ # Let's try with a non-existent model to fail fast but show system info
83
+ test_result = subprocess.run(
84
+ [str(whisper_cli), "-m", "nonexistent.bin"],
85
+ capture_output=True,
86
+ text=True,
87
+ timeout=5
88
+ )
89
+
90
+ test_output = test_result.stdout + test_result.stderr
91
+
92
+ # Parse system_info line for capabilities
93
+ if "COREML = 1" in test_output:
94
+ info["coreml_supported"] = True
95
+ elif "COREML = 0" in test_output:
96
+ info["coreml_supported"] = False
97
+
98
+ if "Metal" in test_output:
99
+ info["metal_supported"] = True
100
+
101
+ if "CUDA = 1" in test_output or "CUBLAS = 1" in test_output:
102
+ info["cuda_supported"] = True
103
+
104
+ # Check if this is a CMake or Make build
105
+ if (whisper_dir / "build" / "CMakeCache.txt").exists():
106
+ info["build_type"] = "CMake"
107
+
108
+ # Parse CMake cache for feature flags
109
+ with open(whisper_dir / "build" / "CMakeCache.txt") as f:
110
+ cmake_content = f.read()
111
+ if "WHISPER_COREML:BOOL=ON" in cmake_content:
112
+ info["coreml_supported"] = True
113
+ if "GGML_METAL:BOOL=ON" in cmake_content or "WHISPER_METAL:BOOL=ON" in cmake_content:
114
+ info["metal_supported"] = True
115
+ if "GGML_CUDA:BOOL=ON" in cmake_content or "WHISPER_CUDA:BOOL=ON" in cmake_content:
116
+ info["cuda_supported"] = True
117
+ else:
118
+ info["build_type"] = "Make"
119
+
120
+ except Exception as e:
121
+ # Silently handle errors
122
+ pass
123
+
124
+ return info
125
+
126
+
127
+ def check_coreml_model_exists(model_name: str) -> bool:
128
+ """Check if a Core ML model exists for the given whisper model.
129
+
130
+ Args:
131
+ model_name: Name of the whisper model (e.g., "large-v3-turbo")
132
+
133
+ Returns:
134
+ True if Core ML model exists, False otherwise
135
+ """
136
+ whisper_dir = Path.home() / ".voicemode" / "services" / "whisper"
137
+ coreml_model = whisper_dir / "models" / f"ggml-{model_name}-encoder.mlmodelc"
138
+ return coreml_model.exists()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-mode
3
- Version: 2.27.0
3
+ Version: 2.28.1
4
4
  Summary: VoiceMode - Voice interaction capabilities for AI assistants (formerly voice-mcp)
5
5
  Project-URL: Homepage, https://github.com/mbailey/voicemode
6
6
  Project-URL: Repository, https://github.com/mbailey/voicemode
@@ -98,6 +98,10 @@ Natural voice conversations for AI assistants. Voice Mode brings human-like voic
98
98
  1. **🎤 Computer with microphone and speakers** OR **☁️ LiveKit server** ([LiveKit Cloud](https://docs.livekit.io/home/cloud/) or [self-hosted](https://github.com/livekit/livekit))
99
99
  2. **🔑 OpenAI API Key** (optional) - Voice Mode can install free, open-source transcription and text-to-speech services locally
100
100
 
101
+ **Optional for enhanced performance:**
102
+
103
+ - **🍎 Xcode** (macOS only) - Required for Core ML acceleration of Whisper models (2-3x faster inference). Install from [Mac App Store](https://apps.apple.com/app/xcode/id497799835) then run `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`
104
+
101
105
  ## Quick Start
102
106
 
103
107
  > 📖 **Using a different tool?** See our [Integration Guides](docs/integrations/README.md) for Cursor, VS Code, Gemini CLI, and more!