| #!/usr/bin/env zsh |
|
|
| |
| |
|
|
| |
| if [[ -f "${ZDOTDIR:-$HOME}/.zshrc" ]]; then |
| source "${ZDOTDIR:-$HOME}/.zshrc" 2>/dev/null |
| fi |
|
|
| |
| 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' |
|
|
| |
| 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}"; } |
|
|
| |
| local PASS="[OK]" |
| local FAIL="[ERROR]" |
| local WARN="[WARN]" |
|
|
| |
| local passed=0 |
| local failed=0 |
| local warnings=0 |
|
|
| |
| function print_section() { |
| echo "" |
| echo "$(bold "$1")" |
| } |
|
|
| |
| 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")" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
|
|
| |
| if [[ -n "$ZSH" ]] && [[ -d "$ZSH" ]]; then |
| local omz_version="" |
| |
| 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 |
|
|
| |
| print_section "Forge Installation" |
|
|
| |
| if command -v forge &> /dev/null; then |
| local forge_path=$(command -v forge) |
| |
| |
| 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 |
|
|
| |
| print_section "Plugin" |
|
|
| |
| 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 |
|
|
|
|
| |
| local zshrc_file="${ZDOTDIR:-$HOME}/.zshrc" |
| if [[ -f "$zshrc_file" ]] && [[ -n "$_FORGE_PLUGIN_LOADED" ]]; then |
| |
| 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 |
| |
| 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 |
|
|
| |
| print_section "FORGE RIGHT PROMPT" |
|
|
| |
| 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 |
|
|
| |
| |
| function version_gte() { |
| local version1=$1 |
| local version2=$2 |
| |
| |
| version1=${version1#v} |
| version2=${version2#v} |
| |
| |
| local -a ver1_parts=(${(s:.:)version1}) |
| local -a ver2_parts=(${(s:.:)version2}) |
| |
| |
| for i in {1..3}; do |
| local v1=${ver1_parts[$i]:-0} |
| local v2=${ver2_parts[$i]:-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 |
| } |
|
|
| |
| print_section "Dependencies" |
|
|
| |
| |
| print_result pass "Interactive picker: built-in (nucleo-picker)" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| print_section "Required Plugins" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| print_section "System" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| print_section "Keyboard Configuration" |
|
|
| local platform=$(uname) |
| local meta_key_ok=false |
| local check_performed=false |
|
|
| if [[ "$platform" == "Darwin" ]]; then |
| |
| if [[ "$TERM_PROGRAM" == "vscode" ]]; then |
| check_performed=true |
| |
| 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 |
| |
| local iterm_prefs="${HOME}/Library/Preferences/com.googlecode.iterm2.plist" |
| if [[ -f "$iterm_prefs" ]]; then |
| |
| 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 |
| |
| 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 [[ "$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 |
| |
| if [[ "$TERM_PROGRAM" == "vscode" ]]; then |
| check_performed=true |
| |
| local vscode_settings="${HOME}/.config/Code/User/settings.json" |
| if [[ -f "$vscode_settings" ]]; then |
| |
| 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 |
| |
| 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) |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 [[ "$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 |
| |
| print_result info "Keyboard check: Platform ${platform} - manual verification needed" |
| print_result info "Ensure Alt/Meta key is configured for word navigation shortcuts" |
| fi |
|
|
| |
| print_section "Nerd Font" |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|