voice-mode-install 5.1.4__py3-none-any.whl → 6.0.4__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.

Potentially problematic release.


This version of voice-mode-install might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-mode-install
3
- Version: 5.1.4
3
+ Version: 6.0.4
4
4
  Summary: Installer for VoiceMode - handles system dependencies and installation
5
5
  Project-URL: Homepage, https://github.com/mbailey/voicemode
6
6
  Project-URL: Repository, https://github.com/mbailey/voicemode
@@ -63,6 +63,7 @@ uvx voice-mode-install --non-interactive
63
63
  - **uv** - Required to run the installer (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
64
64
  - **Python 3.10+** - Usually pre-installed on modern systems
65
65
  - **sudo access** - Needed to install system packages (Linux)
66
+ - **Homebrew** (macOS) - The installer will offer to install it if missing
66
67
 
67
68
  ## Supported Platforms
68
69
 
@@ -80,6 +81,7 @@ uvx voice-mode-install --non-interactive
80
81
  ✅ **Health Check** - Verifies installation after completion
81
82
  ✅ **Version Pinning** - Install specific VoiceMode versions
82
83
  ✅ **Hardware Detection** - Recommends optimal setup for your system
84
+ ✅ **Homebrew Auto-Install** - Offers to install Homebrew on macOS if missing
83
85
 
84
86
  ### Phase 2 (Future)
85
87
 
@@ -90,12 +92,13 @@ uvx voice-mode-install --non-interactive
90
92
 
91
93
  1. **Platform Detection** - Identifies OS, distribution, and architecture
92
94
  2. **Dependency Checking** - Compares installed packages against `dependencies.yaml`
93
- 3. **Package Installation** - Uses platform-specific package managers:
94
- - macOS: `brew install`
95
+ 3. **Package Manager Setup** (macOS only) - Checks for Homebrew and offers to install if missing
96
+ 4. **Package Installation** - Uses platform-specific package managers:
97
+ - macOS: `brew install` (installs Homebrew first if needed)
95
98
  - Ubuntu/Debian: `sudo apt install`
96
99
  - Fedora: `sudo dnf install`
97
- 4. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
98
- 5. **Post-Install** - Configures shell completion and verifies installation
100
+ 5. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
101
+ 6. **Post-Install** - Configures shell completion and verifies installation
99
102
 
100
103
  ## Installation Logs
101
104
 
@@ -0,0 +1,12 @@
1
+ voicemode_install/__init__.py,sha256=_giwAMYtVyzpItm8m9Ebmfw1THrAAqGjNpRQQohRRs0,287
2
+ voicemode_install/checker.py,sha256=DQLIcbNLagkqWG10xyJ-5aH5IuBRoU7X4ZUfcD6M8yw,7314
3
+ voicemode_install/cli.py,sha256=IHw4Cgeuhajx4cKTn1RvAKQuf59SBR1bwQnMMM5xBFs,18535
4
+ voicemode_install/dependencies.yaml,sha256=v9EIa4yMnnDoqZtt8jcp_9XSdqOVjr6lAFOVb7c-jw0,13149
5
+ voicemode_install/hardware.py,sha256=j4bDv1v8bqyhiysWsTD4F-qEJRG-a8Z1o-qPfxofQt4,3529
6
+ voicemode_install/installer.py,sha256=rd4v4HcSGQek-Ta__dV8IG1apaDRtERB08eGEp_9gwQ,5399
7
+ voicemode_install/logger.py,sha256=UpNegF2D-y-7RthzhzSFMiHPRaFV09F2AUxgjm5b2Pw,2859
8
+ voicemode_install/system.py,sha256=8RBJOPH331EBM8zFYySG2c-zZLpQgPmcGWRMZ6NmUp4,5108
9
+ voice_mode_install-6.0.4.dist-info/METADATA,sha256=NTPFyL6VVBux9txTxCpGyDU0tZf3kYYRGkSqHQpFjV4,6227
10
+ voice_mode_install-6.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ voice_mode_install-6.0.4.dist-info/entry_points.txt,sha256=q6994IQ1Ex4weqoPy9VrL_k7NuKSG5wgaY-_hQjzjnc,66
12
+ voice_mode_install-6.0.4.dist-info/RECORD,,
@@ -1,3 +1,8 @@
1
1
  """VoiceMode Installer - System dependency management and installation."""
2
2
 
3
- __version__ = "1.0.0"
3
+ try:
4
+ from importlib.metadata import version
5
+ __version__ = version("voice-mode-install")
6
+ except Exception:
7
+ # Fallback to version from pyproject.toml if package not installed
8
+ __version__ = "5.1.6"
@@ -156,7 +156,16 @@ class DependencyChecker:
156
156
  text=True
157
157
  )
158
158
  # dpkg -l returns 0 even if package not found, check output
159
- return result.returncode == 0 and package_name in result.stdout
159
+ # Look for lines starting with "ii" (installed) followed by the exact package name
160
+ # Note: On ARM64 and other architectures, dpkg may include :arch suffix (e.g., python3-dev:arm64)
161
+ for line in result.stdout.splitlines():
162
+ parts = line.split()
163
+ if len(parts) >= 2 and parts[0] == 'ii':
164
+ # Strip architecture suffix (e.g., :arm64) if present for comparison
165
+ pkg_name_in_output = parts[1].split(':')[0]
166
+ if pkg_name_in_output == package_name:
167
+ return True
168
+ return False
160
169
  except FileNotFoundError:
161
170
  return False
162
171
 
voicemode_install/cli.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Main CLI for VoiceMode installer."""
2
2
 
3
+ import json
3
4
  import shutil
4
5
  import subprocess
5
6
  import sys
@@ -12,7 +13,7 @@ from .checker import DependencyChecker
12
13
  from .hardware import HardwareInfo
13
14
  from .installer import PackageInstaller
14
15
  from .logger import InstallLogger
15
- from .system import detect_platform, get_system_info, check_command_exists
16
+ from .system import detect_platform, get_system_info, check_command_exists, check_homebrew_installed
16
17
 
17
18
 
18
19
  LOGO = """
@@ -32,15 +33,17 @@ LOGO = """
32
33
  ║ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ║
33
34
  ║ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
34
35
  ║ ║
35
- 🎙️ VoiceMode Installer
36
+ 🎙️ VoiceMode Installer
36
37
  ║ ║
37
38
  ╚════════════════════════════════════════════╝
38
39
  """
39
40
 
40
41
 
41
42
  def print_logo():
42
- """Display the VoiceMode logo."""
43
- click.echo(click.style(LOGO, fg='bright_yellow', bold=True))
43
+ """Display the VoiceMode logo in Claude Code orange."""
44
+ # Use ANSI 256-color code 208 (dark orange) which matches Claude Code orange (RGB 208, 128, 0)
45
+ # This works on xterm-256color and other 256-color terminals
46
+ click.echo('\033[38;5;208m' + '\033[1m' + LOGO + '\033[0m')
44
47
 
45
48
 
46
49
  def print_step(message: str):
@@ -54,8 +57,9 @@ def print_success(message: str):
54
57
 
55
58
 
56
59
  def print_warning(message: str):
57
- """Print a warning message."""
58
- click.echo(click.style(f"⚠️ {message}", fg='yellow'))
60
+ """Print a warning message in Claude Code orange."""
61
+ # Use ANSI 256-color code 208 (dark orange)
62
+ click.echo('\033[38;5;208m' + f"⚠️ {message}" + '\033[0m')
59
63
 
60
64
 
61
65
  def print_error(message: str):
@@ -63,44 +67,155 @@ def print_error(message: str):
63
67
  click.echo(click.style(f"❌ {message}", fg='red'))
64
68
 
65
69
 
70
+ def get_installed_version() -> str | None:
71
+ """Get the currently installed VoiceMode version."""
72
+ try:
73
+ result = subprocess.run(
74
+ ['voicemode', '--version'],
75
+ capture_output=True,
76
+ text=True,
77
+ timeout=5
78
+ )
79
+ if result.returncode == 0:
80
+ # Output is like "VoiceMode version 6.0.1" or just "6.0.1"
81
+ version = result.stdout.strip().split()[-1]
82
+ return version
83
+ except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
84
+ pass
85
+ return None
86
+
87
+
88
+ def get_latest_version() -> str | None:
89
+ """Get the latest VoiceMode version from PyPI."""
90
+ try:
91
+ # Use PyPI JSON API to get latest version
92
+ result = subprocess.run(
93
+ ['curl', '-s', 'https://pypi.org/pypi/voice-mode/json'],
94
+ capture_output=True,
95
+ text=True,
96
+ timeout=10
97
+ )
98
+ if result.returncode == 0:
99
+ data = json.loads(result.stdout)
100
+ return data['info']['version']
101
+ except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
102
+ pass
103
+ return None
104
+
105
+
66
106
  def check_existing_installation() -> bool:
67
107
  """Check if VoiceMode is already installed."""
68
108
  return check_command_exists('voicemode')
69
109
 
70
110
 
71
- @click.command()
72
- @click.option('--dry-run', is_flag=True, help='Show what would be installed without installing')
73
- @click.option('--voice-mode-version', default=None, help='Specific VoiceMode version to install')
74
- @click.option('--skip-services', is_flag=True, help='Skip local service installation')
75
- @click.option('--non-interactive', is_flag=True, help='Run without prompts (assumes yes)')
76
- @click.version_option(__version__)
77
- def main(dry_run, voice_mode_version, skip_services, non_interactive):
111
+ def ensure_homebrew_on_macos(platform_info, dry_run: bool, non_interactive: bool) -> bool:
78
112
  """
79
- VoiceMode Installer - Install VoiceMode and its system dependencies.
113
+ Ensure Homebrew is installed on macOS before checking dependencies.
114
+
115
+ Returns True if Homebrew is available or successfully installed, False otherwise.
116
+ """
117
+ # Only needed on macOS
118
+ if platform_info.distribution != 'darwin':
119
+ return True
120
+
121
+ # Check if already installed
122
+ if check_homebrew_installed():
123
+ return True
124
+
125
+ # Not installed
126
+ print_warning("Homebrew is not installed")
127
+ click.echo("Homebrew is the package manager required to install system dependencies on macOS.")
128
+ click.echo("Visit: https://brew.sh")
129
+ click.echo()
130
+
131
+ if dry_run:
132
+ print_step("[DRY RUN] Would install Homebrew (macOS package manager)")
133
+ return True
134
+
135
+ if non_interactive:
136
+ print_error("Homebrew not found and running in non-interactive mode")
137
+ click.echo("Please install Homebrew manually:")
138
+ click.echo(' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
139
+ return False
140
+
141
+ # Prompt user
142
+ if not click.confirm("Install Homebrew now?", default=True):
143
+ print_error("Homebrew installation declined")
144
+ click.echo("Please install Homebrew manually and run the installer again.")
145
+ return False
146
+
147
+ # Install Homebrew
148
+ print_step("Installing Homebrew...")
149
+ click.echo("This may take a few minutes and will require your password.")
150
+ click.echo()
151
+
152
+ try:
153
+ install_script = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
154
+ result = subprocess.run(install_script, shell=True, check=True)
155
+
156
+ if result.returncode == 0:
157
+ print_success("Homebrew installed successfully")
158
+
159
+ # Verify
160
+ if check_homebrew_installed():
161
+ return True
162
+ else:
163
+ print_warning("Homebrew was installed but 'brew' command not found in PATH")
164
+ click.echo("You may need to add Homebrew to your PATH. Check the installation output above.")
165
+ return False
166
+ else:
167
+ print_error("Homebrew installation returned non-zero exit code")
168
+ return False
169
+
170
+ except subprocess.CalledProcessError as e:
171
+ print_error(f"Error installing Homebrew: {e}")
172
+ return False
173
+ except Exception as e:
174
+ print_error(f"Unexpected error installing Homebrew: {e}")
175
+ return False
176
+
177
+
178
+ EPILOG = """
179
+ \b
180
+ Examples:
181
+ # Normal installation
182
+ voice-mode-install
183
+
184
+ # Dry run (see what would be installed)
185
+ voice-mode-install --dry-run
186
+
187
+ # Install specific version
188
+ voice-mode-install --voice-mode-version=5.1.3
189
+
190
+ # Skip service installation
191
+ voice-mode-install --skip-services
192
+ """
193
+
194
+
195
+ @click.command(epilog=EPILOG, context_settings={'help_option_names': ['-h', '--help']})
196
+ @click.option('-d', '--dry-run', is_flag=True, help='Show what would be installed without installing')
197
+ @click.option('-v', '--voice-mode-version', default=None, help='Specific VoiceMode version to install')
198
+ @click.option('-s', '--skip-services', is_flag=True, help='Skip local service installation')
199
+ @click.option('-n', '--non-interactive', is_flag=True, help='Run without prompts (assumes yes)')
200
+ @click.version_option(__version__, '-V', '--version')
201
+ def main(dry_run, voice_mode_version, skip_services, non_interactive):
202
+ """VoiceMode Installer - Install VoiceMode and its system dependencies.
80
203
 
81
204
  This installer will:
82
- \b
83
- 1. Detect your operating system and architecture
84
- 2. Check for missing system dependencies
85
- 3. Install required packages (with your permission)
86
- 4. Install VoiceMode using uv
87
- 5. Optionally install local voice services
88
- 6. Configure shell completions
89
- 7. Verify the installation
90
-
91
- Examples:
92
- \b
93
- # Normal installation
94
- voice-mode-install
95
-
96
- # Dry run (see what would be installed)
97
- voice-mode-install --dry-run
98
-
99
- # Install specific version
100
- voice-mode-install --voice-mode-version=5.1.3
101
-
102
- # Skip service installation
103
- voice-mode-install --skip-services
205
+
206
+ 1. Detect your operating system and architecture
207
+
208
+ 2. Check for missing system dependencies
209
+
210
+ 3. Install required packages (with your permission)
211
+
212
+ 4. Install VoiceMode using uv
213
+
214
+ 5. Optionally install local voice services
215
+
216
+ 6. Configure shell completions
217
+
218
+ 7. Verify the installation
104
219
  """
105
220
  # Initialize logger
106
221
  logger = InstallLogger()
@@ -128,13 +243,57 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
128
243
  print_warning("WSL detected - additional audio configuration may be needed")
129
244
  click.echo()
130
245
 
246
+ # Ensure Homebrew is installed on macOS (before checking dependencies)
247
+ if not ensure_homebrew_on_macos(platform_info, dry_run, non_interactive):
248
+ logger.log_error("Homebrew installation required but not available")
249
+ sys.exit(1)
250
+
131
251
  # Check for existing installation
132
252
  if check_existing_installation():
133
- print_warning("VoiceMode is already installed")
134
- if not non_interactive:
135
- if not click.confirm("Do you want to upgrade it?", default=False):
136
- click.echo("\nTo upgrade manually, run: uv tool install --upgrade voice-mode")
137
- sys.exit(0)
253
+ installed_version = get_installed_version()
254
+ latest_version = get_latest_version()
255
+
256
+ click.echo(click.style(" VoiceMode is currently installed", fg='green'))
257
+
258
+ if installed_version:
259
+ click.echo(f" Installed version: {installed_version}")
260
+ else:
261
+ click.echo(" Installed version: (unable to detect)")
262
+
263
+ if latest_version:
264
+ click.echo(f" Latest version: {latest_version}")
265
+
266
+ # Check if update is available
267
+ if installed_version and latest_version and installed_version != latest_version:
268
+ click.echo()
269
+ if non_interactive:
270
+ print_step("Upgrading VoiceMode...")
271
+ elif not click.confirm(f"Upgrade to version {latest_version}?", default=True):
272
+ click.echo("\nTo upgrade manually later, run: uv tool install --upgrade voice-mode")
273
+ sys.exit(0)
274
+ elif installed_version and latest_version and installed_version == latest_version:
275
+ click.echo()
276
+ click.echo(click.style("✓ VoiceMode is up-to-date", fg='green'))
277
+ if non_interactive:
278
+ click.echo("Reinstalling...")
279
+ elif not click.confirm("Reinstall anyway?", default=False):
280
+ click.echo("\nInstallation cancelled.")
281
+ sys.exit(0)
282
+ else:
283
+ click.echo()
284
+ if not non_interactive:
285
+ if not click.confirm("Reinstall VoiceMode?", default=False):
286
+ click.echo("\nTo upgrade manually, run: uv tool install --upgrade voice-mode")
287
+ sys.exit(0)
288
+ else:
289
+ click.echo(" Latest version: (unable to check)")
290
+ click.echo()
291
+ if not non_interactive:
292
+ if not click.confirm("Reinstall/upgrade VoiceMode?", default=False):
293
+ click.echo("\nTo upgrade manually, run: uv tool install --upgrade voice-mode")
294
+ sys.exit(0)
295
+
296
+ click.echo()
138
297
 
139
298
  # Check dependencies
140
299
  print_step("Checking system dependencies...")
@@ -169,7 +328,7 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
169
328
  print_error("Cannot proceed without required dependencies")
170
329
  sys.exit(1)
171
330
 
172
- installer = PackageInstaller(platform_info, dry_run=dry_run)
331
+ installer = PackageInstaller(platform_info, dry_run=dry_run, non_interactive=non_interactive)
173
332
  if installer.install_packages(missing_deps):
174
333
  print_success("System dependencies installed")
175
334
  logger.log_install('system', missing_names, True)
@@ -185,7 +344,7 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
185
344
 
186
345
  # Install VoiceMode
187
346
  print_step("Installing VoiceMode...")
188
- installer = PackageInstaller(platform_info, dry_run=dry_run)
347
+ installer = PackageInstaller(platform_info, dry_run=dry_run, non_interactive=non_interactive)
189
348
 
190
349
  if installer.install_voicemode(version=voice_mode_version):
191
350
  print_success("VoiceMode installed successfully")
@@ -84,7 +84,7 @@ voicemode:
84
84
  - name: python3-dev
85
85
  description: Python development headers (needed for building extensions)
86
86
  required: true
87
- check_command: dpkg -l python3-dev
87
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
88
88
  note: Required for compiling webrtcvad (C extension used for silence detection)
89
89
 
90
90
  - name: gcc
@@ -102,23 +102,23 @@ voicemode:
102
102
  - name: libasound2-dev
103
103
  description: ALSA development files (audio support)
104
104
  required: true
105
- check_command: dpkg -l libasound2-dev
105
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
106
106
  note: Required for compiling simpleaudio (C extension used for audio playback)
107
107
 
108
108
  - name: libasound2-plugins
109
109
  description: ALSA plugins for PulseAudio
110
110
  required: false
111
- check_command: dpkg -l libasound2-plugins
111
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
112
112
 
113
113
  - name: libportaudio2
114
114
  description: PortAudio library (for sounddevice)
115
115
  required: true
116
- check_command: dpkg -l libportaudio2
116
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
117
117
 
118
118
  - name: portaudio19-dev
119
119
  description: PortAudio development files
120
120
  required: false
121
- check_command: dpkg -l portaudio19-dev
121
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
122
122
  note: Optional - only needed for building, not runtime (libportaudio2 has the runtime library)
123
123
 
124
124
  - name: pulseaudio
@@ -130,7 +130,7 @@ voicemode:
130
130
  - name: pulseaudio-utils
131
131
  description: PulseAudio utilities
132
132
  required: wsl # Required for WSL, optional for native Linux
133
- check_command: dpkg -l pulseaudio-utils
133
+ # Don't use check_command - let it use _check_apt_package which properly checks dpkg output
134
134
  note: Essential for WSL2 audio. Usually pre-installed on native Linux desktops.
135
135
 
136
136
  - name: ffmpeg
@@ -210,6 +210,14 @@ voicemode:
210
210
  whisper:
211
211
  description: Whisper STT service dependencies
212
212
 
213
+ common: # All platforms
214
+ packages:
215
+ - name: git
216
+ description: Version control system (needed to clone whisper.cpp repository)
217
+ required: true
218
+ check_command: git --version
219
+ note: Required for cloning whisper.cpp from GitHub
220
+
213
221
  debian:
214
222
  packages:
215
223
  - name: cmake
@@ -10,9 +10,10 @@ from .system import PlatformInfo, get_package_manager
10
10
  class PackageInstaller:
11
11
  """Install system packages using platform-specific package managers."""
12
12
 
13
- def __init__(self, platform_info: PlatformInfo, dry_run: bool = False):
13
+ def __init__(self, platform_info: PlatformInfo, dry_run: bool = False, non_interactive: bool = False):
14
14
  self.platform = platform_info
15
15
  self.dry_run = dry_run
16
+ self.non_interactive = non_interactive
16
17
  self.package_manager = get_package_manager(platform_info.distribution)
17
18
 
18
19
  def install_packages(self, packages: List[PackageInfo]) -> bool:
@@ -45,7 +46,11 @@ class PackageInstaller:
45
46
  return False
46
47
 
47
48
  def _install_homebrew(self, packages: List[str]) -> bool:
48
- """Install packages using Homebrew."""
49
+ """Install packages using Homebrew.
50
+
51
+ Note: Homebrew should already be installed by the time this is called.
52
+ The CLI ensures Homebrew is present before dependency checking.
53
+ """
49
54
  try:
50
55
  cmd = ['brew', 'install'] + packages
51
56
  result = subprocess.run(
@@ -55,11 +60,11 @@ class PackageInstaller:
55
60
  )
56
61
  return result.returncode == 0
57
62
  except subprocess.CalledProcessError as e:
58
- print(f"Homebrew installation failed: {e}")
63
+ print(f"Homebrew package installation failed: {e}")
59
64
  return False
60
65
  except FileNotFoundError:
61
- print("Error: Homebrew not found. Please install Homebrew first.")
62
- print("Visit: https://brew.sh")
66
+ print("Error: Homebrew not found. This should have been installed earlier.")
67
+ print("Please report this as a bug.")
63
68
  return False
64
69
 
65
70
  def _install_apt(self, packages: List[str]) -> bool:
@@ -107,7 +112,7 @@ class PackageInstaller:
107
112
 
108
113
  def install_voicemode(self, version: Optional[str] = None) -> bool:
109
114
  """
110
- Install voice-mode using uv tool install.
115
+ Install or upgrade voice-mode using uv tool install --upgrade.
111
116
 
112
117
  Args:
113
118
  version: Optional version to install (e.g., "5.1.3")
@@ -117,16 +122,18 @@ class PackageInstaller:
117
122
  """
118
123
  if self.dry_run:
119
124
  if version:
120
- print(f"[DRY RUN] Would install: uv tool install voice-mode=={version}")
125
+ print(f"[DRY RUN] Would install: uv tool install --upgrade voice-mode=={version}")
121
126
  else:
122
- print("[DRY RUN] Would install: uv tool install voice-mode")
127
+ print("[DRY RUN] Would install: uv tool install --upgrade voice-mode")
123
128
  return True
124
129
 
125
130
  try:
131
+ # Always use --upgrade to ensure we get the latest/requested version
132
+ # This also implies --refresh to check for new versions
126
133
  if version:
127
- cmd = ['uv', 'tool', 'install', f'voice-mode=={version}']
134
+ cmd = ['uv', 'tool', 'install', '--upgrade', f'voice-mode=={version}']
128
135
  else:
129
- cmd = ['uv', 'tool', 'install', 'voice-mode']
136
+ cmd = ['uv', 'tool', 'install', '--upgrade', 'voice-mode']
130
137
 
131
138
  result = subprocess.run(
132
139
  cmd,
@@ -126,6 +126,11 @@ def check_command_exists(command: str) -> bool:
126
126
  return shutil.which(command) is not None
127
127
 
128
128
 
129
+ def check_homebrew_installed() -> bool:
130
+ """Check if Homebrew is installed (macOS only)."""
131
+ return check_command_exists('brew')
132
+
133
+
129
134
  def get_system_info() -> dict:
130
135
  """Get comprehensive system information for logging."""
131
136
  platform_info = detect_platform()
@@ -1,12 +0,0 @@
1
- voicemode_install/__init__.py,sha256=xKbCS78wqA7H42wWj8a3VgpYQ700boLgSgyRW_7P_WE,98
2
- voicemode_install/checker.py,sha256=yXfLYkPxCKK64hQKQuuaIkZly_0GksVmNHpzV3gKdRI,6766
3
- voicemode_install/cli.py,sha256=IyIbHbnq11rHWG98vjU-3ddco7mC6SnI6bkrRxhD1Qs,12170
4
- voicemode_install/dependencies.yaml,sha256=xaOt6UKY-XwFI8HFanUUd2zrxMWskkgBevYXZSEpvOM,12550
5
- voicemode_install/hardware.py,sha256=j4bDv1v8bqyhiysWsTD4F-qEJRG-a8Z1o-qPfxofQt4,3529
6
- voicemode_install/installer.py,sha256=vS2G0t6a1jZcxls_go7oDMqnNYLHZF2H_gZa1dRusx0,4920
7
- voicemode_install/logger.py,sha256=UpNegF2D-y-7RthzhzSFMiHPRaFV09F2AUxgjm5b2Pw,2859
8
- voicemode_install/system.py,sha256=TBWdP9pNTH9BtaZwe4NyzvEDoQuBCj6gqHtJyAyFiqo,4971
9
- voice_mode_install-5.1.4.dist-info/METADATA,sha256=QootH_aD1vR3xafpsL5FBG0WlQqzLOpoJJ-uHb1640w,5940
10
- voice_mode_install-5.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- voice_mode_install-5.1.4.dist-info/entry_points.txt,sha256=q6994IQ1Ex4weqoPy9VrL_k7NuKSG5wgaY-_hQjzjnc,66
12
- voice_mode_install-5.1.4.dist-info/RECORD,,