voice-mode 2.32.0__py3-none-any.whl → 2.33.2__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 (73) hide show
  1. voice_mode/__version__.py +1 -1
  2. voice_mode/config.py +1 -1
  3. voice_mode/frontend/.next/BUILD_ID +1 -1
  4. voice_mode/frontend/.next/app-build-manifest.json +5 -5
  5. voice_mode/frontend/.next/build-manifest.json +3 -3
  6. voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -1
  7. voice_mode/frontend/.next/next-server.js.nft.json +1 -1
  8. voice_mode/frontend/.next/prerender-manifest.json +1 -1
  9. voice_mode/frontend/.next/required-server-files.json +1 -1
  10. voice_mode/frontend/.next/server/app/_not-found/page.js +1 -1
  11. voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. voice_mode/frontend/.next/server/app/_not-found.html +1 -1
  13. voice_mode/frontend/.next/server/app/_not-found.rsc +1 -1
  14. voice_mode/frontend/.next/server/app/api/connection-details/route.js +2 -2
  15. voice_mode/frontend/.next/server/app/favicon.ico/route.js +2 -2
  16. voice_mode/frontend/.next/server/app/index.html +1 -1
  17. voice_mode/frontend/.next/server/app/index.rsc +2 -2
  18. voice_mode/frontend/.next/server/app/page.js +2 -2
  19. voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -1
  20. voice_mode/frontend/.next/server/chunks/994.js +2 -2
  21. voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -1
  22. voice_mode/frontend/.next/server/next-font-manifest.js +1 -1
  23. voice_mode/frontend/.next/server/next-font-manifest.json +1 -1
  24. voice_mode/frontend/.next/server/pages/404.html +1 -1
  25. voice_mode/frontend/.next/server/pages/500.html +1 -1
  26. voice_mode/frontend/.next/server/server-reference-manifest.json +1 -1
  27. voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -1
  28. voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +5 -5
  29. voice_mode/frontend/.next/standalone/.next/build-manifest.json +3 -3
  30. voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -1
  31. voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -1
  32. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  33. voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -1
  35. voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  36. voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +2 -2
  37. voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +2 -2
  38. voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -1
  39. voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +2 -2
  40. voice_mode/frontend/.next/standalone/.next/server/app/page.js +2 -2
  41. voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  42. voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +2 -2
  43. voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -1
  45. voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -1
  46. voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -1
  47. voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -1
  48. voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  49. voice_mode/frontend/.next/standalone/server.js +1 -1
  50. voice_mode/frontend/.next/static/chunks/app/{layout-4c59da29fcf0456f.js → layout-b6b174992f2f6afd.js} +1 -1
  51. voice_mode/frontend/.next/static/chunks/app/{page-017e11b769f3a746.js → page-52b6f77b58ca7c9d.js} +1 -1
  52. voice_mode/frontend/.next/static/chunks/{main-app-822552bd94497f44.js → main-app-436d7ffcf2166712.js} +1 -1
  53. voice_mode/frontend/.next/trace +43 -43
  54. voice_mode/frontend/.next/types/app/api/connection-details/route.ts +1 -1
  55. voice_mode/frontend/.next/types/app/layout.ts +1 -1
  56. voice_mode/frontend/.next/types/app/page.ts +1 -1
  57. voice_mode/frontend/package-lock.json +3 -3
  58. voice_mode/prompts/converse.py +0 -1
  59. voice_mode/templates/__init__.py +1 -0
  60. voice_mode/templates/launchd/com.voicemode.whisper.plist +7 -13
  61. voice_mode/templates/scripts/__init__.py +1 -0
  62. voice_mode/templates/scripts/start-whisper-server.sh +80 -0
  63. voice_mode/tools/services/whisper/install.py +100 -132
  64. voice_mode/tools/services/whisper/model_install.py +38 -47
  65. voice_mode/tools/services/whisper/models.py +1 -1
  66. voice_mode/utils/services/coreml_setup.py +234 -0
  67. voice_mode/utils/services/whisper_helpers.py +57 -32
  68. {voice_mode-2.32.0.dist-info → voice_mode-2.33.2.dist-info}/METADATA +11 -12
  69. {voice_mode-2.32.0.dist-info → voice_mode-2.33.2.dist-info}/RECORD +73 -69
  70. /voice_mode/frontend/.next/static/{e8aNOVoFA4vUks2Chn7qv → gdmR4LkC2enrnvJ9K0r0_}/_buildManifest.js +0 -0
  71. /voice_mode/frontend/.next/static/{e8aNOVoFA4vUks2Chn7qv → gdmR4LkC2enrnvJ9K0r0_}/_ssgManifest.js +0 -0
  72. {voice_mode-2.32.0.dist-info → voice_mode-2.33.2.dist-info}/WHEEL +0 -0
  73. {voice_mode-2.32.0.dist-info → voice_mode-2.33.2.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,4 @@
1
- // File: /tmp/build-via-sdist-foqe1vzl/voice_mode-2.32.0/voice_mode/frontend/app/api/connection-details/route.ts
1
+ // File: /tmp/build-via-sdist-tlxizmx7/voice_mode-2.33.2/voice_mode/frontend/app/api/connection-details/route.ts
2
2
  import * as entry from '../../../../../app/api/connection-details/route.js'
3
3
  import type { NextRequest } from 'next/server.js'
4
4
 
@@ -1,4 +1,4 @@
1
- // File: /tmp/build-via-sdist-foqe1vzl/voice_mode-2.32.0/voice_mode/frontend/app/layout.tsx
1
+ // File: /tmp/build-via-sdist-tlxizmx7/voice_mode-2.33.2/voice_mode/frontend/app/layout.tsx
2
2
  import * as entry from '../../../app/layout.js'
3
3
  import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
4
4
 
@@ -1,4 +1,4 @@
1
- // File: /tmp/build-via-sdist-foqe1vzl/voice_mode-2.32.0/voice_mode/frontend/app/page.tsx
1
+ // File: /tmp/build-via-sdist-tlxizmx7/voice_mode-2.33.2/voice_mode/frontend/app/page.tsx
2
2
  import * as entry from '../../../app/page.js'
3
3
  import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
4
4
 
@@ -1774,9 +1774,9 @@
1774
1774
  "license": "MIT"
1775
1775
  },
1776
1776
  "node_modules/electron-to-chromium": {
1777
- "version": "1.5.208",
1778
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz",
1779
- "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==",
1777
+ "version": "1.5.209",
1778
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.209.tgz",
1779
+ "integrity": "sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A==",
1780
1780
  "dev": true,
1781
1781
  "license": "ISC"
1782
1782
  },
@@ -10,7 +10,6 @@ def converse() -> str:
10
10
  "Using tools from voice-mode, have an ongoing two-way conversation",
11
11
  "End the chat when the user indicates they want to end it",
12
12
  "Keep your utterances brief unless a longer response is requested or necessary",
13
- "Listen for up to 120 seconds per response"
14
13
  ]
15
14
 
16
15
  return "\n".join(f"- {instruction}" for instruction in instructions)
@@ -0,0 +1 @@
1
+ # Templates package for Voice Mode
@@ -1,32 +1,26 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <!-- com.voicemode.whisper.plist v1.0.0 -->
4
- <!-- Last updated: 2025-01-25 -->
5
- <!-- Compatible with: whisper.cpp v1.5.0+ -->
3
+ <!-- com.voicemode.whisper.plist v1.1.0 -->
4
+ <!-- Last updated: 2025-08-25 -->
5
+ <!-- Uses unified startup script for dynamic model selection -->
6
6
  <plist version="1.0">
7
7
  <dict>
8
8
  <key>Label</key>
9
9
  <string>com.voicemode.whisper</string>
10
10
  <key>ProgramArguments</key>
11
11
  <array>
12
- <string>{WHISPER_BIN}</string>
13
- <string>--host</string>
14
- <string>0.0.0.0</string>
15
- <string>--port</string>
16
- <string>{WHISPER_PORT}</string>
17
- <string>--model</string>
18
- <string>{MODEL_FILE}</string>
12
+ <string>{START_SCRIPT_PATH}</string>
19
13
  </array>
20
14
  <key>RunAtLoad</key>
21
15
  <true/>
22
16
  <key>KeepAlive</key>
23
17
  <true/>
24
18
  <key>StandardOutPath</key>
25
- <string>{LOG_DIR}/whisper.out.log</string>
19
+ <string>{LOG_DIR}/whisper/whisper.out.log</string>
26
20
  <key>StandardErrorPath</key>
27
- <string>{LOG_DIR}/whisper.err.log</string>
21
+ <string>{LOG_DIR}/whisper/whisper.err.log</string>
28
22
  <key>WorkingDirectory</key>
29
- <string>{WORKING_DIR}</string>
23
+ <string>{INSTALL_DIR}</string>
30
24
  <key>EnvironmentVariables</key>
31
25
  <dict>
32
26
  <key>PATH</key>
@@ -0,0 +1 @@
1
+ # Script templates for Voice Mode services
@@ -0,0 +1,80 @@
1
+ #!/bin/bash
2
+
3
+ # Whisper Service Startup Script
4
+ # This script is used by both macOS (launchd) and Linux (systemd) to start the whisper service
5
+ # It sources the voicemode.env file to get configuration, especially VOICEMODE_WHISPER_MODEL
6
+
7
+ # Determine whisper directory (script is in bin/, whisper root is parent)
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ WHISPER_DIR="$(dirname "$SCRIPT_DIR")"
10
+
11
+ # Voicemode configuration directory
12
+ VOICEMODE_DIR="$HOME/.voicemode"
13
+ LOG_DIR="$VOICEMODE_DIR/logs/whisper"
14
+
15
+ # Create log directory if it doesn't exist
16
+ mkdir -p "$LOG_DIR"
17
+
18
+ # Log file for this script (separate from whisper server logs)
19
+ STARTUP_LOG="$LOG_DIR/startup.log"
20
+
21
+ # Source voicemode configuration if it exists
22
+ if [ -f "$VOICEMODE_DIR/voicemode.env" ]; then
23
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sourcing voicemode.env" >> "$STARTUP_LOG"
24
+ source "$VOICEMODE_DIR/voicemode.env"
25
+ else
26
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Warning: voicemode.env not found" >> "$STARTUP_LOG"
27
+ fi
28
+
29
+ # Model selection with environment variable support
30
+ MODEL_NAME="${VOICEMODE_WHISPER_MODEL:-base}"
31
+ MODEL_PATH="$WHISPER_DIR/models/ggml-$MODEL_NAME.bin"
32
+
33
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting whisper-server with model: $MODEL_NAME" >> "$STARTUP_LOG"
34
+
35
+ # Check if model exists
36
+ if [ ! -f "$MODEL_PATH" ]; then
37
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: Model $MODEL_NAME not found at $MODEL_PATH" >> "$STARTUP_LOG"
38
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Available models:" >> "$STARTUP_LOG"
39
+ ls -1 "$WHISPER_DIR/models/" 2>/dev/null | grep "^ggml-.*\.bin$" >> "$STARTUP_LOG"
40
+
41
+ # Try to find any available model as fallback
42
+ FALLBACK_MODEL=$(ls -1 "$WHISPER_DIR/models/" 2>/dev/null | grep "^ggml-.*\.bin$" | head -1)
43
+ if [ -n "$FALLBACK_MODEL" ]; then
44
+ MODEL_PATH="$WHISPER_DIR/models/$FALLBACK_MODEL"
45
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Using fallback model: $FALLBACK_MODEL" >> "$STARTUP_LOG"
46
+ else
47
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Fatal: No whisper models found" >> "$STARTUP_LOG"
48
+ exit 1
49
+ fi
50
+ fi
51
+
52
+ # Port configuration (with environment variable support)
53
+ WHISPER_PORT="${VOICEMODE_WHISPER_PORT:-2022}"
54
+
55
+ # Determine server binary location
56
+ # Check new CMake build location first, then legacy location
57
+ if [ -f "$WHISPER_DIR/build/bin/whisper-server" ]; then
58
+ SERVER_BIN="$WHISPER_DIR/build/bin/whisper-server"
59
+ elif [ -f "$WHISPER_DIR/server" ]; then
60
+ SERVER_BIN="$WHISPER_DIR/server"
61
+ else
62
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: whisper-server binary not found" >> "$STARTUP_LOG"
63
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checked: $WHISPER_DIR/build/bin/whisper-server" >> "$STARTUP_LOG"
64
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checked: $WHISPER_DIR/server" >> "$STARTUP_LOG"
65
+ exit 1
66
+ fi
67
+
68
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Using binary: $SERVER_BIN" >> "$STARTUP_LOG"
69
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Model path: $MODEL_PATH" >> "$STARTUP_LOG"
70
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Port: $WHISPER_PORT" >> "$STARTUP_LOG"
71
+
72
+ # Start whisper-server
73
+ # Using exec to replace this script process with whisper-server
74
+ cd "$WHISPER_DIR"
75
+ exec "$SERVER_BIN" \
76
+ --host 0.0.0.0 \
77
+ --port "$WHISPER_PORT" \
78
+ --model "$MODEL_PATH" \
79
+ --inference-path /v1/audio/transcriptions \
80
+ --threads 8
@@ -11,6 +11,11 @@ from pathlib import Path
11
11
  from typing import Dict, Any, Optional, Union
12
12
  import asyncio
13
13
  import aiohttp
14
+ try:
15
+ from importlib.resources import files
16
+ except ImportError:
17
+ # Python < 3.9 fallback
18
+ from importlib_resources import files
14
19
 
15
20
  from voice_mode.server import mcp
16
21
  from voice_mode.config import SERVICE_AUTO_ENABLE
@@ -28,7 +33,7 @@ logger = logging.getLogger("voice-mode")
28
33
  @mcp.tool()
29
34
  async def whisper_install(
30
35
  install_dir: Optional[str] = None,
31
- model: str = "large-v2",
36
+ model: str = "base",
32
37
  use_gpu: Optional[Union[bool, str]] = None,
33
38
  force_reinstall: Union[bool, str] = False,
34
39
  auto_enable: Optional[Union[bool, str]] = None,
@@ -42,7 +47,7 @@ async def whisper_install(
42
47
  Args:
43
48
  install_dir: Directory to install whisper.cpp (default: ~/.voicemode/whisper.cpp)
44
49
  model: Whisper model to download (tiny, base, small, medium, large-v2, large-v3, etc.)
45
- Default is large-v2 for best accuracy. Note: large models require ~3GB RAM.
50
+ Default is base for good balance of speed and accuracy (142MB).
46
51
  use_gpu: Enable GPU support if available (default: auto-detect)
47
52
  force_reinstall: Force reinstallation even if already installed
48
53
  auto_enable: Enable service after install. If None, uses VOICEMODE_SERVICE_AUTO_ENABLE config.
@@ -214,7 +219,8 @@ async def whisper_install(
214
219
  if is_macos:
215
220
  # On macOS, always enable Metal
216
221
  cmake_flags.append("-DGGML_METAL=ON")
217
- # On Apple Silicon, also enable Core ML for better performance
222
+ # On Apple Silicon, also enable Core ML support with fallback
223
+ # This allows using CoreML models if available, but falls back to Metal if not
218
224
  if platform.machine() == "arm64":
219
225
  cmake_flags.append("-DWHISPER_COREML=ON")
220
226
  cmake_flags.append("-DWHISPER_COREML_ALLOW_FALLBACK=ON")
@@ -302,59 +308,34 @@ async def whisper_install(
302
308
  if 'original_dir' in locals():
303
309
  os.chdir(original_dir)
304
310
 
305
- # Create start script for whisper-server
306
- logger.info("Creating whisper-server start script...")
307
- start_script_content = f"""#!/bin/bash
308
-
309
- # Configuration
310
- WHISPER_DIR="{install_dir}"
311
- LOG_FILE="{os.path.join(voicemode_dir, 'whisper-server.log')}"
312
-
313
- # Source voicemode configuration if it exists
314
- if [ -f "{voicemode_dir}/voicemode.env" ]; then
315
- source "{voicemode_dir}/voicemode.env"
316
- fi
317
-
318
- # Model selection with environment variable support
319
- MODEL_NAME="${{VOICEMODE_WHISPER_MODEL:-{model}}}"
320
- MODEL_PATH="$WHISPER_DIR/models/ggml-$MODEL_NAME.bin"
321
-
322
- # Check if model exists
323
- if [ ! -f "$MODEL_PATH" ]; then
324
- echo "Error: Model $MODEL_NAME not found at $MODEL_PATH" >> "$LOG_FILE"
325
- echo "Available models:" >> "$LOG_FILE"
326
- ls -1 "$WHISPER_DIR/models/" | grep "^ggml-.*\\.bin$" >> "$LOG_FILE"
327
- exit 1
328
- fi
329
-
330
- echo "Starting whisper-server with model: $MODEL_NAME" >> "$LOG_FILE"
331
-
332
- # Note: whisper-server is now built as part of the main build target
333
-
334
- # Determine server binary location
335
- if [ -f "$WHISPER_DIR/build/bin/whisper-server" ]; then
336
- SERVER_BIN="$WHISPER_DIR/build/bin/whisper-server"
337
- elif [ -f "$WHISPER_DIR/server" ]; then
338
- SERVER_BIN="$WHISPER_DIR/server"
339
- else
340
- echo "Error: whisper-server binary not found" >> "$LOG_FILE"
341
- exit 1
342
- fi
343
-
344
- # Start whisper-server
345
- cd "$WHISPER_DIR"
346
- exec "$SERVER_BIN" \\
347
- --model "$MODEL_PATH" \\
348
- --host 0.0.0.0 \\
349
- --port 2022 \\
350
- --inference-path /v1/audio/transcriptions \\
351
- --threads 8 \\
352
- >> "$LOG_FILE" 2>&1
353
- """
354
-
355
- start_script_path = os.path.join(install_dir, "start-whisper-server.sh")
311
+ # Copy template start script for whisper-server
312
+ logger.info("Installing whisper-server start script from template...")
313
+
314
+ # Create bin directory
315
+ bin_dir = os.path.join(install_dir, "bin")
316
+ os.makedirs(bin_dir, exist_ok=True)
317
+
318
+ # Copy template script
319
+ template_content = None
320
+
321
+ # First try to load from source if running in development
322
+ source_template = Path(__file__).parent.parent.parent / "templates" / "scripts" / "start-whisper-server.sh"
323
+ if source_template.exists():
324
+ logger.info(f"Loading template from source: {source_template}")
325
+ template_content = source_template.read_text()
326
+ else:
327
+ # Try loading from package resources
328
+ try:
329
+ template_resource = files("voice_mode.templates.scripts").joinpath("start-whisper-server.sh")
330
+ template_content = template_resource.read_text()
331
+ logger.info("Loaded template from package resources")
332
+ except Exception as e:
333
+ logger.warning(f"Failed to load template script: {e}. Using fallback inline script.")
334
+
335
+ # Create the start script (whether template was loaded from file or created inline)
336
+ start_script_path = os.path.join(bin_dir, "start-whisper-server.sh")
356
337
  with open(start_script_path, 'w') as f:
357
- f.write(start_script_content)
338
+ f.write(template_content)
358
339
  os.chmod(start_script_path, 0o755)
359
340
 
360
341
  # Install launchagent on macOS
@@ -370,33 +351,22 @@ exec "$SERVER_BIN" \\
370
351
  plist_name = "com.voicemode.whisper.plist"
371
352
  plist_path = os.path.join(launchagents_dir, plist_name)
372
353
 
373
- plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
374
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
375
- <plist version="1.0">
376
- <dict>
377
- <key>Label</key>
378
- <string>com.voicemode.whisper</string>
379
- <key>ProgramArguments</key>
380
- <array>
381
- <string>{start_script_path}</string>
382
- </array>
383
- <key>WorkingDirectory</key>
384
- <string>{install_dir}</string>
385
- <key>RunAtLoad</key>
386
- <true/>
387
- <key>KeepAlive</key>
388
- <true/>
389
- <key>StandardOutPath</key>
390
- <string>{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.out.log')}</string>
391
- <key>StandardErrorPath</key>
392
- <string>{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.err.log')}</string>
393
- <key>EnvironmentVariables</key>
394
- <dict>
395
- <key>PATH</key>
396
- <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin</string>
397
- </dict>
398
- </dict>
399
- </plist>"""
354
+ # Load plist template
355
+ # First try to load from source if running in development
356
+ source_template = Path(__file__).parent.parent.parent / "templates" / "launchd" / "com.voicemode.whisper.plist"
357
+ if source_template.exists():
358
+ logger.info(f"Loading plist template from source: {source_template}")
359
+ plist_content = source_template.read_text()
360
+ else:
361
+ # Load from package resources
362
+ template_resource = files("voice_mode.templates.launchd").joinpath("com.voicemode.whisper.plist")
363
+ plist_content = template_resource.read_text()
364
+ logger.info("Loaded plist template from package resources")
365
+
366
+ # Replace placeholders
367
+ plist_content = plist_content.replace("{START_SCRIPT_PATH}", start_script_path)
368
+ plist_content = plist_content.replace("{LOG_DIR}", os.path.join(voicemode_dir, 'logs'))
369
+ plist_content = plist_content.replace("{INSTALL_DIR}", install_dir)
400
370
 
401
371
  with open(plist_path, 'w') as f:
402
372
  f.write(plist_content)
@@ -444,9 +414,8 @@ exec "$SERVER_BIN" \\
444
414
  "start_script": start_script_path,
445
415
  "message": f"Successfully installed whisper.cpp {current_version} with {gpu_type} support and whisper-server on port 2022{enable_message}{' (' + migration_msg + ')' if migration_msg else ''}"
446
416
  }
447
-
448
- # Install systemd service on Linux
449
417
  elif system == "Linux":
418
+ # Install systemd service on Linux
450
419
  logger.info("Installing systemd user service for whisper-server...")
451
420
  systemd_user_dir = os.path.expanduser("~/.config/systemd/user")
452
421
  os.makedirs(systemd_user_dir, exist_ok=True)
@@ -459,23 +428,22 @@ exec "$SERVER_BIN" \\
459
428
  service_path = os.path.join(systemd_user_dir, service_name)
460
429
 
461
430
  service_content = f"""[Unit]
462
- Description=Whisper.cpp Speech Recognition Server
463
- After=network.target
431
+ Description=Whisper.cpp Speech Recognition Server
432
+ After=network.target
464
433
 
465
- [Service]
466
- Type=simple
467
- ExecStart={start_script_path}
468
- Restart=on-failure
469
- RestartSec=10
470
- WorkingDirectory={install_dir}
471
- StandardOutput=append:{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.out.log')}
472
- StandardError=append:{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.err.log')}
473
- Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/cuda/bin"
474
- Environment="VOICEMODE_WHISPER_MODEL={model}"
434
+ [Service]
435
+ Type=simple
436
+ ExecStart={start_script_path}
437
+ Restart=on-failure
438
+ RestartSec=10
439
+ WorkingDirectory={install_dir}
440
+ StandardOutput=append:{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.out.log')}
441
+ StandardError=append:{os.path.join(voicemode_dir, 'logs', 'whisper', 'whisper.err.log')}
442
+ Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/cuda/bin"
475
443
 
476
- [Install]
477
- WantedBy=default.target
478
- """
444
+ [Install]
445
+ WantedBy=default.target
446
+ """
479
447
 
480
448
  with open(service_path, 'w') as f:
481
449
  f.write(service_content)
@@ -510,49 +478,49 @@ WantedBy=default.target
510
478
  current_version = get_current_version(Path(install_dir))
511
479
  return {
512
480
  "success": True,
513
- "install_path": install_dir,
514
- "model_path": model_path,
515
- "gpu_enabled": use_gpu,
516
- "gpu_type": gpu_type,
517
- "version": current_version,
518
- "performance_info": {
519
- "system": system,
520
- "gpu_acceleration": gpu_type,
521
- "model": model,
522
- "binary_path": main_path if 'main_path' in locals() else os.path.join(install_dir, "main"),
523
- "server_port": 2022,
524
- "server_url": "http://localhost:2022"
525
- },
526
- "systemd_service": service_path,
527
- "systemd_enabled": systemd_enabled,
528
- "start_script": start_script_path,
529
- "message": f"Successfully installed whisper.cpp {current_version} with {gpu_type} support. {systemd_message}{enable_message}{' (' + migration_msg + ')' if migration_msg else ''}"
481
+ "install_path": install_dir,
482
+ "model_path": model_path,
483
+ "gpu_enabled": use_gpu,
484
+ "gpu_type": gpu_type,
485
+ "version": current_version,
486
+ "performance_info": {
487
+ "system": system,
488
+ "gpu_acceleration": gpu_type,
489
+ "model": model,
490
+ "binary_path": main_path if 'main_path' in locals() else os.path.join(install_dir, "main"),
491
+ "server_port": 2022,
492
+ "server_url": "http://localhost:2022"
493
+ },
494
+ "systemd_service": service_path,
495
+ "systemd_enabled": systemd_enabled,
496
+ "start_script": start_script_path,
497
+ "message": f"Successfully installed whisper.cpp {current_version} with {gpu_type} support. {systemd_message}{enable_message}{' (' + migration_msg + ')' if migration_msg else ''}"
530
498
  }
531
-
499
+
532
500
  else:
533
501
  # Handle auto_enable for other systems (if we add Windows support later)
534
502
  enable_message = ""
535
503
  if auto_enable is None:
536
- auto_enable = SERVICE_AUTO_ENABLE
504
+ auto_enable = SERVICE_AUTO_ENABLE
537
505
 
538
506
  if auto_enable:
539
- logger.info("Auto-enable not supported on this platform")
507
+ logger.info("Auto-enable not supported on this platform")
540
508
 
541
509
  current_version = get_current_version(Path(install_dir))
542
510
  return {
543
511
  "success": True,
544
- "install_path": install_dir,
545
- "model_path": model_path,
546
- "gpu_enabled": use_gpu,
547
- "gpu_type": gpu_type,
548
- "version": current_version,
549
- "performance_info": {
550
- "system": system,
551
- "gpu_acceleration": gpu_type,
552
- "model": model,
553
- "binary_path": main_path if 'main_path' in locals() else os.path.join(install_dir, "main")
554
- },
555
- "message": f"Successfully installed whisper.cpp {current_version} with {gpu_type} support{enable_message}{' (' + migration_msg + ')' if migration_msg else ''}"
512
+ "install_path": install_dir,
513
+ "model_path": model_path,
514
+ "gpu_enabled": use_gpu,
515
+ "gpu_type": gpu_type,
516
+ "version": current_version,
517
+ "performance_info": {
518
+ "system": system,
519
+ "gpu_acceleration": gpu_type,
520
+ "model": model,
521
+ "binary_path": main_path if 'main_path' in locals() else os.path.join(install_dir, "main")
522
+ },
523
+ "message": f"Successfully installed whisper.cpp {current_version} with {gpu_type} support{enable_message}{' (' + migration_msg + ')' if migration_msg else ''}"
556
524
  }
557
525
 
558
526
  except subprocess.CalledProcessError as e:
@@ -569,4 +537,4 @@ WantedBy=default.target
569
537
  return {
570
538
  "success": False,
571
539
  "error": str(e)
572
- }
540
+ }
@@ -127,7 +127,8 @@ async def whisper_model_install(
127
127
  result = await download_whisper_model(
128
128
  model_name,
129
129
  actual_models_dir,
130
- force_download=force_download
130
+ force_download=force_download,
131
+ skip_core_ml=skip_core_ml
131
132
  )
132
133
 
133
134
  # Build comprehensive result entry
@@ -242,58 +243,48 @@ async def _handle_coreml_dependencies(
242
243
  if skip_core_ml:
243
244
  return {"continue": True}
244
245
 
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
246
+ # Check if the CoreML environment already exists
247
+ whisper_dir = Path.home() / ".voicemode" / "services" / "whisper"
248
+ venv_coreml = whisper_dir / "venv-coreml" / "bin" / "python"
249
+
250
+ if venv_coreml.exists():
251
+ # Test if it has the required packages
252
+ try:
253
+ result = subprocess.run(
254
+ [str(venv_coreml), "-c", "import torch, coremltools, whisper"],
255
+ capture_output=True,
256
+ timeout=5
257
+ )
258
+ if result.returncode == 0:
259
+ logger.info("CoreML environment already exists and is valid")
260
+ # Return with a flag indicating CoreML is ready
261
+ return {
262
+ "continue": True,
263
+ "coreml_ready": True,
264
+ "coreml_deps_note": "CoreML environment exists and is valid"
265
+ }
266
+ except:
267
+ pass
252
268
 
253
- # Check if user wants to install torch
269
+ # Check if user wants to create CoreML environment
254
270
  if not install_torch and not auto_confirm:
255
271
  return {
256
272
  "continue": False,
257
273
  "success": False,
258
274
  "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)"
275
+ "message": "CoreML conversion requires a dedicated Python environment with PyTorch. Setup may download up to 2.5GB if packages aren't cached.",
276
+ "recommendation": "💡 Set install_torch=True for CoreML acceleration (2-3x faster)"
261
277
  }
262
278
 
263
- # Install CoreML dependencies
264
- logger.info("Installing CoreML dependencies...")
279
+ # Note: We don't actually install CoreML dependencies in the voicemode environment anymore
280
+ # The CoreML conversion uses its own dedicated environment in ~/.voicemode/services/whisper/venv-coreml
281
+ # This is handled automatically by whisper_helpers.convert_to_coreml()
265
282
 
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
- }
283
+ logger.info("CoreML dependencies will be handled by the conversion process")
284
+
285
+ # We still return success to continue with the model download
286
+ # The actual CoreML environment setup happens during conversion
287
+ return {
288
+ "continue": True,
289
+ "coreml_deps_note": "CoreML environment will be created during conversion if needed"
290
+ }
@@ -113,7 +113,7 @@ def get_active_model() -> str:
113
113
 
114
114
  # Validate it's a known model
115
115
  if model not in WHISPER_MODEL_REGISTRY:
116
- return "large-v2" # Default fallback
116
+ return "base" # Default fallback
117
117
 
118
118
  return model
119
119