AI Agents: Installing LSPs for Crush
This post is mostly for me to document things and not forget. As I said in the last few posts, I’ve been using Charm’s Crush as my favorite AI coding agent. Repeating myself: it doesn’t matter if you use Claude Code or OpenCode, they’re all equally good. My personal preference is still Crush because I think it looks nicer, that’s all.
I still don’t fully understand how much of a difference it makes, but both OpenCode and Crush support Language Servers (LSPs), so I thought it’d be cool to have it configured. Here’s my ~/.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"
]
}
}
}Note that I primarily use Claude Opus 4.5 via OpenRouter and I have LM Studio and Ollama configured as providers for testing other open source models.
I also use Mise to organize the programming languages I use.
But now you need to install the LSPs for each language. I don’t know of a better way to manage this the same way Mason does in Neovim. So I made a script to install everything for me (on my Arch Linux). For that I created this 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 "$@"In theory that’s all there is to it. If anyone knows a better way, drop it in the comments below.