voice-mode-install 7.4.0__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.
- voice_mode_install-7.4.0.dist-info/METADATA +192 -0
- voice_mode_install-7.4.0.dist-info/RECORD +12 -0
- voice_mode_install-7.4.0.dist-info/WHEEL +4 -0
- voice_mode_install-7.4.0.dist-info/entry_points.txt +2 -0
- voicemode_install/__init__.py +8 -0
- voicemode_install/checker.py +200 -0
- voicemode_install/cli.py +478 -0
- voicemode_install/dependencies.yaml +367 -0
- voicemode_install/hardware.py +92 -0
- voicemode_install/installer.py +150 -0
- voicemode_install/logger.py +90 -0
- voicemode_install/system.py +146 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# VoiceMode System Dependencies
|
|
2
|
+
# Organized by: voicemode → component → OS/distribution → package list
|
|
3
|
+
#
|
|
4
|
+
# ## SUMMARY
|
|
5
|
+
#
|
|
6
|
+
# ### Installation Tool
|
|
7
|
+
# **UV (required for installation):**
|
|
8
|
+
# - All platforms: UV package manager (https://astral.sh/uv)
|
|
9
|
+
# - Used by install script and for `uv tool install voice-mode`
|
|
10
|
+
# - Provides consistent binary location at ~/.local/bin
|
|
11
|
+
#
|
|
12
|
+
# ### Core (VoiceMode base)
|
|
13
|
+
# **Required (runtime):**
|
|
14
|
+
# - macOS: portaudio, ffmpeg
|
|
15
|
+
# - Ubuntu/Debian: libportaudio2, ffmpeg
|
|
16
|
+
# - Fedora: portaudio, ffmpeg
|
|
17
|
+
#
|
|
18
|
+
# **Required (build tools - for webrtcvad and simpleaudio):**
|
|
19
|
+
# - Ubuntu/Debian: python3-dev, gcc, libasound2-dev
|
|
20
|
+
# - Fedora: python3-devel, gcc, alsa-lib-devel
|
|
21
|
+
# - Note: webrtcvad is a Python C extension used for silence detection. It must be compiled during installation.
|
|
22
|
+
# simpleaudio is a Python C extension used for audio playback. It requires ALSA headers to compile.
|
|
23
|
+
# Testing confirmed: Only gcc is required for core, g++/gcc-c++ not needed.
|
|
24
|
+
#
|
|
25
|
+
# **Optional (build from source):**
|
|
26
|
+
# - Ubuntu/Debian: portaudio19-dev
|
|
27
|
+
# - Fedora: portaudio-devel
|
|
28
|
+
#
|
|
29
|
+
# **Required for WSL:**
|
|
30
|
+
# - WSL Ubuntu/Debian: pulseaudio, pulseaudio-utils, libasound2-plugins
|
|
31
|
+
# - WSL Fedora: pulseaudio, pulseaudio-utils
|
|
32
|
+
# - Note: Must be running for audio to work in WSL2
|
|
33
|
+
#
|
|
34
|
+
# **Optional (audio server - native Linux):**
|
|
35
|
+
# - Usually pre-installed on desktop Linux distributions
|
|
36
|
+
#
|
|
37
|
+
# ### Whisper (STT)
|
|
38
|
+
# - macOS: cmake, portaudio
|
|
39
|
+
# - Ubuntu/Debian: cmake, gcc, g++, make, portaudio19-dev, libasound2-dev
|
|
40
|
+
# - Fedora: cmake, gcc, gcc-c++, make, portaudio-devel, alsa-lib-devel
|
|
41
|
+
#
|
|
42
|
+
# ### Kokoro (TTS)
|
|
43
|
+
# - macOS: rust (optional)
|
|
44
|
+
# - Ubuntu/Debian: cargo, rustc (ARM64 only)
|
|
45
|
+
# - Fedora: cargo, rust (ARM64 only)
|
|
46
|
+
#
|
|
47
|
+
# ### LiveKit
|
|
48
|
+
# - All platforms: No system dependencies (single binary)
|
|
49
|
+
#
|
|
50
|
+
# ---
|
|
51
|
+
#
|
|
52
|
+
# ## DETAIL
|
|
53
|
+
#
|
|
54
|
+
# Structure:
|
|
55
|
+
# voicemode:
|
|
56
|
+
# component: # core, whisper, kokoro, livekit
|
|
57
|
+
# description: What this component does
|
|
58
|
+
# os_or_distro:
|
|
59
|
+
# packages:
|
|
60
|
+
# - name: package_name
|
|
61
|
+
# description: Why this package is needed
|
|
62
|
+
# required: true/false
|
|
63
|
+
# min_version: optional minimum version
|
|
64
|
+
# check_command: optional command to verify installation
|
|
65
|
+
|
|
66
|
+
voicemode:
|
|
67
|
+
installation:
|
|
68
|
+
description: Tools required for installing VoiceMode
|
|
69
|
+
|
|
70
|
+
common: # All platforms
|
|
71
|
+
packages:
|
|
72
|
+
- name: uv
|
|
73
|
+
description: UV package manager for Python
|
|
74
|
+
required: true
|
|
75
|
+
check_command: uv --version
|
|
76
|
+
install_command: curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
77
|
+
note: Provides consistent installation and binary location (~/.local/bin)
|
|
78
|
+
|
|
79
|
+
core:
|
|
80
|
+
description: VoiceMode core audio and Python dependencies
|
|
81
|
+
|
|
82
|
+
debian: # Ubuntu, Debian
|
|
83
|
+
packages:
|
|
84
|
+
- name: python3-dev
|
|
85
|
+
description: Python development headers (needed for building extensions)
|
|
86
|
+
required: true
|
|
87
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
88
|
+
note: Required for compiling webrtcvad (C extension used for silence detection)
|
|
89
|
+
|
|
90
|
+
- name: gcc
|
|
91
|
+
description: C compiler (needed for building Python extensions)
|
|
92
|
+
required: true
|
|
93
|
+
check_command: gcc --version
|
|
94
|
+
note: Required for compiling webrtcvad (C extension used for silence detection)
|
|
95
|
+
|
|
96
|
+
- name: g++
|
|
97
|
+
description: C++ compiler (needed for building Python extensions)
|
|
98
|
+
required: false
|
|
99
|
+
check_command: g++ --version
|
|
100
|
+
note: Optional - not needed for webrtcvad. Only needed if building other C++ packages from source.
|
|
101
|
+
|
|
102
|
+
- name: libasound2-dev
|
|
103
|
+
description: ALSA development files (audio support)
|
|
104
|
+
required: true
|
|
105
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
106
|
+
note: Required for compiling simpleaudio (C extension used for audio playback)
|
|
107
|
+
|
|
108
|
+
- name: libasound2-plugins
|
|
109
|
+
description: ALSA plugins for PulseAudio
|
|
110
|
+
required: false
|
|
111
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
112
|
+
|
|
113
|
+
- name: libportaudio2
|
|
114
|
+
description: PortAudio library (for sounddevice)
|
|
115
|
+
required: true
|
|
116
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
117
|
+
|
|
118
|
+
- name: portaudio19-dev
|
|
119
|
+
description: PortAudio development files
|
|
120
|
+
required: false
|
|
121
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
122
|
+
note: Optional - only needed for building, not runtime (libportaudio2 has the runtime library)
|
|
123
|
+
|
|
124
|
+
- name: pulseaudio
|
|
125
|
+
description: PulseAudio sound server
|
|
126
|
+
required: wsl # Required for WSL, optional for native Linux
|
|
127
|
+
check_command: pulseaudio --version
|
|
128
|
+
note: Essential for WSL2 audio. Usually pre-installed on native Linux desktops.
|
|
129
|
+
|
|
130
|
+
- name: pulseaudio-utils
|
|
131
|
+
description: PulseAudio utilities
|
|
132
|
+
required: wsl # Required for WSL, optional for native Linux
|
|
133
|
+
# Don't use check_command - let it use _check_apt_package which properly checks dpkg output
|
|
134
|
+
note: Essential for WSL2 audio. Usually pre-installed on native Linux desktops.
|
|
135
|
+
|
|
136
|
+
- name: ffmpeg
|
|
137
|
+
description: Audio/video processing
|
|
138
|
+
required: true
|
|
139
|
+
check_command: ffmpeg -version
|
|
140
|
+
|
|
141
|
+
fedora: # Fedora, RHEL, CentOS
|
|
142
|
+
packages:
|
|
143
|
+
- name: python3-devel
|
|
144
|
+
description: Python development headers (needed for building extensions)
|
|
145
|
+
required: true
|
|
146
|
+
check_command: rpm -q python3-devel
|
|
147
|
+
note: Required for compiling webrtcvad (C extension used for silence detection)
|
|
148
|
+
|
|
149
|
+
- name: gcc
|
|
150
|
+
description: C compiler (needed for building Python extensions)
|
|
151
|
+
required: true
|
|
152
|
+
check_command: gcc --version
|
|
153
|
+
note: Required for compiling webrtcvad (C extension used for silence detection)
|
|
154
|
+
|
|
155
|
+
- name: gcc-c++
|
|
156
|
+
description: C++ compiler (needed for building Python extensions)
|
|
157
|
+
required: false
|
|
158
|
+
check_command: g++ --version
|
|
159
|
+
note: Optional - not needed for webrtcvad. Only needed if building other C++ packages from source.
|
|
160
|
+
|
|
161
|
+
- name: alsa-lib-devel
|
|
162
|
+
description: ALSA development files (audio support)
|
|
163
|
+
required: true
|
|
164
|
+
check_command: rpm -q alsa-lib-devel
|
|
165
|
+
note: Required for compiling simpleaudio (C extension used for audio playback)
|
|
166
|
+
|
|
167
|
+
- name: portaudio
|
|
168
|
+
description: PortAudio library (for sounddevice runtime)
|
|
169
|
+
required: true
|
|
170
|
+
check_command: rpm -q portaudio
|
|
171
|
+
|
|
172
|
+
- name: portaudio-devel
|
|
173
|
+
description: PortAudio development files
|
|
174
|
+
required: false
|
|
175
|
+
check_command: rpm -q portaudio-devel
|
|
176
|
+
note: Optional - only needed for building, not runtime
|
|
177
|
+
|
|
178
|
+
- name: pulseaudio
|
|
179
|
+
description: PulseAudio sound server
|
|
180
|
+
required: wsl # Required for WSL, optional for native Linux
|
|
181
|
+
check_command: pulseaudio --version
|
|
182
|
+
note: Essential for WSL2 audio. Usually pre-installed on native Linux desktops.
|
|
183
|
+
|
|
184
|
+
- name: pulseaudio-utils
|
|
185
|
+
description: PulseAudio utilities
|
|
186
|
+
required: wsl # Required for WSL, optional for native Linux
|
|
187
|
+
check_command: rpm -q pulseaudio-utils
|
|
188
|
+
note: Essential for WSL2 audio. Usually pre-installed on native Linux desktops.
|
|
189
|
+
|
|
190
|
+
- name: ffmpeg
|
|
191
|
+
description: Audio/video processing
|
|
192
|
+
required: true
|
|
193
|
+
check_command: ffmpeg -version
|
|
194
|
+
note: May need RPM Fusion repository enabled
|
|
195
|
+
|
|
196
|
+
darwin: # macOS
|
|
197
|
+
packages:
|
|
198
|
+
- name: portaudio
|
|
199
|
+
description: PortAudio library (for sounddevice)
|
|
200
|
+
required: true
|
|
201
|
+
check_command: brew list portaudio
|
|
202
|
+
install_via: homebrew
|
|
203
|
+
|
|
204
|
+
- name: ffmpeg
|
|
205
|
+
description: Audio/video processing
|
|
206
|
+
required: true
|
|
207
|
+
check_command: ffmpeg -version
|
|
208
|
+
install_via: homebrew
|
|
209
|
+
|
|
210
|
+
whisper:
|
|
211
|
+
description: Whisper STT service dependencies
|
|
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
|
+
|
|
221
|
+
debian:
|
|
222
|
+
packages:
|
|
223
|
+
- name: cmake
|
|
224
|
+
description: Build system (needed for compiling whisper.cpp)
|
|
225
|
+
required: true
|
|
226
|
+
min_version: "3.10"
|
|
227
|
+
check_command: cmake --version
|
|
228
|
+
|
|
229
|
+
- name: gcc
|
|
230
|
+
description: C compiler
|
|
231
|
+
required: true
|
|
232
|
+
min_version: "9.0"
|
|
233
|
+
check_command: gcc --version
|
|
234
|
+
|
|
235
|
+
- name: g++
|
|
236
|
+
description: C++ compiler
|
|
237
|
+
required: true
|
|
238
|
+
min_version: "9.0"
|
|
239
|
+
check_command: g++ --version
|
|
240
|
+
|
|
241
|
+
- name: make
|
|
242
|
+
description: Build tool
|
|
243
|
+
required: true
|
|
244
|
+
check_command: make --version
|
|
245
|
+
|
|
246
|
+
- name: portaudio19-dev
|
|
247
|
+
description: Audio I/O library (for real-time transcription)
|
|
248
|
+
required: true
|
|
249
|
+
check_command: pkg-config --exists portaudio-2.0
|
|
250
|
+
|
|
251
|
+
- name: libasound2-dev
|
|
252
|
+
description: Advanced Linux Sound Architecture
|
|
253
|
+
required: true
|
|
254
|
+
check_command: pkg-config --exists alsa
|
|
255
|
+
|
|
256
|
+
fedora:
|
|
257
|
+
packages:
|
|
258
|
+
- name: cmake
|
|
259
|
+
description: Build system (needed for compiling whisper.cpp)
|
|
260
|
+
required: true
|
|
261
|
+
min_version: "3.10"
|
|
262
|
+
check_command: cmake --version
|
|
263
|
+
|
|
264
|
+
- name: gcc
|
|
265
|
+
description: C compiler
|
|
266
|
+
required: true
|
|
267
|
+
min_version: "9.0"
|
|
268
|
+
check_command: gcc --version
|
|
269
|
+
|
|
270
|
+
- name: gcc-c++
|
|
271
|
+
description: C++ compiler
|
|
272
|
+
required: true
|
|
273
|
+
min_version: "9.0"
|
|
274
|
+
check_command: g++ --version
|
|
275
|
+
|
|
276
|
+
- name: make
|
|
277
|
+
description: Build tool
|
|
278
|
+
required: true
|
|
279
|
+
check_command: make --version
|
|
280
|
+
|
|
281
|
+
- name: portaudio-devel
|
|
282
|
+
description: Audio I/O library (for real-time transcription)
|
|
283
|
+
required: true
|
|
284
|
+
check_command: pkg-config --exists portaudio-2.0
|
|
285
|
+
|
|
286
|
+
- name: alsa-lib-devel
|
|
287
|
+
description: Advanced Linux Sound Architecture
|
|
288
|
+
required: true
|
|
289
|
+
check_command: pkg-config --exists alsa
|
|
290
|
+
|
|
291
|
+
darwin:
|
|
292
|
+
packages:
|
|
293
|
+
- name: cmake
|
|
294
|
+
description: Build system
|
|
295
|
+
required: true
|
|
296
|
+
check_command: cmake --version
|
|
297
|
+
install_via: homebrew
|
|
298
|
+
|
|
299
|
+
- name: portaudio
|
|
300
|
+
description: Audio I/O library
|
|
301
|
+
required: true
|
|
302
|
+
check_command: brew list portaudio
|
|
303
|
+
install_via: homebrew
|
|
304
|
+
|
|
305
|
+
kokoro:
|
|
306
|
+
description: Kokoro TTS service dependencies
|
|
307
|
+
|
|
308
|
+
common: # All platforms
|
|
309
|
+
packages:
|
|
310
|
+
- name: git
|
|
311
|
+
description: Version control system (needed to clone kokoro repository)
|
|
312
|
+
required: true
|
|
313
|
+
check_command: git --version
|
|
314
|
+
note: Required for installing Kokoro from GitHub repository
|
|
315
|
+
|
|
316
|
+
debian:
|
|
317
|
+
packages:
|
|
318
|
+
- name: cargo
|
|
319
|
+
description: Rust package manager (needed for building sudachipy on ARM)
|
|
320
|
+
required: false # Only on ARM64, not x86_64
|
|
321
|
+
check_command: cargo --version
|
|
322
|
+
note: Only required for ARM64 architecture
|
|
323
|
+
|
|
324
|
+
- name: rustc
|
|
325
|
+
description: Rust compiler (needed for building sudachipy on ARM)
|
|
326
|
+
required: false # Only on ARM64, not x86_64
|
|
327
|
+
check_command: rustc --version
|
|
328
|
+
note: Only required for ARM64 architecture
|
|
329
|
+
|
|
330
|
+
fedora:
|
|
331
|
+
packages:
|
|
332
|
+
- name: cargo
|
|
333
|
+
description: Rust package manager (needed for building sudachipy)
|
|
334
|
+
required: true
|
|
335
|
+
check_command: cargo --version
|
|
336
|
+
note: Required for building sudachipy dependency
|
|
337
|
+
|
|
338
|
+
- name: rust
|
|
339
|
+
description: Rust compiler
|
|
340
|
+
required: true
|
|
341
|
+
check_command: rustc --version
|
|
342
|
+
note: Required for building sudachipy dependency
|
|
343
|
+
|
|
344
|
+
darwin:
|
|
345
|
+
packages:
|
|
346
|
+
- name: rust
|
|
347
|
+
description: Rust compiler (if needed for building dependencies)
|
|
348
|
+
required: false
|
|
349
|
+
check_command: rustc --version
|
|
350
|
+
install_via: homebrew or rustup
|
|
351
|
+
note: kokoro-fastapi handles most deps via UV
|
|
352
|
+
|
|
353
|
+
livekit:
|
|
354
|
+
description: LiveKit server dependencies (optional)
|
|
355
|
+
|
|
356
|
+
common:
|
|
357
|
+
note: LiveKit server is distributed as a single binary, no system dependencies required
|
|
358
|
+
packages: []
|
|
359
|
+
|
|
360
|
+
# Notes:
|
|
361
|
+
# - macOS typically uses Homebrew for all packages
|
|
362
|
+
# - Linux distributions may need additional repositories (e.g., RPM Fusion for Fedora ffmpeg)
|
|
363
|
+
# - ARM64 systems may need Rust for building certain Python packages from source
|
|
364
|
+
# - WSL requires PulseAudio to be running for audio support
|
|
365
|
+
# - webrtcvad (C extension for silence detection) MUST be compiled during installation on all platforms
|
|
366
|
+
# This requires gcc/g++, python3-dev/python3-devel to be installed before running 'uv tool install voice-mode'
|
|
367
|
+
# - Most other Python packages (numpy, scipy, sounddevice, etc.) have pre-built wheels for x86_64
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Hardware detection for service recommendations."""
|
|
2
|
+
|
|
3
|
+
import psutil
|
|
4
|
+
|
|
5
|
+
from .system import PlatformInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HardwareInfo:
|
|
9
|
+
"""Detect and provide hardware recommendations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, platform_info: PlatformInfo):
|
|
12
|
+
self.platform = platform_info
|
|
13
|
+
self.cpu_count = psutil.cpu_count(logical=False) or 1
|
|
14
|
+
self.total_ram_gb = psutil.virtual_memory().total / (1024 ** 3)
|
|
15
|
+
|
|
16
|
+
def is_apple_silicon(self) -> bool:
|
|
17
|
+
"""Check if running on Apple Silicon."""
|
|
18
|
+
return self.platform.os_type == 'darwin' and self.platform.architecture == 'arm64'
|
|
19
|
+
|
|
20
|
+
def is_arm64(self) -> bool:
|
|
21
|
+
"""Check if running on ARM64 architecture."""
|
|
22
|
+
return self.platform.architecture == 'arm64'
|
|
23
|
+
|
|
24
|
+
def get_ram_category(self) -> str:
|
|
25
|
+
"""Categorize RAM amount."""
|
|
26
|
+
if self.total_ram_gb < 4:
|
|
27
|
+
return 'low'
|
|
28
|
+
elif self.total_ram_gb < 8:
|
|
29
|
+
return 'medium'
|
|
30
|
+
elif self.total_ram_gb < 16:
|
|
31
|
+
return 'good'
|
|
32
|
+
else:
|
|
33
|
+
return 'excellent'
|
|
34
|
+
|
|
35
|
+
def should_recommend_local_services(self) -> bool:
|
|
36
|
+
"""Determine if local services should be recommended."""
|
|
37
|
+
# Apple Silicon is great for local services
|
|
38
|
+
if self.is_apple_silicon():
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
# Other ARM64 with good RAM
|
|
42
|
+
if self.is_arm64() and self.total_ram_gb >= 8:
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
# x86_64 with good specs
|
|
46
|
+
if self.total_ram_gb >= 8 and self.cpu_count >= 4:
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def get_recommendation_message(self) -> str:
|
|
52
|
+
"""Get a recommendation message for local services."""
|
|
53
|
+
if self.is_apple_silicon():
|
|
54
|
+
return (
|
|
55
|
+
f"Your Apple Silicon Mac with {self.total_ram_gb:.1f}GB RAM is great for local services.\n"
|
|
56
|
+
f"Whisper and Kokoro will run fast and privately on your hardware."
|
|
57
|
+
)
|
|
58
|
+
elif self.is_arm64():
|
|
59
|
+
if self.total_ram_gb >= 8:
|
|
60
|
+
return (
|
|
61
|
+
f"Your ARM64 system with {self.total_ram_gb:.1f}GB RAM can run local services well.\n"
|
|
62
|
+
f"Recommended for privacy and offline use."
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
return (
|
|
66
|
+
f"Your ARM64 system has {self.total_ram_gb:.1f}GB RAM.\n"
|
|
67
|
+
f"Local services may work but cloud services might be more responsive."
|
|
68
|
+
)
|
|
69
|
+
else: # x86_64
|
|
70
|
+
if self.total_ram_gb >= 8 and self.cpu_count >= 4:
|
|
71
|
+
return (
|
|
72
|
+
f"Your system ({self.cpu_count} cores, {self.total_ram_gb:.1f}GB RAM) can run local services.\n"
|
|
73
|
+
f"Recommended for privacy and offline use."
|
|
74
|
+
)
|
|
75
|
+
elif self.total_ram_gb >= 4:
|
|
76
|
+
return (
|
|
77
|
+
f"Your system has {self.total_ram_gb:.1f}GB RAM.\n"
|
|
78
|
+
f"Local services will work but may be slower. Cloud services recommended for best performance."
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
return (
|
|
82
|
+
f"Your system has {self.total_ram_gb:.1f}GB RAM.\n"
|
|
83
|
+
f"Cloud services strongly recommended - local services may struggle."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def get_download_estimate(self) -> str:
|
|
87
|
+
"""Estimate download size for local services."""
|
|
88
|
+
# Rough estimates:
|
|
89
|
+
# Whisper: ~150MB (base model) to ~3GB (large model)
|
|
90
|
+
# Kokoro: ~500MB
|
|
91
|
+
# Total: ~2-4GB for full setup
|
|
92
|
+
return "~2-4GB total (Whisper models + Kokoro)"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""System package installation."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from .checker import PackageInfo
|
|
7
|
+
from .system import PlatformInfo, get_package_manager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PackageInstaller:
|
|
11
|
+
"""Install system packages using platform-specific package managers."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, platform_info: PlatformInfo, dry_run: bool = False, non_interactive: bool = False):
|
|
14
|
+
self.platform = platform_info
|
|
15
|
+
self.dry_run = dry_run
|
|
16
|
+
self.non_interactive = non_interactive
|
|
17
|
+
self.package_manager = get_package_manager(platform_info.distribution)
|
|
18
|
+
|
|
19
|
+
def install_packages(self, packages: List[PackageInfo]) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Install a list of packages.
|
|
22
|
+
|
|
23
|
+
Returns True if all installations succeeded, False otherwise.
|
|
24
|
+
"""
|
|
25
|
+
if not packages:
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
package_names = [pkg.name for pkg in packages]
|
|
29
|
+
|
|
30
|
+
if self.dry_run:
|
|
31
|
+
print(f"[DRY RUN] Would install: {', '.join(package_names)}")
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
if self.platform.distribution == 'darwin':
|
|
36
|
+
return self._install_homebrew(package_names)
|
|
37
|
+
elif self.platform.distribution == 'debian':
|
|
38
|
+
return self._install_apt(package_names)
|
|
39
|
+
elif self.platform.distribution == 'fedora':
|
|
40
|
+
return self._install_dnf(package_names)
|
|
41
|
+
else:
|
|
42
|
+
print(f"Error: Unsupported distribution: {self.platform.distribution}")
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"Error installing packages: {e}")
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
def _install_homebrew(self, packages: List[str]) -> bool:
|
|
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
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
cmd = ['brew', 'install'] + packages
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
cmd,
|
|
58
|
+
check=True,
|
|
59
|
+
capture_output=False # Show output to user
|
|
60
|
+
)
|
|
61
|
+
return result.returncode == 0
|
|
62
|
+
except subprocess.CalledProcessError as e:
|
|
63
|
+
print(f"Homebrew package installation failed: {e}")
|
|
64
|
+
return False
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
print("Error: Homebrew not found. This should have been installed earlier.")
|
|
67
|
+
print("Please report this as a bug.")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def _install_apt(self, packages: List[str]) -> bool:
|
|
71
|
+
"""Install packages using apt."""
|
|
72
|
+
try:
|
|
73
|
+
# Update package lists first
|
|
74
|
+
print("Updating package lists...")
|
|
75
|
+
subprocess.run(
|
|
76
|
+
['sudo', 'apt', 'update'],
|
|
77
|
+
check=True,
|
|
78
|
+
capture_output=False
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Install packages
|
|
82
|
+
cmd = ['sudo', 'apt', 'install', '-y'] + packages
|
|
83
|
+
result = subprocess.run(
|
|
84
|
+
cmd,
|
|
85
|
+
check=True,
|
|
86
|
+
capture_output=False
|
|
87
|
+
)
|
|
88
|
+
return result.returncode == 0
|
|
89
|
+
except subprocess.CalledProcessError as e:
|
|
90
|
+
print(f"apt installation failed: {e}")
|
|
91
|
+
return False
|
|
92
|
+
except FileNotFoundError:
|
|
93
|
+
print("Error: apt not found")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def _install_dnf(self, packages: List[str]) -> bool:
|
|
97
|
+
"""Install packages using dnf."""
|
|
98
|
+
try:
|
|
99
|
+
cmd = ['sudo', 'dnf', 'install', '-y'] + packages
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
cmd,
|
|
102
|
+
check=True,
|
|
103
|
+
capture_output=False
|
|
104
|
+
)
|
|
105
|
+
return result.returncode == 0
|
|
106
|
+
except subprocess.CalledProcessError as e:
|
|
107
|
+
print(f"dnf installation failed: {e}")
|
|
108
|
+
return False
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
print("Error: dnf not found")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def install_voicemode(self, version: Optional[str] = None) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Install or upgrade voice-mode using uv tool install --upgrade.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
version: Optional version to install (e.g., "5.1.3")
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if installation succeeded, False otherwise.
|
|
122
|
+
"""
|
|
123
|
+
if self.dry_run:
|
|
124
|
+
if version:
|
|
125
|
+
print(f"[DRY RUN] Would install: uv tool install --upgrade voice-mode=={version}")
|
|
126
|
+
else:
|
|
127
|
+
print("[DRY RUN] Would install: uv tool install --upgrade voice-mode")
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
# Always use --upgrade to ensure we get the latest/requested version
|
|
132
|
+
# This also implies --refresh to check for new versions
|
|
133
|
+
if version:
|
|
134
|
+
cmd = ['uv', 'tool', 'install', '--upgrade', f'voice-mode=={version}']
|
|
135
|
+
else:
|
|
136
|
+
cmd = ['uv', 'tool', 'install', '--upgrade', 'voice-mode']
|
|
137
|
+
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
cmd,
|
|
140
|
+
check=True,
|
|
141
|
+
capture_output=False
|
|
142
|
+
)
|
|
143
|
+
return result.returncode == 0
|
|
144
|
+
except subprocess.CalledProcessError as e:
|
|
145
|
+
print(f"VoiceMode installation failed: {e}")
|
|
146
|
+
return False
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
print("Error: uv not found. Please install uv first:")
|
|
149
|
+
print(" curl -LsSf https://astral.sh/uv/install.sh | sh")
|
|
150
|
+
return False
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Installation logging."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InstallLogger:
|
|
10
|
+
"""Log installation progress and results."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, log_path: Path = None):
|
|
13
|
+
if log_path is None:
|
|
14
|
+
voicemode_dir = Path.home() / '.voicemode'
|
|
15
|
+
voicemode_dir.mkdir(exist_ok=True)
|
|
16
|
+
log_path = voicemode_dir / 'install.log'
|
|
17
|
+
|
|
18
|
+
self.log_path = log_path
|
|
19
|
+
self.session_id = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
20
|
+
self.events = []
|
|
21
|
+
|
|
22
|
+
def log_event(self, event_type: str, message: str, details: Dict[str, Any] = None):
|
|
23
|
+
"""Log an installation event."""
|
|
24
|
+
event = {
|
|
25
|
+
'timestamp': datetime.now().isoformat(),
|
|
26
|
+
'session_id': self.session_id,
|
|
27
|
+
'type': event_type,
|
|
28
|
+
'message': message,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if details:
|
|
32
|
+
event['details'] = details
|
|
33
|
+
|
|
34
|
+
self.events.append(event)
|
|
35
|
+
|
|
36
|
+
# Append to log file
|
|
37
|
+
with open(self.log_path, 'a') as f:
|
|
38
|
+
f.write(json.dumps(event) + '\n')
|
|
39
|
+
|
|
40
|
+
def log_start(self, system_info: Dict[str, Any]):
|
|
41
|
+
"""Log installation start."""
|
|
42
|
+
self.log_event('start', 'Installation started', {'system': system_info})
|
|
43
|
+
|
|
44
|
+
def log_check(self, component: str, packages_found: int, packages_missing: int):
|
|
45
|
+
"""Log dependency check results."""
|
|
46
|
+
self.log_event(
|
|
47
|
+
'check',
|
|
48
|
+
f'Checked {component} dependencies',
|
|
49
|
+
{
|
|
50
|
+
'component': component,
|
|
51
|
+
'found': packages_found,
|
|
52
|
+
'missing': packages_missing
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def log_install(self, package_type: str, packages: list, success: bool):
|
|
57
|
+
"""Log package installation."""
|
|
58
|
+
self.log_event(
|
|
59
|
+
'install',
|
|
60
|
+
f'{"Successfully installed" if success else "Failed to install"} {package_type} packages',
|
|
61
|
+
{
|
|
62
|
+
'package_type': package_type,
|
|
63
|
+
'packages': packages,
|
|
64
|
+
'success': success
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def log_error(self, message: str, error: Exception = None):
|
|
69
|
+
"""Log an error."""
|
|
70
|
+
details = {'message': message}
|
|
71
|
+
if error:
|
|
72
|
+
details['error'] = str(error)
|
|
73
|
+
details['error_type'] = type(error).__name__
|
|
74
|
+
|
|
75
|
+
self.log_event('error', message, details)
|
|
76
|
+
|
|
77
|
+
def log_complete(self, success: bool, voicemode_installed: bool):
|
|
78
|
+
"""Log installation completion."""
|
|
79
|
+
self.log_event(
|
|
80
|
+
'complete',
|
|
81
|
+
'Installation completed' if success else 'Installation failed',
|
|
82
|
+
{
|
|
83
|
+
'success': success,
|
|
84
|
+
'voicemode_installed': voicemode_installed
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def get_log_path(self) -> str:
|
|
89
|
+
"""Get the path to the log file."""
|
|
90
|
+
return str(self.log_path)
|