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.
@@ -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)