voice-mode-install 1.0.1__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.

@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: voice-mode-install
3
+ Version: 1.0.1
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
+ # voicemode-install
28
+
29
+ A standalone installer package for VoiceMode that handles system dependency detection and installation.
30
+
31
+ ## Overview
32
+
33
+ `voicemode-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 voicemode-install
47
+
48
+ # Dry run (see what would be installed)
49
+ uvx voicemode-install --dry-run
50
+
51
+ # Install specific version
52
+ uvx voicemode-install --voice-mode-version=5.1.3
53
+
54
+ # Skip service installation
55
+ uvx voicemode-install --skip-services
56
+
57
+ # Non-interactive mode
58
+ uvx voicemode-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
+
67
+ ## Supported Platforms
68
+
69
+ - **macOS** - Intel and Apple Silicon (via Homebrew)
70
+ - **Ubuntu/Debian** - Using apt package manager
71
+ - **Fedora/RHEL** - Using dnf package manager
72
+
73
+ ## Features
74
+
75
+ ### Phase 1 (Included)
76
+
77
+ ✅ **Dry-run Mode** - Preview what will be installed
78
+ ✅ **Installation Logging** - Detailed logs saved to `~/.voicemode/install.log`
79
+ ✅ **Shell Completion** - Auto-configures tab completion for bash/zsh
80
+ ✅ **Health Check** - Verifies installation after completion
81
+ ✅ **Version Pinning** - Install specific VoiceMode versions
82
+ ✅ **Hardware Detection** - Recommends optimal setup for your system
83
+
84
+ ### Phase 2 (Future)
85
+
86
+ ⏱️ Config Validation - Check for conflicting settings
87
+ ⏱️ Uninstall Support - Clean removal of VoiceMode
88
+
89
+ ## How It Works
90
+
91
+ 1. **Platform Detection** - Identifies OS, distribution, and architecture
92
+ 2. **Dependency Checking** - Compares installed packages against `dependencies.yaml`
93
+ 3. **Package Installation** - Uses platform-specific package managers:
94
+ - macOS: `brew install`
95
+ - Ubuntu/Debian: `sudo apt install`
96
+ - Fedora: `sudo dnf install`
97
+ 4. **VoiceMode Installation** - Runs `uv tool install voice-mode[==version]`
98
+ 5. **Post-Install** - Configures shell completion and verifies installation
99
+
100
+ ## Installation Logs
101
+
102
+ Logs are saved to `~/.voicemode/install.log` in JSONL format:
103
+
104
+ ```json
105
+ {"timestamp": "2025-10-12T10:30:00", "type": "start", "message": "Installation started"}
106
+ {"timestamp": "2025-10-12T10:30:15", "type": "check", "message": "Checked core dependencies"}
107
+ {"timestamp": "2025-10-12T10:30:45", "type": "install", "message": "Successfully installed system packages"}
108
+ {"timestamp": "2025-10-12T10:31:30", "type": "complete", "message": "Installation completed"}
109
+ ```
110
+
111
+ ## Troubleshooting
112
+
113
+ ### VoiceMode command not found after installation
114
+
115
+ Restart your shell or run:
116
+ ```bash
117
+ source ~/.bashrc # or ~/.zshrc for zsh
118
+ ```
119
+
120
+ ### Permission denied during package installation
121
+
122
+ The installer needs sudo access to install system packages. Run:
123
+ ```bash
124
+ sudo -v # Refresh sudo credentials
125
+ uvx voicemode-install
126
+ ```
127
+
128
+ ### Network errors during installation
129
+
130
+ - Check your internet connection
131
+ - Try again with: `uvx voicemode-install`
132
+ - Use `uvx --refresh voicemode-install` to get the latest installer
133
+
134
+ ### Installation hangs or fails
135
+
136
+ 1. Check the log file: `~/.voicemode/install.log`
137
+ 2. Try a dry run: `uvx voicemode-install --dry-run`
138
+ 3. Report issues with log file attached
139
+
140
+ ## Development
141
+
142
+ ### Building from Source
143
+
144
+ ```bash
145
+ cd installer/
146
+ uv build
147
+ ```
148
+
149
+ ### Testing Locally
150
+
151
+ ```bash
152
+ cd installer/
153
+ uv pip install -e .
154
+ voicemode-install --dry-run
155
+ ```
156
+
157
+ ### Project Structure
158
+
159
+ ```
160
+ installer/
161
+ ├── pyproject.toml # Package definition
162
+ ├── voicemode_install/
163
+ │ ├── __init__.py # Version and exports
164
+ │ ├── cli.py # Main CLI entry point
165
+ │ ├── system.py # Platform detection
166
+ │ ├── checker.py # Dependency checking
167
+ │ ├── installer.py # Package installation
168
+ │ ├── hardware.py # Hardware detection
169
+ │ ├── logger.py # Installation logging
170
+ │ └── dependencies.yaml # System dependencies
171
+ └── README.md
172
+ ```
173
+
174
+ ## Design Decisions
175
+
176
+ See `DECISIONS.md` in the task directory for detailed rationale behind:
177
+ - Version management strategy
178
+ - Dependency synchronization approach
179
+ - Error handling philosophy
180
+ - Platform coverage priorities
181
+ - Service installation scope
182
+
183
+ ## Contributing
184
+
185
+ This installer is part of the [VoiceMode](https://github.com/mbailey/voicemode) project.
186
+
187
+ ## License
188
+
189
+ MIT License - Same as VoiceMode
@@ -0,0 +1,12 @@
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ voicemode-install = voicemode_install.cli:main
@@ -0,0 +1,3 @@
1
+ """VoiceMode Installer - System dependency management and installation."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,191 @@
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
+ return result.returncode == 0 and package_name in result.stdout
160
+ except FileNotFoundError:
161
+ return False
162
+
163
+ def _check_dnf_package(self, package_name: str) -> bool:
164
+ """Check if a dnf/yum package is installed."""
165
+ try:
166
+ subprocess.run(
167
+ ['rpm', '-q', package_name],
168
+ capture_output=True,
169
+ check=True
170
+ )
171
+ return True
172
+ except (subprocess.CalledProcessError, FileNotFoundError):
173
+ return False
174
+
175
+ def get_missing_packages(self, packages: List[PackageInfo]) -> List[PackageInfo]:
176
+ """Filter to only required packages that are not installed."""
177
+ return [pkg for pkg in packages if pkg.required and not pkg.installed]
178
+
179
+ def get_summary(self, packages: List[PackageInfo]) -> dict:
180
+ """Get a summary of package status."""
181
+ total = len(packages)
182
+ required = sum(1 for pkg in packages if pkg.required)
183
+ installed = sum(1 for pkg in packages if pkg.installed)
184
+ missing_required = sum(1 for pkg in packages if pkg.required and not pkg.installed)
185
+
186
+ return {
187
+ 'total': total,
188
+ 'required': required,
189
+ 'installed': installed,
190
+ 'missing_required': missing_required
191
+ }