Crush — Structure#

Layout pattern#

Custom “all-internal” monolith — a single main.go entry point at the repo root, with every package hidden under internal/. There is no pkg/ subtree and no cmd/ subdirectory; the multi-command CLI is implemented inside internal/cmd/ which is imported by main.go. This is an unusual but deliberate choice for an application binary that has no intention of being used as a library.


Directory map#

crush/
├── main.go                   Single entry point; registers swag annotations, delegates to internal/cmd
├── go.mod / go.sum           Module: github.com/charmbracelet/crush
├── Taskfile.yaml             Task runner (go-task): build, test, lint, sqlc, swag, release
├── crush.json                Runtime configuration schema defaults
├── schema.json               JSON Schema for config (generated by `task schema`)
├── sqlc.yaml                 sqlc configuration for query codegen
├── AGENTS.md                 In-repo agent skill definitions for AI tools
│
├── docs/                     User-facing documentation
│   └── hooks/examples/       Hook examples for the extensibility system
│
├── scripts/                  Two shell scripts (log-capitalisation lint, labeler helper)
│
├── .github/workflows/        GitHub Actions: build, lint, nightly, release, snapshot,
│                             schema-update, security scan, CLA check
│
└── internal/                 All application code (~359 .go files, ~50 packages)
    │
    ├── cmd/                  CLI layer — cobra commands + workspace wiring
    │   ├── root.go           Root command, Execute(), workspace bootstrap
    │   ├── run.go            `crush run` (non-interactive) command
    │   ├── server.go         `crush server` command (detached server mode)
    │   ├── session.go        `crush session` command
    │   ├── login.go          `crush login` command (OAuth flows)
    │   ├── projects.go       `crush projects` command
    │   ├── models.go         `crush models` command
    │   ├── logs.go           `crush logs` command
    │   ├── schema.go         `crush schema` command
    │   ├── update_providers.go
    │   ├── stats/            Embedded HTML/CSS/JS stats dashboard
    │   └── gitignore/        Embedded .gitignore templates
    │
    ├── app/                  In-process application core (when not using client/server)
    ├── workspace/            Workspace interface + two implementations (AppWorkspace, ClientWorkspace)
    ├── server/               HTTP server over Unix socket / Windows named pipe
    ├── client/               HTTP client SDK for the server API
    ├── proto/                Shared wire types for client↔server communication
    │
    ├── agent/                AI agent orchestration
    │   ├── coordinator.go    Agent loop + multi-agent coordination
    │   ├── tools/            Tool implementations (file ops, shell, diff, MCP bridge)
    │   ├── prompt/           System prompt templates
    │   ├── hyper/            Hyper provider integration (embedded provider.json)
    │   └── notify/           Desktop notification integration
    │
    ├── backend/              LLM provider abstraction layer
    ├── client/               HTTP API client (server SDK)
    │
    ├── config/               Configuration loading, schema, provider resolution
    ├── db/                   SQLite connection, goose migrations, sqlc-generated queries
    │   ├── migrations/       SQL migration files
    │   └── sql/              Hand-written SQL queries for sqlc
    │
    ├── session/              Session model and persistence
    ├── message/              Message model (user/assistant/tool messages)
    ├── history/              File-edit history tracking
    ├── projects/             Project registry
    ├── workspace/            Workspace interface (see above)
    │
    ├── ui/                   BubbleTea TUI — all view/model code
    │   ├── model/            Root TUI model (main BubbleTea Model)
    │   ├── chat/             Chat pane
    │   ├── dialog/           Modal dialogs (permission, onboarding, etc.)
    │   ├── diffview/         Diff viewer component
    │   ├── completions/      Autocomplete component
    │   ├── attachments/      File/image attachment UI
    │   ├── list/             Reusable list component
    │   ├── notification/     Desktop notification bridge
    │   ├── anim/             Spinner/animation components
    │   ├── logo/             Brand logo renderer
    │   ├── styles/           Lipgloss styles and per-provider themes
    │   ├── common/           Shared TUI state (Common struct)
    │   ├── image/            Image rendering (sixel/kitty)
    │   └── xchroma/          Chroma syntax highlighter integration
    │
    ├── lsp/                  Language Server Protocol client
    ├── hooks/                User-defined hook execution system
    ├── skills/               Built-in and user-defined agent skills
    │   └── builtin/          Embedded skill files (crush-config, crush-hooks, jq)
    ├── commands/             Slash-command registry
    ├── permission/           Permission request/grant system
    ├── pubsub/               In-process publish-subscribe event bus
    ├── event/                Telemetry/metrics event emission
    ├── oauth/                OAuth flows (Copilot, Hyper providers)
    │   ├── copilot/
    │   └── hyper/
    ├── diff/                 Diff computation utilities
    ├── diffdetect/           Diff detection heuristics
    ├── format/               Output formatting (spinner, markdown, etc.)
    ├── filetracker/          Agent file-read tracking
    ├── filepathext/          File-path utilities
    ├── fsext/                Filesystem utilities
    ├── stringext/            String utilities
    ├── ansiext/              ANSI escape utilities
    ├── csync/                Concurrent-safe synchronization helpers
    ├── env/                  Environment variable helpers
    ├── home/                 User home-directory resolution
    ├── log/                  Structured logging setup (wraps log/slog)
    ├── update/               Self-update / version-check logic
    ├── version/              Version string (injected at build time via ldflags)
    └── swagger/              Generated OpenAPI/Swagger spec (docs.go, swagger.json/yaml)

Entry points#

BinarySourceDescription
crushmain.gointernal/cmd.Execute()Single compiled binary; all subcommands are cobra sub-commands registered in internal/cmd/root.go

There is no cmd/ top-level directory. All commands are registered under internal/cmd/:

SubcommandFilePurpose
crush (default)root.goLaunch interactive TUI
crush runrun.goRun a single non-interactive prompt
crush serverserver.goStart a detached HTTP server
crush sessionsession.goManage sessions (list, delete)
crush loginlogin.goOAuth login for providers
crush projectsprojects.goList registered projects
crush modelsmodels.goList available models
crush logslogs.goTail log output
crush schemaschema.goPrint JSON Schema for config
crush dirsdirs.goPrint data/config directories
crush statsstats.goLaunch embedded stats dashboard
crush update-providersupdate_providers.goRefresh provider metadata

Package organization#

Internal packages#

All packages are under internal/ — the project exports nothing as a library.

Core application layer:

  • internal/app — In-process application object; owns the agent, config, db, and HTTP server lifecycle
  • internal/workspaceWorkspace interface + AppWorkspace (in-process) / ClientWorkspace (HTTP client) implementations; the seam between CLI/TUI and the backend
  • internal/server — HTTP server over Unix socket/Windows named pipe; exposes REST API documented with Swaggo
  • internal/client — Typed HTTP client for the server API (used by ClientWorkspace)
  • internal/proto — Shared DTO types used between server and client

Agent layer:

  • internal/agent — AI agent loop, tool dispatch, coordinator for sub-agents
  • internal/agent/tools — Individual agent tools (file read/write, shell exec, diff, grep, etc.)
  • internal/agent/tools/mcp — MCP tool bridge (forwards LLM tool calls to MCP servers)
  • internal/agent/prompt — System prompt assembly from templates
  • internal/agent/hyper — Hyper AI provider integration with embedded provider catalog
  • internal/backend — LLM provider abstraction (Anthropic, OpenAI, Gemini, Bedrock, etc.)

Persistence layer:

  • internal/db — SQLite connection via ncruces/go-sqlite3; goose migrations; sqlc-generated type-safe query code
  • internal/session — Session domain model and CRUD
  • internal/message — Message domain model (user/assistant/tool parts)
  • internal/history — File-edit history per session

TUI layer (internal/ui/):

  • model — Root BubbleTea model: composes all sub-views, handles global key bindings
  • chat — Chat pane (primary view)
  • dialog — Modal dialogs (permission grants, onboarding wizard, provider setup)
  • diffview — Inline diff viewer
  • completions — Slash-command autocomplete
  • styles — Lipgloss styles and per-provider color themes
  • common — Shared Common struct passed to all sub-models

Extension/integration layer:

  • internal/lsp — LSP client (language server integration via sourcegraph/jsonrpc2)
  • internal/hooks — User-defined hook scripts executed on agent events
  • internal/skills — Skill (.md instruction files) management; built-in skills embedded
  • internal/commands — Slash-command registry (/clear, /compact, etc.)
  • internal/permission — Permission request and grant tracking
  • internal/oauth — OAuth token flows for Copilot and Hyper providers
  • internal/event — Telemetry event emission (Catwalk analytics)

Utility packages:

  • internal/config — Config loading, schema validation, provider resolution
  • internal/pubsub — In-process typed pub-sub bus
  • internal/format, internal/diff, internal/diffdetect — Output formatting, diff computation
  • internal/filetracker, internal/filepathext, internal/fsext — File operation helpers
  • internal/stringext, internal/ansiext, internal/csync — Misc utilities
  • internal/log, internal/env, internal/home, internal/version — Infrastructure stubs

Public packages (pkg/)#

None. Crush is a pure application binary with no exported library surface.

Layering#

CLI (internal/cmd)
       │
       ▼
Workspace interface (internal/workspace)
       │                         │
       ▼                         ▼
AppWorkspace                ClientWorkspace
(in-process)               (HTTP client)
       │                         │
       ▼                         ▼
internal/app            internal/server (Unix socket)
       │
  ┌────┴────────────────────────────┐
  │                                 │
Agent (internal/agent)         TUI (internal/ui)
  │
  ├── Backend (internal/backend)  — LLM providers
  ├── Tools (internal/agent/tools) — file/shell/diff ops
  ├── LSP (internal/lsp)
  ├── MCP (internal/agent/tools/mcp)
  └── DB (internal/db)            — SQLite persistence

The layering is clean: the CLI never calls the agent or TUI directly — everything goes through the Workspace interface. The TUI and the crush run non-interactive path use the same Workspace abstraction. The client/server split is optional (CRUSH_CLIENT_SERVER=1) and implemented transparently behind the same Workspace interface.


Build system#

  • Build tool: go-task (Taskfile.yaml) — replaces Makefile
  • Key targets:
    • task buildgo build . with version ldflags; outputs ./crush (or crush.exe)
    • task testgo test -race -failfast ./...
    • task lintgolangci-lint + log capitalisation script
    • task sqlc — regenerate type-safe DB queries via sqlc generate
    • task swag — regenerate OpenAPI spec from Swaggo annotations in main.go/internal/server/
    • task hyper — regenerate embedded provider.json via go generate
    • task schema — regenerate schema.json (JSON Schema for config)
    • task release — tag + push release (semver via svu)
    • task installgo install with version ldflags
  • Code generation: Three code generators in the build: sqlc (DB queries), swag (OpenAPI), go generate (Hyper provider catalog)
  • CGO: Explicitly disabled (CGO_ENABLED: 0) even though SQLite is used — achieved via the CGo-free ncruces/go-sqlite3 driver using WASM
  • Go experiment: GOEXPERIMENT: greenteagc is set globally (enables the experimental green-tea GC for reduced pause times)
  • Docker: No Dockerfile present. Releases are built by GitHub Actions (snapshot.yml / release.yml, likely using goreleaser)

Notable structural decisions#

  1. No pkg/ or cmd/ top-level directories. All code lives under internal/. The multi-command CLI is inside internal/cmd/ rather than the idiomatic cmd/<binary>/main.go layout. This removes the false impression that any of these packages are importable.

  2. Workspace interface as the central seam. Rather than passing *app.App to the TUI and CLI directly, both consume a Workspace interface. This single design choice enables the optional client/server split (CRUSH_CLIENT_SERVER=1) — the TUI does not know or care whether the backend is in-process or across a Unix socket.

  3. Client/server architecture is opt-in but first-class. The server (crush server) is a fully functional detached process exposing a REST API over a Unix socket. The client auto-starts, version-checks, and reconnects to this server. This architecture exists even though the default mode is in-process — it anticipates IDE integrations and headless automation.

  4. Code generation is a first-class citizen. Three separate generators (sqlc, swag, go generate) are wired into the task runner. The generated files (internal/swagger/, internal/db/, internal/agent/hyper/provider.json) are committed to the repo.

  5. TUI package isolation. The internal/ui/ subtree is organised into fine-grained sub-packages per component (chat, dialog, diffview, completions, etc.) rather than a single flat package. Each sub-package returns a BubbleTea Model and communicates with the root model only via tea.Msg messages — a textbook BubbleTea architecture.

  6. WASM-based SQLite. By using ncruces/go-sqlite3 (a pure-Go WASM-embedded SQLite), the project achieves CGO-free static binaries while still having a full SQL database for persistence. This is an unusual and deliberate dependency choice that affects the entire build pipeline.