Skip to main content
Glama

Voice Mode

by mbailey
install.sh69.8 kB
#!/bin/bash # VoiceMode Universal Installer # Usage: curl -O https://getvoicemode.com/install.sh && bash install.sh # Parse command line arguments NON_INTERACTIVE=false CI_MODE=false show_help() { cat <<EOF VoiceMode Universal Installer Usage: $0 [OPTIONS] Options: -h, --help Show this help message -d, --debug Enable debug output -n, --non-interactive Run without prompts (assumes yes to all) --ci CI mode (non-interactive + skip audio/API checks) Environment variables: VOICEMODE_INSTALL_DEBUG=true Enable debug output DEBUG=true Enable debug output CI=true Enables CI mode automatically Examples: # Normal installation curl -O https://getvoicemode.com/install.sh && bash install.sh # Debug mode VOICEMODE_INSTALL_DEBUG=true ./install.sh ./install.sh --debug # Non-interactive mode ./install.sh --non-interactive # CI mode ./install.sh --ci EOF exit 0 } # Process arguments while [[ $# -gt 0 ]]; do case $1 in -h | --help) show_help ;; -d | --debug) export VOICEMODE_INSTALL_DEBUG=true shift ;; -n | --non-interactive) NON_INTERACTIVE=true shift ;; --ci) CI_MODE=true NON_INTERACTIVE=true shift ;; *) echo "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done # Support DEBUG=true as well as VOICEMODE_INSTALL_DEBUG=true if [[ "${DEBUG:-}" == "true" ]]; then export VOICEMODE_INSTALL_DEBUG=true fi # Detect CI environment if [[ "${CI:-}" == "true" ]] || [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then CI_MODE=true NON_INTERACTIVE=true fi # Reattach stdin to terminal for interactive prompts when run via curl | bash # Only attempt this if /dev/tty exists and is accessible (skip in CI mode) if [ ! -t 0 ] && [ -e /dev/tty ] && [ -r /dev/tty ] && [ "$CI_MODE" != "true" ]; then exec </dev/tty 2>/dev/null || true fi # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' ORANGE='\033[38;5;208m' # Claude Code orange BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' # No Color # Global variables OS="" ARCH="" HOMEBREW_INSTALLED=false XCODE_TOOLS_INSTALLED=false IS_WSL=false print_step() { echo -e "${BLUE}🔧 $1${NC}" } print_success() { echo -e "${GREEN}✅ $1${NC}" } print_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } print_error() { echo -e "${RED}❌ $1${NC}" exit 1 } detect_os() { print_step "Detecting operating system..." # Check if running in WSL # More robust WSL detection to avoid false positives # Note: WSLInterop-late is used on newer systems with systemd if [[ -n "$WSL_DISTRO_NAME" ]] || [[ -n "$WSL_INTEROP" ]] || [[ -f "/proc/sys/fs/binfmt_misc/WSLInterop" ]] || [[ -f "/proc/sys/fs/binfmt_misc/WSLInterop-late" ]]; then IS_WSL=true print_warning "Running in WSL2 - additional audio setup may be required" elif grep -qi "microsoft.*WSL" /proc/version 2>/dev/null; then # Only match if both "microsoft" AND "WSL" are present (case-insensitive) IS_WSL=true print_warning "Running in WSL2 - additional audio setup may be required" fi if [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" ARCH=$(uname -m) local macos_version=$(sw_vers -productVersion) print_success "Detected macOS $macos_version on $ARCH" elif [[ -f /etc/fedora-release ]]; then OS="fedora" ARCH=$(uname -m) local fedora_version=$(cat /etc/fedora-release | grep -oP '\d+' | head -1) print_success "Detected Fedora $fedora_version on $ARCH$([[ "$IS_WSL" == "true" ]] && echo " (WSL2)" || echo "")" elif [[ -f /etc/os-release ]]; then source /etc/os-release if [[ "$ID" == "ubuntu" ]] || [[ "$ID_LIKE" == *"ubuntu"* ]] || [[ "$ID" == "debian" ]] || [[ "$ID_LIKE" == *"debian"* ]]; then OS="debian" # Use debian for all Debian-based distros (Ubuntu, Debian, etc - they use apt) ARCH=$(uname -m) local distro_name="$NAME" print_success "Detected $distro_name $VERSION_ID on $ARCH$([[ "$IS_WSL" == "true" ]] && echo " (WSL2)" || echo "")" elif [[ "$ID" == "fedora" ]]; then OS="fedora" ARCH=$(uname -m) print_success "Detected Fedora $VERSION_ID on $ARCH$([[ "$IS_WSL" == "true" ]] && echo " (WSL2)" || echo "")" else print_error "Unsupported Linux distribution: $ID. Currently only Ubuntu, Debian and Fedora are supported." fi else print_error "Unsupported operating system: $OSTYPE" fi } check_xcode_tools() { print_step "Checking for Xcode Command Line Tools..." if xcode-select -p >/dev/null 2>&1; then XCODE_TOOLS_INSTALLED=true print_success "Xcode Command Line Tools are already installed" else print_warning "Xcode Command Line Tools not found" fi } install_xcode_tools() { if [ "$XCODE_TOOLS_INSTALLED" = false ]; then print_step "Installing Xcode Command Line Tools..." echo "This will open a dialog to install Xcode Command Line Tools." echo "Please follow the prompts and re-run this installer after installation completes." xcode-select --install print_warning "Please complete the Xcode Command Line Tools installation and re-run this installer." exit 0 fi } check_homebrew() { print_step "Checking for Homebrew..." if command -v brew >/dev/null 2>&1; then HOMEBREW_INSTALLED=true print_success "Homebrew is already installed" else print_warning "Homebrew not found" fi } collect_system_info() { print_step "Collecting system information..." # Initialize global variables for system info SYSTEM_INFO_OS="" SYSTEM_INFO_OS_VERSION="" SYSTEM_INFO_ARCH="" SYSTEM_INFO_SHELL="" SYSTEM_INFO_SHELL_VERSION="" SYSTEM_INFO_SHELL_RC_FILE="" SYSTEM_INFO_NODE_VERSION="" SYSTEM_INFO_NPM_VERSION="" SYSTEM_INFO_PYTHON_VERSION="" SYSTEM_INFO_PIP_VERSION="" SYSTEM_INFO_CURL_VERSION="" SYSTEM_INFO_GIT_VERSION="" SYSTEM_INFO_FFMPEG_INSTALLED="" SYSTEM_INFO_DOCKER_INSTALLED="" SYSTEM_INFO_HOMEBREW_VERSION="" SYSTEM_INFO_APT_INSTALLED="" SYSTEM_INFO_TERMINAL_APP="" SYSTEM_INFO_USER_HOME="$HOME" SYSTEM_INFO_CURRENT_DIR="$(pwd)" SYSTEM_INFO_DISK_AVAILABLE="" # OS and Architecture (already detected) SYSTEM_INFO_OS="$OS" SYSTEM_INFO_ARCH="$ARCH" # OS Version details if [[ "$OS" == "macos" ]]; then SYSTEM_INFO_OS_VERSION="$(sw_vers -productVersion 2>/dev/null || echo 'unknown')" elif [[ "$OS" == "linux" ]]; then if [ -f /etc/os-release ]; then . /etc/os-release SYSTEM_INFO_OS_VERSION="${PRETTY_NAME:-${NAME:-unknown}}" else SYSTEM_INFO_OS_VERSION="$(uname -v 2>/dev/null || echo 'unknown')" fi else SYSTEM_INFO_OS_VERSION="$(uname -v 2>/dev/null || echo 'unknown')" fi # Default shell and version SYSTEM_INFO_SHELL="${SHELL:-/bin/bash}" if command -v "$SYSTEM_INFO_SHELL" >/dev/null 2>&1; then if [[ "$SYSTEM_INFO_SHELL" == *"zsh"* ]]; then SYSTEM_INFO_SHELL_VERSION="$($SYSTEM_INFO_SHELL --version 2>/dev/null | head -n1 || echo 'unknown')" elif [[ "$SYSTEM_INFO_SHELL" == *"bash"* ]]; then SYSTEM_INFO_SHELL_VERSION="$($SYSTEM_INFO_SHELL --version 2>/dev/null | head -n1 || echo 'unknown')" elif [[ "$SYSTEM_INFO_SHELL" == *"fish"* ]]; then SYSTEM_INFO_SHELL_VERSION="$($SYSTEM_INFO_SHELL --version 2>/dev/null || echo 'unknown')" else SYSTEM_INFO_SHELL_VERSION="unknown" fi fi # Determine shell RC file if [[ "$SYSTEM_INFO_SHELL" == *"zsh"* ]]; then SYSTEM_INFO_SHELL_RC_FILE="$HOME/.zshrc" elif [[ "$SYSTEM_INFO_SHELL" == *"bash"* ]]; then if [[ "$OS" == "macos" ]]; then # macOS uses .bash_profile for login shells SYSTEM_INFO_SHELL_RC_FILE="$HOME/.bash_profile" else SYSTEM_INFO_SHELL_RC_FILE="$HOME/.bashrc" fi elif [[ "$SYSTEM_INFO_SHELL" == *"fish"* ]]; then SYSTEM_INFO_SHELL_RC_FILE="$HOME/.config/fish/config.fish" else # Fallback to .profile SYSTEM_INFO_SHELL_RC_FILE="$HOME/.profile" fi # Check if RC file exists, if not try alternatives if [[ ! -f "$SYSTEM_INFO_SHELL_RC_FILE" ]]; then if [[ "$SYSTEM_INFO_SHELL" == *"bash"* ]]; then # Try alternative bash files for rcfile in "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.profile"; do if [[ -f "$rcfile" ]]; then SYSTEM_INFO_SHELL_RC_FILE="$rcfile" break fi done fi fi # Node.js and npm versions if command -v node >/dev/null 2>&1; then SYSTEM_INFO_NODE_VERSION="$(node --version 2>/dev/null || echo 'not installed')" else SYSTEM_INFO_NODE_VERSION="not installed" fi if command -v npm >/dev/null 2>&1; then SYSTEM_INFO_NPM_VERSION="$(npm --version 2>/dev/null || echo 'not installed')" else SYSTEM_INFO_NPM_VERSION="not installed" fi # Python and pip versions if command -v python3 >/dev/null 2>&1; then SYSTEM_INFO_PYTHON_VERSION="$(python3 --version 2>&1 | cut -d' ' -f2 || echo 'not installed')" elif command -v python >/dev/null 2>&1; then SYSTEM_INFO_PYTHON_VERSION="$(python --version 2>&1 | cut -d' ' -f2 || echo 'not installed')" else SYSTEM_INFO_PYTHON_VERSION="not installed" fi if command -v pip3 >/dev/null 2>&1; then SYSTEM_INFO_PIP_VERSION="$(pip3 --version 2>/dev/null | cut -d' ' -f2 || echo 'not installed')" elif command -v pip >/dev/null 2>&1; then SYSTEM_INFO_PIP_VERSION="$(pip --version 2>/dev/null | cut -d' ' -f2 || echo 'not installed')" else SYSTEM_INFO_PIP_VERSION="not installed" fi # Curl version if command -v curl >/dev/null 2>&1; then SYSTEM_INFO_CURL_VERSION="$(curl --version 2>/dev/null | head -n1 | cut -d' ' -f2 || echo 'not installed')" else SYSTEM_INFO_CURL_VERSION="not installed" fi # Git version if command -v git >/dev/null 2>&1; then SYSTEM_INFO_GIT_VERSION="$(git --version 2>/dev/null | cut -d' ' -f3 || echo 'not installed')" else SYSTEM_INFO_GIT_VERSION="not installed" fi # FFmpeg installed if command -v ffmpeg >/dev/null 2>&1; then SYSTEM_INFO_FFMPEG_INSTALLED="yes" else SYSTEM_INFO_FFMPEG_INSTALLED="no" fi # Docker installed if command -v docker >/dev/null 2>&1; then SYSTEM_INFO_DOCKER_INSTALLED="yes" else SYSTEM_INFO_DOCKER_INSTALLED="no" fi # Package managers if [[ "$OS" == "macos" ]] && command -v brew >/dev/null 2>&1; then SYSTEM_INFO_HOMEBREW_VERSION="$(brew --version 2>/dev/null | head -n1 | cut -d' ' -f2 || echo 'not installed')" else SYSTEM_INFO_HOMEBREW_VERSION="not installed" fi if command -v apt >/dev/null 2>&1; then SYSTEM_INFO_APT_INSTALLED="yes" else SYSTEM_INFO_APT_INSTALLED="no" fi # Terminal application (if detectable) SYSTEM_INFO_TERMINAL_APP="${TERM_PROGRAM:-${TERMINAL_EMULATOR:-unknown}}" # Available disk space (in GB) on home partition if [[ "$OS" == "macos" ]]; then SYSTEM_INFO_DISK_AVAILABLE="$(df -h "$HOME" 2>/dev/null | awk 'NR==2 {print $4}' || echo 'unknown')" else SYSTEM_INFO_DISK_AVAILABLE="$(df -h "$HOME" 2>/dev/null | awk 'NR==2 {print $4}' || echo 'unknown')" fi if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "System Information:" echo " OS: $SYSTEM_INFO_OS" echo " OS Version: $SYSTEM_INFO_OS_VERSION" echo " Architecture: $SYSTEM_INFO_ARCH" echo " Shell: $SYSTEM_INFO_SHELL" echo " Shell Version: $SYSTEM_INFO_SHELL_VERSION" echo " Shell RC File: $SYSTEM_INFO_SHELL_RC_FILE" echo " Node Version: $SYSTEM_INFO_NODE_VERSION" echo " NPM Version: $SYSTEM_INFO_NPM_VERSION" echo " Python Version: $SYSTEM_INFO_PYTHON_VERSION" echo " FFmpeg: $SYSTEM_INFO_FFMPEG_INSTALLED" echo " Terminal: $SYSTEM_INFO_TERMINAL_APP" echo " Disk Available: $SYSTEM_INFO_DISK_AVAILABLE" fi print_success "System information collected" } display_dependency_status() { echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 📋 System Dependency Status" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Required dependencies echo -e "${BOLD}Required Dependencies:${NC}" echo "" # Node.js if [[ "$SYSTEM_INFO_NODE_VERSION" != "not installed" ]]; then echo -e " ${GREEN}✅${NC} Node.js ${DIM}$SYSTEM_INFO_NODE_VERSION${NC}" else echo -e " ${RED}❌${NC} Node.js ${DIM}(required for Claude Code)${NC}" fi # npm if [[ "$SYSTEM_INFO_NPM_VERSION" != "not installed" ]]; then echo -e " ${GREEN}✅${NC} npm ${DIM}v$SYSTEM_INFO_NPM_VERSION${NC}" else echo -e " ${RED}❌${NC} npm ${DIM}(required for package management)${NC}" fi # Python if [[ "$SYSTEM_INFO_PYTHON_VERSION" != "not installed" ]]; then echo -e " ${GREEN}✅${NC} Python ${DIM}$SYSTEM_INFO_PYTHON_VERSION${NC}" else echo -e " ${RED}❌${NC} Python ${DIM}(required for VoiceMode)${NC}" fi # FFmpeg if [[ "$SYSTEM_INFO_FFMPEG_INSTALLED" == "yes" ]]; then echo -e " ${GREEN}✅${NC} FFmpeg ${DIM}(audio processing)${NC}" else echo -e " ${RED}❌${NC} FFmpeg ${DIM}(required for audio processing)${NC}" fi # Git if [[ "$SYSTEM_INFO_GIT_VERSION" != "not installed" ]]; then echo -e " ${GREEN}✅${NC} Git ${DIM}v$SYSTEM_INFO_GIT_VERSION${NC}" else echo -e " ${YELLOW}⚠️ ${NC} Git ${DIM}(recommended for development)${NC}" fi echo "" echo -e "${BOLD}Package Managers:${NC}" echo "" # Platform-specific package managers if [[ "$OS" == "macos" ]]; then if [[ "$SYSTEM_INFO_HOMEBREW_VERSION" != "not installed" ]]; then echo -e " ${GREEN}✅${NC} Homebrew ${DIM}v$SYSTEM_INFO_HOMEBREW_VERSION${NC}" else echo -e " ${RED}❌${NC} Homebrew ${DIM}(required for macOS dependencies)${NC}" fi elif [[ "$OS" == "linux" ]]; then if [[ "$SYSTEM_INFO_APT_INSTALLED" == "yes" ]]; then echo -e " ${GREEN}✅${NC} APT ${DIM}(system package manager)${NC}" else echo -e " ${YELLOW}⚠️ ${NC} APT ${DIM}(package manager not detected)${NC}" fi fi echo "" echo -e "${BOLD}System Information:${NC}" echo "" echo " • OS: $SYSTEM_INFO_OS_VERSION" echo " • Architecture: $SYSTEM_INFO_ARCH" echo -e " • Shell: ${SYSTEM_INFO_SHELL##*/} ${DIM}(${SYSTEM_INFO_SHELL_RC_FILE})${NC}" echo " • Terminal: $SYSTEM_INFO_TERMINAL_APP" echo " • Disk Space: $SYSTEM_INFO_DISK_AVAILABLE available" if [[ "$IS_WSL" == true ]]; then echo "" echo -e " ${YELLOW}⚠️ WSL2 Detected${NC} - Additional audio setup may be required" fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" } check_missing_dependencies() { # Returns 0 if all required dependencies are met, 1 if any are missing local missing=0 if [[ "$SYSTEM_INFO_NODE_VERSION" == "not installed" ]]; then missing=1 fi if [[ "$SYSTEM_INFO_NPM_VERSION" == "not installed" ]]; then missing=1 fi if [[ "$SYSTEM_INFO_PYTHON_VERSION" == "not installed" ]]; then missing=1 fi if [[ "$SYSTEM_INFO_FFMPEG_INSTALLED" != "yes" ]]; then missing=1 fi # Platform-specific requirements if [[ "$OS" == "macos" ]] && [[ "$SYSTEM_INFO_HOMEBREW_VERSION" == "not installed" ]]; then missing=1 fi return $missing } confirm_action() { local action="$1" local default_yes="${2:-true}" # Default to yes unless specified # In non-interactive mode, always return success if [[ "$NON_INTERACTIVE" == "true" ]]; then echo "" if [[ "$action" == *"?"* ]]; then echo "$action" else echo "About to: $action" fi echo "→ Auto-accepting (non-interactive mode)" return 0 fi echo "" # Check if action is already a question (contains "?") if [[ "$action" == *"?"* ]]; then echo "$action" else echo "About to: $action" fi if [[ "$default_yes" == "true" ]]; then read -p "Continue? [Y/n]: " choice # Empty response defaults to yes if [[ -z "$choice" ]]; then return 0 fi else read -p "Continue? [y/N]: " choice # Empty response defaults to no if [[ -z "$choice" ]]; then echo "Skipping: $action" return 1 fi fi case $choice in [Yy]*) return 0 ;; *) echo "Skipping..." return 1 ;; esac } install_homebrew() { if [ "$HOMEBREW_INSTALLED" = false ]; then if confirm_action "Install Homebrew (this will also install Xcode Command Line Tools)"; then print_step "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # Add Homebrew to PATH for current session if [[ "$ARCH" == "arm64" ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" else eval "$(/usr/local/bin/brew shellenv)" fi print_success "Homebrew installed successfully" # Update the status variables since Homebrew installs Xcode tools HOMEBREW_INSTALLED=true XCODE_TOOLS_INSTALLED=true else print_error "Homebrew is required for VoiceMode dependencies. Installation aborted." fi fi } check_system_dependencies() { print_step "Checking system dependencies..." if [[ "$OS" == "macos" ]]; then local packages=("node" "portaudio" "ffmpeg") local missing_packages=() for package in "${packages[@]}"; do if brew list "$package" >/dev/null 2>&1; then print_success "$package is already installed" else missing_packages+=("$package") fi done if [ ${#missing_packages[@]} -eq 0 ]; then print_success "All system dependencies are already installed" return 0 else echo "Missing packages: ${missing_packages[*]}" return 1 fi elif [[ "$OS" == "fedora" ]]; then local packages=("nodejs" "portaudio-devel" "ffmpeg" "cmake" "python3-devel" "alsa-lib-devel" "gcc") local missing_packages=() for package in "${packages[@]}"; do # Special handling for ffmpeg which might be installed from RPM Fusion if [[ "$package" == "ffmpeg" ]]; then if command -v ffmpeg >/dev/null 2>&1; then print_success "$package is already installed" else missing_packages+=("$package") fi elif rpm -q "$package" >/dev/null 2>&1; then print_success "$package is already installed" else missing_packages+=("$package") fi done if [ ${#missing_packages[@]} -eq 0 ]; then print_success "All system dependencies are already installed" return 0 else echo "Missing packages: ${missing_packages[*]}" return 1 fi elif [[ "$OS" == "debian" ]]; then local packages=("nodejs" "npm" "portaudio19-dev" "ffmpeg" "cmake" "python3-dev" "libasound2-dev" "libasound2-plugins") local missing_packages=() for package in "${packages[@]}"; do if dpkg -l "$package" 2>/dev/null | grep -q '^ii'; then print_success "$package is already installed" else missing_packages+=("$package") fi done if [ ${#missing_packages[@]} -eq 0 ]; then print_success "All system dependencies are already installed" return 0 else echo "Missing packages: ${missing_packages[*]}" return 1 fi fi } install_system_dependencies() { if ! check_system_dependencies; then if [[ "$OS" == "macos" ]]; then # Build list of what needs to be installed local needs_homebrew=false local needs_deps=false local install_message="" if [ "$HOMEBREW_INSTALLED" = false ]; then needs_homebrew=true install_message="• Homebrew (package manager)\n • Xcode Command Line Tools (automatically installed)\n " fi # Check for missing audio dependencies local missing_packages=() for package in ffmpeg portaudio; do if ! brew list $package &>/dev/null; then missing_packages+=($package) needs_deps=true fi done if [ "$needs_homebrew" = true ] || [ "$needs_deps" = true ]; then echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 📦 System Dependencies Required" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "VoiceMode needs to install the following:" if [ "$needs_homebrew" = true ]; then echo -e "$install_message" fi if [ "$needs_deps" = true ]; then echo "• Audio dependencies: ${missing_packages[*]}" fi echo "" if confirm_action "Install all required dependencies?"; then # Install Homebrew if needed if [ "$needs_homebrew" = true ]; then install_homebrew if [ "$HOMEBREW_INSTALLED" = false ]; then print_warning "Failed to install Homebrew. Cannot proceed." return 1 fi fi # Install missing packages if [ "$needs_deps" = true ]; then print_step "Installing audio dependencies..." brew update for package in "${missing_packages[@]}"; do print_step "Installing $package..." brew install "$package" print_success "$package installed" done fi else print_warning "Skipping system dependencies. VoiceMode may not work properly without them." return 1 fi fi elif [[ "$OS" == "fedora" ]]; then if confirm_action "Install missing system dependencies via DNF"; then print_step "Installing system dependencies..." # Update package lists (ignore exit code as dnf check-update returns 100 when updates are available) sudo dnf check-update || true # Install required packages local packages=("nodejs" "portaudio-devel" "ffmpeg" "cmake" "python3-devel" "alsa-lib-devel" "gcc") print_step "Installing packages: ${packages[*]}" sudo dnf install -y "${packages[@]}" print_success "System dependencies installed" else print_warning "Skipping system dependencies. VoiceMode may not work properly without them." fi elif [[ "$OS" == "debian" ]]; then if confirm_action "Install missing system dependencies via APT"; then print_step "Installing system dependencies..." # Update package lists sudo apt update # Install required packages local packages=("nodejs" "npm" "portaudio19-dev" "ffmpeg" "cmake" "python3-dev" "libasound2-dev" "libasound2-plugins" "pulseaudio" "pulseaudio-utils") # Add WSL-specific packages if detected if [[ "$IS_WSL" == true ]]; then print_warning "WSL2 detected - installing additional audio packages" packages+=("libasound2-plugins" "pulseaudio") fi print_step "Installing packages: ${packages[*]}" sudo apt install -y "${packages[@]}" print_success "System dependencies installed" # WSL-specific audio setup if [[ "$IS_WSL" == true ]]; then print_step "Setting up WSL2 audio support..." # Start PulseAudio if not running if ! pgrep -x "pulseaudio" >/dev/null; then pulseaudio --start 2>/dev/null || true print_success "Started PulseAudio service" fi # Check audio devices if command -v pactl >/dev/null 2>&1; then if pactl list sources short 2>/dev/null | grep -q .; then print_success "Audio devices detected in WSL2" else print_warning "No audio devices detected. WSL2 audio setup may require:" echo " 1. Enable Windows microphone permissions for your terminal" echo " 2. Ensure WSL version is 2.3.26.0 or higher (run 'wsl --version')" echo " 3. See: https://github.com/mbailey/voicemode/blob/main/docs/troubleshooting/wsl2-microphone-access.md" fi fi fi else print_warning "Skipping system dependencies. VoiceMode may not work properly without them." fi fi fi } check_python() { print_step "Checking Python installation..." if command -v python3 >/dev/null 2>&1; then local python_version=$(python3 --version | cut -d' ' -f2) print_success "Python 3 found: $python_version" else print_warning "Python 3 not found in PATH" echo " UV will manage Python installation automatically" fi } install_uv() { if ! command -v uv >/dev/null 2>&1; then if confirm_action "Install UV (required for VoiceMode)"; then print_step "Installing UV..." # Install UV using the official installer curl -LsSf https://astral.sh/uv/install.sh | sh # Add UV to PATH for current session export PATH="$HOME/.local/bin:$PATH" # Verify installation immediately if ! command -v uv >/dev/null 2>&1; then print_error "UV installation failed - command not found after installation" return 1 fi # Test uv actually works if ! uv --version >/dev/null 2>&1; then print_error "UV installation failed - command not working" return 1 fi # Add to shell profile if not already there local shell_profile="${SYSTEM_INFO_SHELL_RC_FILE:-$HOME/.bashrc}" if [ -n "$shell_profile" ] && [ -f "$shell_profile" ]; then if ! grep -q "\.local/bin" "$shell_profile"; then echo 'export PATH="$HOME/.local/bin:$PATH"' >>"$shell_profile" print_success "Added UV to PATH in $shell_profile" # Source the profile to make UV immediately available source "$shell_profile" print_success "Loaded $shell_profile to update PATH" fi fi print_success "UV installed and verified successfully" else print_error "UV is required for VoiceMode. Installation aborted." return 1 fi else print_success "UV is already installed" # Even if already installed, verify it works if ! uv --version >/dev/null 2>&1; then print_error "UV is installed but not working properly" return 1 fi fi } install_voicemode() { print_step "Installing VoiceMode..." # Install voice-mode package with uv tool install if uv tool install --upgrade --force voice-mode; then print_success "VoiceMode installed successfully" # Update shell to ensure PATH includes UV tools print_step "Updating shell PATH configuration..." if uv tool update-shell; then print_success "Shell PATH updated" # Export PATH for current session export PATH="$HOME/.local/bin:$PATH" # Source shell profile for immediate availability if [[ -n "${SYSTEM_INFO_SHELL_RC_FILE}" ]] && [[ -f "${SYSTEM_INFO_SHELL_RC_FILE}" ]]; then source "${SYSTEM_INFO_SHELL_RC_FILE}" 2>/dev/null || true fi else print_warning "Could not update shell PATH automatically" fi # Verify the voicemode command is available if command -v voicemode >/dev/null 2>&1; then print_success "VoiceMode command verified: voicemode" return 0 else print_warning "VoiceMode installed but command not immediately available" if [[ -n "${SYSTEM_INFO_SHELL_RC_FILE}" ]]; then echo " You may need to restart your shell or run: source ${SYSTEM_INFO_SHELL_RC_FILE}" else echo " You may need to restart your shell" fi return 0 fi else print_error "Failed to install VoiceMode" return 1 fi } setup_local_npm() { print_step "Setting up local npm configuration..." # Set up npm to use local directory (no sudo required) mkdir -p "$HOME/.npm-global" npm config set prefix "$HOME/.npm-global" # Add to PATH for current session export PATH="$HOME/.npm-global/bin:$PATH" # Add to shell profile if not already there local shell_profile="${SYSTEM_INFO_SHELL_RC_FILE:-$HOME/.bashrc}" if [ -n "$shell_profile" ] && [ -f "$shell_profile" ]; then if ! grep -q "\.npm-global/bin" "$shell_profile"; then echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >>"$shell_profile" print_success "Added npm global bin to PATH in $shell_profile" # Source the profile to make npm immediately available source "$shell_profile" print_success "Loaded $shell_profile to update PATH" fi fi print_success "Local npm configuration complete" } setup_shell_completion() { # Safe shell completion setup that checks command availability at runtime # This prevents shell startup errors by only enabling completions when the command exists # Detect current shell local shell_type="" local shell_rc="" if [[ -n "${BASH_VERSION:-}" ]]; then shell_type="bash" shell_rc="$HOME/.bashrc" elif [[ -n "${ZSH_VERSION:-}" ]]; then shell_type="zsh" shell_rc="$HOME/.zshrc" else # Try to detect from SHELL environment variable case "${SHELL:-}" in */bash) shell_type="bash" shell_rc="$HOME/.bashrc" ;; */zsh) shell_type="zsh" shell_rc="$HOME/.zshrc" ;; *) print_warning "Unsupported shell for completion: ${SHELL:-unknown}" echo " VoiceMode completion supports bash and zsh only" return 1 ;; esac fi if [[ -z "$shell_type" ]]; then print_debug "Could not detect shell type for completion setup" return 1 fi print_step "Setting up shell completion for $shell_type..." # Set up completion based on shell type if [[ "$shell_type" == "bash" ]]; then # Check for existing bash-completion support local has_bash_completion=false local user_completions_dir="${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions" # Check if bash-completion is installed if [[ -f "/usr/share/bash-completion/bash_completion" ]] || [[ -f "/etc/bash_completion" ]] || (command -v brew >/dev/null 2>&1 && brew list bash-completion@2 >/dev/null 2>&1); then has_bash_completion=true fi if [[ "$has_bash_completion" == true ]]; then # Install completion file to user directory mkdir -p "$user_completions_dir" # Generate and save completion file with bash version compatibility if command -v voicemode >/dev/null 2>&1; then # Check bash version - nosort option is only available in bash 4.4+ local bash_version="${BASH_VERSION%%.*}" local bash_minor="${BASH_VERSION#*.}" bash_minor="${bash_minor%%.*}" if [[ $bash_version -gt 4 ]] || [[ $bash_version -eq 4 && $bash_minor -ge 4 ]]; then # Bash 4.4+ supports nosort _VOICEMODE_COMPLETE=bash_source voicemode >"$user_completions_dir/voicemode" 2>/dev/null else # Older bash - filter out nosort option to prevent errors _VOICEMODE_COMPLETE=bash_source voicemode 2>/dev/null | sed 's/complete -o nosort/complete/g' >"$user_completions_dir/voicemode" fi if [[ -s "$user_completions_dir/voicemode" ]]; then print_success "Installed bash completion to $user_completions_dir/" echo " Tab completion will be available in new bash sessions" else rm -f "$user_completions_dir/voicemode" # Fallback to shell RC method has_bash_completion=false fi fi fi if [[ "$has_bash_completion" == false ]]; then # Fallback: Add to shell RC file with bash version check local completion_line='# VoiceMode shell completion if command -v voicemode >/dev/null 2>&1; then # Check bash version for nosort compatibility bash_version="${BASH_VERSION%%.*}" bash_minor="${BASH_VERSION#*.}" bash_minor="${bash_minor%%.*}" if [[ $bash_version -gt 4 ]] || [[ $bash_version -eq 4 && $bash_minor -ge 4 ]]; then eval "$(_VOICEMODE_COMPLETE=bash_source voicemode)" else # Filter out nosort for older bash versions eval "$(_VOICEMODE_COMPLETE=bash_source voicemode | sed '\''s/complete -o nosort/complete/g'\'')" fi fi' if [[ -f "$shell_rc" ]] && grep -q "_VOICEMODE_COMPLETE\|_VOICE_MODE_COMPLETE" "$shell_rc" 2>/dev/null; then print_success "Shell completion already configured in $shell_rc" else echo "" >>"$shell_rc" echo "$completion_line" >>"$shell_rc" print_success "Added shell completion to $shell_rc" echo " Tab completion will be available in new shell sessions" fi fi elif [[ "$shell_type" == "zsh" ]]; then # Detect best location for zsh completions local completion_dir="" local needs_fpath_update=false # Check for Homebrew on macOS if [[ "$OS" == "macos" ]] && command -v brew >/dev/null 2>&1; then local brew_prefix=$(brew --prefix) if [[ -d "$brew_prefix/share/zsh/site-functions" ]]; then completion_dir="$brew_prefix/share/zsh/site-functions" print_debug "Using Homebrew zsh completion directory" fi fi # Check standard locations if not found if [[ -z "$completion_dir" ]]; then for dir in \ "/usr/local/share/zsh/site-functions" \ "/usr/share/zsh/site-functions" \ "$HOME/.zsh/completions"; do if [[ -d "$dir" ]] || [[ "$dir" == "$HOME/.zsh/completions" ]]; then completion_dir="$dir" if [[ "$dir" == "$HOME/.zsh/completions" ]]; then needs_fpath_update=true fi break fi done fi # Create directory if needed if [[ ! -d "$completion_dir" ]]; then mkdir -p "$completion_dir" fi # Generate and install completion file (with underscore prefix) if command -v voicemode >/dev/null 2>&1; then _VOICEMODE_COMPLETE=zsh_source voicemode >"$completion_dir/_voicemode" 2>/dev/null if [[ -s "$completion_dir/_voicemode" ]]; then print_success "Installed zsh completion to $completion_dir/" # Update fpath if using custom directory if [[ "$needs_fpath_update" == true ]]; then local fpath_line="fpath=($completion_dir \$fpath)" if [[ -f "$shell_rc" ]] && ! grep -q "$completion_dir" "$shell_rc" 2>/dev/null; then echo "" >>"$shell_rc" echo "# Add VoiceMode completions to fpath" >>"$shell_rc" echo "$fpath_line" >>"$shell_rc" print_success "Added $completion_dir to fpath in $shell_rc" fi fi echo " Tab completion will be available in new zsh sessions" else # Fallback to shell RC method rm -f "$completion_dir/_voicemode" local completion_line='# VoiceMode shell completion if command -v voicemode >/dev/null 2>&1; then eval "$(_VOICEMODE_COMPLETE=zsh_source voicemode)" fi' if [[ -f "$shell_rc" ]] && grep -q "_VOICEMODE_COMPLETE\|_VOICE_MODE_COMPLETE" "$shell_rc" 2>/dev/null; then print_success "Shell completion already configured in $shell_rc" else echo "" >>"$shell_rc" echo "$completion_line" >>"$shell_rc" print_success "Added shell completion to $shell_rc" echo " Tab completion will be available in new shell sessions" fi fi fi fi return 0 } configure_api_key() { # Skip API key configuration in CI mode if [[ "$CI_MODE" == "true" ]]; then print_step "Skipping OpenAI API key configuration (CI mode)" echo "→ API key can be configured later if needed" export VOICEMODE_API_KEY_CONFIGURED=false return 0 fi print_step "Checking OpenAI API key configuration..." # Track if we have an API key configured local api_key_configured=false # Check if OPENAI_API_KEY is already set if [ -n "${OPENAI_API_KEY:-}" ]; then print_success "OPENAI_API_KEY is already set in environment" echo "" echo "Your OpenAI API key is configured. VoiceMode will use OpenAI for speech" echo "recognition and text-to-speech, with automatic fallback support." api_key_configured=true # Export a flag for later use export VOICEMODE_API_KEY_CONFIGURED=true return 0 fi # Check if it's in shell config files for file in ~/.bashrc ~/.zshrc ~/.bash_profile; do if [ -f "$file" ] && grep -q "export OPENAI_API_KEY" "$file" 2>/dev/null; then print_success "OPENAI_API_KEY found in $file" echo "" echo "Your OpenAI API key is configured. VoiceMode will use OpenAI for speech" echo "recognition and text-to-speech, with automatic fallback support." api_key_configured=true # Export a flag for later use export VOICEMODE_API_KEY_CONFIGURED=true return 0 fi done # API key not found - recommend setting it up echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 🎯 Quick Start with OpenAI (Recommended)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "For the best first experience, we recommend using OpenAI's API:" echo " ✓ Works immediately - no complex setup required" echo " ✓ High-quality speech recognition and natural voices" echo " ✓ Automatic fallback when local services are unavailable" echo "" echo "Local services (Whisper/Kokoro) can be added later for:" echo " • Reduced costs" echo " • Enhanced privacy" echo " • Offline capability" echo " (Claude can help you set these up when you're ready)" echo "" if confirm_action "Would you like to set up your OpenAI API key now? (recommended)"; then echo "" echo "To get an API key:" echo " 1. Visit: https://platform.openai.com" echo " 2. Sign in or create an account" echo " 3. Click 'Create new secret key'" echo " 4. Copy the key (it starts with 'sk-')" echo "" # Offer to open the page if command -v open >/dev/null 2>&1; then if confirm_action "Would you like to open the OpenAI API keys page in your browser?"; then open "https://platform.openai.com/api-keys" echo "" echo "Waiting for you to create and copy your API key..." sleep 2 fi fi # Platform-specific paste instructions echo "" if [[ "$OS" == "macos" ]]; then echo "Please paste your OpenAI API key (press Cmd+V to paste, or press Enter to skip):" elif [[ "$IS_WSL" == true ]]; then echo "Please paste your OpenAI API key (right-click to paste, or press Enter to skip):" else echo "Please paste your OpenAI API key (Ctrl+Shift+V or right-click to paste, or press Enter to skip):" fi echo "(The key will be hidden as you type)" # Allow up to 3 attempts for API key entry local attempts=0 local max_attempts=3 local api_key="" while [[ $attempts -lt $max_attempts ]]; do read -s api_key echo "" if [ -z "$api_key" ]; then # User pressed Enter to skip break fi # Validate that it looks like an API key if [[ ! "$api_key" =~ ^sk- ]]; then ((attempts++)) if [[ $attempts -lt $max_attempts ]]; then print_warning "That doesn't look like an OpenAI API key (should start with 'sk-')" echo "Please try again (attempt $((attempts + 1)) of $max_attempts):" echo "(Make sure to copy the entire key, it should start with 'sk-')" else print_warning "Invalid API key format after $max_attempts attempts" echo "You can add it manually later to your shell configuration" return 1 fi else # Validate the API key by making a test request print_step "Validating API key with OpenAI..." local http_code=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" \ "https://api.openai.com/v1/models" 2>/dev/null) if [[ "$http_code" == "200" ]]; then print_success "API key validated successfully!" break elif [[ "$http_code" == "401" ]]; then ((attempts++)) if [[ $attempts -lt $max_attempts ]]; then print_warning "Invalid API key - authentication failed" echo "Please check your key and try again (attempt $((attempts + 1)) of $max_attempts):" else print_error "Invalid API key after $max_attempts attempts" echo "You can add a valid key later to your shell configuration" return 1 fi else # Network error or other issue - proceed anyway print_warning "Could not validate API key (network issue?)" echo "Proceeding with configuration anyway..." break fi fi done if [ -n "$api_key" ] && [[ "$api_key" =~ ^sk- ]]; then # Add to shell configuration local shell_profile="${SYSTEM_INFO_SHELL_RC_FILE:-$HOME/.bashrc}" if [ -n "$shell_profile" ]; then echo "" >>"$shell_profile" echo "# OpenAI API Key for VoiceMode" >>"$shell_profile" echo "export OPENAI_API_KEY='$api_key'" >>"$shell_profile" print_success "Added OPENAI_API_KEY to $shell_profile" # Export for current session export OPENAI_API_KEY="$api_key" export VOICEMODE_API_KEY_CONFIGURED=true echo "" print_success "OpenAI API key configured successfully!" return 0 fi else print_warning "Skipping OpenAI API key configuration" fi else print_warning "Skipping OpenAI API key configuration" echo "" echo "You can add it later to your shell configuration:" echo " export OPENAI_API_KEY='your-api-key-here'" echo "" echo "Without an API key, only local services will work (after installation)." fi return 1 } test_voice_setup() { print_step "Testing voice setup..." # Check if voicemode command is available if ! command -v voicemode >/dev/null 2>&1; then print_warning "voicemode command not found in PATH" return 1 fi # Check for audio devices first print_step "Checking audio devices..." local device_output=$(voicemode diag devices 2>&1) if echo "$device_output" | grep -q "Input Devices:"; then # Extract only the input devices section (between "Input Devices:" and next empty line or "Output") local input_section=$(echo "$device_output" | sed -n '/Input Devices:/,/^$/p' | sed -n '/Input Devices:/,/Output Devices:/p') local input_count=$(echo "$input_section" | grep -c "^\s*\[[0-9]\].*channels)") if [ "$input_count" -gt 0 ]; then print_success "Found $input_count microphone(s)" # Show the default input device echo "$device_output" | grep "Default Input:" | sed 's/^/ /' else print_warning "No microphones detected" echo " Voice features require a microphone to be connected" return 1 fi else print_warning "Could not detect audio devices" fi # Check if we have an API key or local services local has_tts=false if [ -n "${OPENAI_API_KEY:-}" ]; then has_tts=true echo " OpenAI API key is configured ✓" fi # Quick check for local services if voicemode whisper status 2>/dev/null | grep -q "running"; then has_tts=true echo " Whisper (STT) is running ✓" fi if voicemode kokoro status 2>/dev/null | grep -q "available"; then has_tts=true echo " Kokoro (TTS) is running ✓" fi if [ "$has_tts" = false ]; then print_warning "No TTS/STT services available yet" echo " You'll need either an OpenAI API key or local services to use voice features" return 1 fi echo "" if confirm_action "Would you like to test voice mode now?"; then echo "" echo "Starting voice test..." echo " • Say 'Hello' when you hear the chime" echo " • The test will confirm your microphone and API are working" echo " • Press Ctrl+C to exit the test" echo "" # Run a voice test echo "" echo "Running voice test..." echo "" # Run a single interaction test # This will speak, then listen for a response (default 120 seconds) voicemode converse --message "Hello! Testing voice mode. Say something to test your microphone." 2>&1 || true echo "" print_success "Voice test complete!" fi return 0 } configure_claude_voicemode() { if command -v claude >/dev/null 2>&1; then # Check if voicemode is already configured if claude mcp list 2>/dev/null | grep -q -e "voicemode" -e "voice-mode"; then print_success "VoiceMode is already configured in Claude Code" setup_shell_completion return 0 else if confirm_action "Add VoiceMode to Claude Code (adds MCP server)"; then print_step "Adding VoiceMode to Claude Code..." # Try with --scope flag first (newer versions) if claude mcp add --scope user voicemode -- uvx --refresh voice-mode 2>/dev/null; then print_success "VoiceMode added to Claude Code" setup_shell_completion return 0 else print_error "Failed to add VoiceMode to Claude Code" return 1 fi else print_step "Skipping VoiceMode configuration" echo "You can configure it later with:" echo " claude mcp add --scope user voicemode -- uvx --refresh voice-mode" return 1 fi fi else print_warning "Claude Code not found. Please install it first to use VoiceMode." return 1 fi } check_and_suggest_working_directory() { # Check if user is in home directory local current_dir=$(pwd) local home_dir="$HOME" if [[ "$current_dir" == "$home_dir" ]]; then echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 📁 Working Directory Setup" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "You're currently in your home directory." echo "Claude Code works best when launched from a project directory." echo "" echo "Where would you like to start Claude from?" echo " • Press Enter for ~/claude (recommended sandbox)" echo " • Type a path like ~/projects or ~/code" echo " • Type 'here' to use your home directory" echo "" read -p "Directory [~/claude]: " chosen_dir # Handle the user's choice if [[ -z "$chosen_dir" ]]; then # Default to ~/claude chosen_dir="$HOME/claude" elif [[ "$chosen_dir" == "here" ]] || [[ "$chosen_dir" == "." ]]; then # Use current directory (home) chosen_dir="$HOME" else # Expand tilde if present chosen_dir="${chosen_dir/#\~/$HOME}" fi # Create directory if it doesn't exist (unless it's home) if [[ "$chosen_dir" != "$HOME" ]]; then if [[ ! -d "$chosen_dir" ]]; then print_step "Creating directory: $chosen_dir" mkdir -p "$chosen_dir" print_success "Created $chosen_dir" else print_success "Using existing directory: $chosen_dir" fi # Save for later reference export CLAUDE_SUGGESTED_DIR="$chosen_dir" echo "" echo "After installation completes, you can:" echo " 1. cd $chosen_dir" echo " 2. claude converse" echo "" else print_success "Will use home directory" echo "" echo "You can start Claude from your home directory after installation." echo "" fi fi } install_claude_if_needed() { if ! command -v claude >/dev/null 2>&1; then if confirm_action "Install Claude Code (required for VoiceMode)"; then print_step "Installing Claude Code..." # Check for npm and install Node.js if missing if ! command -v npm >/dev/null 2>&1; then print_step "npm not found. Installing Node.js..." if [[ "$OS" == "macos" ]]; then # Install Node.js via Homebrew on macOS if command -v brew >/dev/null 2>&1; then brew install node print_success "Node.js installed via Homebrew" else print_error "Homebrew not found. Please install Homebrew first." return 1 fi elif [[ "$OS" == "linux" ]]; then # Install Node.js on Linux if command -v apt-get >/dev/null 2>&1; then sudo apt-get update && sudo apt-get install -y nodejs npm print_success "Node.js installed via apt" elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y nodejs npm print_success "Node.js installed via dnf" else print_error "Unable to install Node.js. Please install it manually." return 1 fi fi # Set up local npm configuration to avoid sudo for global installs setup_local_npm fi # Now install Claude Code if command -v npm >/dev/null 2>&1; then npm install -g @anthropic-ai/claude-code print_success "Claude Code installed" else print_error "Failed to install npm. Please install Node.js manually." return 1 fi else print_warning "Claude Code is required for VoiceMode. Skipping configuration." return 1 fi fi return 0 } # Service installation functions check_voice_mode_cli() { # Check for locally installed voicemode command # This should be available after uv tool install if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "[DEBUG] Checking for voicemode CLI availability..." >&2 fi # Check if voicemode command is available if command -v voicemode >/dev/null 2>&1; then print_success "VoiceMode CLI is available" >&2 echo "voicemode" return 0 else print_warning "VoiceMode CLI not found" >&2 echo " Please ensure VoiceMode was installed correctly with 'uv tool install --upgrade --force voice-mode'" >&2 echo " You may need to restart your shell or run: source ~/.bashrc" >&2 return 1 fi } install_service() { local service_name="$1" local voice_mode_cmd="$2" local description="$3" print_step "Installing $description..." # Debug mode check if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "[DEBUG] Checking service command: $voice_mode_cmd $service_name --help" fi # Check if the service subcommand exists first # Redirect stderr to stdout and check for the actual help output # This handles cases where Python warnings are printed to stderr local help_output local help_exit_code help_output=$(timeout 30 $voice_mode_cmd $service_name --help 2>&1 || true) help_exit_code=$? if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "[DEBUG] Help command exit code: $help_exit_code" echo "[DEBUG] Help output length: ${#help_output} bytes" echo "[DEBUG] First 500 chars of help output:" echo "$help_output" | head -c 500 echo "" echo "[DEBUG] Checking for 'Commands:' in output..." fi if ! echo "$help_output" | grep -q "Commands:"; then print_warning "$description service command not available" if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "[DEBUG] 'Commands:' not found in help output" echo "[DEBUG] Full help output:" echo "$help_output" fi return 1 fi if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo "[DEBUG] Service command check passed" fi # Install with timeout and capture output local temp_log=$(mktemp) local install_success=false print_step "Running: $voice_mode_cmd $service_name install --auto-enable" if timeout 600 $voice_mode_cmd $service_name install --auto-enable 2>&1 | tee "$temp_log"; then install_success=true fi print_step "Running: $voice_mode_cmd $service_name enable" if timeout 600 $voice_mode_cmd $service_name enable 2>&1 | tee "$temp_log"; then install_success=true fi # Check for specific success/failure indicators if [[ "$install_success" == true ]] && ! grep -qi "error\|failed\|traceback" "$temp_log"; then print_success "$description installed successfully" rm -f "$temp_log" return 0 else print_warning "$description installation may have failed" echo "Last few lines of output:" tail -10 "$temp_log" | sed 's/^/ /' rm -f "$temp_log" return 1 fi } install_all_services() { local voice_mode_cmd="$1" local success_count=0 local total_count=3 print_step "Installing all VoiceMode services..." # Install each service independently if install_service "whisper" "$voice_mode_cmd" "Whisper (Speech-to-Text)"; then ((success_count++)) # CoreML acceleration info for Apple Silicon Macs (now automatic!) setup_coreml_acceleration fi if install_service "kokoro" "$voice_mode_cmd" "Kokoro (Text-to-Speech)"; then ((success_count++)) fi if install_service "livekit" "$voice_mode_cmd" "LiveKit (Real-time Communication)"; then ((success_count++)) fi # Report results echo "" if [[ $success_count -eq $total_count ]]; then print_success "All voice services installed successfully!" elif [[ $success_count -gt 0 ]]; then print_warning "$success_count of $total_count services installed successfully" echo " Check error messages above for failed installations" else print_error "No services were installed successfully" fi } install_services_selective() { local voice_mode_cmd="$1" if confirm_action "Would you like to install Whisper (Speech-to-Text)?" false; then install_service "whisper" "$voice_mode_cmd" "Whisper" # CoreML acceleration info for Apple Silicon Macs (now automatic!) setup_coreml_acceleration fi if confirm_action "Would you like to install Kokoro (Text-to-Speech)?" false; then install_service "kokoro" "$voice_mode_cmd" "Kokoro" fi if confirm_action "Would you like to install LiveKit (Real-time Communication)?" false; then install_service "livekit" "$voice_mode_cmd" "LiveKit" fi } verify_voice_mode_after_mcp() { print_step "Verifying VoiceMode CLI availability after MCP configuration..." # Give a moment for any caching to settle sleep 2 # Check voice-mode CLI availability local voice_mode_cmd if ! voice_mode_cmd=$(check_voice_mode_cli); then print_warning "VoiceMode CLI not available yet. This could be due to:" echo " • PATH not updated in current shell" echo " • VoiceMode not installed yet" echo " • Need to restart shell for PATH updates" echo "" echo "You can install services manually later with:" echo " voicemode whisper install" echo " voicemode kokoro install" echo " voicemode livekit install" return 1 fi print_success "VoiceMode CLI verified: $voice_mode_cmd" return 0 } install_voice_services() { # Verify voice-mode CLI availability first if ! verify_voice_mode_after_mcp; then return 1 fi # Get the verified command local voice_mode_cmd voice_mode_cmd=$(check_voice_mode_cli) echo "" echo -e "${BLUE}🎤 VoiceMode Services${NC}" echo "" echo "VoiceMode can install local services for the best experience:" echo " • Whisper - Fast local speech-to-text (no cloud required)" echo " • Kokoro - Natural text-to-speech with multiple voices" echo " • LiveKit - Real-time voice communication server" echo "" echo "Benefits:" echo " • Privacy - All processing happens locally" echo " • Speed - No network latency" echo " • Reliability - Works offline" echo "" echo "Note: Service installation may take several minutes and requires internet access." echo "" # Quick mode or selective read -p "Install all recommended services? [Y/n/s]: " choice case $choice in [Ss]*) # Selective mode install_services_selective "$voice_mode_cmd" ;; [Nn]*) print_warning "Skipping service installation. VoiceMode will use cloud services." ;; *) # Default: install all install_all_services "$voice_mode_cmd" ;; esac } show_banner() { # Clear screen for clean presentation clear # Display VoiceMode ASCII art echo "" echo -e "${ORANGE}${BOLD}" cat <<'EOF' ╔════════════════════════════════════════════╗ ║ ║ ║ ██╗ ██╗ ██████╗ ██╗ ██████╗███████╗ ║ ║ ██║ ██║██╔═══██╗██║██╔════╝██╔════╝ ║ ║ ██║ ██║██║ ██║██║██║ █████╗ ║ ║ ╚██╗ ██╔╝██║ ██║██║██║ ██╔══╝ ║ ║ ╚████╔╝ ╚██████╔╝██║╚██████╗███████╗ ║ ║ ╚═══╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝ ║ ║ ║ ║ ███╗ ███╗ ██████╗ ██████╗ ███████╗ ║ ║ ████╗ ████║██╔═══██╗██╔══██╗██╔════╝ ║ ║ ██╔████╔██║██║ ██║██║ ██║█████╗ ║ ║ ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ║ ║ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ║ ║ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ║ ║ ║ ║ 🎙️ VoiceMode for Claude Code 🤖 ║ ║ ║ ╚════════════════════════════════════════════╝ EOF echo -e "${NC}" echo -e "${BOLD}Talk to Claude like a colleague, not a chatbot.${NC}" echo "" echo -e "${DIM}Transform your AI coding experience with natural voice conversations.${NC}" echo "" } setup_coreml_acceleration() { # Check if we're on Apple Silicon Mac if [[ "$OS" == "macos" ]] && [[ "$ARCH" == "arm64" ]]; then echo "" echo -e "${CYAN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${BLUE}${BOLD}🚀 CoreML Acceleration Included!${NC}" echo "" echo "Your Mac supports CoreML acceleration for Whisper!" echo "" echo -e "${GREEN}Great News:${NC}" echo " • Pre-built Core ML models download automatically" echo " • NO Python dependencies (PyTorch) required" echo " • NO Xcode installation needed" echo " • NO manual setup or configuration" echo "" echo -e "${GREEN}Benefits:${NC}" echo " • 2-3x faster transcription than Metal-only" echo " • Lower CPU usage during speech recognition" echo " • Better battery life on MacBooks" echo " • Instant setup - just downloads and works!" echo "" echo -e "${DIM}Core ML models are downloaded automatically when you install${NC}" echo -e "${DIM}Whisper models. No additional steps required!${NC}" echo "" echo -e "${CYAN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" fi } main() { # Show banner first show_banner # Check for debug mode if [[ "${VOICEMODE_INSTALL_DEBUG:-}" == "true" ]]; then echo -e "${YELLOW}[DEBUG MODE ENABLED]${NC}" echo "" fi # Anonymous analytics beacon (privacy-respecting) # Only tracks: install event, OS type, timestamp # No personal data collected { if command -v curl >/dev/null 2>&1; then # Simple beacon to track installs # Uses Goatcounter's pixel tracking endpoint curl -s "https://getvoicemode.goatcounter.com/count?p=/install&t=Install%20Script" \ -H "User-Agent: VoiceMode-Installer/${OS:-unknown}" \ -H "Referer: https://getvoicemode.com/install.sh" \ >/dev/null 2>&1 || true fi } & # Pre-flight checks detect_os # Collect comprehensive system information collect_system_info # Display dependency status display_dependency_status # Check if we have missing dependencies if check_missing_dependencies; then echo "All required dependencies are installed!" else print_warning "Some dependencies are missing and will be installed" fi # Early sudo caching for service installation (Linux only) if [[ "$OS" == "linux" ]] && command -v sudo >/dev/null 2>&1; then print_step "Requesting administrator access for system configuration..." if ! sudo -v; then print_warning "Administrator access declined. Some features may not be available." fi fi # OS-specific setup if [[ "$OS" == "macos" ]]; then check_homebrew # Don't install Homebrew here - let install_system_dependencies handle it fi check_python install_uv # Install dependencies (handles Homebrew installation if needed) install_system_dependencies # Install VoiceMode package locally install_voicemode # Setup npm for non-macOS systems if [[ "$OS" != "macos" ]]; then setup_local_npm fi # Configure OpenAI API key for quick start configure_api_key # Check if user is in home directory and suggest creating a workspace check_and_suggest_working_directory # Install Claude Code if needed, then configure VoiceMode # Skip Claude Code installation in CI mode if [[ "$CI_MODE" == "true" ]]; then print_step "Skipping Claude Code installation (CI mode)" echo "" echo "✅ VoiceMode installation completed successfully!" echo "" echo "VoiceMode has been installed to: $(which voicemode)" echo "Version: $(voicemode --version 2>/dev/null || echo 'unknown')" elif install_claude_if_needed; then if configure_claude_voicemode; then # VoiceMode configured successfully echo "" echo "🎉 VoiceMode is ready!" echo "" # Test voice setup if API key is configured (from environment or config file) if [ -n "${OPENAI_API_KEY:-}" ] || [ "${VOICEMODE_API_KEY_CONFIGURED:-}" = "true" ]; then test_voice_setup fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " ✨ VoiceMode Installation Complete!" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Advanced options as a side note echo "📝 Advanced Options (not required):" echo " Local voice services can reduce latency/costs and improve privacy:" echo " • voicemode whisper install # Local speech-to-text" echo " • voicemode kokoro install # Local text-to-speech" echo "" # Main instructions echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "🎯 How to use VoiceMode with Claude:" echo "" echo "1. From terminal: claude converse" echo "2. From Claude Code: Type 'converse' or '/voicemode:converse'" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Important note about shell restart echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "⚠️ IMPORTANT: Terminal Restart Required" echo "" echo "The 'claude' command won't work until you:" echo " Option 1: Close and reopen your terminal (recommended)" if [[ -n "${SYSTEM_INFO_SHELL_RC_FILE}" ]]; then echo " Option 2: Run: source ${SYSTEM_INFO_SHELL_RC_FILE}" else echo " Option 2: Run: source your shell configuration file" fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "But we can start a conversation right now without restarting!" echo "" # Offer to start Claude immediately if confirm_action "Would you like to start a voice conversation with Claude now? [Y/n]"; then echo "" print_step "Starting Claude with voice mode..." echo " • Say something when you hear the chime" echo " • Press Ctrl+C to exit when done" echo "" # Use the full path to claude since PATH may not be updated yet if [ -f "$HOME/.npm-global/bin/claude" ]; then "$HOME/.npm-global/bin/claude" converse elif command -v claude >/dev/null 2>&1; then claude converse else print_warning "Claude command not found in expected location." echo "After restarting your terminal, run: claude converse" fi else echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "🚀 To start using VoiceMode:" echo "" if [[ -n "${SYSTEM_INFO_SHELL_RC_FILE}" ]]; then echo " 1. Restart your terminal (or run: source ${SYSTEM_INFO_SHELL_RC_FILE})" else echo " 1. Restart your terminal" fi if [[ -n "${CLAUDE_SUGGESTED_DIR:-}" ]]; then echo " 2. cd ${CLAUDE_SUGGESTED_DIR}" echo " 3. claude converse" else echo " 2. claude converse (from your current directory)" fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi else print_warning "VoiceMode configuration was skipped or failed." echo "" echo "You can manually configure VoiceMode later with:" echo " claude mcp add --scope user voicemode -- uvx --refresh voice-mode" echo "" echo "Then install services with:" echo " voicemode whisper install" echo " voicemode kokoro install" echo " voicemode livekit install" fi fi # WSL-specific instructions if [[ "$IS_WSL" == true ]]; then echo "" echo -e "${YELLOW}WSL2 Audio Setup:${NC}" echo "VoiceMode requires microphone access in WSL2. If you encounter audio issues:" echo " 1. Enable Windows microphone permissions for your terminal app" echo " 2. Ensure PulseAudio is running: pulseaudio --start" echo " 3. Test audio devices: python3 -m sounddevice" echo " 4. See troubleshooting guide: https://github.com/mbailey/voicemode/blob/main/docs/troubleshooting/wsl2-microphone-access.md" fi echo "" echo "For more information, visit: https://github.com/mbailey/voicemode" } # Run main function main "$@"

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mbailey/voicemode'

If you have feedback or need assistance with the MCP directory API, please join our Discord server