Ctrl K
ComunidadGuías…

shell-plugin/doctor.zsh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env zsh

# ZSH Doctor - Diagnostic tool for Forge shell environment
# Checks for common configuration issues and environment setup

# Source user's .zshrc to get their environment (suppress errors from non-interactive mode)
if [[ -f "${ZDOTDIR:-$HOME}/.zshrc" ]]; then
    source "${ZDOTDIR:-$HOME}/.zshrc" 2>/dev/null
fi

# ANSI codes
local RESET='\033[0m'
local _BOLD='\033[1m'
local _DIM='\033[2m'
local _GREEN='\033[0;32m'
local _RED='\033[0;31m'
local _YELLOW='\033[0;33m'
local _CYAN='\033[0;36m'

# Text formatting helpers - auto-reset
function bold() { echo "${_BOLD}${1}${RESET}"; }
function dim() { echo "${_DIM}${1}${RESET}"; }
function green() { echo "${_GREEN}${1}${RESET}"; }
function red() { echo "${_RED}${1}${RESET}"; }
function yellow() { echo "${_YELLOW}${1}${RESET}"; }
function cyan() { echo "${_CYAN}${1}${RESET}"; }

# Simple ASCII symbols
local PASS="[OK]"
local FAIL="[ERROR]"
local WARN="[WARN]"

# Counters
local passed=0
local failed=0
local warnings=0

# Helper function to print section headers
function print_section() {
    echo ""
    echo "$(bold "$1")"
}

# Helper function to print results
function print_result() {
    local result_status=$1
    local message=$2
    local detail=$3
    
    case $result_status in
        pass)
            echo "  $(green "${PASS}") ${message}"
            ((passed++))
            ;;
        fail)
            echo "  $(red "${FAIL}") ${message}"
            [[ -n "$detail" ]] && echo "  $(dim "· ${detail}")"
            ((failed++))
            ;;
        warn)
            echo "  $(yellow "${WARN}") ${message}"
            [[ -n "$detail" ]] && echo "  $(dim "· ${detail}")"
            ((warnings++))
            ;;
        info)
            echo "  $(dim "· ${message}")"
            ;;
        code)
            echo "  $(dim "· ${message}")"
            ;;
        instruction)
            echo "  $(dim "· ${message}")"
            ;;
    esac
}

echo "$(bold "FORGE ENVIRONMENT DIAGNOSTICS")"

# 1. Check ZSH version
print_section "Shell Environment"
local zsh_version="${ZSH_VERSION}"
if [[ -n "$zsh_version" ]]; then
    local major=$(echo $zsh_version | cut -d. -f1)
    local minor=$(echo $zsh_version | cut -d. -f2)
    if [[ $major -ge 5 ]] && [[ $minor -ge 0 ]]; then
        print_result pass "zsh: ${zsh_version}"
    else
        print_result warn "zsh: ${zsh_version}" "Recommended: 5.0+"
    fi
else
    print_result fail "Unable to detect ZSH version"
fi

# Check terminal information
if [[ -n "$TERM_PROGRAM" ]]; then
    if [[ -n "$TERM_PROGRAM_VERSION" ]]; then
        print_result pass "Terminal: ${TERM_PROGRAM} ${TERM_PROGRAM_VERSION}"
    else
        print_result pass "Terminal: ${TERM_PROGRAM}"
    fi
elif [[ -n "$TERM" ]]; then
    print_result pass "Terminal: ${TERM}"
else
    print_result info "Terminal: unknown"
fi


# Check if Oh My Zsh is installed
if [[ -n "$ZSH" ]] && [[ -d "$ZSH" ]]; then
    local omz_version=""
    # Try to get OMZ version from version file if it exists
    if [[ -f "$ZSH/.git/refs/heads/master" ]]; then
        omz_version=$(cd "$ZSH" && git describe --tags 2>/dev/null || echo "custom")
    fi
    
    if [[ -n "$omz_version" ]]; then
        print_result pass "Oh My Zsh: ${omz_version}"
    else
        print_result pass "Oh My Zsh: installed"
    fi
    print_result info "${ZSH}"
else
    print_result warn "Oh My Zsh not found" "Install: sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\""
fi

# 2. Check if forge is installed and in PATH
print_section "Forge Installation"

# Check if forge is in PATH
if command -v forge &> /dev/null; then
    local forge_path=$(command -v forge)
    
    # Get forge version and extract just the version number
    local forge_version=$(forge --version 2>&1 | head -n1 | awk '{print $2}')
    if [[ -n "$forge_version" ]]; then
        print_result pass "forge: ${forge_version}"
        print_result info "${forge_path}"
    else
        print_result pass "forge: installed"
        print_result info "${forge_path}"
    fi
else
    print_result fail "Forge binary not found in PATH" "Installation: curl -fsSL https://forgecode.dev/cli | sh"
fi

# 3. Check shell plugin
print_section "Plugin"

# Check if forge plugin is loaded by checking environment variable
if [[ -n "$_FORGE_PLUGIN_LOADED" ]]; then
    print_result pass "Forge plugin loaded"
else
    print_result fail "Forge plugin not loaded"
    print_result instruction "Add to your ~/.zshrc:"
    print_result code "eval \"\$(forge zsh plugin)\""
    print_result instruction "Or run: forge zsh setup"
fi


# Check plugin loading order in .zshrc
local zshrc_file="${ZDOTDIR:-$HOME}/.zshrc"
if [[ -f "$zshrc_file" ]] && [[ -n "$_FORGE_PLUGIN_LOADED" ]]; then
    # Extract line numbers for plugin declarations and forge plugin eval
    local plugins_line=$(grep -n "^[[:space:]]*plugins=(" "$zshrc_file" 2>/dev/null | head -n1 | cut -d: -f1)
    local forge_plugin_line=$(grep -n "eval.*forge.*zsh plugin" "$zshrc_file" 2>/dev/null | head -n1 | cut -d: -f1)

    if [[ -n "$plugins_line" ]] && [[ -n "$forge_plugin_line" ]]; then
        if [[ $forge_plugin_line -lt $plugins_line ]]; then
            print_result fail "Plugin loading order incorrect"
            print_result instruction "Forge plugin (line ${forge_plugin_line}) should be loaded AFTER plugins=() (line ${plugins_line})"
            print_result instruction "Move the forge plugin eval statement after the plugins=() array in ~/.zshrc"
        else
            print_result pass "Plugin loading order correct"
        fi
    elif [[ -n "$forge_plugin_line" ]] && [[ -z "$plugins_line" ]]; then
        # Forge plugin found but no plugins=() array - check for individual plugin sources
        local has_other_plugins=false
        if grep -q "source.*zsh-autosuggestions" "$zshrc_file" 2>/dev/null || \
           grep -q "source.*zsh-syntax-highlighting" "$zshrc_file" 2>/dev/null; then
            has_other_plugins=true
        fi
        
        if [[ "$has_other_plugins" == "true" ]]; then
            print_result warn "Manual plugin loading detected"
            print_result info "Ensure forge plugin is sourced AFTER zsh-autosuggestions and zsh-syntax-highlighting"
        fi
    fi
fi

# 4. Check ZSH theme RPROMPT
print_section "FORGE RIGHT PROMPT"

# Check if forge theme is loaded by checking environment variable
if [[ -n "$_FORGE_THEME_LOADED" ]]; then
    print_result pass "Forge theme loaded"
elif (( $+functions[p10k] )); then
    print_result info "Powerlevel10k detected (not using Forge theme)"
elif [[ -n "$ZSH_THEME" ]]; then
    print_result warn "Using theme: ${ZSH_THEME}"
    print_result instruction "To use Forge theme, add to ~/.zshrc:"
    print_result code "eval \"\$(forge zsh theme)\""
else
    print_result warn "No theme loaded"
    print_result instruction "To use Forge theme, add to ~/.zshrc:"
    print_result code "eval \"\$(forge zsh theme)\""
fi

# Helper function to compare versions
# Returns 0 if version1 >= version2, 1 otherwise
function version_gte() {
    local version1=$1
    local version2=$2
    
    # Remove 'v' prefix if present
    version1=${version1#v}
    version2=${version2#v}
    
    # Split versions into arrays
    local -a ver1_parts=(${(s:.:)version1})
    local -a ver2_parts=(${(s:.:)version2})
    
    # Compare each part
    for i in {1..3}; do
        local v1=${ver1_parts[$i]:-0}
        local v2=${ver2_parts[$i]:-0}
        
        # Remove any non-numeric suffix (e.g., "0-rc1" -> "0")
        v1=${v1%%[^0-9]*}
        v2=${v2%%[^0-9]*}
        
        if [[ $v1 -gt $v2 ]]; then
            return 0
        elif [[ $v1 -lt $v2 ]]; then
            return 1
        fi
    done
    
    return 0  # versions are equal
}

# 5. Check dependencies
print_section "Dependencies"

# Forge uses its built-in nucleo-picker for interactive selection
# No external fuzzy finder (like fzf) is required
print_result pass "Interactive picker: built-in (nucleo-picker)"

# Check for fd/fdfind - used for file discovery
if command -v fd &> /dev/null; then
    local fd_version=$(fd --version 2>&1 | awk '{print $2}')
    if [[ -n "$fd_version" ]]; then
        if version_gte "$fd_version" "10.0.0"; then
            print_result pass "fd: ${fd_version}"
        else
            print_result fail "fd: ${fd_version}" "Version 10.0.0 or higher required. Update: https://github.com/sharkdp/fd#installation"
        fi
    else
        print_result pass "fd: installed"
    fi
elif command -v fdfind &> /dev/null; then
    local fd_version=$(fdfind --version 2>&1 | awk '{print $2}')
    if [[ -n "$fd_version" ]]; then
        if version_gte "$fd_version" "10.0.0"; then
            print_result pass "fdfind: ${fd_version}"
        else
            print_result fail "fdfind: ${fd_version}" "Version 10.0.0 or higher required. Update: https://github.com/sharkdp/fd#installation"
        fi
    else
        print_result pass "fdfind: installed"
    fi
else
    print_result warn "fd/fdfind not found" "Enhanced file discovery. See installation: https://github.com/sharkdp/fd#installation"
fi

# Check for bat - used for syntax highlighting
if command -v bat &> /dev/null; then
    local bat_version=$(bat --version 2>&1 | awk '{print $2}')
    if [[ -n "$bat_version" ]]; then
        if version_gte "$bat_version" "0.20.0"; then
            print_result pass "bat: ${bat_version}"
        else
            print_result fail "bat: ${bat_version}" "Version 0.20.0 or higher required. Update: https://github.com/sharkdp/bat#installation"
        fi
    else
        print_result pass "bat: installed"
    fi
else
    print_result warn "bat not found" "Enhanced preview. See installation: https://github.com/sharkdp/bat#installation"
fi

# 6. Check required ZSH plugins
print_section "Required Plugins"

# Check for zsh-autosuggestions
if [[ " ${plugins[*]} " =~ " zsh-autosuggestions " ]] || \
   [[ -n "$fpath[(r)*zsh-autosuggestions*]" ]] || \
   (( $+functions[_zsh_autosuggest_accept] )); then
    print_result pass "zsh-autosuggestions loaded"
else
    print_result warn "zsh-autosuggestions not found"
    print_result info "Install plugin and add to plugins=() in .zshrc"
    print_result info "Installation guide: https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md"
fi

# Check for zsh-syntax-highlighting
if [[ " ${plugins[*]} " =~ " zsh-syntax-highlighting " ]] || \
   [[ -n "$fpath[(r)*zsh-syntax-highlighting*]" ]] || \
   (( $+functions[_zsh_highlight] )); then
    print_result pass "zsh-syntax-highlighting loaded"
else
    print_result warn "zsh-syntax-highlighting not found"
    print_result info "Install plugin and add to plugins=() in .zshrc"
    print_result info "Installation guide: https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md"
fi

# 7. Check system configuration
print_section "System"

# Check editor configuration (FORGE_EDITOR takes precedence over EDITOR)
if [[ -n "$FORGE_EDITOR" ]]; then
    print_result pass "FORGE_EDITOR: ${FORGE_EDITOR}"
    if [[ -n "$EDITOR" ]]; then
        print_result info "EDITOR also set: ${EDITOR} (ignored)"
    fi
elif [[ -n "$EDITOR" ]]; then
    print_result pass "EDITOR: ${EDITOR}"
    print_result info "TIP: Set FORGE_EDITOR for forge-specific editor"
else
    print_result warn "No editor configured" "export EDITOR=vim or export FORGE_EDITOR=vim"
fi

# Check PATH for common issues
if [[ "$PATH" == *"/usr/local/bin"* ]] || [[ "$PATH" == *"/usr/bin"* ]]; then
    print_result pass "PATH: configured"
else
    print_result warn "PATH may need common directories" "Ensure /usr/local/bin or /usr/bin is in PATH"
fi

# 7. Check keyboard configuration (Alt/Option key as Meta)
print_section "Keyboard Configuration"

local platform=$(uname)
local meta_key_ok=false
local check_performed=false

if [[ "$platform" == "Darwin" ]]; then
    # macOS checks
    if [[ "$TERM_PROGRAM" == "vscode" ]]; then
        check_performed=true
        # Check VS Code settings
        local vscode_settings="${HOME}/Library/Application Support/Code/User/settings.json"
        if [[ -f "$vscode_settings" ]]; then
            if grep -q '"terminal.integrated.macOptionIsMeta"[[:space:]]*:[[:space:]]*true' "$vscode_settings" 2>/dev/null; then
                print_result pass "VS Code: Option key configured as Meta"
                meta_key_ok=true
            else
                print_result warn "VS Code: Option key NOT configured as Meta"
                print_result instruction "Option+F and Option+B shortcuts won't work for word navigation"
                print_result instruction "Add to VS Code settings.json:"
                print_result code '"terminal.integrated.macOptionIsMeta": true'
                print_result instruction "Then reload VS Code: Cmd+Shift+P → Reload Window"
            fi
        else
            print_result warn "VS Code settings file not found"
            print_result info "Expected: ${vscode_settings}"
        fi
    elif [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then
        check_performed=true
        # Check iTerm2 preferences
        local iterm_prefs="${HOME}/Library/Preferences/com.googlecode.iterm2.plist"
        if [[ -f "$iterm_prefs" ]]; then
            # Check if either Left or Right Option key is set to Esc+ (value 2)
            local option_setting=$(defaults read com.googlecode.iterm2 2>/dev/null | grep -E '"(Left |Right )?Option Key Sends"' | grep -o '[0-9]' | head -1)
            if [[ "$option_setting" == "2" ]]; then
                print_result pass "iTerm2: Option key configured as Esc+"
                meta_key_ok=true
            else
                print_result warn "iTerm2: Option key NOT configured as Esc+"
                print_result instruction "Option+F and Option+B shortcuts won't work for word navigation"
                print_result instruction "Configure in iTerm2:"
                print_result info "Preferences → Profiles → Keys → Left/Right Option Key → Esc+"
            fi
        else
            print_result warn "iTerm2 preferences not found"
            print_result info "Expected: ${iterm_prefs}"
        fi
    elif [[ "$TERM_PROGRAM" == "Apple_Terminal" ]]; then
        check_performed=true
        # Check Terminal.app preferences
        local terminal_prefs="${HOME}/Library/Preferences/com.apple.Terminal.plist"
        if [[ -f "$terminal_prefs" ]]; then
            local use_option=$(defaults read com.apple.Terminal 2>/dev/null | grep -E 'useOptionAsMetaKey' | grep -o '[0-9]' | head -1)
            if [[ "$use_option" == "1" ]]; then
                print_result pass "Terminal.app: Option key configured as Meta"
                meta_key_ok=true
            else
                print_result warn "Terminal.app: Option key NOT configured as Meta"
                print_result instruction "Option+F and Option+B shortcuts won't work for word navigation"
                print_result instruction "Configure in Terminal.app:"
                print_result info "Preferences → Profiles → Keyboard → ✓ Use Option as Meta key"
            fi
        else
            print_result warn "Terminal.app preferences not found"
            print_result info "Expected: ${terminal_prefs}"
        fi
    fi
    
    # If no specific terminal detected, provide general guidance for macOS
    if [[ "$check_performed" == "false" ]]; then
        print_result info "Terminal: ${TERM_PROGRAM:-unknown}"
        print_result info "For Option key shortcuts (word navigation) to work:"
        print_result info "• VS Code: Settings → terminal.integrated.macOptionIsMeta → true"
        print_result info "• iTerm2: Preferences → Profiles → Keys → Option Key → Esc+"
        print_result info "• Terminal.app: Preferences → Profiles → Keyboard → Use Option as Meta"
        print_result info "Run 'forge zsh keyboard' for detailed keyboard shortcuts"
    fi
    
elif [[ "$platform" == "Linux" ]]; then
    # Linux checks
    if [[ "$TERM_PROGRAM" == "vscode" ]]; then
        check_performed=true
        # Check VS Code settings on Linux
        local vscode_settings="${HOME}/.config/Code/User/settings.json"
        if [[ -f "$vscode_settings" ]]; then
            # On Linux, check for sendAltAsMetaKey (deprecated but still works) or macOptionIsMeta
            if grep -q '"terminal.integrated.sendAltAsMetaKey"[[:space:]]*:[[:space:]]*true' "$vscode_settings" 2>/dev/null || \
               grep -q '"terminal.integrated.macOptionIsMeta"[[:space:]]*:[[:space:]]*true' "$vscode_settings" 2>/dev/null; then
                print_result pass "VS Code: Alt key configured as Meta"
                meta_key_ok=true
            else
                print_result warn "VS Code: Alt key NOT configured as Meta"
                print_result instruction "Alt+F and Alt+B shortcuts won't work for word navigation"
                print_result instruction "Add to VS Code settings.json:"
                print_result code '"terminal.integrated.sendAltAsMetaKey": true'
                print_result instruction "Then reload VS Code: Ctrl+Shift+P → Reload Window"
            fi
        else
            print_result warn "VS Code settings file not found"
            print_result info "Expected: ${vscode_settings}"
        fi
    elif [[ -n "$GNOME_TERMINAL_SERVICE" ]] || [[ "$COLORTERM" == "gnome-terminal" ]]; then
        check_performed=true
        # GNOME Terminal check
        local profile_id=$(gsettings get org.gnome.Terminal.ProfilesList default 2>/dev/null | tr -d "'")
        if [[ -n "$profile_id" ]]; then
            local alt_sends_escape=$(gsettings get org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:${profile_id}/ use-theme-colors 2>/dev/null)
            # GNOME Terminal doesn't have a simple setting check - most distributions enable Alt by default
            print_result pass "GNOME Terminal: Alt key typically works by default"
            print_result info "If Alt+F/B don't work, check: Preferences → Profile → Keyboard"
            meta_key_ok=true
        else
            print_result info "GNOME Terminal detected"
            print_result info "Alt key typically works by default for word navigation"
        fi
    elif [[ "$COLORTERM" == "truecolor" ]] && command -v konsole &> /dev/null; then
        check_performed=true
        # Konsole (KDE) - Alt typically works by default
        print_result pass "Konsole: Alt key typically works by default"
        print_result info "If Alt+F/B don't work, check: Settings → Edit Profile → Keyboard"
        meta_key_ok=true
    elif [[ -n "$ALACRITTY_SOCKET" ]] || [[ "$TERM" == "alacritty" ]]; then
        check_performed=true
        # Alacritty check - look for config file
        local alacritty_config="${HOME}/.config/alacritty/alacritty.yml"
        local alacritty_config_toml="${HOME}/.config/alacritty/alacritty.toml"
        
        if [[ -f "$alacritty_config" ]] || [[ -f "$alacritty_config_toml" ]]; then
            print_result pass "Alacritty: Alt key typically works by default"
            print_result info "If Alt+F/B don't work, ensure no conflicting key bindings"
            meta_key_ok=true
        else
            print_result pass "Alacritty: Alt key typically works by default"
            print_result info "Config: ${alacritty_config} or ${alacritty_config_toml}"
        fi
    elif [[ "$TERM" == "xterm" ]] || [[ "$TERM" == "xterm-256color" ]]; then
        check_performed=true
        # xterm - check .Xresources
        local xresources="${HOME}/.Xresources"
        if [[ -f "$xresources" ]]; then
            if grep -q "XTerm\*metaSendsEscape:[[:space:]]*true" "$xresources" 2>/dev/null || \
               grep -q "XTerm\*eightBitInput:[[:space:]]*false" "$xresources" 2>/dev/null; then
                print_result pass "xterm: Meta key configured"
                meta_key_ok=true
            else
                print_result warn "xterm: Meta key may not be configured"
                print_result instruction "Add to ~/.Xresources:"
                print_result code "XTerm*metaSendsEscape: true"
                print_result instruction "Then reload: xrdb ~/.Xresources"
            fi
        else
            print_result info "xterm detected"
            print_result info "To enable Alt as Meta, add to ~/.Xresources:"
            print_result info "XTerm*metaSendsEscape: true"
        fi
    fi
    
    # If no specific terminal detected, provide general guidance for Linux
    if [[ "$check_performed" == "false" ]]; then
        print_result info "Terminal: ${TERM_PROGRAM:-$TERM}"
        print_result info "For Alt key shortcuts (word navigation) to work:"
        print_result info "• VS Code: Settings → terminal.integrated.sendAltAsMetaKey → true"
        print_result info "• GNOME Terminal: Usually works by default"
        print_result info "• Konsole: Usually works by default"
        print_result info "• xterm: Add 'XTerm*metaSendsEscape: true' to ~/.Xresources"
        print_result info "Run 'forge zsh keyboard' for detailed keyboard shortcuts"
    fi
else
    # Other platforms (BSD, etc.)
    print_result info "Keyboard check: Platform ${platform} - manual verification needed"
    print_result info "Ensure Alt/Meta key is configured for word navigation shortcuts"
fi

# 8. Check font and Nerd Font support
print_section "Nerd Font"

# Check if Nerd Font is enabled via environment variables
if [[ -n "$NERD_FONT" ]]; then
    if [[ "$NERD_FONT" == "1" || "$NERD_FONT" == "true" ]]; then
        print_result pass "NERD_FONT: enabled"
    else
        print_result warn "NERD_FONT: disabled (${NERD_FONT})"
        print_result instruction "Enable Nerd Font by setting:"
        print_result code "export NERD_FONT=1"
    fi
elif [[ -n "$USE_NERD_FONT" ]]; then
    if [[ "$USE_NERD_FONT" == "1" || "$USE_NERD_FONT" == "true" ]]; then
        print_result pass "USE_NERD_FONT: enabled"
    else
        print_result warn "USE_NERD_FONT: disabled (${USE_NERD_FONT})"
        print_result instruction "Enable Nerd Font by setting:"
        print_result code "export NERD_FONT=1"
    fi
else
    print_result pass "Nerd Font: enabled (default)"
    print_result info "Forge will auto-detect based on terminal capabilities"
fi

# Show actual icons used in Forge theme for manual verification (skip if explicitly disabled)
local nerd_font_disabled=false
if [[ -n "$NERD_FONT" && "$NERD_FONT" != "1" && "$NERD_FONT" != "true" ]]; then
    nerd_font_disabled=true
elif [[ -n "$USE_NERD_FONT" && "$USE_NERD_FONT" != "1" && "$USE_NERD_FONT" != "true" ]]; then
    nerd_font_disabled=true
fi

if [[ "$nerd_font_disabled" == "false" ]]; then
    echo ""
    echo "$(yellow "Visual Check [Manual Verification Required]")"
echo "   $(bold "󱙺 FORGE 33.0k") $(cyan " tonic-1.0")"
    echo ""
    echo "   Forge uses Nerd Fonts to enrich cli experience, can you see all the icons clearly without any overlap?"
    echo "   If you see boxes (□) or question marks (?), install a Nerd Font from:"
    echo "   $(dim "https://www.nerdfonts.com/")"
    echo ""
fi

# Summary
echo ""

if [[ $failed -eq 0 && $warnings -eq 0 ]]; then
    echo "$(green "${PASS}") $(bold "All checks passed") $(dim "(${passed})")"
    exit 0
elif [[ $failed -eq 0 ]]; then
    echo "$(yellow "${WARN}") $(bold "${warnings} warnings") $(dim "(${passed} passed)")"
    exit 0
else
    echo "$(red "${FAIL}") $(bold "${failed} failed") $(dim "(${warnings} warnings, ${passed} passed)")"
    exit 1
fi