voice-mode 2.27.0__tar.gz → 2.28.1__tar.gz

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 (118) hide show
  1. {voice_mode-2.27.0 → voice_mode-2.28.1}/.gitignore +6 -0
  2. {voice_mode-2.27.0 → voice_mode-2.28.1}/CHANGELOG.md +67 -0
  3. {voice_mode-2.27.0 → voice_mode-2.28.1}/PKG-INFO +5 -1
  4. {voice_mode-2.27.0 → voice_mode-2.28.1}/README.md +4 -0
  5. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/__version__.py +1 -1
  6. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/cli.py +152 -37
  7. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/cli_commands/exchanges.py +6 -0
  8. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/converse.py +44 -24
  9. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/service.py +30 -3
  10. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/kokoro/install.py +1 -1
  11. voice_mode-2.28.1/voice_mode/tools/services/whisper/__init__.py +23 -0
  12. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/whisper/install.py +41 -9
  13. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/whisper/list_models.py +14 -14
  14. voice_mode-2.28.1/voice_mode/tools/services/whisper/model_active.py +54 -0
  15. voice_mode-2.28.1/voice_mode/tools/services/whisper/model_benchmark.py +159 -0
  16. voice_mode-2.27.0/voice_mode/tools/services/whisper/download_model.py → voice_mode-2.28.1/voice_mode/tools/services/whisper/model_install.py +72 -11
  17. voice_mode-2.28.1/voice_mode/tools/services/whisper/model_remove.py +36 -0
  18. voice_mode-2.28.1/voice_mode/tools/services/whisper/models.py +473 -0
  19. voice_mode-2.28.1/voice_mode/utils/services/whisper_helpers.py +446 -0
  20. voice_mode-2.28.1/voice_mode/utils/services/whisper_version.py +138 -0
  21. voice_mode-2.27.0/voice_mode/tools/services/whisper/__init__.py +0 -13
  22. voice_mode-2.27.0/voice_mode/tools/services/whisper/list_models_tool.py +0 -65
  23. voice_mode-2.27.0/voice_mode/tools/services/whisper/models.py +0 -274
  24. voice_mode-2.27.0/voice_mode/utils/services/whisper_helpers.py +0 -259
  25. {voice_mode-2.27.0 → voice_mode-2.28.1}/build_hooks.py +0 -0
  26. {voice_mode-2.27.0 → voice_mode-2.28.1}/pyproject.toml +0 -0
  27. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/__init__.py +0 -0
  28. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/__main__.py +0 -0
  29. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/cli_commands/__init__.py +0 -0
  30. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/config.py +0 -0
  31. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/conversation_logger.py +0 -0
  32. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/core.py +0 -0
  33. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/data/versions.json +0 -0
  34. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/__init__.py +0 -0
  35. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/conversations.py +0 -0
  36. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/filters.py +0 -0
  37. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/formatters.py +0 -0
  38. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/models.py +0 -0
  39. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/reader.py +0 -0
  40. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/exchanges/stats.py +0 -0
  41. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/README.md +0 -0
  42. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
  43. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/app/favicon.ico +0 -0
  44. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/app/globals.css +0 -0
  45. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/app/layout.tsx +0 -0
  46. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/app/page.tsx +0 -0
  47. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
  48. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
  49. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
  50. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
  51. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
  52. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/next-env.d.ts +0 -0
  53. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/next.config.mjs +0 -0
  54. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/package-lock.json +0 -0
  55. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/package.json +0 -0
  56. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/pnpm-lock.yaml +0 -0
  57. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/postcss.config.mjs +0 -0
  58. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/tailwind.config.ts +0 -0
  59. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/frontend/tsconfig.json +0 -0
  60. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/prompts/README.md +0 -0
  61. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/prompts/__init__.py +0 -0
  62. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/prompts/converse.py +0 -0
  63. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/prompts/release_notes.py +0 -0
  64. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/prompts/services.py +0 -0
  65. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/provider_discovery.py +0 -0
  66. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/providers.py +0 -0
  67. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/__init__.py +0 -0
  68. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/audio_files.py +0 -0
  69. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/changelog.py +0 -0
  70. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/configuration.py +0 -0
  71. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/statistics.py +0 -0
  72. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/version.py +0 -0
  73. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/resources/whisper_models.py +0 -0
  74. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/server.py +0 -0
  75. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/shared.py +0 -0
  76. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/simple_failover.py +0 -0
  77. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/statistics.py +0 -0
  78. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/streaming.py +0 -0
  79. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
  80. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
  81. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
  82. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
  83. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
  84. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
  85. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
  86. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
  87. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
  88. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
  89. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/__init__.py +0 -0
  90. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/configuration_management.py +0 -0
  91. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/dependencies.py +0 -0
  92. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/devices.py +0 -0
  93. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/diagnostics.py +0 -0
  94. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/providers.py +0 -0
  95. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
  96. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/list_versions.py +0 -0
  97. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/livekit/__init__.py +0 -0
  98. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/livekit/frontend.py +0 -0
  99. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/livekit/install.py +0 -0
  100. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/livekit/production_server.py +0 -0
  101. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/livekit/uninstall.py +0 -0
  102. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/version_info.py +0 -0
  103. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/services/whisper/uninstall.py +0 -0
  104. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/statistics.py +0 -0
  105. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/tools/voice_registry.py +0 -0
  106. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/__init__.py +0 -0
  107. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/audio_diagnostics.py +0 -0
  108. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/event_logger.py +0 -0
  109. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/ffmpeg_check.py +0 -0
  110. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/format_migration.py +0 -0
  111. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/gpu_detection.py +0 -0
  112. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/migration_helpers.py +0 -0
  113. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/services/common.py +0 -0
  114. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/services/kokoro_helpers.py +0 -0
  115. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/services/livekit_helpers.py +0 -0
  116. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/utils/version_helpers.py +0 -0
  117. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/version.py +0 -0
  118. {voice_mode-2.27.0 → voice_mode-2.28.1}/voice_mode/voice_preferences.py +0 -0
@@ -111,3 +111,9 @@ testdir/
111
111
 
112
112
  # Profiling output
113
113
  *.prof
114
+
115
+ # Model files (should be downloaded, not committed)
116
+ models/
117
+ *.mlpackage/
118
+ *.mlmodel
119
+ *.mlmodelc/
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.28.1] - 2025-08-24
11
+
12
+ ### Added
13
+ - **Standardized project naming as VoiceMode MCP**
14
+ - Consistent branding across all documentation and code
15
+ - Updated project descriptions and metadata
16
+ - Renamed internal references from "voice-mode" to "VoiceMode MCP"
17
+ - Maintains backward compatibility with existing installations
18
+
19
+ ### Fixed
20
+ - **CoreML fallback for whisper.cpp on Apple Silicon**
21
+ - Added proper error handling when CoreML models fail to load
22
+ - Automatically falls back to CPU processing if CoreML initialization fails
23
+ - Prevents whisper-server crashes on systems with CoreML issues
24
+ - Improves reliability on various macOS configurations
25
+
26
+ ## [2.28.0] - 2025-08-23
27
+
28
+ ### Added
29
+ - **Comprehensive CLI help support**
30
+ - Added `-h` and `--help` options to all CLI commands and subcommands
31
+ - Consistent help functionality across all command groups (kokoro, whisper, livekit, config, etc.)
32
+ - Help options available for both groups and individual commands
33
+ - Improved user experience with quick access to command documentation
34
+
35
+ - **Core ML support for whisper.cpp installation**
36
+ - Whisper install now uses CMake instead of Make for better control
37
+ - Automatically enables Core ML support on Apple Silicon Macs
38
+ - Provides ~3x faster encoding performance with Core ML acceleration
39
+ - Core ML models automatically converted during installation
40
+ - Falls back gracefully if Core ML conversion fails
41
+
42
+ - **Enhanced whisper status command**
43
+ - Shows whisper.cpp version information
44
+ - Displays Core ML support status (enabled/disabled)
45
+ - Shows if Core ML model is active for current model
46
+ - Reports GPU acceleration type (Metal/CUDA)
47
+ - Helper utility in `whisper_version.py` for capability detection
48
+
49
+ - **Audio conversion optimization for local whisper**
50
+ - Automatically detects truly local whisper (not SSH-forwarded)
51
+ - Skips WAV to MP3 conversion for local whisper, sending WAV directly
52
+ - Adds timing measurements for audio format conversion
53
+ - Logs conversion time at INFO level for performance monitoring
54
+ - Significantly reduces STT processing time for local deployments
55
+
56
+ - **Whisper model benchmark command**
57
+ - New `whisper model benchmark` CLI command
58
+ - Compares performance across multiple models
59
+ - Shows load time, encode time, and total processing time
60
+ - Calculates real-time factor for each model
61
+ - Fixed timing output by removing --no-prints flag
62
+ - Helps users choose optimal model for speed/accuracy tradeoffs
63
+ - Provides personalized recommendations based on results
64
+
65
+ ### Fixed
66
+ - **MCP server configuration**
67
+ - Fixed .mcp.json to use `uv run voicemode` for local development
68
+ - Removed hardcoded paths for better portability
69
+ - Works correctly with project-local development version
70
+
71
+ - **Whisper model management**
72
+ - Fixed model active command to properly update configuration
73
+ - Fixed naming conflict in model install CLI command
74
+ - Benchmark now correctly shows timing information
75
+ - Core ML conversion errors are now properly reported and handled
76
+
10
77
  ## [2.27.0] - 2025-08-20
11
78
 
12
79
  ### Added
@@ -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!
@@ -29,6 +29,10 @@ Natural voice conversations for AI assistants. Voice Mode brings human-like voic
29
29
  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))
30
30
  2. **🔑 OpenAI API Key** (optional) - Voice Mode can install free, open-source transcription and text-to-speech services locally
31
31
 
32
+ **Optional for enhanced performance:**
33
+
34
+ - **🍎 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`
35
+
32
36
  ## Quick Start
33
37
 
34
38
  > 📖 **Using a different tool?** See our [Integration Guides](docs/integrations/README.md) for Cursor, VS Code, Gemini CLI, and more!
@@ -1,3 +1,3 @@
1
1
  # This file is automatically updated by 'make release'
2
2
  # Do not edit manually
3
- __version__ = "2.27.0"
3
+ __version__ = "2.28.1"
@@ -58,18 +58,21 @@ def voice_mode() -> None:
58
58
 
59
59
  # Service group commands
60
60
  @voice_mode_main_cli.group()
61
+ @click.help_option('-h', '--help', help='Show this message and exit')
61
62
  def kokoro():
62
63
  """Manage Kokoro TTS service."""
63
64
  pass
64
65
 
65
66
 
66
67
  @voice_mode_main_cli.group()
68
+ @click.help_option('-h', '--help', help='Show this message and exit')
67
69
  def whisper():
68
70
  """Manage Whisper STT service."""
69
71
  pass
70
72
 
71
73
 
72
74
  @voice_mode_main_cli.group()
75
+ @click.help_option('-h', '--help', help='Show this message and exit')
73
76
  def livekit():
74
77
  """Manage LiveKit RTC service."""
75
78
  pass
@@ -128,6 +131,7 @@ def disable():
128
131
 
129
132
 
130
133
  @kokoro.command()
134
+ @click.help_option('-h', '--help')
131
135
  @click.option('--lines', '-n', default=50, help='Number of log lines to show')
132
136
  def logs(lines):
133
137
  """View Kokoro service logs."""
@@ -172,6 +176,7 @@ def health():
172
176
 
173
177
 
174
178
  @kokoro.command()
179
+ @click.help_option('-h', '--help')
175
180
  @click.option('--install-dir', help='Directory to install kokoro-fastapi')
176
181
  @click.option('--port', default=8880, help='Port to configure for the service')
177
182
  @click.option('--force', '-f', is_flag=True, help='Force reinstall even if already installed')
@@ -209,6 +214,7 @@ def install(install_dir, port, force, version, auto_enable):
209
214
 
210
215
 
211
216
  @kokoro.command()
217
+ @click.help_option('-h', '--help')
212
218
  @click.option('--remove-models', is_flag=True, help='Also remove downloaded Kokoro models')
213
219
  @click.option('--remove-all-data', is_flag=True, help='Remove all Kokoro data including logs and cache')
214
220
  @click.confirmation_option(prompt='Are you sure you want to uninstall Kokoro?')
@@ -294,6 +300,7 @@ def disable():
294
300
 
295
301
 
296
302
  @whisper.command()
303
+ @click.help_option('-h', '--help')
297
304
  @click.option('--lines', '-n', default=50, help='Number of log lines to show')
298
305
  def logs(lines):
299
306
  """View Whisper service logs."""
@@ -316,7 +323,7 @@ def health():
316
323
  import subprocess
317
324
  try:
318
325
  result = subprocess.run(
319
- ["curl", "-s", "http://127.0.0.1:8090/health"],
326
+ ["curl", "-s", "http://127.0.0.1:2022/health"],
320
327
  capture_output=True, text=True, timeout=5
321
328
  )
322
329
  if result.returncode == 0:
@@ -330,7 +337,7 @@ def health():
330
337
  except json.JSONDecodeError:
331
338
  click.echo("✅ Whisper is responding (non-JSON response)")
332
339
  else:
333
- click.echo("❌ Whisper not responding on port 8090")
340
+ click.echo("❌ Whisper not responding on port 2022")
334
341
  except subprocess.TimeoutExpired:
335
342
  click.echo("❌ Whisper health check timed out")
336
343
  except Exception as e:
@@ -338,6 +345,7 @@ def health():
338
345
 
339
346
 
340
347
  @whisper.command()
348
+ @click.help_option('-h', '--help')
341
349
  @click.option('--install-dir', help='Directory to install whisper.cpp')
342
350
  @click.option('--model', default='large-v2', help='Whisper model to download (default: large-v2)')
343
351
  @click.option('--use-gpu/--no-gpu', default=None, help='Enable GPU support if available')
@@ -386,6 +394,7 @@ def install(install_dir, model, use_gpu, force, version, auto_enable):
386
394
 
387
395
 
388
396
  @whisper.command()
397
+ @click.help_option('-h', '--help')
389
398
  @click.option('--remove-models', is_flag=True, help='Also remove downloaded Whisper models')
390
399
  @click.option('--remove-all-data', is_flag=True, help='Remove all Whisper data including logs and transcriptions')
391
400
  @click.confirmation_option(prompt='Are you sure you want to uninstall Whisper?')
@@ -422,6 +431,7 @@ def uninstall(remove_models, remove_all_data):
422
431
 
423
432
 
424
433
  @whisper.group("model")
434
+ @click.help_option('-h', '--help', help='Show this message and exit')
425
435
  def whisper_model():
426
436
  """Manage Whisper models.
427
437
 
@@ -434,6 +444,7 @@ def whisper_model():
434
444
 
435
445
 
436
446
  @whisper_model.command("active")
447
+ @click.help_option('-h', '--help')
437
448
  @click.argument('model_name', required=False)
438
449
  def whisper_model_active(model_name):
439
450
  """Show or set the active Whisper model.
@@ -442,34 +453,34 @@ def whisper_model_active(model_name):
442
453
  With MODEL_NAME: Sets the active model (updates VOICEMODE_WHISPER_MODEL)
443
454
  """
444
455
  from voice_mode.tools.services.whisper.models import (
445
- get_current_model,
446
- WHISPER_MODELS,
447
- is_model_installed,
448
- set_current_model
456
+ get_active_model,
457
+ WHISPER_MODEL_REGISTRY,
458
+ is_whisper_model_installed,
459
+ set_active_model
449
460
  )
450
461
  import os
451
462
  import subprocess
452
463
 
453
464
  if model_name:
454
465
  # Set model mode
455
- if model_name not in WHISPER_MODELS:
466
+ if model_name not in WHISPER_MODEL_REGISTRY:
456
467
  click.echo(f"Error: '{model_name}' is not a valid model.", err=True)
457
468
  click.echo("\nAvailable models:", err=True)
458
- for name in WHISPER_MODELS.keys():
469
+ for name in WHISPER_MODEL_REGISTRY.keys():
459
470
  click.echo(f" - {name}", err=True)
460
471
  return
461
472
 
462
473
  # Check if model is installed
463
- if not is_model_installed(model_name):
474
+ if not is_whisper_model_installed(model_name):
464
475
  click.echo(f"Error: Model '{model_name}' is not installed.", err=True)
465
476
  click.echo(f"Install it with: voice-mode whisper model install {model_name}", err=True)
466
477
  raise click.Abort()
467
478
 
468
479
  # Get previous model
469
- previous_model = get_current_model()
480
+ previous_model = get_active_model()
470
481
 
471
482
  # Update the configuration file
472
- set_current_model(model_name)
483
+ set_active_model(model_name)
473
484
 
474
485
  click.echo(f"✓ Active model set to: {model_name}")
475
486
  if previous_model != model_name:
@@ -492,14 +503,14 @@ def whisper_model_active(model_name):
492
503
 
493
504
  else:
494
505
  # Show current model
495
- current = get_current_model()
506
+ current = get_active_model()
496
507
 
497
508
  # Check if current model is installed
498
- installed = is_model_installed(current)
509
+ installed = is_whisper_model_installed(current)
499
510
  status = click.style("[✓ Installed]", fg="green") if installed else click.style("[Not installed]", fg="red")
500
511
 
501
512
  # Get model info
502
- model_info = WHISPER_MODELS.get(current, {})
513
+ model_info = WHISPER_MODEL_REGISTRY.get(current, {})
503
514
 
504
515
  click.echo(f"\nActive Whisper model: {click.style(current, fg='yellow', bold=True)} {status}")
505
516
  if model_info:
@@ -524,24 +535,25 @@ def whisper_model_active(model_name):
524
535
  def whisper_models():
525
536
  """List available Whisper models and their installation status."""
526
537
  from voice_mode.tools.services.whisper.models import (
527
- WHISPER_MODELS,
538
+ WHISPER_MODEL_REGISTRY,
528
539
  get_model_directory,
529
- get_current_model,
530
- is_model_installed,
531
- get_installed_models,
532
- format_size
540
+ get_active_model,
541
+ is_whisper_model_installed,
542
+ get_installed_whisper_models,
543
+ format_size,
544
+ has_whisper_coreml_model
533
545
  )
534
546
 
535
547
  model_dir = get_model_directory()
536
- current_model = get_current_model()
537
- installed_models = get_installed_models()
548
+ current_model = get_active_model()
549
+ installed_models = get_installed_whisper_models()
538
550
 
539
551
  # Calculate totals
540
552
  total_installed_size = sum(
541
- WHISPER_MODELS[m]["size_mb"] for m in installed_models
553
+ WHISPER_MODEL_REGISTRY[m]["size_mb"] for m in installed_models
542
554
  )
543
555
  total_available_size = sum(
544
- m["size_mb"] for m in WHISPER_MODELS.values()
556
+ m["size_mb"] for m in WHISPER_MODEL_REGISTRY.values()
545
557
  )
546
558
 
547
559
  # Print header
@@ -549,9 +561,9 @@ def whisper_models():
549
561
  click.echo("")
550
562
 
551
563
  # Print models table
552
- for model_name, info in WHISPER_MODELS.items():
564
+ for model_name, info in WHISPER_MODEL_REGISTRY.items():
553
565
  # Check status
554
- is_installed = is_model_installed(model_name)
566
+ is_installed = is_whisper_model_installed(model_name)
555
567
  is_current = model_name == current_model
556
568
 
557
569
  # Format status
@@ -564,7 +576,11 @@ def whisper_models():
564
576
 
565
577
  # Format installation status
566
578
  if is_installed:
567
- install_status = click.style("[✓ Installed]", fg="green")
579
+ # Check for Core ML model
580
+ if has_whisper_coreml_model(model_name):
581
+ install_status = click.style("[✓ Installed+ML]", fg="green")
582
+ else:
583
+ install_status = click.style("[✓ Installed]", fg="green")
568
584
  else:
569
585
  install_status = click.style("[ Download ]", fg="bright_black")
570
586
 
@@ -581,7 +597,7 @@ def whisper_models():
581
597
  desc = click.style(desc, fg="yellow")
582
598
 
583
599
  # Print row
584
- click.echo(f"{status} {model_display} {install_status:14} {size_str} {lang_str} {desc}")
600
+ click.echo(f"{status} {model_display} {install_status:18} {size_str} {lang_str} {desc}")
585
601
 
586
602
  # Print footer
587
603
  click.echo("")
@@ -593,6 +609,7 @@ def whisper_models():
593
609
 
594
610
 
595
611
  @whisper_model.command("install")
612
+ @click.help_option('-h', '--help')
596
613
  @click.argument('model', default='large-v2')
597
614
  @click.option('--force', '-f', is_flag=True, help='Re-download even if model exists')
598
615
  @click.option('--skip-core-ml', is_flag=True, help='Skip Core ML conversion on Apple Silicon')
@@ -606,8 +623,11 @@ def whisper_model_install(model, force, skip_core_ml):
606
623
  medium, medium.en, large-v1, large-v2, large-v3, large-v3-turbo
607
624
  """
608
625
  import json
609
- from voice_mode.tools.services.whisper.download_model import download_model
610
- result = asyncio.run(download_model.fn(
626
+ import voice_mode.tools.services.whisper.model_install as install_module
627
+ # Get the actual function from the MCP tool wrapper
628
+ tool = install_module.whisper_model_install
629
+ install_func = tool.fn if hasattr(tool, 'fn') else tool
630
+ result = asyncio.run(install_func(
611
631
  model=model,
612
632
  force_download=force,
613
633
  skip_core_ml=skip_core_ml
@@ -645,6 +665,7 @@ def whisper_model_install(model, force, skip_core_ml):
645
665
 
646
666
 
647
667
  @whisper_model.command("remove")
668
+ @click.help_option('-h', '--help')
648
669
  @click.argument('model')
649
670
  @click.option('--force', '-f', is_flag=True, help='Remove without confirmation')
650
671
  def whisper_model_remove(model, force):
@@ -653,28 +674,28 @@ def whisper_model_remove(model, force):
653
674
  MODEL is the name of the model to remove (e.g., 'large-v2').
654
675
  """
655
676
  from voice_mode.tools.services.whisper.models import (
656
- WHISPER_MODELS,
657
- is_model_installed,
677
+ WHISPER_MODEL_REGISTRY,
678
+ is_whisper_model_installed,
658
679
  get_model_directory,
659
- get_current_model
680
+ get_active_model
660
681
  )
661
682
  import os
662
683
 
663
684
  # Validate model name
664
- if model not in WHISPER_MODELS:
685
+ if model not in WHISPER_MODEL_REGISTRY:
665
686
  click.echo(f"Error: '{model}' is not a valid model.", err=True)
666
687
  click.echo("\nAvailable models:", err=True)
667
- for name in WHISPER_MODELS.keys():
688
+ for name in WHISPER_MODEL_REGISTRY.keys():
668
689
  click.echo(f" - {name}", err=True)
669
690
  ctx.exit(1)
670
691
 
671
692
  # Check if model is installed
672
- if not is_model_installed(model):
693
+ if not is_whisper_model_installed(model):
673
694
  click.echo(f"Model '{model}' is not installed.")
674
695
  return
675
696
 
676
697
  # Check if it's the current model
677
- current = get_current_model()
698
+ current = get_active_model()
678
699
  if model == current:
679
700
  click.echo(f"Warning: '{model}' is the currently selected model.", err=True)
680
701
  if not force:
@@ -683,7 +704,7 @@ def whisper_model_remove(model, force):
683
704
 
684
705
  # Get model path
685
706
  model_dir = get_model_directory()
686
- model_info = WHISPER_MODELS[model]
707
+ model_info = WHISPER_MODEL_REGISTRY[model]
687
708
  model_path = model_dir / model_info["filename"]
688
709
 
689
710
  # Also check for Core ML models
@@ -712,6 +733,83 @@ def whisper_model_remove(model, force):
712
733
  click.echo(f"Error removing model: {e}", err=True)
713
734
 
714
735
 
736
+ @whisper_model.command("benchmark")
737
+ @click.help_option('-h', '--help')
738
+ @click.option('--models', default='installed', help='Models to benchmark: installed, all, or comma-separated list')
739
+ @click.option('--sample', help='Audio file to use for benchmarking')
740
+ @click.option('--runs', default=1, help='Number of benchmark runs per model')
741
+ def whisper_model_benchmark_cmd(models, sample, runs):
742
+ """Benchmark Whisper model performance.
743
+
744
+ Runs performance tests on specified models to help choose the optimal model
745
+ for your use case based on speed vs accuracy trade-offs.
746
+ """
747
+ from voice_mode.tools.services.whisper.model_benchmark import whisper_model_benchmark
748
+
749
+ # Parse models parameter
750
+ if ',' in models:
751
+ model_list = [m.strip() for m in models.split(',')]
752
+ else:
753
+ model_list = models
754
+
755
+ # Run benchmark
756
+ result = asyncio.run(whisper_model_benchmark(
757
+ models=model_list,
758
+ sample_file=sample,
759
+ runs=runs
760
+ ))
761
+
762
+ if not result.get('success'):
763
+ click.echo(f"❌ Benchmark failed: {result.get('error', 'Unknown error')}", err=True)
764
+ return
765
+
766
+ # Display results
767
+ click.echo("\n" + "="*60)
768
+ click.echo("Whisper Model Benchmark Results")
769
+ click.echo("="*60)
770
+
771
+ if result.get('sample_file'):
772
+ click.echo(f"Sample: {result['sample_file']}")
773
+ if result.get('runs_per_model') > 1:
774
+ click.echo(f"Runs per model: {result['runs_per_model']} (showing best)")
775
+ click.echo("")
776
+
777
+ # Display benchmark table
778
+ click.echo(f"{'Model':<20} {'Load (ms)':<12} {'Encode (ms)':<12} {'Total (ms)':<12} {'Speed':<10}")
779
+ click.echo("-"*70)
780
+
781
+ for bench in result.get('benchmarks', []):
782
+ if bench.get('success'):
783
+ model = bench['model']
784
+ load_time = f"{bench.get('load_time_ms', 0):.1f}"
785
+ encode_time = f"{bench.get('encode_time_ms', 0):.1f}"
786
+ total_time = f"{bench.get('total_time_ms', 0):.1f}"
787
+ rtf = f"{bench.get('real_time_factor', 0):.1f}x"
788
+
789
+ # Highlight fastest model
790
+ if bench['model'] == result.get('fastest_model'):
791
+ model = click.style(model, fg='green', bold=True)
792
+ rtf = click.style(rtf, fg='green', bold=True)
793
+
794
+ click.echo(f"{model:<20} {load_time:<12} {encode_time:<12} {total_time:<12} {rtf:<10}")
795
+ else:
796
+ click.echo(f"{bench['model']:<20} {'Failed':<12} {bench.get('error', 'Unknown error')}")
797
+
798
+ # Display recommendations
799
+ if result.get('recommendations'):
800
+ click.echo("\nRecommendations:")
801
+ for rec in result['recommendations']:
802
+ click.echo(f" • {rec}")
803
+
804
+ # Summary
805
+ if result.get('fastest_model'):
806
+ click.echo(f"\nFastest model: {click.style(result['fastest_model'], fg='yellow', bold=True)}")
807
+ click.echo(f"Processing time: {result.get('fastest_time_ms', 'N/A')} ms")
808
+
809
+ click.echo("\nNote: Speed values show real-time factor (higher is better)")
810
+ click.echo(" 1.0x = real-time, 10x = 10 times faster than real-time")
811
+
812
+
715
813
  # LiveKit service commands
716
814
  @livekit.command()
717
815
  def status():
@@ -762,6 +860,7 @@ def disable():
762
860
 
763
861
 
764
862
  @livekit.command()
863
+ @click.help_option('-h', '--help')
765
864
  @click.option('--lines', '-n', default=50, help='Number of log lines to show')
766
865
  def logs(lines):
767
866
  """View LiveKit service logs."""
@@ -785,6 +884,7 @@ def update():
785
884
 
786
885
 
787
886
  @livekit.command()
887
+ @click.help_option('-h', '--help')
788
888
  @click.option('--install-dir', help='Directory to install LiveKit')
789
889
  @click.option('--port', default=7880, help='Port for LiveKit server (default: 7880)')
790
890
  @click.option('--force', '-f', is_flag=True, help='Force reinstall even if already installed')
@@ -825,6 +925,7 @@ def install(install_dir, port, force, version, auto_enable):
825
925
 
826
926
 
827
927
  @livekit.command()
928
+ @click.help_option('-h', '--help')
828
929
  @click.option('--remove-config', is_flag=True, help='Also remove LiveKit configuration files')
829
930
  @click.option('--remove-all-data', is_flag=True, help='Remove all LiveKit data including logs')
830
931
  @click.confirmation_option(prompt='Are you sure you want to uninstall LiveKit?')
@@ -854,12 +955,14 @@ def uninstall(remove_config, remove_all_data):
854
955
 
855
956
  # LiveKit frontend subcommands
856
957
  @livekit.group()
958
+ @click.help_option('-h', '--help', help='Show this message and exit')
857
959
  def frontend():
858
960
  """Manage LiveKit Voice Assistant Frontend."""
859
961
  pass
860
962
 
861
963
 
862
964
  @frontend.command("install")
965
+ @click.help_option('-h', '--help')
863
966
  @click.option('--auto-enable/--no-auto-enable', default=None, help='Enable service after installation (default: from config)')
864
967
  def frontend_install(auto_enable):
865
968
  """Install and setup LiveKit Voice Assistant Frontend."""
@@ -887,6 +990,7 @@ def frontend_install(auto_enable):
887
990
 
888
991
 
889
992
  @frontend.command("start")
993
+ @click.help_option('-h', '--help')
890
994
  @click.option('--port', default=3000, help='Port to run frontend on (default: 3000)')
891
995
  @click.option('--host', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)')
892
996
  def frontend_start(port, host):
@@ -967,6 +1071,7 @@ def frontend_open():
967
1071
 
968
1072
 
969
1073
  @frontend.command("logs")
1074
+ @click.help_option('-h', '--help')
970
1075
  @click.option("--lines", "-n", default=50, help="Number of lines to show (default: 50)")
971
1076
  @click.option("--follow", "-f", is_flag=True, help="Follow log output (tail -f)")
972
1077
  def frontend_logs(lines, follow):
@@ -1020,6 +1125,7 @@ def frontend_disable():
1020
1125
 
1021
1126
 
1022
1127
  @frontend.command("build")
1128
+ @click.help_option('-h', '--help')
1023
1129
  @click.option('--force', '-f', is_flag=True, help='Force rebuild even if build exists')
1024
1130
  def frontend_build(force):
1025
1131
  """Build frontend for production (requires Node.js)."""
@@ -1077,6 +1183,7 @@ def frontend_build(force):
1077
1183
 
1078
1184
  # Configuration management group
1079
1185
  @voice_mode_main_cli.group()
1186
+ @click.help_option('-h', '--help', help='Show this message and exit')
1080
1187
  def config():
1081
1188
  """Manage voice-mode configuration."""
1082
1189
  pass
@@ -1091,6 +1198,7 @@ def config_list():
1091
1198
 
1092
1199
 
1093
1200
  @config.command("get")
1201
+ @click.help_option('-h', '--help')
1094
1202
  @click.argument('key')
1095
1203
  def config_get(key):
1096
1204
  """Get a configuration value."""
@@ -1128,6 +1236,7 @@ def config_get(key):
1128
1236
 
1129
1237
 
1130
1238
  @config.command("set")
1239
+ @click.help_option('-h', '--help')
1131
1240
  @click.argument('key')
1132
1241
  @click.argument('value')
1133
1242
  def config_set(key, value):
@@ -1139,6 +1248,7 @@ def config_set(key, value):
1139
1248
 
1140
1249
  # Shell completion group
1141
1250
  @voice_mode_main_cli.group()
1251
+ @click.help_option('-h', '--help', help='Show this message and exit')
1142
1252
  def completion():
1143
1253
  """Generate shell completion scripts for voice-mode."""
1144
1254
  pass
@@ -1218,6 +1328,7 @@ def completion_fish():
1218
1328
 
1219
1329
 
1220
1330
  @completion.command("install")
1331
+ @click.help_option('-h', '--help')
1221
1332
  @click.option('--shell', type=click.Choice(['bash', 'zsh', 'fish', 'auto']), default='auto', help='Shell type to install for')
1222
1333
  def completion_install(shell):
1223
1334
  """Show installation instructions for shell completion.
@@ -1283,6 +1394,7 @@ def completion_install(shell):
1283
1394
 
1284
1395
  # Diagnostics group
1285
1396
  @voice_mode_main_cli.group()
1397
+ @click.help_option('-h', '--help', help='Show this message and exit')
1286
1398
  def diag():
1287
1399
  """Diagnostic tools for voice-mode."""
1288
1400
  pass
@@ -1380,6 +1492,7 @@ voice_mode_main_cli.add_command(exchanges_cmd.exchanges)
1380
1492
 
1381
1493
  # Converse command - direct voice conversation from CLI
1382
1494
  @voice_mode_main_cli.command()
1495
+ @click.help_option('-h', '--help')
1383
1496
  @click.option('--message', '-m', default="Hello! How can I help you today?", help='Initial message to speak')
1384
1497
  @click.option('--wait/--no-wait', default=True, help='Wait for response after speaking')
1385
1498
  @click.option('--duration', '-d', type=float, default=30.0, help='Listen duration in seconds')
@@ -1578,6 +1691,7 @@ def version():
1578
1691
 
1579
1692
  # Update command
1580
1693
  @voice_mode_main_cli.command()
1694
+ @click.help_option('-h', '--help')
1581
1695
  @click.option('--force', is_flag=True, help='Force reinstall even if already up to date')
1582
1696
  def update(force):
1583
1697
  """Update Voice Mode to the latest version."""
@@ -1659,6 +1773,7 @@ def update(force):
1659
1773
 
1660
1774
  # Completions command
1661
1775
  @voice_mode_main_cli.command()
1776
+ @click.help_option('-h', '--help')
1662
1777
  @click.argument('shell', type=click.Choice(['bash', 'zsh', 'fish']))
1663
1778
  @click.option('--install', is_flag=True, help='Install completion script to the appropriate location')
1664
1779
  def completions(shell, install):