voice-mode 2.28.3__tar.gz → 2.30.0__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 (114) hide show
  1. {voice_mode-2.28.3 → voice_mode-2.30.0}/CHANGELOG.md +26 -0
  2. {voice_mode-2.28.3 → voice_mode-2.30.0}/PKG-INFO +6 -1
  3. {voice_mode-2.28.3 → voice_mode-2.30.0}/pyproject.toml +6 -0
  4. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/__version__.py +1 -1
  5. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/cli.py +129 -34
  6. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/converse.py +7 -8
  7. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/model_install.py +99 -3
  8. {voice_mode-2.28.3 → voice_mode-2.30.0}/.gitignore +0 -0
  9. {voice_mode-2.28.3 → voice_mode-2.30.0}/README.md +0 -0
  10. {voice_mode-2.28.3 → voice_mode-2.30.0}/build_hooks.py +0 -0
  11. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/__init__.py +0 -0
  12. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/__main__.py +0 -0
  13. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/cli_commands/__init__.py +0 -0
  14. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/cli_commands/exchanges.py +0 -0
  15. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/config.py +0 -0
  16. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/conversation_logger.py +0 -0
  17. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/core.py +0 -0
  18. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/data/versions.json +0 -0
  19. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/__init__.py +0 -0
  20. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/conversations.py +0 -0
  21. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/filters.py +0 -0
  22. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/formatters.py +0 -0
  23. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/models.py +0 -0
  24. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/reader.py +0 -0
  25. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/exchanges/stats.py +0 -0
  26. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/README.md +0 -0
  27. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
  28. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/app/favicon.ico +0 -0
  29. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/app/globals.css +0 -0
  30. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/app/layout.tsx +0 -0
  31. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/app/page.tsx +0 -0
  32. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
  33. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
  34. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
  35. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
  36. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
  37. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/next-env.d.ts +0 -0
  38. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/next.config.mjs +0 -0
  39. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/package-lock.json +0 -0
  40. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/package.json +0 -0
  41. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/pnpm-lock.yaml +0 -0
  42. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/postcss.config.mjs +0 -0
  43. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/tailwind.config.ts +0 -0
  44. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/frontend/tsconfig.json +0 -0
  45. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/prompts/README.md +0 -0
  46. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/prompts/__init__.py +0 -0
  47. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/prompts/converse.py +0 -0
  48. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/prompts/release_notes.py +0 -0
  49. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/prompts/services.py +0 -0
  50. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/provider_discovery.py +0 -0
  51. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/providers.py +0 -0
  52. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/__init__.py +0 -0
  53. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/audio_files.py +0 -0
  54. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/changelog.py +0 -0
  55. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/configuration.py +0 -0
  56. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/statistics.py +0 -0
  57. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/version.py +0 -0
  58. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/resources/whisper_models.py +0 -0
  59. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/server.py +0 -0
  60. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/shared.py +0 -0
  61. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/simple_failover.py +0 -0
  62. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/statistics.py +0 -0
  63. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/streaming.py +0 -0
  64. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
  65. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
  66. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
  67. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
  68. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
  69. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
  70. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
  71. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
  72. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
  73. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
  74. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/__init__.py +0 -0
  75. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/configuration_management.py +0 -0
  76. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/dependencies.py +0 -0
  77. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/devices.py +0 -0
  78. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/diagnostics.py +0 -0
  79. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/providers.py +0 -0
  80. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/service.py +0 -0
  81. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/kokoro/install.py +0 -0
  82. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
  83. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/list_versions.py +0 -0
  84. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/livekit/__init__.py +0 -0
  85. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/livekit/frontend.py +0 -0
  86. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/livekit/install.py +0 -0
  87. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/livekit/production_server.py +0 -0
  88. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/livekit/uninstall.py +0 -0
  89. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/version_info.py +0 -0
  90. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/__init__.py +0 -0
  91. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/install.py +0 -0
  92. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/list_models.py +0 -0
  93. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/model_active.py +0 -0
  94. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/model_benchmark.py +0 -0
  95. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/model_remove.py +0 -0
  96. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/models.py +0 -0
  97. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/services/whisper/uninstall.py +0 -0
  98. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/statistics.py +0 -0
  99. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/tools/voice_registry.py +0 -0
  100. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/__init__.py +0 -0
  101. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/audio_diagnostics.py +0 -0
  102. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/event_logger.py +0 -0
  103. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/ffmpeg_check.py +0 -0
  104. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/format_migration.py +0 -0
  105. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/gpu_detection.py +0 -0
  106. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/migration_helpers.py +0 -0
  107. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/services/common.py +0 -0
  108. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/services/kokoro_helpers.py +0 -0
  109. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/services/livekit_helpers.py +0 -0
  110. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/services/whisper_helpers.py +0 -0
  111. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/services/whisper_version.py +0 -0
  112. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/utils/version_helpers.py +0 -0
  113. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/version.py +0 -0
  114. {voice_mode-2.28.3 → voice_mode-2.30.0}/voice_mode/voice_preferences.py +0 -0
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.30.0] - 2025-08-25
11
+
12
+ ## [2.29.0] - 2025-08-25
13
+
14
+ ### Added
15
+ - **CoreML acceleration support for Whisper on Apple Silicon**
16
+ - Added optional dependency group 'coreml' with PyTorch and CoreMLTools
17
+ - Enhanced whisper_model_install tool with install_torch and auto_confirm parameters
18
+ - Automatic detection of Apple Silicon Macs with CoreML acceleration offer
19
+ - User-friendly confirmation prompts for large (~2.5GB) PyTorch download
20
+ - Graceful fallback to Metal acceleration if CoreML requirements not met
21
+ - Clear instructions for enabling CoreML later if initially skipped
22
+
23
+ - **Beautiful installer experience**
24
+ - Added Voice Mode ASCII art in Claude Code orange color
25
+ - Enhanced preamble with clear value proposition and privacy messaging
26
+ - Early system detection with special recognition for Apple Silicon
27
+ - Professional presentation with centered text and visual hierarchy
28
+
29
+ ### Fixed
30
+ - **Improved converse tool documentation**
31
+ - Simplified listen_duration parameter documentation
32
+ - Removed confusing duration recommendations that led to unnecessary overrides
33
+ - Clarified that silence detection handles timing well with sensible defaults
34
+ - Reduces cognitive load and prevents token waste from explicit duration settings
35
+
10
36
  ## [2.28.3] - 2025-08-24
11
37
 
12
38
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-mode
3
- Version: 2.28.3
3
+ Version: 2.30.0
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
@@ -39,6 +39,11 @@ Requires-Dist: simpleaudio
39
39
  Requires-Dist: sounddevice
40
40
  Requires-Dist: uv>=0.4.0
41
41
  Requires-Dist: webrtcvad>=2.0.10
42
+ Provides-Extra: coreml
43
+ Requires-Dist: ane-transformers; extra == 'coreml'
44
+ Requires-Dist: coremltools>=7.0; extra == 'coreml'
45
+ Requires-Dist: torch>=2.0.0; extra == 'coreml'
46
+ Requires-Dist: transformers; extra == 'coreml'
42
47
  Provides-Extra: dev
43
48
  Requires-Dist: build>=1.0.0; extra == 'dev'
44
49
  Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
@@ -50,6 +50,12 @@ dependencies = [
50
50
  ]
51
51
 
52
52
  [project.optional-dependencies]
53
+ coreml = [
54
+ "torch>=2.0.0",
55
+ "coremltools>=7.0",
56
+ "transformers",
57
+ "ane-transformers",
58
+ ]
53
59
  dev = [
54
60
  "build>=1.0.0",
55
61
  "twine>=4.0.0",
@@ -1,3 +1,3 @@
1
1
  # This file is automatically updated by 'make release'
2
2
  # Do not edit manually
3
- __version__ = "2.28.3"
3
+ __version__ = "2.30.0"
@@ -1694,18 +1694,89 @@ def version():
1694
1694
  @click.help_option('-h', '--help')
1695
1695
  @click.option('--force', is_flag=True, help='Force reinstall even if already up to date')
1696
1696
  def update(force):
1697
- """Update Voice Mode to the latest version."""
1697
+ """Update Voice Mode to the latest version.
1698
+
1699
+ Automatically detects installation method (UV tool, UV pip, or regular pip)
1700
+ and uses the appropriate update command.
1701
+ """
1698
1702
  import subprocess
1699
1703
  import requests
1704
+ from pathlib import Path
1700
1705
  from importlib.metadata import version as get_version, PackageNotFoundError
1701
1706
 
1707
+ def detect_uv_tool_installation():
1708
+ """Detect if running from a UV tool installation."""
1709
+ prefix_path = Path(sys.prefix).resolve()
1710
+ uv_tools_base = Path.home() / ".local" / "share" / "uv" / "tools"
1711
+
1712
+ # Check if sys.prefix is within UV tools directory
1713
+ if uv_tools_base in prefix_path.parents or prefix_path.parent == uv_tools_base:
1714
+ # Find the tool directory
1715
+ tool_dir = prefix_path if prefix_path.parent == uv_tools_base else None
1716
+
1717
+ if not tool_dir:
1718
+ for parent in prefix_path.parents:
1719
+ if parent.parent == uv_tools_base:
1720
+ tool_dir = parent
1721
+ break
1722
+
1723
+ if tool_dir:
1724
+ # Verify with uv-receipt.toml
1725
+ receipt_file = tool_dir / "uv-receipt.toml"
1726
+ if receipt_file.exists():
1727
+ # Parse tool name from receipt or use directory name
1728
+ try:
1729
+ with open(receipt_file) as f:
1730
+ content = f.read()
1731
+ import re
1732
+ match = re.search(r'name = "([^"]+)"', content)
1733
+ tool_name = match.group(1) if match else tool_dir.name
1734
+ return True, tool_name
1735
+ except Exception:
1736
+ return True, tool_dir.name
1737
+
1738
+ return False, None
1739
+
1740
+ def detect_uv_venv():
1741
+ """Detect if running in a UV-managed virtual environment."""
1742
+ # Check if we're in a venv
1743
+ if sys.prefix == sys.base_prefix:
1744
+ return False
1745
+
1746
+ # Check for UV markers in pyvenv.cfg
1747
+ pyvenv_cfg = Path(sys.prefix) / "pyvenv.cfg"
1748
+ if pyvenv_cfg.exists():
1749
+ try:
1750
+ with open(pyvenv_cfg) as f:
1751
+ content = f.read()
1752
+ if "uv" in content.lower() or "managed by uv" in content:
1753
+ return True
1754
+ except Exception:
1755
+ pass
1756
+
1757
+ return False
1758
+
1759
+ def check_uv_available():
1760
+ """Check if UV is available."""
1761
+ try:
1762
+ result = subprocess.run(
1763
+ ["uv", "--version"],
1764
+ capture_output=True,
1765
+ text=True,
1766
+ timeout=2
1767
+ )
1768
+ return result.returncode == 0
1769
+ except (FileNotFoundError, subprocess.TimeoutExpired):
1770
+ return False
1771
+
1772
+ # Get current version
1702
1773
  try:
1703
1774
  current_version = get_version("voice-mode")
1704
1775
  except PackageNotFoundError:
1705
1776
  current_version = "development"
1706
1777
 
1778
+ # Check if update needed (unless forced)
1707
1779
  if not force and current_version != "development":
1708
- # Check if update is needed
1709
1780
  try:
1710
1781
  response = requests.get(
1711
1782
  "https://pypi.org/pypi/voice-mode/json",
@@ -1717,58 +1788,82 @@ def update(force):
1717
1788
  click.echo(f"Already running the latest version ({current_version})")
1718
1789
  return
1719
1790
  except (requests.RequestException, KeyError, ValueError):
1720
- # Continue with update if we can't check
1721
- pass
1791
+ pass # Continue with update if we can't check
1722
1792
 
1723
- click.echo("Updating Voice Mode to the latest version...")
1793
+ # Detect installation method
1794
+ is_uv_tool, tool_name = detect_uv_tool_installation()
1724
1795
 
1725
- # Try UV first, fall back to pip
1726
- try:
1727
- # Check if UV is available
1796
+ if is_uv_tool:
1797
+ # UV tool installation - use uv tool upgrade
1798
+ click.echo(f"Updating Voice Mode (UV tool: {tool_name})...")
1799
+
1728
1800
  result = subprocess.run(
1729
- ["uv", "--version"],
1801
+ ["uv", "tool", "upgrade", tool_name],
1730
1802
  capture_output=True,
1731
- text=True,
1732
- check=False
1803
+ text=True
1733
1804
  )
1734
1805
 
1735
1806
  if result.returncode == 0:
1736
- # Use UV for update
1807
+ try:
1808
+ new_version = get_version("voice-mode")
1809
+ click.echo(f"✅ Successfully updated to version {new_version}")
1810
+ except PackageNotFoundError:
1811
+ click.echo("✅ Successfully updated Voice Mode")
1812
+ else:
1813
+ click.echo(f"❌ Update failed: {result.stderr}")
1814
+ click.echo(f"Try running manually: uv tool upgrade {tool_name}")
1815
+
1816
+ elif detect_uv_venv():
1817
+ # UV-managed virtual environment
1818
+ click.echo("Updating Voice Mode (UV virtual environment)...")
1819
+
1820
+ result = subprocess.run(
1821
+ ["uv", "pip", "install", "--upgrade", "voice-mode"],
1822
+ capture_output=True,
1823
+ text=True
1824
+ )
1825
+
1826
+ if result.returncode == 0:
1827
+ try:
1828
+ new_version = get_version("voice-mode")
1829
+ click.echo(f"✅ Successfully updated to version {new_version}")
1830
+ except PackageNotFoundError:
1831
+ click.echo("✅ Successfully updated Voice Mode")
1832
+ else:
1833
+ click.echo(f"❌ Update failed: {result.stderr}")
1834
+ click.echo("Try running: uv pip install --upgrade voice-mode")
1835
+
1836
+ else:
1837
+ # Standard installation - try UV if available, else pip
1838
+ has_uv = check_uv_available()
1839
+
1840
+ if has_uv:
1841
+ click.echo("Updating Voice Mode (using UV)...")
1737
1842
  result = subprocess.run(
1738
1843
  ["uv", "pip", "install", "--upgrade", "voice-mode"],
1739
1844
  capture_output=True,
1740
1845
  text=True
1741
1846
  )
1742
- if result.returncode == 0:
1743
- # Get new version
1744
- try:
1745
- new_version = get_version("voice-mode")
1746
- click.echo(f"✅ Successfully updated to version {new_version}")
1747
- except PackageNotFoundError:
1748
- click.echo("✅ Successfully updated Voice Mode")
1749
- else:
1750
- click.echo(f"❌ Update failed: {result.stderr}")
1751
- click.echo("Try running: uv pip install --upgrade voice-mode")
1752
1847
  else:
1753
- # Fall back to pip
1848
+ click.echo("Updating Voice Mode (using pip)...")
1754
1849
  result = subprocess.run(
1755
1850
  [sys.executable, "-m", "pip", "install", "--upgrade", "voice-mode"],
1756
1851
  capture_output=True,
1757
1852
  text=True
1758
1853
  )
1759
- if result.returncode == 0:
1760
- try:
1761
- new_version = get_version("voice-mode")
1762
- click.echo(f"✅ Successfully updated to version {new_version}")
1763
- except PackageNotFoundError:
1764
- click.echo("✅ Successfully updated Voice Mode")
1854
+
1855
+ if result.returncode == 0:
1856
+ try:
1857
+ new_version = get_version("voice-mode")
1858
+ click.echo(f"✅ Successfully updated to version {new_version}")
1859
+ except PackageNotFoundError:
1860
+ click.echo("✅ Successfully updated Voice Mode")
1861
+ else:
1862
+ click.echo(f"❌ Update failed: {result.stderr}")
1863
+ if has_uv:
1864
+ click.echo("Try running: uv pip install --upgrade voice-mode")
1765
1865
  else:
1766
- click.echo(f"❌ Update failed: {result.stderr}")
1767
1866
  click.echo("Try running: pip install --upgrade voice-mode")
1768
-
1769
- except FileNotFoundError as e:
1770
- click.echo(f"❌ Update failed: {e}")
1771
- click.echo("Please install UV or pip and try again")
1772
1867
 
1773
1868
 
1774
1869
  # Completions command
@@ -1351,14 +1351,13 @@ async def converse(
1351
1351
  message: The message to speak
1352
1352
  wait_for_response: Whether to listen for a response after speaking (default: True)
1353
1353
  listen_duration: How long to listen for response in seconds (default: 120.0)
1354
- Recommended durations based on expected response:
1355
- - Simple yes/no questions: 10 seconds
1356
- - Normal conversational responses: 30 seconds
1357
- - Open-ended questions: 60 seconds
1358
- - Detailed explanations: 120 seconds (default)
1359
- - Stories or long explanations: 300 seconds
1360
- Always err on the side of longer duration - it's better to have
1361
- silence at the end than to cut off the user mid-sentence.
1354
+ The tool handles silence detection well and uses a sensible default.
1355
+ It's unusual to need to set the duration - only override if you have
1356
+ specific requirements such as:
1357
+ - Silence detection is disabled and you need a specific timeout
1358
+ - You know the response will be exceptionally long (>120s)
1359
+ - You're in a special mode that requires different timing
1360
+ In most cases, just let the default and silence detection handle it.
1362
1361
  min_listen_duration: Minimum time to record before silence detection can stop (default: 2.0)
1363
1362
  Useful for preventing premature cutoffs when users need thinking time.
1364
1363
  Examples:
@@ -1,10 +1,13 @@
1
1
  """Download Whisper models with Core ML support."""
2
2
 
3
3
  import os
4
+ import sys
4
5
  import json
5
6
  import logging
7
+ import platform
8
+ import subprocess
6
9
  from pathlib import Path
7
- from typing import Union, List
10
+ from typing import Union, List, Dict, Any
8
11
 
9
12
  from voice_mode.server import mcp
10
13
  from voice_mode.config import logger, MODELS_DIR
@@ -17,7 +20,9 @@ logger = logging.getLogger("voice-mode")
17
20
  async def whisper_model_install(
18
21
  model: Union[str, List[str]] = "large-v2",
19
22
  force_download: Union[bool, str] = False,
20
- skip_core_ml: Union[bool, str] = False
23
+ skip_core_ml: Union[bool, str] = False,
24
+ install_torch: Union[bool, str] = False,
25
+ auto_confirm: Union[bool, str] = False
21
26
  ) -> str:
22
27
  """Download Whisper model(s) with optional Core ML conversion.
23
28
 
@@ -31,6 +36,8 @@ async def whisper_model_install(
31
36
  - "all" to download all available models
32
37
  force_download: Re-download even if model exists (default: False)
33
38
  skip_core_ml: Skip Core ML conversion on Apple Silicon (default: False)
39
+ install_torch: Install PyTorch for CoreML (adds ~2.5GB) (default: False)
40
+ auto_confirm: Skip all confirmation prompts (default: False)
34
41
 
35
42
  Available models:
36
43
  - tiny, tiny.en
@@ -77,6 +84,20 @@ async def whisper_model_install(
77
84
  "error": "Whisper.cpp not installed. Please run whisper_install first."
78
85
  }, indent=2)
79
86
 
87
+ # Handle CoreML dependencies if needed
88
+ coreml_status = await _handle_coreml_dependencies(
89
+ install_torch=install_torch,
90
+ auto_confirm=auto_confirm,
91
+ skip_core_ml=skip_core_ml
92
+ )
93
+
94
+ if not coreml_status["continue"]:
95
+ return json.dumps(coreml_status, indent=2)
96
+
97
+ # If CoreML deps were installed, skip_core_ml may have been updated
98
+ if coreml_status.get("coreml_deps_failed"):
99
+ skip_core_ml = True
100
+
80
101
  # Parse model input
81
102
  available_models = get_available_models()
82
103
 
@@ -200,4 +221,79 @@ async def whisper_model_install(
200
221
  return json.dumps({
201
222
  "success": False,
202
223
  "error": str(e)
203
- }, indent=2)
224
+ }, indent=2)
225
+
226
+
227
+ async def _handle_coreml_dependencies(
228
+ install_torch: bool = False,
229
+ auto_confirm: bool = False,
230
+ skip_core_ml: bool = False
231
+ ) -> Dict[str, Any]:
232
+ """Handle CoreML dependency installation for Apple Silicon Macs.
233
+
234
+ Returns:
235
+ Dict with 'continue' key indicating whether to proceed with model download
236
+ """
237
+ # Check if we're on Apple Silicon Mac
238
+ if platform.system() != "Darwin" or platform.machine() != "arm64":
239
+ return {"continue": True}
240
+
241
+ # If skipping CoreML, no need to check dependencies
242
+ if skip_core_ml:
243
+ return {"continue": True}
244
+
245
+ # Check if torch is already installed
246
+ try:
247
+ import torch
248
+ logger.info("PyTorch already installed for CoreML support")
249
+ return {"continue": True}
250
+ except ImportError:
251
+ pass
252
+
253
+ # Check if user wants to install torch
254
+ if not install_torch and not auto_confirm:
255
+ return {
256
+ "continue": False,
257
+ "success": False,
258
+ "requires_confirmation": True,
259
+ "message": "CoreML requires PyTorch (~2.5GB). Rerun with install_torch=True to confirm.",
260
+ "recommendation": "Set install_torch=True for CoreML acceleration (2-3x faster)"
261
+ }
262
+
263
+ # Install CoreML dependencies
264
+ logger.info("Installing CoreML dependencies...")
265
+
266
+ try:
267
+ # Detect environment and install appropriately
268
+ packages = ["torch>=2.0.0", "coremltools>=7.0", "transformers", "ane-transformers"]
269
+
270
+ # Try UV first (most common)
271
+ if subprocess.run(["which", "uv"], capture_output=True).returncode == 0:
272
+ cmd = ["uv", "pip", "install"] + packages
273
+ logger.info("Installing via UV...")
274
+ else:
275
+ # Fallback to pip
276
+ cmd = [sys.executable, "-m", "pip", "install"] + packages
277
+ logger.info("Installing via pip...")
278
+
279
+ # Run installation
280
+ result = subprocess.run(cmd, capture_output=True, text=True)
281
+
282
+ if result.returncode == 0:
283
+ logger.info("CoreML dependencies installed successfully")
284
+ return {"continue": True, "coreml_deps_installed": True}
285
+ else:
286
+ logger.warning(f"Failed to install CoreML dependencies: {result.stderr}")
287
+ return {
288
+ "continue": True,
289
+ "coreml_deps_failed": True,
290
+ "warning": "CoreML dependencies installation failed. Models will use Metal acceleration."
291
+ }
292
+
293
+ except Exception as e:
294
+ logger.warning(f"Error installing CoreML dependencies: {e}")
295
+ return {
296
+ "continue": True,
297
+ "coreml_deps_failed": True,
298
+ "warning": f"CoreML setup error: {str(e)}. Models will use Metal acceleration."
299
+ }
File without changes
File without changes
File without changes