Self-hosted LLM chat bound to a git repository. Single binary (Go + Lit). Works as a git chat subcommand or standalone CLI. Chat with an LLM about your codebase -- the knowledge base automatically invalidates when source code changes.
# Install
go install github.com/pders01/git-chat/cmd/git-chat@latest
# Run (auto-detects repo, opens browser)
cd ~/code/my-repo
git chat
# Explicit backend
git chat --llm-backend=anthropic --llm-api-key=$ANTHROPIC_API_KEYgit chat # local mode, auto-detect repo, auto-open browser
git chat serve --repo /path/to/repo1 --repo /path/to/repo2
git chat local ~/workspace # scan directory for git repos (multi-repo workspace)
git chat local --no-scan . # treat CWD as single repo (fail if not valid)
git chat local --max-repos=5 . # scan directory, load at most 5 repos
git chat mcp # MCP server mode (stdio)
git chat add-key paul@laptop < ~/.ssh/id_ed25519.pub
When running git chat on a directory that contains multiple git repositories:
- Auto-scan: If the path is not a valid git repo, subdirectories are scanned for
.gitfolders - Explicit repos: Use
--repo(repeatable) to specify exact repo paths, bypassing scan - Limit loading:
--max-repos=Ncaps the number of repos loaded from a scan - Single repo mode:
--no-scantreats the path as a single repo (fails if invalid) - UI switcher: When multiple repos are loaded, a dropdown selector appears in the header, and
Cmd/Ctrl+Kcommand palette shows "Switch to: {repo}" actions
- Chat -- streaming LLM responses with markdown rendering, @-mention file injection (recursive autocomplete),
[[diff]]marker expansion, KB cache hits - Composer slash commands --
/diffto hand-author[[diff]]markers,/model <id>and/profile <name>to switch LLM for the current chat,/helpto list commands; arg autocomplete suggests branches/tags/paths for/diff, discovered models for/model, saved profiles for/profile - Persistent model indicator -- small status line above the composer shows the model and profile the next turn will hit; refreshes instantly after
/modelor/profile - Knowledge base -- FTS5 fuzzy matching, N>=2 promotion threshold, blob-SHA provenance, git-aware invalidation, webhook notifications (Slack/Discord) with exponential-backoff retry
- KB management -- card list with detail view, provenance display, delete, hit counts, invalidation status
- File browser -- expandable file tree (VS Code-style, lazy-load, arrow-key nav) + syntax highlighting (Shiki, 25 languages) + focus mode
- Git blame -- 2-panel info pane with interactive tooltip (cursor-following, 300ms debounce), "view in log" / "ask in chat" action buttons
- Commit log -- 3-column layout (list | info pane | diff pane), per-file diffs, SVG timeline graph via parent SHA linking
- Branch comparison -- file-by-file diff between any two refs
- Working tree changes -- staged, unstaged, untracked files with per-file diff viewer
- 3D code city -- Three.js visualization of file churn (commit frequency, additions/deletions, file size mapped to building dimensions), squarified treemap layout, time slider; hover tooltip surfaces filename/directory split plus the file's top author, keyboard navigable (arrows rotate, +/- zoom, Tab cycles buildings, Esc closes detail)
- File history -- commits filtered to a single file path
- Side-by-side diff -- LCS-based word-level diff highlighting via Shiki diff grammar
- Commit search -- filter by message or author
- Branch/tag switching -- global ref selector in header (branches + tags)
- Session pinning -- pinned sessions float to the top of the sidebar; star/unstar to toggle
- Streaming token counter -- live token count + cost estimate per turn
- LLM dashboard summaries -- auto-generated activity summary with suggested questions, rendered as markdown so list-shaped fallbacks become real bullets
- Runtime-configurable settings -- sidebar-navigated settings UI with LLM provider/model switching, effective-model status card (active profile / config override / compiled default), 3-tier resolution (SQLite override -> env var -> compiled default), API keys encrypted at rest (AES-256-GCM)
- Ad-hoc model discovery -- typing a custom base URL in the advanced config kicks a debounced
/v1/modelsprobe; results feed the model combobox so providers outside the catalog (Fireworks, Groq, custom deployments) just work - Cross-tab navigation -- "explain in chat" from log, "ask in chat" from browser, blame-to-log SHA linking, Cmd+click files
- MCP server mode -- 10 tools:
search_knowledge,get_file,get_diff,list_commits,search_files,search_code,outline,list_tree,list_branches,get_blame - Two LLM backends -- OpenAI-compatible (LM Studio, Ollama, vLLM, OpenAI, Groq, Fireworks) + Anthropic native, switchable at runtime via settings UI
- Theme support -- system/light/dark with Shiki dual-theme
- Auth -- local mode (token URL) or SSH key pairing for multi-user
Go backend (Connect-RPC, connectrpc.com/connect) + Lit frontend (embedded via go:embed). Single .proto source of truth generates both Go server handlers and TypeScript clients. All state in one SQLite file (modernc.org/sqlite, pure Go, no CGO).
See docs/ARCHITECTURE.md for the full design document.
| Variable | Default | Description |
|---|---|---|
LLM_BACKEND |
openai |
openai or anthropic |
LLM_BASE_URL |
http://localhost:1234/v1 |
OpenAI-compatible endpoint (ignored for anthropic) |
LLM_MODEL |
(empty) | Model name; empty falls back to the backend default |
LLM_API_KEY |
-- | API key (required for anthropic) |
LLM_TEMPERATURE |
(empty) | Sampling temperature; empty = use startup flag, 0 = deterministic |
LLM_MAX_TOKENS |
(empty) | Max tokens per response; empty = use startup flag, 0 = provider default |
LLM_ACTIVE_PROFILE |
(empty) | Active LLM profile ID; empty = use individual LLM_* settings |
LLM_SYSTEM_PROMPT |
(empty) | Custom system prompt snippet (appended to base prompt) |
| Variable | Default | Description |
|---|---|---|
GITCHAT_MAX_DIFF_BYTES |
524288 |
Max whole-commit diff size |
GITCHAT_DEFAULT_COMMIT_LIMIT |
50 |
Default commit list page size |
GITCHAT_DIFF_CONTEXT_LINES |
3 |
Context lines around diff hunks |
GITCHAT_DEFAULT_FILE_BYTES |
524288 |
Default max file size for GetFile |
| Variable | Default | Description |
|---|---|---|
GITCHAT_MAX_FILE_BYTES |
4096 |
Per @-file injection cap |
GITCHAT_MAX_TOTAL_INJECT |
12288 |
Total @-file budget per turn |
GITCHAT_MAX_BASELINE_BYTES |
4096 |
Overview doc injection cap |
GITCHAT_MAX_HISTORY_DIFF |
4096 |
Per-diff cap in history expansion |
GITCHAT_MAX_TREE_LINES |
60 |
Max lines in baseline tree listing |
GITCHAT_MAX_TREE_BYTES |
2048 |
Max bytes in baseline tree listing |
GITCHAT_RECENT_COMMITS |
5 |
Recent commits in system prompt |
GITCHAT_MAX_MESSAGE_BYTES |
32768 |
Max user message size |
GITCHAT_MAX_HISTORY_TURNS |
20 |
Sliding window of history turns sent to LLM |
GITCHAT_TITLE_MAX_LEN |
48 |
Max auto-generated session title length |
GITCHAT_TITLE_TIMEOUT |
15s |
Timeout for LLM title generation |
GITCHAT_CARD_TIMEOUT |
10s |
Timeout for KB card promotion |
GITCHAT_KB_PROMOTION_THRESHOLD |
2 |
Min similar questions before promotion |
| Variable | Default | Description |
|---|---|---|
GITCHAT_SESSION_TTL |
168h |
Browser session cookie lifetime |
GITCHAT_DEFAULT_SESSION_LIMIT |
100 |
Default session list page size |
| Variable | Default | Description |
|---|---|---|
GITCHAT_WEBHOOK_URL |
(empty) | Slack/Discord incoming webhook URL (empty = disabled). Sends card_invalidated and card_created events; transient failures (network, 5xx, 429) retried with exponential backoff up to 3 attempts. |
| Variable | Default | Description |
|---|---|---|
GITCHAT_DB |
~/.local/state/git-chat/state.db |
SQLite state file |
XDG_STATE_HOME |
~/.local/state |
Base for default DB path |
All GITCHAT_* and LLM_* variables are runtime-configurable via the settings UI. Values set in the UI are stored as SQLite overrides and take precedence over env vars. Secret values (LLM_API_KEY) are encrypted at rest with AES-256-GCM. Sensitive keys (LLM_API_KEY, LLM_BASE_URL, GITCHAT_WEBHOOK_URL) can only be changed in local mode.
| Key | Action |
|---|---|
Cmd+K / Ctrl+K |
Command palette |
Cmd+F / Ctrl+F |
Search |
Double-press Cmd+F / Cmd+K |
Fall through to the browser native (find / URL bar) — press twice within 300ms |
Cmd+1-4 / Ctrl+1-4 |
Switch tabs (chat, browse, log, kb) |
Cmd+\ / Ctrl+\ |
Toggle focus mode |
/ |
Focus composer (or open the slash-command menu inside the composer) |
Esc |
Blur / close modal |
? |
Shortcut help |
F2 |
Rename selected session |
ArrowUp/Down |
Navigate lists (sessions, files, commits, mentions, slash menus) |
Tab |
Accept suggestion and keep editing (slash arg menus) |
Enter |
Select / expand / accept + submit |
ArrowRight/Left |
Expand / collapse file tree nodes |
Code-city Arrows / + - / Tab / Shift+Tab / Esc |
Orbit camera / zoom / cycle buildings / close detail |
make dev # vite HMR on :5173 + Go server on :8080
make check # go vet + go test + tsc + oxlint + oxfmt
make test-e2e # Playwright (desktop + mobile), 29 tests
make size-check # fails if embedded SPA exceeds MAX_BUNDLE_BYTES (default 6 MiB)
make proto # buf generate
make all # frontend + Go binaryFrontend unit tests (via bun:test, happy-dom harness): cd web && bun run test.
Requirements: Go 1.25+, Bun 1.2+, Chromium (for Playwright).