[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "voice-mode" dynamic = ["version"] description = "VoiceMode - Voice interaction capabilities for AI assistants (formerly voice-mcp)" readme = "README.md" requires-python = ">=3.10" license = {text = "MIT"} authors = [ {name = "Mike Bailey", email = "mike@failmode.com"}, ] keywords = ["mcp", "voice", "livekit", "speech", "tts", "stt", "ai", "llm"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", "Topic :: Multimedia :: Sound/Audio :: Speech", ] dependencies = [ "uv>=0.4.0", "fastmcp>=3.2.0,<4", # Migrated to 3.x in VM-1242. Fixes CVSS 10.0 (GHSA-vv7q-7jx5-f767). "numpy", "sounddevice", "scipy", "openai>=2", # was >=1.0.0 (VM-1640 / VM-1240) "pydub", "audioop-lts; python_version >= '3.13'", "simpleaudio", "httpx", "aiohttp", # Required for Whisper and Kokoro service installers "psutil>=5.9.0", "webrtcvad-wheels>=2.0.14", # Maintained fork with importlib.metadata (no pkg_resources) "click>=8.0.0", "pyyaml>=6.0.0", "keyring>=25.0.0", ] [project.optional-dependencies] dev = [ "build>=1.0.0", "twine>=4.0.0", "pytest>=8.0.0", "pytest-asyncio>=0.23.0", "pytest-cov>=4.1.0", "pytest-mock>=3.10.0", ] test = [ "pytest>=8.0.0", "pytest-asyncio>=0.23.0", "pytest-cov>=4.1.0", "pytest-mock>=3.10.0", "pytest-xdist>=3.5.0", # For parallel testing "pytest-timeout>=2.2.0", # For test timeouts "coverage[toml]>=7.4.0", ] scripts = [ "flask>=3.0.0", ] docs = [ "mkdocs>=1.5.0", # VM-1667: dropped the [imaging] extra (pillow + cairosvg) — it only # powered mkdocs-material's social-cards plugin, which we removed from # mkdocs.yml. mkdocs is a local-only preview (undeployed; ReadTheDocs # retired in VMMT-120), so OG social cards have no use. Clears 7 alerts. "mkdocs-material>=9.0.0", "pymdown-extensions>=10.0", "mkdocs-git-revision-date-localized-plugin>=1.2.0", "mkdocs-minify-plugin>=0.7.0", "mkdocs-gen-files>=0.5.0", ] [project.urls] Homepage = "https://getvoicemode.com/" Repository = "https://github.com/mbailey/voicemode" Issues = "https://github.com/mbailey/voicemode/issues" [project.scripts] voice-mode = "voice_mode.cli:voice_mode" voicemode = "voice_mode.cli:voice_mode" # VM-1314: the plugin's single .mcp.json entry runs this smart launcher. # Unset VOICEMODE_MCP_URL -> local stdio server; set -> native remote bridge. voicemode-mcp-launcher = "voice_mode.mcp_launcher:main" [tool.uv] # Constrain transitive deps that have known vulnerabilities but aren't bumped # automatically because nothing in the tree requires a newer version. # Format: each entry is a PEP 508 specifier that uv applies during resolution. constraint-dependencies = [ # Cryptography is pulled in via authlib/joserfc/pyjwt. 45.0.7 has 3 known # vulns (GHSA-r6ph-v2qm-q3c2 CVSS 8.2, plus 6.9 and 5.3 mediums). None of # the transitive deps require >=46, so without this constraint uv resolves # to the lowest acceptable (vulnerable) version. See VM-1244. "cryptography>=46.0.7", # VM-1244 "urllib3>=2.7.0", # VM-1640: GHSA-2xpw/38jv/gm62 + PYSEC-2026-141 "jaraco-context>=6.1.0", # VM-1640: GHSA-58pv-8j8x-9vj2 # VM-1667: remaining Dependabot transitives. None of the requiring deps # (fastmcp/openai/httpx/twine for runtime; flask for scripts; # mkdocs-git-revision-date-localized-plugin for docs) pin a patched # version, so uv resolves to the lowest acceptable (vulnerable) one. "idna>=3.15", # VM-1667: idna.encode() bypass of CVE-2024-3651 fix "python-dotenv>=1.2.2", # VM-1667: set_key symlink-following arbitrary write "requests>=2.33.0", # VM-1667: insecure temp-file reuse in extract_zipped_paths "pygments>=2.20.0", # VM-1667: ReDoS in GUID regex (low) "werkzeug>=3.1.6", # VM-1667: safe_join() Windows special device names (via flask) "GitPython>=3.1.50", # VM-1667: RCE via core.hooksPath / path traversal (via docs plugin) ] [tool.hatch.build.targets.wheel] packages = ["voice_mode"] include = [ "voice_mode/**/*.py", "voice_mode/**/*.sh", "voice_mode/**/*.plist", "voice_mode/**/*.service", "voice_mode/templates/**/*", "voice_mode/data/**/*.yaml", "voice_mode/data/**/*.json", "voice_mode/data/**/*.cue", "voice_mode/data/**/*.ffmeta", "voice_mode/data/**/*.sha256", "voice_mode/data/**/*.bash", "voice_mode/data/**/*.patch", ] # artifacts overrides .gitignore for VCS-ignored files that should be packaged artifacts = [ "voice_mode/data/**/*.mp3", "voice_mode/data/**/*.wav", ] exclude = [ "**/__pycache__", "**/*.pyc", "**/*.pyo", "**/*.pyd", "**/.DS_Store", "**/*.log", "**/node_modules", "**/.next/cache", "**/tests", "**/.env", "**/.git", ] [tool.hatch.build.targets.sdist] include = [ "/voice_mode", "/README.md", "/LICENSE", "/pyproject.toml", "/CHANGELOG.md", ] # artifacts overrides .gitignore for VCS-ignored files that should be packaged artifacts = [ "voice_mode/data/**/*.mp3", "voice_mode/data/**/*.wav", ] exclude = [ "**/__pycache__", "**/*.pyc", "**/*.pyo", "**/*.pyd", "**/.DS_Store", "**/*.log", ] [tool.hatch.version] path = "voice_mode/__version__.py" [tool.hatch.metadata] allow-direct-references = true [tool.pytest.ini_options] minversion = "7.0" testpaths = ["tests"] python_files = "test_*.py" python_classes = "Test*" python_functions = "test_*" asyncio_mode = "auto" addopts = [ "-ra", "--strict-markers", "--strict-config", "--ignore=tests/manual", "--cov=voice_mode", "--cov-branch", "--cov-report=term-missing:skip-covered", "--cov-report=html", "--cov-report=xml", ] markers = [ "unit: Unit tests (fast, isolated)", "integration: Integration tests (may interact with services)", "slow: Tests that take > 1s", "manual: Manual tests requiring human interaction", ] filterwarnings = [ "ignore::DeprecationWarning", ] [tool.coverage.run] source = ["voice_mode"] branch = true parallel = true omit = [ "*/tests/*", "*/test_*.py", "*/__pycache__/*", "*/site-packages/*", "test-env/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "if TYPE_CHECKING:", "raise NotImplementedError", "if __name__ == .__main__.:", "@abstractmethod", "except ImportError:", ] precision = 2 skip_covered = true show_missing = true [tool.coverage.html] directory = "htmlcov" [tool.coverage.xml] output = "coverage.xml"