voice-mode-install 1.0.1__tar.gz → 6.0.3__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.

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: 1.0.1
3
+ Version: 6.0.3
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
@@ -24,13 +24,13 @@ Requires-Dist: psutil>=5.9.0
24
24
  Requires-Dist: pyyaml>=6.0.0
25
25
  Description-Content-Type: text/markdown
26
26
 
27
- # voicemode-install
27
+ # voice-mode-install
28
28
 
29
29
  A standalone installer package for VoiceMode that handles system dependency detection and installation.
30
30
 
31
31
  ## Overview
32
32
 
33
- `voicemode-install` simplifies the VoiceMode installation process by:
33
+ `voice-mode-install` simplifies the VoiceMode installation process by:
34
34
 
35
35
  1. **Detecting your platform** - Identifies your OS, distribution, and architecture
36
36
  2. **Checking dependencies** - Scans for required system packages
@@ -43,19 +43,19 @@ A standalone installer package for VoiceMode that handles system dependency dete
43
43
 
44
44
  ```bash
45
45
  # Install and run
46
- uvx voicemode-install
46
+ uvx voice-mode-install
47
47
 
48
48
  # Dry run (see what would be installed)
49
- uvx voicemode-install --dry-run
49
+ uvx voice-mode-install --dry-run
50
50
 
51
51
  # Install specific version
52
- uvx voicemode-install --voice-mode-version=5.1.3
52
+ uvx voice-mode-install --voice-mode-version=5.1.3
53
53
 
54
54
  # Skip service installation
55
- uvx voicemode-install --skip-services
55
+ uvx voice-mode-install --skip-services
56
56
 
57
57
  # Non-interactive mode
58
- uvx voicemode-install --non-interactive
58
+ uvx voice-mode-install --non-interactive
59
59
  ```
60
60
 
61
61
  ## Prerequisites
@@ -63,6 +63,7 @@ uvx voicemode-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 voicemode-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 voicemode-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
 
@@ -122,19 +125,19 @@ source ~/.bashrc # or ~/.zshrc for zsh
122
125
  The installer needs sudo access to install system packages. Run:
123
126
  ```bash
124
127
  sudo -v # Refresh sudo credentials
125
- uvx voicemode-install
128
+ uvx voice-mode-install
126
129
  ```
127
130
 
128
131
  ### Network errors during installation
129
132
 
130
133
  - Check your internet connection
131
- - Try again with: `uvx voicemode-install`
132
- - Use `uvx --refresh voicemode-install` to get the latest installer
134
+ - Try again with: `uvx voice-mode-install`
135
+ - Use `uvx --refresh voice-mode-install` to get the latest installer
133
136
 
134
137
  ### Installation hangs or fails
135
138
 
136
139
  1. Check the log file: `~/.voicemode/install.log`
137
- 2. Try a dry run: `uvx voicemode-install --dry-run`
140
+ 2. Try a dry run: `uvx voice-mode-install --dry-run`
138
141
  3. Report issues with log file attached
139
142
 
140
143
  ## Development
@@ -151,7 +154,7 @@ uv build
151
154
  ```bash
152
155
  cd installer/
153
156
  uv pip install -e .
154
- voicemode-install --dry-run
157
+ voice-mode-install --dry-run
155
158
  ```
156
159
 
157
160
  ### Project Structure
@@ -1,10 +1,10 @@
1
- # voicemode-install
1
+ # voice-mode-install
2
2
 
3
3
  A standalone installer package for VoiceMode that handles system dependency detection and installation.
4
4
 
5
5
  ## Overview
6
6
 
7
- `voicemode-install` simplifies the VoiceMode installation process by:
7
+ `voice-mode-install` simplifies the VoiceMode installation process by:
8
8
 
9
9
  1. **Detecting your platform** - Identifies your OS, distribution, and architecture
10
10
  2. **Checking dependencies** - Scans for required system packages
@@ -17,19 +17,19 @@ A standalone installer package for VoiceMode that handles system dependency dete
17
17
 
18
18
  ```bash
19
19
  # Install and run
20
- uvx voicemode-install
20
+ uvx voice-mode-install
21
21
 
22
22
  # Dry run (see what would be installed)
23
- uvx voicemode-install --dry-run
23
+ uvx voice-mode-install --dry-run
24
24
 
25
25
  # Install specific version
26
- uvx voicemode-install --voice-mode-version=5.1.3
26
+ uvx voice-mode-install --voice-mode-version=5.1.3
27
27
 
28
28
  # Skip service installation
29
- uvx voicemode-install --skip-services
29
+ uvx voice-mode-install --skip-services
30
30
 
31
31
  # Non-interactive mode
32
- uvx voicemode-install --non-interactive
32
+ uvx voice-mode-install --non-interactive
33
33
  ```
34
34
 
35
35
  ## Prerequisites
@@ -37,6 +37,7 @@ uvx voicemode-install --non-interactive
37
37
  - **uv** - Required to run the installer (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
38
38
  - **Python 3.10+** - Usually pre-installed on modern systems
39
39
  - **sudo access** - Needed to install system packages (Linux)
40
+ - **Homebrew** (macOS) - The installer will offer to install it if missing
40
41
 
41
42
  ## Supported Platforms
42
43
 
@@ -54,6 +55,7 @@ uvx voicemode-install --non-interactive
54
55
  ✅ **Health Check** - Verifies installation after completion
55
56
  ✅ **Version Pinning** - Install specific VoiceMode versions
56
57
  ✅ **Hardware Detection** - Recommends optimal setup for your system
58
+ ✅ **Homebrew Auto-Install** - Offers to install Homebrew on macOS if missing
57
59
 
58
60
  ### Phase 2 (Future)
59
61
 
@@ -64,12 +66,13 @@ uvx voicemode-install --non-interactive
64
66
 
65
67
  1. **Platform Detection** - Identifies OS, distribution, and architecture
66
68
  2. **Dependency Checking** - Compares installed packages against `dependencies.yaml`
67
- 3. **Package Installation** - Uses platform-specific package managers:
68
- - macOS: `brew install`
69
+ 3. **Package Manager Setup** (macOS only) - Checks for Homebrew and offers to install if missing
70
+ 4. **Package Installation** - Uses platform-specific package managers:
71
+ - macOS: `brew install` (installs Homebrew first if needed)
69
72
  - Ubuntu/Debian: `sudo apt install`
70
73
  - Fedora: `sudo dnf install`
71
- 4. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
72
- 5. **Post-Install** - Configures shell completion and verifies installation
74
+ 5. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
75
+ 6. **Post-Install** - Configures shell completion and verifies installation
73
76
 
74
77
  ## Installation Logs
75
78
 
@@ -96,19 +99,19 @@ source ~/.bashrc # or ~/.zshrc for zsh
96
99
  The installer needs sudo access to install system packages. Run:
97
100
  ```bash
98
101
  sudo -v # Refresh sudo credentials
99
- uvx voicemode-install
102
+ uvx voice-mode-install
100
103
  ```
101
104
 
102
105
  ### Network errors during installation
103
106
 
104
107
  - Check your internet connection
105
- - Try again with: `uvx voicemode-install`
106
- - Use `uvx --refresh voicemode-install` to get the latest installer
108
+ - Try again with: `uvx voice-mode-install`
109
+ - Use `uvx --refresh voice-mode-install` to get the latest installer
107
110
 
108
111
  ### Installation hangs or fails
109
112
 
110
113
  1. Check the log file: `~/.voicemode/install.log`
111
- 2. Try a dry run: `uvx voicemode-install --dry-run`
114
+ 2. Try a dry run: `uvx voice-mode-install --dry-run`
112
115
  3. Report issues with log file attached
113
116
 
114
117
  ## Development
@@ -125,7 +128,7 @@ uv build
125
128
  ```bash
126
129
  cd installer/
127
130
  uv pip install -e .
128
- voicemode-install --dry-run
131
+ voice-mode-install --dry-run
129
132
  ```
130
133
 
131
134
  ### Project Structure
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "voice-mode-install"
7
- version = "1.0.1"
7
+ version = "6.0.3"
8
8
  description = "Installer for VoiceMode - handles system dependencies and installation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -38,7 +38,7 @@ Repository = "https://github.com/mbailey/voicemode"
38
38
  Issues = "https://github.com/mbailey/voicemode/issues"
39
39
 
40
40
  [project.scripts]
41
- voicemode-install = "voicemode_install.cli:main"
41
+ voice-mode-install = "voicemode_install.cli:main"
42
42
 
43
43
  [tool.hatch.build.targets.wheel]
44
44
  packages = ["voicemode_install"]
@@ -0,0 +1,8 @@
1
+ """VoiceMode Installer - System dependency management and installation."""
2
+
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
 
@@ -12,7 +12,7 @@ from .checker import DependencyChecker
12
12
  from .hardware import HardwareInfo
13
13
  from .installer import PackageInstaller
14
14
  from .logger import InstallLogger
15
- from .system import detect_platform, get_system_info, check_command_exists
15
+ from .system import detect_platform, get_system_info, check_command_exists, check_homebrew_installed
16
16
 
17
17
 
18
18
  LOGO = """
@@ -32,15 +32,15 @@ LOGO = """
32
32
  ║ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ║
33
33
  ║ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
34
34
  ║ ║
35
- 🎙️ VoiceMode Installer
35
+ 🎙️ VoiceMode Installer
36
36
  ║ ║
37
37
  ╚════════════════════════════════════════════╝
38
38
  """
39
39
 
40
40
 
41
41
  def print_logo():
42
- """Display the VoiceMode logo."""
43
- click.echo(click.style(LOGO, fg='bright_yellow', bold=True))
42
+ """Display the VoiceMode logo in Claude Code orange."""
43
+ click.echo(click.style(LOGO, fg=(208, 128, 0), bold=True))
44
44
 
45
45
 
46
46
  def print_step(message: str):
@@ -54,8 +54,9 @@ def print_success(message: str):
54
54
 
55
55
 
56
56
  def print_warning(message: str):
57
- """Print a warning message."""
58
- click.echo(click.style(f"⚠️ {message}", fg='yellow'))
57
+ """Print a warning message in Claude Code orange."""
58
+ # Use ANSI color code for Claude Code orange: \033[38;5;208m
59
+ click.echo(click.style(f"⚠️ {message}", fg=(208, 128, 0)))
59
60
 
60
61
 
61
62
  def print_error(message: str):
@@ -63,44 +64,160 @@ def print_error(message: str):
63
64
  click.echo(click.style(f"❌ {message}", fg='red'))
64
65
 
65
66
 
67
+ def get_installed_version() -> str | None:
68
+ """Get the currently installed VoiceMode version."""
69
+ try:
70
+ result = subprocess.run(
71
+ ['voicemode', '--version'],
72
+ capture_output=True,
73
+ text=True,
74
+ timeout=5
75
+ )
76
+ if result.returncode == 0:
77
+ # Output is like "VoiceMode version 6.0.1" or just "6.0.1"
78
+ version = result.stdout.strip().split()[-1]
79
+ return version
80
+ except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
81
+ pass
82
+ return None
83
+
84
+
85
+ def get_latest_version() -> str | None:
86
+ """Get the latest VoiceMode version from PyPI."""
87
+ try:
88
+ result = subprocess.run(
89
+ ['uv', 'pip', 'index', 'versions', 'voice-mode'],
90
+ capture_output=True,
91
+ text=True,
92
+ timeout=10
93
+ )
94
+ if result.returncode == 0:
95
+ # Parse output to get latest version
96
+ for line in result.stdout.split('\n'):
97
+ if 'Available versions:' in line or line.strip().startswith('voice-mode'):
98
+ # Extract first version listed (usually the latest)
99
+ parts = line.split()
100
+ for part in parts:
101
+ if part[0].isdigit():
102
+ return part.rstrip(',')
103
+ except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
104
+ pass
105
+ return None
106
+
107
+
66
108
  def check_existing_installation() -> bool:
67
109
  """Check if VoiceMode is already installed."""
68
110
  return check_command_exists('voicemode')
69
111
 
70
112
 
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):
113
+ def ensure_homebrew_on_macos(platform_info, dry_run: bool, non_interactive: bool) -> bool:
78
114
  """
79
- VoiceMode Installer - Install VoiceMode and its system dependencies.
115
+ Ensure Homebrew is installed on macOS before checking dependencies.
116
+
117
+ Returns True if Homebrew is available or successfully installed, False otherwise.
118
+ """
119
+ # Only needed on macOS
120
+ if platform_info.distribution != 'darwin':
121
+ return True
122
+
123
+ # Check if already installed
124
+ if check_homebrew_installed():
125
+ return True
126
+
127
+ # Not installed
128
+ print_warning("Homebrew is not installed")
129
+ click.echo("Homebrew is the package manager required to install system dependencies on macOS.")
130
+ click.echo("Visit: https://brew.sh")
131
+ click.echo()
132
+
133
+ if dry_run:
134
+ print_step("[DRY RUN] Would install Homebrew (macOS package manager)")
135
+ return True
136
+
137
+ if non_interactive:
138
+ print_error("Homebrew not found and running in non-interactive mode")
139
+ click.echo("Please install Homebrew manually:")
140
+ click.echo(' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"')
141
+ return False
142
+
143
+ # Prompt user
144
+ if not click.confirm("Install Homebrew now?", default=True):
145
+ print_error("Homebrew installation declined")
146
+ click.echo("Please install Homebrew manually and run the installer again.")
147
+ return False
148
+
149
+ # Install Homebrew
150
+ print_step("Installing Homebrew...")
151
+ click.echo("This may take a few minutes and will require your password.")
152
+ click.echo()
153
+
154
+ try:
155
+ install_script = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
156
+ result = subprocess.run(install_script, shell=True, check=True)
157
+
158
+ if result.returncode == 0:
159
+ print_success("Homebrew installed successfully")
160
+
161
+ # Verify
162
+ if check_homebrew_installed():
163
+ return True
164
+ else:
165
+ print_warning("Homebrew was installed but 'brew' command not found in PATH")
166
+ click.echo("You may need to add Homebrew to your PATH. Check the installation output above.")
167
+ return False
168
+ else:
169
+ print_error("Homebrew installation returned non-zero exit code")
170
+ return False
171
+
172
+ except subprocess.CalledProcessError as e:
173
+ print_error(f"Error installing Homebrew: {e}")
174
+ return False
175
+ except Exception as e:
176
+ print_error(f"Unexpected error installing Homebrew: {e}")
177
+ return False
178
+
179
+
180
+ EPILOG = """
181
+ \b
182
+ Examples:
183
+ # Normal installation
184
+ voice-mode-install
185
+
186
+ # Dry run (see what would be installed)
187
+ voice-mode-install --dry-run
188
+
189
+ # Install specific version
190
+ voice-mode-install --voice-mode-version=5.1.3
191
+
192
+ # Skip service installation
193
+ voice-mode-install --skip-services
194
+ """
195
+
196
+
197
+ @click.command(epilog=EPILOG, context_settings={'help_option_names': ['-h', '--help']})
198
+ @click.option('-d', '--dry-run', is_flag=True, help='Show what would be installed without installing')
199
+ @click.option('-v', '--voice-mode-version', default=None, help='Specific VoiceMode version to install')
200
+ @click.option('-s', '--skip-services', is_flag=True, help='Skip local service installation')
201
+ @click.option('-n', '--non-interactive', is_flag=True, help='Run without prompts (assumes yes)')
202
+ @click.version_option(__version__, '-V', '--version')
203
+ def main(dry_run, voice_mode_version, skip_services, non_interactive):
204
+ """VoiceMode Installer - Install VoiceMode and its system dependencies.
80
205
 
81
206
  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
- voicemode-install
95
-
96
- # Dry run (see what would be installed)
97
- voicemode-install --dry-run
98
-
99
- # Install specific version
100
- voicemode-install --voice-mode-version=5.1.3
101
-
102
- # Skip service installation
103
- voicemode-install --skip-services
207
+
208
+ 1. Detect your operating system and architecture
209
+
210
+ 2. Check for missing system dependencies
211
+
212
+ 3. Install required packages (with your permission)
213
+
214
+ 4. Install VoiceMode using uv
215
+
216
+ 5. Optionally install local voice services
217
+
218
+ 6. Configure shell completions
219
+
220
+ 7. Verify the installation
104
221
  """
105
222
  # Initialize logger
106
223
  logger = InstallLogger()
@@ -128,13 +245,57 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
128
245
  print_warning("WSL detected - additional audio configuration may be needed")
129
246
  click.echo()
130
247
 
248
+ # Ensure Homebrew is installed on macOS (before checking dependencies)
249
+ if not ensure_homebrew_on_macos(platform_info, dry_run, non_interactive):
250
+ logger.log_error("Homebrew installation required but not available")
251
+ sys.exit(1)
252
+
131
253
  # Check for existing installation
132
254
  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)
255
+ installed_version = get_installed_version()
256
+ latest_version = get_latest_version()
257
+
258
+ click.echo(click.style(" VoiceMode is currently installed", fg='green'))
259
+
260
+ if installed_version:
261
+ click.echo(f" Installed version: {installed_version}")
262
+ else:
263
+ click.echo(" Installed version: (unable to detect)")
264
+
265
+ if latest_version:
266
+ click.echo(f" Latest version: {latest_version}")
267
+
268
+ # Check if update is available
269
+ if installed_version and latest_version and installed_version != latest_version:
270
+ click.echo()
271
+ if non_interactive:
272
+ print_step("Upgrading VoiceMode...")
273
+ elif not click.confirm(f"Upgrade to version {latest_version}?", default=True):
274
+ click.echo("\nTo upgrade manually later, run: uv tool install --upgrade voice-mode")
275
+ sys.exit(0)
276
+ elif installed_version and latest_version and installed_version == latest_version:
277
+ click.echo()
278
+ click.echo(click.style("✓ VoiceMode is up-to-date", fg='green'))
279
+ if non_interactive:
280
+ click.echo("Reinstalling...")
281
+ elif not click.confirm("Reinstall anyway?", default=False):
282
+ click.echo("\nInstallation cancelled.")
283
+ sys.exit(0)
284
+ else:
285
+ click.echo()
286
+ if not non_interactive:
287
+ if not click.confirm("Reinstall VoiceMode?", default=False):
288
+ click.echo("\nTo upgrade manually, run: uv tool install --upgrade voice-mode")
289
+ sys.exit(0)
290
+ else:
291
+ click.echo(" Latest version: (unable to check)")
292
+ click.echo()
293
+ if not non_interactive:
294
+ if not click.confirm("Reinstall/upgrade VoiceMode?", default=False):
295
+ click.echo("\nTo upgrade manually, run: uv tool install --upgrade voice-mode")
296
+ sys.exit(0)
297
+
298
+ click.echo()
138
299
 
139
300
  # Check dependencies
140
301
  print_step("Checking system dependencies...")
@@ -169,7 +330,7 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
169
330
  print_error("Cannot proceed without required dependencies")
170
331
  sys.exit(1)
171
332
 
172
- installer = PackageInstaller(platform_info, dry_run=dry_run)
333
+ installer = PackageInstaller(platform_info, dry_run=dry_run, non_interactive=non_interactive)
173
334
  if installer.install_packages(missing_deps):
174
335
  print_success("System dependencies installed")
175
336
  logger.log_install('system', missing_names, True)
@@ -185,7 +346,7 @@ def main(dry_run, voice_mode_version, skip_services, non_interactive):
185
346
 
186
347
  # Install VoiceMode
187
348
  print_step("Installing VoiceMode...")
188
- installer = PackageInstaller(platform_info, dry_run=dry_run)
349
+ installer = PackageInstaller(platform_info, dry_run=dry_run, non_interactive=non_interactive)
189
350
 
190
351
  if installer.install_voicemode(version=voice_mode_version):
191
352
  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
@@ -235,6 +243,12 @@ voicemode:
235
243
  required: true
236
244
  check_command: make --version
237
245
 
246
+ - name: libsdl2-dev
247
+ description: SDL2 development library (for whisper-stream real-time transcription)
248
+ required: true
249
+ check_command: pkg-config --exists sdl2
250
+ note: Required for building whisper-stream binary
251
+
238
252
  - name: portaudio19-dev
239
253
  description: Audio I/O library (for real-time transcription)
240
254
  required: true
@@ -270,6 +284,12 @@ voicemode:
270
284
  required: true
271
285
  check_command: make --version
272
286
 
287
+ - name: SDL2-devel
288
+ description: SDL2 development library (for whisper-stream real-time transcription)
289
+ required: true
290
+ check_command: pkg-config --exists sdl2
291
+ note: Required for building whisper-stream binary
292
+
273
293
  - name: portaudio-devel
274
294
  description: Audio I/O library (for real-time transcription)
275
295
  required: true
@@ -288,6 +308,13 @@ voicemode:
288
308
  check_command: cmake --version
289
309
  install_via: homebrew
290
310
 
311
+ - name: sdl2
312
+ description: SDL2 library (for whisper-stream real-time transcription)
313
+ required: true
314
+ check_command: brew list sdl2
315
+ install_via: homebrew
316
+ note: Required for building whisper-stream binary
317
+
291
318
  - name: portaudio
292
319
  description: Audio I/O library
293
320
  required: true
@@ -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,3 +0,0 @@
1
- """VoiceMode Installer - System dependency management and installation."""
2
-
3
- __version__ = "1.0.0"