Back to Articles

Crush: The Terminal AI Coding Assistant That Survives Model Switching

[ View on GitHub ]

Crush: The Terminal AI Coding Assistant That Survives Model Switching

Hook

Most AI coding assistants force you to restart your entire conversation when switching from GPT-4 to Claude. Crush preserves your context across model changes—a technical feat that reveals how few tools actually understand session management.

Context

The AI coding assistant landscape has bifurcated into two camps: GUI-first tools like Cursor that lock you into specific editors, and CLI tools like Aider that keep you in the terminal but force you to commit to a single model per session. This creates a frustrating workflow: you start a complex refactoring conversation with GPT-4, realize Claude would handle the specific pattern better, and face the choice of either losing all context or pushing through with a suboptimal model.

Charm Bracelet—the team behind beloved terminal tools like Glow and VHS—built Crush to solve this exact problem for terminal-centric developers. Rather than treating the LLM as the architecture's centerpiece, Crush treats it as a swappable component while making the session and context management the architectural foundation. It's built entirely in Go using the Bubble Tea TUI framework, integrating Language Server Protocol (LSP) for code understanding and Model Context Protocol (MCP) for extensible tool integration. The result is an AI coding assistant that feels native to terminal workflows while offering flexibility that GUI tools can't match.

Technical Insight

Crush's architecture centers on session persistence as a first-class concern. Each project can maintain multiple concurrent sessions stored as JSON files in ~/.config/crush/sessions/, with each session preserving not just the conversation history but the entire context graph—file references, LSP responses, and tool invocations. When you switch from gpt-4 to claude-3-5-sonnet mid-conversation, Crush re-serializes the message history into the new provider's format without losing the semantic context.

The LSP integration is what elevates Crush beyond simple chat-with-context tools. When you reference a file or ask about code structure, Crush spawns LSP clients (gopls for Go, typescript-language-server for TypeScript, etc.) and queries them exactly as your editor would. Here's what a typical interaction flow looks like:

// Crush's LSP client requesting definition information
type LSPClient struct {
    conn   *jsonrpc2.Conn
    ctx    context.Context
    rootURI lsp.DocumentURI
}

func (c *LSPClient) GetDefinition(file string, line, char int) (*lsp.Location, error) {
    params := &lsp.TextDocumentPositionParams{
        TextDocument: lsp.TextDocumentIdentifier{URI: lsp.DocumentURI(file)},
        Position:     lsp.Position{Line: line, Character: char},
    }
    
    var result lsp.Location
    err := c.conn.Call(c.ctx, "textDocument/definition", params, &result)
    return &result, err
}

This means when you ask "What does the ProcessSession function do?", Crush doesn't just grep for the function name—it asks the LSP server for the definition, retrieves type information, finds all references, and includes that structured data in the LLM prompt. The LLM receives the same code intelligence your editor has.

The Model Context Protocol (MCP) integration is where extensibility shines. Unlike hardcoded tool integrations, MCP servers expose capabilities through a standardized protocol. Crush supports three MCP transports: stdio (local executables), HTTP (remote services), and SSE (server-sent events for streaming). Configuration is declarative:

mcp:
  servers:
    filesystem:
      command: "mcp-server-filesystem"
      args: ["--root", "/path/to/project"]
      transport: "stdio"
    
    web-search:
      url: "https://api.example.com/mcp"
      transport: "http"
      headers:
        Authorization: "Bearer ${WEB_SEARCH_TOKEN}"

When an LLM decides it needs to search documentation or read a file, it requests a tool invocation through the standard MCP format. Crush routes this to the appropriate server, executes the tool, and streams results back into the conversation—all while maintaining the session state.

The multi-model switching works because Crush normalizes conversation history into a provider-agnostic format before persisting sessions. Each message stores role, content, and metadata separately from provider-specific fields. When you switch providers, a translation layer converts this canonical format into whatever the new provider expects (OpenAI's messages array vs. Anthropic's separate system and messages fields). Tool calls are particularly tricky since providers use different schemas, but Crush maintains a translation map that preserves tool invocation semantics across switches.

The TUI itself leverages Bubble Tea's Elm-inspired architecture—the entire interface is a pure function of application state. Every keystroke generates a message that flows through an update function, producing new state and side effects (like LLM API calls). This makes the complex async operations (streaming LLM responses while handling LSP requests) surprisingly tractable. Lip Gloss handles the styling, giving Crush that signature Charm aesthetic without manual ANSI code wrangling.

Gotcha

Crush's terminal-first design is both its strength and its Achilles heel. When an LLM suggests code changes, you see them as text diffs in the conversation stream—there's no side-by-side visual comparison or inline accept/reject buttons like GUI tools provide. For small changes this is fine, but when reviewing a 200-line refactoring across multiple files, manually applying changes or piping output to patch files becomes tedious. The terminal simply can't compete with GUI diffing tools for complex change review.

The LSP integration requires you to have language servers installed and properly configured for each language you work with. Crush doesn't bundle LSP servers or auto-install them—if gopls isn't in your PATH or your TypeScript project lacks proper tsconfig.json, you lose the code intelligence benefits and fall back to basic file reading. This is reasonable for experienced developers who likely already have LSPs configured for their editors, but it's friction that GUI tools like Cursor hide entirely. Additionally, MCP is still nascent (the protocol was announced in late 2024), so the ecosystem of pre-built MCP servers is limited compared to the hundreds of integrations available for more established tools. You'll likely need to write your own MCP servers or wait for the community to build them.

Verdict

Use Crush if you live in the terminal, regularly switch between different LLM providers to leverage their unique strengths (GPT-4 for reasoning, Claude for refactoring, etc.), or work across multiple projects where maintaining separate conversation contexts is valuable. It's ideal for developers who already have LSP servers configured, appreciate keyboard-driven workflows, and want to avoid vendor lock-in by bringing their own API keys. The session persistence alone makes it worth trying if you've ever lost an important AI conversation mid-task. Skip Crush if you prefer visual code review with inline diffs, want a plug-and-play experience without configuring LSPs, or work primarily in a single IDE where extensions like Continue or Cursor provide tighter integration. Also skip it if you need a mature ecosystem of pre-built tool integrations today rather than the promise of MCP's future extensibility.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-agents/charmbracelet-crush.svg)](https://starlog.is/api/badge-click/ai-agents/charmbracelet-crush)