voice-mode-install 1.0.1__py3-none-any.whl → 6.0.3__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.
- {voice_mode_install-1.0.1.dist-info → voice_mode_install-6.0.3.dist-info}/METADATA +20 -17
- voice_mode_install-6.0.3.dist-info/RECORD +12 -0
- voice_mode_install-6.0.3.dist-info/entry_points.txt +2 -0
- voicemode_install/__init__.py +6 -1
- voicemode_install/checker.py +10 -1
- voicemode_install/cli.py +204 -43
- voicemode_install/dependencies.yaml +33 -6
- voicemode_install/installer.py +17 -10
- voicemode_install/system.py +5 -0
- voice_mode_install-1.0.1.dist-info/RECORD +0 -12
- voice_mode_install-1.0.1.dist-info/entry_points.txt +0 -2
- {voice_mode_install-1.0.1.dist-info → voice_mode_install-6.0.3.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-mode-install
|
|
3
|
-
Version:
|
|
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
|
-
#
|
|
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
|
-
`
|
|
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
|
|
46
|
+
uvx voice-mode-install
|
|
47
47
|
|
|
48
48
|
# Dry run (see what would be installed)
|
|
49
|
-
uvx
|
|
49
|
+
uvx voice-mode-install --dry-run
|
|
50
50
|
|
|
51
51
|
# Install specific version
|
|
52
|
-
uvx
|
|
52
|
+
uvx voice-mode-install --voice-mode-version=5.1.3
|
|
53
53
|
|
|
54
54
|
# Skip service installation
|
|
55
|
-
uvx
|
|
55
|
+
uvx voice-mode-install --skip-services
|
|
56
56
|
|
|
57
57
|
# Non-interactive mode
|
|
58
|
-
uvx
|
|
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
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
|
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
|
|
132
|
-
- Use `uvx --refresh
|
|
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
|
|
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
|
-
|
|
157
|
+
voice-mode-install --dry-run
|
|
155
158
|
```
|
|
156
159
|
|
|
157
160
|
### Project Structure
|
|
@@ -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=M_5bwrzY-DXwiUnEYG50Db1-GMxm4YeKW80oYO2U5eM,18639
|
|
4
|
+
voicemode_install/dependencies.yaml,sha256=CUYQCw-99G1GpsxlBBsO-G3K41hpUWrI8Joa1VgCWKM,13922
|
|
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.3.dist-info/METADATA,sha256=vcoAyMcO2h9HwsUwYHj37a7v1YPZK92gQViGCu2Y5-k,6227
|
|
10
|
+
voice_mode_install-6.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
voice_mode_install-6.0.3.dist-info/entry_points.txt,sha256=q6994IQ1Ex4weqoPy9VrL_k7NuKSG5wgaY-_hQjzjnc,66
|
|
12
|
+
voice_mode_install-6.0.3.dist-info/RECORD,,
|
voicemode_install/__init__.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
"""VoiceMode Installer - System dependency management and installation."""
|
|
2
2
|
|
|
3
|
-
|
|
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"
|
voicemode_install/checker.py
CHANGED
|
@@ -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
|
-
|
|
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
|
@@ -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
|
-
║
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
voicemode_install/installer.py
CHANGED
|
@@ -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.
|
|
62
|
-
print("
|
|
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,
|
voicemode_install/system.py
CHANGED
|
@@ -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=BMdiDDbutinjUXY9k6gkt11QGYu5YThaT5DMCX1DyIg,12166
|
|
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-1.0.1.dist-info/METADATA,sha256=xyFE0w1Ee-W9jwpP1L8EDjnpq5xTDaHYq9O2LfTtYkg,5928
|
|
10
|
-
voice_mode_install-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
voice_mode_install-1.0.1.dist-info/entry_points.txt,sha256=Xq6JCA8TzWWoQjvito_nT8SM2RcNEBu4OBtp9pz8JR8,65
|
|
12
|
-
voice_mode_install-1.0.1.dist-info/RECORD,,
|
|
File without changes
|