"""Version information tools for voice services.
These are CLI helper functions, not MCP tools. The @mcp.tool() decorators
were removed because:
1. This module is in utils/services/, not tools/, so they weren't auto-loaded
2. Importing server.py triggers tools/__init__.py which imports converse.py
which imports pydub, causing audioop deprecation warnings for simple CLI commands
"""
import subprocess
import json
import re
import logging
from pathlib import Path
from typing import Dict, Any, Optional
from datetime import datetime
from voice_mode.config import BASE_DIR
from voice_mode.utils.services.whisper_helpers import find_whisper_server
from voice_mode.utils.services.kokoro_helpers import find_kokoro_fastapi
from voice_mode.utils.services.common import find_process_by_port
from voice_mode.utils.version_helpers import get_current_version
logger = logging.getLogger("voicemode")
def get_whisper_version() -> Dict[str, Any]:
"""Get Whisper installation and version information."""
info = {
"installed": False,
"version": None,
"binary_path": None,
"build_info": None,
"model_info": {},
"running": False,
"error": None
}
try:
# Check if whisper is installed
whisper_bin = find_whisper_server()
if not whisper_bin:
# Try to find main whisper binary as fallback
whisper_main = BASE_DIR / "whisper.cpp" / "main"
if whisper_main.exists():
whisper_bin = str(whisper_main)
if whisper_bin:
info["installed"] = True
info["binary_path"] = whisper_bin
# Try to get version from whisper binary
try:
# whisper.cpp doesn't have a --version flag, but we can check git info
whisper_dir = BASE_DIR / "whisper.cpp"
if whisper_dir.exists():
# Get version using version helper
version = get_current_version(whisper_dir)
if version:
info["version"] = version
# Get build info from CMakeCache if available
cmake_cache = whisper_dir / "build" / "CMakeCache.txt"
if cmake_cache.exists():
build_info = {}
with open(cmake_cache) as f:
for line in f:
if "CMAKE_BUILD_TYPE" in line:
build_info["build_type"] = line.split("=")[1].strip()
elif "WHISPER_METAL" in line:
build_info["metal_enabled"] = "ON" in line
elif "WHISPER_CUDA" in line:
build_info["cuda_enabled"] = "ON" in line
info["build_info"] = build_info
except Exception as e:
logger.debug(f"Could not get whisper version info: {e}")
# Check available models
models_dir = whisper_dir / "models" if whisper_dir else None
if models_dir and models_dir.exists():
models = []
for model_file in models_dir.glob("ggml-*.bin"):
model_info = {
"name": model_file.name,
"size_mb": model_file.stat().st_size / (1024 * 1024),
"modified": datetime.fromtimestamp(model_file.stat().st_mtime).isoformat()
}
models.append(model_info)
info["model_info"]["models"] = models
info["model_info"]["count"] = len(models)
# Check if running
proc = find_process_by_port(2022)
if proc:
info["running"] = True
info["pid"] = proc.pid
try:
info["uptime_seconds"] = int(proc.create_time())
except:
pass
except Exception as e:
info["error"] = str(e)
logger.error(f"Error getting whisper version info: {e}")
return info
def get_kokoro_version() -> Dict[str, Any]:
"""Get Kokoro installation and version information."""
info = {
"installed": False,
"version": None,
"installation_path": None,
"api_version": None,
"models_info": {},
"running": False,
"error": None
}
try:
# Check if kokoro is installed
kokoro_dir = find_kokoro_fastapi()
if kokoro_dir:
info["installed"] = True
info["installation_path"] = kokoro_dir
# Try to get version from git
try:
version = get_current_version(Path(kokoro_dir))
if version:
info["version"] = version
except:
pass
# Check if running and get API version
proc = find_process_by_port(8880)
if proc:
info["running"] = True
info["pid"] = proc.pid
try:
info["uptime_seconds"] = int(proc.create_time())
except:
pass
# Try to get version from API
try:
import httpx
response = httpx.get("http://localhost:8880/", timeout=2.0)
if response.status_code == 200:
data = response.json()
if isinstance(data, dict):
info["api_version"] = data.get("version", "unknown")
# Extract other useful info
if "models" in data:
info["models_info"]["available_models"] = data["models"]
except Exception as e:
logger.debug(f"Could not get Kokoro API info: {e}")
# Check for models
models_dir = Path(kokoro_dir) / "models"
if not models_dir.exists():
# Check alternative location
models_dir = BASE_DIR / "kokoro-models"
if models_dir.exists():
model_files = list(models_dir.glob("*.onnx")) + list(models_dir.glob("*.bin"))
info["models_info"]["count"] = len(model_files)
info["models_info"]["total_size_mb"] = sum(f.stat().st_size for f in model_files) / (1024 * 1024)
except Exception as e:
info["error"] = str(e)
logger.error(f"Error getting kokoro version info: {e}")
return info
async def service_version(
service_name: Optional[str] = None
) -> Dict[str, Any]:
"""Get version information for voice services.
Shows detailed version, installation, and runtime information for Whisper and/or Kokoro.
Args:
service_name: Specific service to check ("whisper" or "kokoro"). If not specified, checks both.
Returns:
Dictionary with version information for requested service(s)
"""
result = {}
if service_name is None or service_name == "whisper":
result["whisper"] = get_whisper_version()
if service_name is None or service_name == "kokoro":
result["kokoro"] = get_kokoro_version()
# Add voice-mode version info
result["voice_mode"] = {
"version": "2.15.0", # This should ideally come from package metadata
"config_dir": str(BASE_DIR),
"service_files_version": get_service_files_version()
}
return result
def get_service_files_version() -> Dict[str, str]:
"""Get version info for service files."""
versions_file = Path(__file__).parent.parent.parent / "data" / "versions.json"
if versions_file.exists():
try:
with open(versions_file) as f:
data = json.load(f)
return data.get("service_files", {})
except:
pass
return {}
async def check_updates(
service_name: Optional[str] = None
) -> Dict[str, Any]:
"""Check for available updates for voice services.
Checks if newer versions are available for Whisper and/or Kokoro.
Args:
service_name: Specific service to check ("whisper" or "kokoro"). If not specified, checks both.
Returns:
Dictionary with update availability information
"""
result = {}
if service_name is None or service_name == "whisper":
whisper_info = {
"current_version": None,
"latest_version": None,
"update_available": False,
"error": None
}
try:
# Get current version
version_info = get_whisper_version()
if version_info["installed"] and version_info["version"]:
whisper_info["current_version"] = version_info["version"]
# Check latest from GitHub
import httpx
response = httpx.get(
"https://api.github.com/repos/ggerganov/whisper.cpp/commits/master",
headers={"Accept": "application/vnd.github.v3+json"},
timeout=5.0
)
if response.status_code == 200:
data = response.json()
latest_commit = data.get("sha", "")[:7]
latest_message = data.get("commit", {}).get("message", "").split('\n')[0]
whisper_info["latest_version"] = f"{latest_commit} {latest_message}"
# Simple check if update available (if current version commit != latest)
if whisper_info["current_version"] and latest_commit not in whisper_info["current_version"]:
whisper_info["update_available"] = True
except Exception as e:
whisper_info["error"] = str(e)
result["whisper"] = whisper_info
if service_name is None or service_name == "kokoro":
kokoro_info = {
"current_version": None,
"latest_version": None,
"update_available": False,
"error": None
}
try:
# Get current version
version_info = get_kokoro_version()
if version_info["installed"] and version_info["version"]:
kokoro_info["current_version"] = version_info["version"]
# Check latest from GitHub
import httpx
response = httpx.get(
"https://api.github.com/repos/remsky/Kokoro-FastAPI/commits/main",
headers={"Accept": "application/vnd.github.v3+json"},
timeout=5.0
)
if response.status_code == 200:
data = response.json()
latest_commit = data.get("sha", "")[:7]
latest_message = data.get("commit", {}).get("message", "").split('\n')[0]
kokoro_info["latest_version"] = f"{latest_commit} {latest_message}"
# Simple check if update available
if kokoro_info["current_version"] and latest_commit not in kokoro_info["current_version"]:
kokoro_info["update_available"] = True
except Exception as e:
kokoro_info["error"] = str(e)
result["kokoro"] = kokoro_info
return result