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,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: voice-mode-install
3
+ Version: 7.4.0
4
+ Summary: Installer for VoiceMode - handles system dependencies and installation
5
+ Project-URL: Homepage, https://github.com/mbailey/voicemode
6
+ Project-URL: Repository, https://github.com/mbailey/voicemode
7
+ Project-URL: Issues, https://github.com/mbailey/voicemode/issues
8
+ Author-email: mbailey <mbailey@example.com>
9
+ License: MIT
10
+ Keywords: installer,mcp,setup,voice,voicemode
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: System :: Installation/Setup
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: click>=8.0.0
23
+ Requires-Dist: psutil>=5.9.0
24
+ Requires-Dist: pyyaml>=6.0.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # voice-mode-install
28
+
29
+ A standalone installer package for VoiceMode that handles system dependency detection and installation.
30
+
31
+ ## Overview
32
+
33
+ `voice-mode-install` simplifies the VoiceMode installation process by:
34
+
35
+ 1. **Detecting your platform** - Identifies your OS, distribution, and architecture
36
+ 2. **Checking dependencies** - Scans for required system packages
37
+ 3. **Installing packages** - Uses your system's package manager (apt, dnf, brew)
38
+ 4. **Installing VoiceMode** - Runs `uv tool install voice-mode`
39
+ 5. **Hardware recommendations** - Suggests optimal configuration for your system
40
+ 6. **Logging everything** - Saves installation logs for troubleshooting
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ # Install and run
46
+ uvx voice-mode-install
47
+
48
+ # Dry run (see what would be installed)
49
+ uvx voice-mode-install --dry-run
50
+
51
+ # Install specific version
52
+ uvx voice-mode-install --voice-mode-version=5.1.3
53
+
54
+ # Skip service installation
55
+ uvx voice-mode-install --skip-services
56
+
57
+ # Non-interactive mode
58
+ uvx voice-mode-install --non-interactive
59
+ ```
60
+
61
+ ## Prerequisites
62
+
63
+ - **uv** - Required to run the installer (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
64
+ - **Python 3.10+** - Usually pre-installed on modern systems
65
+ - **sudo access** - Needed to install system packages (Linux)
66
+ - **Homebrew** (macOS) - The installer will offer to install it if missing
67
+
68
+ ## Supported Platforms
69
+
70
+ - **macOS** - Intel and Apple Silicon (via Homebrew)
71
+ - **Ubuntu/Debian** - Using apt package manager
72
+ - **Fedora/RHEL** - Using dnf package manager
73
+
74
+ ## Features
75
+
76
+ ### Phase 1 (Included)
77
+
78
+ ✅ **Dry-run Mode** - Preview what will be installed
79
+ ✅ **Installation Logging** - Detailed logs saved to `~/.voicemode/install.log`
80
+ ✅ **Shell Completion** - Auto-configures tab completion for bash/zsh
81
+ ✅ **Health Check** - Verifies installation after completion
82
+ ✅ **Version Pinning** - Install specific VoiceMode versions
83
+ ✅ **Hardware Detection** - Recommends optimal setup for your system
84
+ ✅ **Homebrew Auto-Install** - Offers to install Homebrew on macOS if missing
85
+
86
+ ### Phase 2 (Future)
87
+
88
+ ⏱️ Config Validation - Check for conflicting settings
89
+ ⏱️ Uninstall Support - Clean removal of VoiceMode
90
+
91
+ ## How It Works
92
+
93
+ 1. **Platform Detection** - Identifies OS, distribution, and architecture
94
+ 2. **Dependency Checking** - Compares installed packages against `dependencies.yaml`
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)
98
+ - Ubuntu/Debian: `sudo apt install`
99
+ - Fedora: `sudo dnf install`
100
+ 5. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
101
+ 6. **Post-Install** - Configures shell completion and verifies installation
102
+
103
+ ## Installation Logs
104
+
105
+ Logs are saved to `~/.voicemode/install.log` in JSONL format:
106
+
107
+ ```json
108
+ {"timestamp": "2025-10-12T10:30:00", "type": "start", "message": "Installation started"}
109
+ {"timestamp": "2025-10-12T10:30:15", "type": "check", "message": "Checked core dependencies"}
110
+ {"timestamp": "2025-10-12T10:30:45", "type": "install", "message": "Successfully installed system packages"}
111
+ {"timestamp": "2025-10-12T10:31:30", "type": "complete", "message": "Installation completed"}
112
+ ```
113
+
114
+ ## Troubleshooting
115
+
116
+ ### VoiceMode command not found after installation
117
+
118
+ Restart your shell or run:
119
+ ```bash
120
+ source ~/.bashrc # or ~/.zshrc for zsh
121
+ ```
122
+
123
+ ### Permission denied during package installation
124
+
125
+ The installer needs sudo access to install system packages. Run:
126
+ ```bash
127
+ sudo -v # Refresh sudo credentials
128
+ uvx voice-mode-install
129
+ ```
130
+
131
+ ### Network errors during installation
132
+
133
+ - Check your internet connection
134
+ - Try again with: `uvx voice-mode-install`
135
+ - Use `uvx --refresh voice-mode-install` to get the latest installer
136
+
137
+ ### Installation hangs or fails
138
+
139
+ 1. Check the log file: `~/.voicemode/install.log`
140
+ 2. Try a dry run: `uvx voice-mode-install --dry-run`
141
+ 3. Report issues with log file attached
142
+
143
+ ## Development
144
+
145
+ ### Building from Source
146
+
147
+ ```bash
148
+ cd installer/
149
+ uv build
150
+ ```
151
+
152
+ ### Testing Locally
153
+
154
+ ```bash
155
+ cd installer/
156
+ uv pip install -e .
157
+ voice-mode-install --dry-run
158
+ ```
159
+
160
+ ### Project Structure
161
+
162
+ ```
163
+ installer/
164
+ ├── pyproject.toml # Package definition
165
+ ├── voicemode_install/
166
+ │ ├── __init__.py # Version and exports
167
+ │ ├── cli.py # Main CLI entry point
168
+ │ ├── system.py # Platform detection
169
+ │ ├── checker.py # Dependency checking
170
+ │ ├── installer.py # Package installation
171
+ │ ├── hardware.py # Hardware detection
172
+ │ ├── logger.py # Installation logging
173
+ │ └── dependencies.yaml # System dependencies
174
+ └── README.md
175
+ ```
176
+
177
+ ## Design Decisions
178
+
179
+ See `DECISIONS.md` in the task directory for detailed rationale behind:
180
+ - Version management strategy
181
+ - Dependency synchronization approach
182
+ - Error handling philosophy
183
+ - Platform coverage priorities
184
+ - Service installation scope
185
+
186
+ ## Contributing
187
+
188
+ This installer is part of the [VoiceMode](https://github.com/mbailey/voicemode) project.
189
+
190
+ ## License
191
+
192
+ MIT License - Same as VoiceMode
@@ -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=YrK1dlpRD7IS-U4HOnvVR9zkXnScUPzrOnVt8oOeets,19740
4
+ voicemode_install/dependencies.yaml,sha256=v9EIa4yMnnDoqZtt8jcp_9XSdqOVjr6lAFOVb7c-jw0,13149
5
+ voicemode_install/hardware.py,sha256=j4bDv1v8bqyhiysWsTD4F-qEJRG-a8Z1o-qPfxofQt4,3529
6
+ voicemode_install/installer.py,sha256=rd4v4HcSGQek-Ta__dV8IG1apaDRtERB08eGEp_9gwQ,5399
7
+ voicemode_install/logger.py,sha256=UpNegF2D-y-7RthzhzSFMiHPRaFV09F2AUxgjm5b2Pw,2859
8
+ voicemode_install/system.py,sha256=8RBJOPH331EBM8zFYySG2c-zZLpQgPmcGWRMZ6NmUp4,5108
9
+ voice_mode_install-7.4.0.dist-info/METADATA,sha256=J8qpO3HijOT6031FXGJ2zeztHlUzgQ09AF9osr_JKjw,6227
10
+ voice_mode_install-7.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
+ voice_mode_install-7.4.0.dist-info/entry_points.txt,sha256=q6994IQ1Ex4weqoPy9VrL_k7NuKSG5wgaY-_hQjzjnc,66
12
+ voice_mode_install-7.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ voice-mode-install = voicemode_install.cli:main
@@ -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"
@@ -0,0 +1,200 @@
1
+ """Dependency checking logic."""
2
+
3
+ import subprocess
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import List, Optional
7
+
8
+ import yaml
9
+
10
+ from .system import PlatformInfo, check_command_exists
11
+
12
+
13
+ @dataclass
14
+ class PackageInfo:
15
+ """Information about a system package."""
16
+
17
+ name: str
18
+ description: str
19
+ required: bool
20
+ installed: bool
21
+ check_command: Optional[str] = None
22
+ min_version: Optional[str] = None
23
+ note: Optional[str] = None
24
+
25
+
26
+ class DependencyChecker:
27
+ """Check system dependencies against dependencies.yaml."""
28
+
29
+ def __init__(self, platform_info: PlatformInfo):
30
+ self.platform = platform_info
31
+ self.dependencies = self._load_dependencies()
32
+
33
+ def _load_dependencies(self) -> dict:
34
+ """Load dependencies from bundled YAML file."""
35
+ deps_file = Path(__file__).parent / 'dependencies.yaml'
36
+ with open(deps_file) as f:
37
+ return yaml.safe_load(f)
38
+
39
+ def check_core_dependencies(self) -> List[PackageInfo]:
40
+ """Check core VoiceMode dependencies."""
41
+ return self._check_component_dependencies('core')
42
+
43
+ def check_whisper_dependencies(self) -> List[PackageInfo]:
44
+ """Check Whisper STT dependencies."""
45
+ return self._check_component_dependencies('whisper')
46
+
47
+ def check_kokoro_dependencies(self) -> List[PackageInfo]:
48
+ """Check Kokoro TTS dependencies."""
49
+ return self._check_component_dependencies('kokoro')
50
+
51
+ def _check_component_dependencies(self, component: str) -> List[PackageInfo]:
52
+ """Check dependencies for a specific component."""
53
+ packages = []
54
+
55
+ # Get the component section
56
+ voicemode_deps = self.dependencies.get('voicemode', {})
57
+ component_deps = voicemode_deps.get(component, {})
58
+
59
+ if not component_deps:
60
+ return packages
61
+
62
+ # Check common packages first (all platforms)
63
+ if 'common' in component_deps:
64
+ packages.extend(self._check_packages(component_deps['common'].get('packages', [])))
65
+
66
+ # Check platform-specific packages
67
+ if self.platform.distribution in component_deps:
68
+ platform_packages = component_deps[self.platform.distribution].get('packages', [])
69
+ packages.extend(self._check_packages(platform_packages))
70
+
71
+ return packages
72
+
73
+ def _check_packages(self, package_list: List[dict]) -> List[PackageInfo]:
74
+ """Check a list of packages and return their status."""
75
+ results = []
76
+
77
+ for pkg_dict in package_list:
78
+ name = pkg_dict.get('name')
79
+ if not name:
80
+ continue
81
+
82
+ description = pkg_dict.get('description', '')
83
+ required = pkg_dict.get('required', True)
84
+ check_command = pkg_dict.get('check_command')
85
+ min_version = pkg_dict.get('min_version')
86
+ note = pkg_dict.get('note')
87
+
88
+ # Handle WSL-specific requirements
89
+ if required == 'wsl':
90
+ required = self.platform.is_wsl
91
+
92
+ # Check if package is installed
93
+ installed = self._is_package_installed(name, check_command)
94
+
95
+ results.append(PackageInfo(
96
+ name=name,
97
+ description=description,
98
+ required=bool(required),
99
+ installed=installed,
100
+ check_command=check_command,
101
+ min_version=min_version,
102
+ note=note
103
+ ))
104
+
105
+ return results
106
+
107
+ def _is_package_installed(self, package_name: str, check_command: Optional[str] = None) -> bool:
108
+ """Check if a package is installed."""
109
+ # If a check command is provided, use it
110
+ if check_command:
111
+ return self._run_check_command(check_command)
112
+
113
+ # Otherwise, use package manager-specific checks
114
+ if self.platform.distribution == 'darwin':
115
+ return self._check_homebrew_package(package_name)
116
+ elif self.platform.distribution == 'debian':
117
+ return self._check_apt_package(package_name)
118
+ elif self.platform.distribution == 'fedora':
119
+ return self._check_dnf_package(package_name)
120
+ else:
121
+ # Fallback: check if command exists
122
+ return check_command_exists(package_name)
123
+
124
+ def _run_check_command(self, command: str) -> bool:
125
+ """Run a check command and return whether it succeeded."""
126
+ try:
127
+ subprocess.run(
128
+ command,
129
+ shell=True,
130
+ capture_output=True,
131
+ check=True,
132
+ timeout=5
133
+ )
134
+ return True
135
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
136
+ return False
137
+
138
+ def _check_homebrew_package(self, package_name: str) -> bool:
139
+ """Check if a Homebrew package is installed."""
140
+ try:
141
+ subprocess.run(
142
+ ['brew', 'list', package_name],
143
+ capture_output=True,
144
+ check=True
145
+ )
146
+ return True
147
+ except (subprocess.CalledProcessError, FileNotFoundError):
148
+ return False
149
+
150
+ def _check_apt_package(self, package_name: str) -> bool:
151
+ """Check if an apt package is installed."""
152
+ try:
153
+ result = subprocess.run(
154
+ ['dpkg', '-l', package_name],
155
+ capture_output=True,
156
+ text=True
157
+ )
158
+ # dpkg -l returns 0 even if package not found, check output
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
169
+ except FileNotFoundError:
170
+ return False
171
+
172
+ def _check_dnf_package(self, package_name: str) -> bool:
173
+ """Check if a dnf/yum package is installed."""
174
+ try:
175
+ subprocess.run(
176
+ ['rpm', '-q', package_name],
177
+ capture_output=True,
178
+ check=True
179
+ )
180
+ return True
181
+ except (subprocess.CalledProcessError, FileNotFoundError):
182
+ return False
183
+
184
+ def get_missing_packages(self, packages: List[PackageInfo]) -> List[PackageInfo]:
185
+ """Filter to only required packages that are not installed."""
186
+ return [pkg for pkg in packages if pkg.required and not pkg.installed]
187
+
188
+ def get_summary(self, packages: List[PackageInfo]) -> dict:
189
+ """Get a summary of package status."""
190
+ total = len(packages)
191
+ required = sum(1 for pkg in packages if pkg.required)
192
+ installed = sum(1 for pkg in packages if pkg.installed)
193
+ missing_required = sum(1 for pkg in packages if pkg.required and not pkg.installed)
194
+
195
+ return {
196
+ 'total': total,
197
+ 'required': required,
198
+ 'installed': installed,
199
+ 'missing_required': missing_required
200
+ }