AI Agents: Instalando LSPs pro Crush

Este post é mais pra eu documentar e não esquecer. Como falei nos últimos posts, tenho usado Charm’s Crush como meu agente favorito de IA pra programação. Repetindo: tanto faz se você usa Claude Code ou OpenCode, todos eles são igualmente bons. Minha preferência pessoal ainda é Crush porque acho mais bonito, só isso.

Ainda não entendi o quanto faz diferença ou não, mas tanto OpenCode quanto Crush tem suporte a Language Servers (LSPs) então achei legal deixar configurado. Eis meu ~/.config/crush/crush.json:

{
  "$schema": "https://charm.land/crush.json",
  "default_provider": "openrouter",
  "providers": {
    "openrouter": {
      "api_key": "sk-..."
    },
    "lmstudio": {
      "name": "LM Studio",
      "base_url": "http://localhost:1234/v1/",
      "type": "openai",
      "models": [
        {
          "name": "gpt-oss-120b",
          "id": "gpt-oss:120b",
          "context_window": 100000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "name": "gpt-oss-20b",
          "id": "gpt-oss:20b",
          "context_window": 130000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "name": "zai-org/glm-4.6v-flash",
          "id": "glm-4.6v-flash",
          "context_window": 130000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "name": "deepseek-coder-v2-lite-instruct",
          "id": "deepseek-coder-v2-lite-instruct",
          "context_window": 64000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "qwen/qwen3-coder-30b",
          "name": "qwen3-coder-30b",
          "context_window": 120000,
          "default_max_tokens": -1,
          "supports_tools": true
        }
      ]
    },
    "ollama": {
      "type": "openai-compat",
      "base_url": "http://127.0.0.1:11434/v1",
      "api_key": "ollama",
      "models": [
        {
          "name": "glm-4.7-flash",
          "id": "glm-4.7-flash",
          "context_window": 65536,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "name": "gpt-oss-20b",
          "id": "gpt-oss:20b",
          "context_window": 64000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "qwen3-nuclear",
          "name": "qwen3-nuclear",
          "context_window": 120000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "qwen3-coder:30b",
          "name": "qwen3-coder:30b",
          "context_window": 120000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "qwen2.5-coder:32b",
          "name": "qwen2.5-coder:32b",
          "context_window": 32768,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "deepseek-coder-v2:16b",
          "name": "deepseek-coder-v2:16b",
          "context_window": 128000,
          "default_max_tokens": -1,
          "supports_tools": true
        },
        {
          "id": "glm-4.7:cloud",
          "name": "glm-4.7:cloud",
          "context_window": 128000,
          "default_max_tokens": -1,
          "supports_tools": true
        }
      ]
    }
  },
  "lsp": {
    "gopls": {
      "command": "gopls"
    },
    "rust-analyzer": {
      "command": "rust-analyzer"
    },
    "ruby-lsp": {
      "command": "ruby-lsp"
    },
    "solargraph": {
      "command": "solargraph",
      "args": [
        "stdio"
      ]
    },
    "pylsp": {
      "command": "pylsp"
    },
    "pyright": {
      "command": "pyright-langserver",
      "args": [
        "--stdio"
      ]
    },
    "elixir-ls": {
      "command": "elixir-ls"
    },
    "docker-langserver": {
      "command": "docker-langserver",
      "args": [
        "--stdio"
      ],
      "languages": [
        "dockerfile"
      ]
    },
    "bash-language-server": {
      "command": "bash-language-server",
      "args": [
        "start"
      ]
    },
    "marksman": {
      "command": "marksman",
      "args": [
        "server"
      ]
    },
    "typescript-language-server": {
      "command": "typescript-language-server",
      "args": [
        "--stdio"
      ]
    },
    "vscode-html-language-server": {
      "command": "vscode-html-language-server",
      "args": [
        "--stdio"
      ]
    },
    "vscode-css-language-server": {
      "command": "vscode-css-language-server",
      "args": [
        "--stdio"
      ]
    },
    "vscode-json-language-server": {
      "command": "vscode-json-language-server",
      "args": [
        "--stdio"
      ]
    },
    "yaml-language-server": {
      "command": "yaml-language-server",
      "args": [
        "--stdio"
      ]
    },
    "taplo": {
      "command": "taplo",
      "args": [
        "lsp",
        "stdio"
      ]
    }
  },
  "mcp": {
    "blender": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "blender-mcp"
      ]
    }
  }
}

Notem que eu uso primariamente Claude Opus 4.5 via OpenRouter e tenho LM Studio e Ollama configurados como providers pra testar outros modelos open source.

Também uso Mise pra organizar as linguagens de programação que uso.

Porém, agora precisa instalar os LSPs de cada linguagem. Eu não conheço um jeito melhor de gerenciar isso da mesma forma como com um Mason no Neovim. Então fiz um script pra instalar tudo pra mim (no meu Arch Linux). Pra isso criei este script ~/.local/bin/install-lsps.sh:

#!/usr/bin/env bash
set -euo pipefail

# LSP Installer for Arch Linux with Mise
# Installs major LSPs using native package managers or yay
# Only installs LSPs for languages that are already installed

GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

info() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[SKIP]${NC} $1"; }
skip_lang() { echo -e "${RED}[LANG]${NC} $1 not installed, skipping LSPs"; }

command_exists() { command -v "$1" &>/dev/null; }

# ─────────────────────────────────────────────────────────────
# Language detection helpers
# ─────────────────────────────────────────────────────────────
has_c()       { command_exists gcc || command_exists clang; }
has_rust()    { command_exists rustc || command_exists cargo; }
has_go()      { command_exists go; }
has_python()  { command_exists python || command_exists python3; }
has_node()    { command_exists node || command_exists npm; }
has_java()    { command_exists java || command_exists javac; }
has_kotlin()  { command_exists kotlin || command_exists kotlinc; }
has_ruby()    { command_exists ruby; }
has_php()     { command_exists php; }
has_lua()     { command_exists lua || command_exists luajit; }
has_zig()     { command_exists zig; }
has_haskell() { command_exists ghc || command_exists stack || command_exists cabal; }
has_elixir()  { command_exists elixir || command_exists iex; }
has_scala()   { command_exists scala || command_exists sbt; }
has_dotnet()  { command_exists dotnet; }
has_ocaml()   { command_exists ocaml || command_exists opam; }
has_clojure() { command_exists clojure || command_exists clj || command_exists lein; }
has_nix()     { command_exists nix; }
has_terraform() { command_exists terraform || command_exists tofu; }
has_latex()   { command_exists latex || command_exists pdflatex || command_exists xelatex; }
has_docker()  { command_exists docker || command_exists podman; }
has_ansible() { command_exists ansible; }
has_sql()     { command_exists psql || command_exists mysql || command_exists sqlite3; }
has_bash()    { command_exists bash; }

# ─────────────────────────────────────────────────────────────
# Package installation helpers
# ─────────────────────────────────────────────────────────────
install_pacman() {
    local pkg="$1"
    if pacman -Qi "$pkg" &>/dev/null; then
        warn "$pkg already installed"
    else
        sudo pacman -S --noconfirm --needed "$pkg" && success "$pkg" || warn "Failed: $pkg"
    fi
}

install_yay() {
    local pkg="$1"
    if ! command_exists yay; then
        warn "yay not found, skipping $pkg"
        return
    fi
    if yay -Qi "$pkg" &>/dev/null; then
        warn "$pkg already installed"
    else
        yay -S --noconfirm --needed "$pkg" && success "$pkg" || warn "Failed: $pkg"
    fi
}

install_npm() {
    local pkg="$1"
    if npm list -g "$pkg" &>/dev/null 2>&1; then
        warn "$pkg already installed"
    else
        npm install -g "$pkg" && success "$pkg" || warn "Failed: $pkg"
    fi
}

install_pipx() {
    local pkg="$1"
    if ! command_exists pipx; then
        pip install --user pipx 2>/dev/null || {
            pip install --user "$pkg" && success "$pkg" || warn "Failed: $pkg"
            return
        }
    fi
    if pipx list 2>/dev/null | grep -q "$pkg"; then
        warn "$pkg already installed"
    else
        pipx install "$pkg" && success "$pkg" || warn "Failed: $pkg"
    fi
}

install_go() {
    local pkg="$1"
    local name="${pkg##*/}"
    name="${name%%@*}"
    
    # Check if binary already exists in GOPATH/bin
    local gobin="${GOBIN:-${GOPATH:-$HOME/go}/bin}"
    if [[ -x "$gobin/$name" ]]; then
        warn "$name already installed"
        return
    fi
    
    go install "$pkg" && success "$name" || warn "Failed: $name"
}

install_gem() {
    local pkg="$1"
    if gem list -i "^${pkg}$" &>/dev/null; then
        warn "$pkg already installed"
    else
        gem install "$pkg" && success "$pkg" || warn "Failed: $pkg"
    fi
}

# ─────────────────────────────────────────────────────────────
# C/C++ LSPs
# ─────────────────────────────────────────────────────────────
install_c_lsps() {
    if ! has_c; then
        skip_lang "C/C++ (gcc/clang)"
        return
    fi
    info "Installing C/C++ LSPs..."
    install_pacman clang  # includes clangd
}

# ─────────────────────────────────────────────────────────────
# Rust LSPs
# ─────────────────────────────────────────────────────────────
install_rust_lsps() {
    if ! has_rust; then
        skip_lang "Rust"
        return
    fi
    info "Installing Rust LSPs..."
    
    if command_exists rustup; then
        rustup component add rust-analyzer 2>/dev/null && success "rust-analyzer (rustup)" || install_pacman rust-analyzer
    else
        install_pacman rust-analyzer
    fi
}

# ─────────────────────────────────────────────────────────────
# Go LSPs
# ─────────────────────────────────────────────────────────────
install_go_lsps() {
    if ! has_go; then
        skip_lang "Go"
        return
    fi
    info "Installing Go LSPs..."
    
    install_go golang.org/x/tools/gopls@latest
    install_go github.com/go-delve/delve/cmd/dlv@latest
    install_go github.com/nametake/golangci-lint-langserver@latest
}

# ─────────────────────────────────────────────────────────────
# Python LSPs
# ─────────────────────────────────────────────────────────────
install_python_lsps() {
    if ! has_python; then
        skip_lang "Python"
        return
    fi
    info "Installing Python LSPs..."
    
    # Prefer npm pyright (faster updates) if node available
    if has_node; then
        install_npm pyright
    else
        install_pipx pyright
    fi
    
    install_pipx python-lsp-server
    install_pipx ruff-lsp
}

# ─────────────────────────────────────────────────────────────
# Node.js/TypeScript LSPs
# ─────────────────────────────────────────────────────────────
install_node_lsps() {
    if ! has_node; then
        skip_lang "Node.js"
        return
    fi
    info "Installing Node.js/TypeScript LSPs..."
    
    install_npm typescript
    install_npm typescript-language-server
    install_npm vscode-langservers-extracted  # HTML, CSS, JSON, ESLint
    install_npm "@tailwindcss/language-server"
    install_npm "@vue/language-server"
    install_npm svelte-language-server
    install_npm graphql-language-service-cli
    install_npm "@prisma/language-server"
    install_npm emmet-ls
}

# ─────────────────────────────────────────────────────────────
# Java LSPs
# ─────────────────────────────────────────────────────────────
install_java_lsps() {
    if ! has_java; then
        skip_lang "Java"
        return
    fi
    info "Installing Java LSPs..."
    install_yay jdtls
}

# ─────────────────────────────────────────────────────────────
# Kotlin LSPs
# ─────────────────────────────────────────────────────────────
install_kotlin_lsps() {
    if ! has_kotlin; then
        skip_lang "Kotlin"
        return
    fi
    info "Installing Kotlin LSPs..."
    install_yay kotlin-language-server
}

# ─────────────────────────────────────────────────────────────
# Ruby LSPs
# ─────────────────────────────────────────────────────────────
install_ruby_lsps() {
    if ! has_ruby; then
        skip_lang "Ruby"
        return
    fi
    info "Installing Ruby LSPs..."
    install_gem solargraph
    install_gem ruby-lsp
}

# ─────────────────────────────────────────────────────────────
# PHP LSPs
# ─────────────────────────────────────────────────────────────
install_php_lsps() {
    if ! has_php; then
        skip_lang "PHP"
        return
    fi
    if ! has_node; then
        warn "PHP LSP (intelephense) requires npm, skipping"
        return
    fi
    info "Installing PHP LSPs..."
    install_npm intelephense
}

# ─────────────────────────────────────────────────────────────
# Lua LSPs
# ─────────────────────────────────────────────────────────────
install_lua_lsps() {
    if ! has_lua; then
        skip_lang "Lua"
        return
    fi
    info "Installing Lua LSPs..."
    install_pacman lua-language-server
}

# ─────────────────────────────────────────────────────────────
# Zig LSPs
# ─────────────────────────────────────────────────────────────
install_zig_lsps() {
    if ! has_zig; then
        skip_lang "Zig"
        return
    fi
    info "Installing Zig LSPs..."
    install_pacman zls
}

# ─────────────────────────────────────────────────────────────
# Haskell LSPs
# ─────────────────────────────────────────────────────────────
install_haskell_lsps() {
    if ! has_haskell; then
        skip_lang "Haskell"
        return
    fi
    info "Installing Haskell LSPs..."
    install_yay haskell-language-server
}

# ─────────────────────────────────────────────────────────────
# Elixir LSPs
# ─────────────────────────────────────────────────────────────
install_elixir_lsps() {
    if ! has_elixir; then
        skip_lang "Elixir"
        return
    fi
    info "Installing Elixir LSPs..."
    install_yay elixir-ls
}

# ─────────────────────────────────────────────────────────────
# Scala LSPs
# ─────────────────────────────────────────────────────────────
install_scala_lsps() {
    if ! has_scala; then
        skip_lang "Scala"
        return
    fi
    info "Installing Scala LSPs..."
    install_yay metals
}

# ─────────────────────────────────────────────────────────────
# C# / F# (.NET) LSPs
# ─────────────────────────────────────────────────────────────
install_dotnet_lsps() {
    if ! has_dotnet; then
        skip_lang ".NET (C#/F#)"
        return
    fi
    info "Installing .NET LSPs..."
    install_yay omnisharp-roslyn
    install_yay fsautocomplete
}

# ─────────────────────────────────────────────────────────────
# OCaml LSPs
# ─────────────────────────────────────────────────────────────
install_ocaml_lsps() {
    if ! has_ocaml; then
        skip_lang "OCaml"
        return
    fi
    info "Installing OCaml LSPs..."
    if command_exists opam; then
        opam install ocaml-lsp-server -y && success "ocaml-lsp-server" || warn "Failed"
    else
        install_yay ocaml-lsp-server
    fi
}

# ─────────────────────────────────────────────────────────────
# Clojure LSPs
# ─────────────────────────────────────────────────────────────
install_clojure_lsps() {
    if ! has_clojure; then
        skip_lang "Clojure"
        return
    fi
    info "Installing Clojure LSPs..."
    install_yay clojure-lsp-bin
}

# ─────────────────────────────────────────────────────────────
# Nix LSPs
# ─────────────────────────────────────────────────────────────
install_nix_lsps() {
    if ! has_nix; then
        skip_lang "Nix"
        return
    fi
    info "Installing Nix LSPs..."
    install_pacman nil
}

# ─────────────────────────────────────────────────────────────
# Bash LSPs
# ─────────────────────────────────────────────────────────────
install_bash_lsps() {
    if ! has_bash; then
        skip_lang "Bash"
        return
    fi
    info "Installing Bash LSPs..."
    install_pacman bash-language-server
}

# ─────────────────────────────────────────────────────────────
# Terraform LSPs
# ─────────────────────────────────────────────────────────────
install_terraform_lsps() {
    if ! has_terraform; then
        skip_lang "Terraform"
        return
    fi
    info "Installing Terraform LSPs..."
    install_pacman terraform-ls
}

# ─────────────────────────────────────────────────────────────
# LaTeX LSPs
# ─────────────────────────────────────────────────────────────
install_latex_lsps() {
    if ! has_latex; then
        skip_lang "LaTeX"
        return
    fi
    info "Installing LaTeX LSPs..."
    install_pacman texlab
}

# ─────────────────────────────────────────────────────────────
# Docker LSPs
# ─────────────────────────────────────────────────────────────
install_docker_lsps() {
    if ! has_docker; then
        skip_lang "Docker"
        return
    fi
    info "Installing Docker LSPs..."
    install_yay dockerfile-language-server
}

# ─────────────────────────────────────────────────────────────
# Ansible LSPs
# ─────────────────────────────────────────────────────────────
install_ansible_lsps() {
    if ! has_ansible; then
        skip_lang "Ansible"
        return
    fi
    if ! has_node; then
        warn "Ansible LSP requires npm, skipping"
        return
    fi
    info "Installing Ansible LSPs..."
    install_npm "@ansible/ansible-language-server"
}

# ─────────────────────────────────────────────────────────────
# SQL LSPs
# ─────────────────────────────────────────────────────────────
install_sql_lsps() {
    if ! has_sql; then
        skip_lang "SQL (psql/mysql/sqlite)"
        return
    fi
    info "Installing SQL LSPs..."
    install_pacman sqls
}

# ─────────────────────────────────────────────────────────────
# Generic config file LSPs (always install)
# ─────────────────────────────────────────────────────────────
install_generic_lsps() {
    info "Installing generic config LSPs (YAML, TOML, JSON, Markdown, XML)..."
    install_pacman yaml-language-server
    install_pacman taplo               # TOML
    install_pacman marksman            # Markdown
    install_yay lemminx                # XML
}

# ─────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────
main() {
    echo "═══════════════════════════════════════════════════════════"
    echo "         LSP Installer for Arch Linux + Mise"
    echo "    (Only installs LSPs for detected languages)"
    echo "═══════════════════════════════════════════════════════════"
    echo
    
    # Language-specific LSPs
    install_c_lsps
    install_rust_lsps
    install_go_lsps
    install_python_lsps
    install_node_lsps
    install_java_lsps
    install_kotlin_lsps
    install_ruby_lsps
    install_php_lsps
    install_lua_lsps
    install_zig_lsps
    install_haskell_lsps
    install_elixir_lsps
    install_scala_lsps
    install_dotnet_lsps
    install_ocaml_lsps
    install_clojure_lsps
    install_nix_lsps
    install_bash_lsps
    install_terraform_lsps
    install_latex_lsps
    install_docker_lsps
    install_ansible_lsps
    install_sql_lsps
    
    echo
    # Always-useful LSPs for config files
    install_generic_lsps
    
    echo
    echo "═══════════════════════════════════════════════════════════"
    success "LSP installation complete!"
    echo "═══════════════════════════════════════════════════════════"
}

main "$@"

Teoricamente é só isso. Se alguém conhecer um jeito melhor, mande nos comentários abaixo.