|
1 | | -# CLAUDE.md — Codebase CLI |
| 1 | +# CLAUDE.md — codebase-cli |
2 | 2 |
|
3 | | -## What This Is |
| 3 | +## What this is |
4 | 4 |
|
5 | | -An AI coding agent CLI written in Go. Goal: build a **better Claude Code** — simpler |
6 | | -architecture, any LLM provider, single binary, zero runtime dependencies. |
| 5 | +A TypeScript coding-agent CLI on top of the pi-mono runtime |
| 6 | +(`@earendil-works/pi-agent-core` + `@earendil-works/pi-ai`). We layer the |
| 7 | +TUI, tools, slash commands, permissions, OAuth, sessions, and the headless |
| 8 | +/ JSON-RPC entry points; pi-mono owns the agent loop and provider |
| 9 | +protocols. |
7 | 10 |
|
8 | | -This is NOT a Claude Code clone. We study CC's source as a benchmark for feature coverage |
9 | | -and learn from their architectural mistakes. Every feature we add should be a better |
10 | | -implementation, not a copy-paste of their approach. |
| 11 | +Goal: a **better Claude Code** — simpler architecture, any LLM provider, |
| 12 | +single `npm i -g` install. Not a clone. We study CC's source as a feature-coverage |
| 13 | +benchmark and to learn from their architectural mistakes; we do not copy |
| 14 | +their code or prompt wording verbatim (open-source IP discipline). |
11 | 15 |
|
12 | | -## Quick Reference |
| 16 | +For deep context, read [`.settings/`](.settings/) first — `tenets.md`, |
| 17 | +`architecture.md`, `extending.md`, `testing.md`. Those are the canonical |
| 18 | +docs; this file is the quick reference. |
| 19 | + |
| 20 | +## Quick reference |
13 | 21 |
|
14 | 22 | ``` |
15 | | -Language: Go 1.24 |
16 | | -TUI: BubbleTea (Charm) |
17 | | -Styling: Lipgloss |
18 | | -Highlighting: Chroma |
| 23 | +Language: TypeScript (ESM), Node ≥ 20 |
| 24 | +TUI: ink (React for terminals) |
| 25 | +Schema: typebox (runtime validation of tool args) |
| 26 | +Tests: vitest 2.x |
| 27 | +Lint/format: biome 2.x |
| 28 | +Build: tsc → dist/, no bundler |
19 | 29 | Binary name: codebase |
20 | 30 | Config dir: ~/.codebase/ |
21 | | -Sessions: ~/.codebase/sessions/ |
| 31 | +Sessions: ~/.codebase/sessions/<cwd-hash>/ |
22 | 32 | ``` |
23 | 33 |
|
24 | | -## Build & Run |
| 34 | +## Build & run |
25 | 35 |
|
26 | 36 | ```sh |
27 | | -go build -o codebase . |
28 | | -./codebase # run in current dir |
29 | | -./codebase -dir /some/path # run in specific dir |
30 | | -./codebase -model claude-sonnet-4-20250514 # override model |
31 | | -./codebase -resume # resume previous session |
32 | | -CODEBASE_NOBOOT=1 ./codebase # skip boot animation |
| 37 | +npm run build # tsc + chmod |
| 38 | +npm run check # typecheck + lint + tests (the pre-publish gate) |
| 39 | +node dist/cli.js # interactive TUI in current dir |
| 40 | +node dist/cli.js --new # skip auto-resume of prior session |
| 41 | +node dist/cli.js run "<prompt>" # one-shot headless |
| 42 | +node dist/cli.js run --output json "<prompt>" |
| 43 | +node dist/cli.js auth login # OAuth via codebase.foundation |
| 44 | +node dist/cli.js app-server # JSON-RPC over stdio (for IDE extensions) |
33 | 45 | ``` |
34 | 46 |
|
35 | | -## Test |
| 47 | +## Test & bench |
36 | 48 |
|
37 | 49 | ```sh |
38 | | -go test ./... # all tests |
39 | | -go test -run TestToolReadFile # specific test |
40 | | -go test -v -count=1 ./... # verbose, no cache |
| 50 | +npm test # all unit tests (576+) |
| 51 | +npm test -- -t "name fragment" # filter |
| 52 | +npm run test:watch |
| 53 | +npm run bench:micro # vitest microbenchmarks (*.bench.ts) |
| 54 | +npm run bench # end-to-end LLM bench (needs API key) |
41 | 55 | ``` |
42 | 56 |
|
43 | | -## Architecture Overview |
44 | | - |
45 | | -See `docs/ARCHITECTURE.md` for the full blueprint and migration plan. |
46 | | - |
47 | | -### Current Structure (pre-refactor) |
| 57 | +## Architecture in 60 seconds |
48 | 58 |
|
49 | 59 | ``` |
50 | | -main.go Entry point, config loading, flag parsing |
51 | | -app.go BubbleTea root model — routes between screens (boot/setup/chat) |
52 | | -chat.go Main chat TUI (1,603 LOC — god object, being refactored) |
53 | | -agent.go Agent loop — goroutine that drives LLM ↔ tool cycle |
54 | | -tools.go Tool definitions + execution dispatcher (switch statement) |
55 | | -git_tools.go Git-specific tools (status, diff, log, commit, branch) |
56 | | -llm.go OpenAI-compatible LLM client + SSE streaming |
57 | | -llm_anthropic.go Anthropic Messages API adapter + streaming |
58 | | -permission.go Permission system (ask/trust-tool/trust-all) |
59 | | -compact.go Conversation compaction via LLM summarization |
60 | | -session.go Session persistence to ~/.codebase/sessions/ |
61 | | -commands.go Slash commands (/help, /clear, /compact, /status, etc.) |
62 | | -glue.go Sidecar LLM for intent routing, narration, titles |
63 | | -plan.go Planning mode (Q&A before building) |
64 | | -render.go Viewport rendering helpers, tool block formatters |
65 | | -search.go Web search (Tavily, Brave, SearXNG, DuckDuckGo) |
66 | | -boot.go Demoscene boot animation (plasma, 3D cube, pixel font) |
67 | | -audio.go Chiptune synthesizer (4-channel MOD tracker, pure Go) |
68 | | -setup.go First-run setup wizard |
69 | | -diagnostics.go Post-edit language checkers (Go, TypeScript, Python) |
70 | | -notify.go Notification manager (status bar messages) |
71 | | -highlight.go Syntax highlighting via Chroma |
72 | | -theme.go Color themes (dark, light, retro) |
73 | | -dotenv.go .env file loader |
| 60 | +User input → ChatApp (src/ui/App.tsx) |
| 61 | + → /cmd → CommandRegistry (src/commands/) |
| 62 | + → !cmd → runShellEscape (src/ui/shell-escape.ts) |
| 63 | + → @path → collectAttachments → augmented prompt |
| 64 | + → router (src/agent/router.ts) → chat | plan | agent |
| 65 | + → bundle.agent.prompt(text) # pi-agent-core owns the loop |
| 66 | + → AgentEvents stream over bundle.subscribe(...) |
| 67 | + → useCoalescedAgentEvents flushes at 16ms (60fps cap) |
| 68 | + → reducer (src/agent/events.ts) → ChatState → React render |
74 | 69 | ``` |
75 | 70 |
|
76 | | -### Data Flow |
77 | | - |
78 | | -``` |
79 | | -User Input → chatModel.Update() |
80 | | - → slash command? → commands.go handler |
81 | | - → agent message? → agent.go goroutine |
82 | | - → LLM streaming via llm.go / llm_anthropic.go |
83 | | - → tool calls dispatched via tools.go |
84 | | - → permission checks via permission.go |
85 | | - → results sent back as AgentEvent on channel |
86 | | - → chatModel renders events via render.go |
87 | | -``` |
| 71 | +Full src layout, data flow, and component responsibilities live in |
| 72 | +[`.settings/architecture.md`](.settings/architecture.md). |
88 | 73 |
|
89 | | -### Key Patterns |
| 74 | +## Environment variables |
90 | 75 |
|
91 | | -- **Channel-based concurrency**: Agent runs in goroutine, sends events on `eventCh`, |
92 | | - TUI receives them in `Update()`. No shared mutable state. |
93 | | -- **Segment-based rendering**: Conversation is `[]segment` (text/user/tool/divider/error). |
94 | | - Avoids fragile ANSI string manipulation. |
95 | | -- **Debounced viewport**: Streaming rebuilds throttled to 50ms to prevent TUI thrashing. |
96 | | -- **Read-only parallel execution**: Tools declaring `EffectReadsFS` run concurrently. |
97 | | -- **Atomic writes**: Multi-edit uses write→rename for crash safety. |
| 76 | +### Provider credentials |
| 77 | +Provided per-provider via pi-ai's resolver. The first-run wizard |
| 78 | +detects what's set and offers it. Common ones: |
98 | 79 |
|
99 | | -## Environment Variables |
100 | | - |
101 | | -### Required (one of) |
102 | 80 | ``` |
103 | | -OPENAI_API_KEY OpenAI or compatible provider API key |
104 | | -ANTHROPIC_API_KEY Anthropic API key (auto-detects protocol) |
| 81 | +OPENAI_API_KEY OpenAI (also catches any OAI-compatible endpoint) |
| 82 | +OPENAI_BASE_URL override endpoint for OAI-compatible providers |
| 83 | +ANTHROPIC_API_KEY Anthropic |
| 84 | +GROQ_API_KEY Groq |
| 85 | +OPENROUTER_API_KEY OpenRouter |
105 | 86 | ``` |
106 | 87 |
|
107 | | -### Optional — LLM |
108 | | -``` |
109 | | -OPENAI_BASE_URL Custom endpoint (Groq, Ollama, OpenRouter, etc.) |
110 | | -OPENAI_MODEL Override default model |
111 | | -``` |
| 88 | +OAuth users sign in via `codebase auth login`; tokens land in |
| 89 | +`~/.codebase/credentials.json` and `process.env` is not required. |
112 | 90 |
|
113 | | -### Optional — Glue sidecar |
| 91 | +### Web search (only if `web_search` tool is invoked) |
114 | 92 | ``` |
115 | | -GLUE_API_KEY Separate key for cheap/fast model |
116 | | -GLUE_BASE_URL Separate endpoint for glue |
117 | | -GLUE_FAST_MODEL Fast model for intent/titles (default: same as main) |
118 | | -GLUE_SMART_MODEL Smart model for planning/narration |
| 93 | +TAVILY_API_KEY Tavily (recommended) |
| 94 | +BRAVE_API_KEY Brave Search |
| 95 | +SEARXNG_URL self-hosted SearXNG instance |
119 | 96 | ``` |
120 | 97 |
|
121 | | -### Optional — Web Search |
| 98 | +### Behavior toggles |
122 | 99 | ``` |
123 | | -TAVILY_API_KEY Tavily search (recommended) |
124 | | -BRAVE_API_KEY Brave Search |
125 | | -SEARXNG_URL Self-hosted SearXNG instance |
126 | | -SEARCH_API_KEY Alias for TAVILY_API_KEY |
| 100 | +CODEBASE_FRESH=1 skip auto-resume of prior session (same as --new) |
| 101 | +CODEBASE_NO_SUGGESTIONS=1 disable ghost-text prompt suggestions |
| 102 | +CODEBASE_DEBUG=1 verbose stderr logging |
| 103 | +CODEBASE_DEBUG_INPUT=1 log every keystroke to ~/.codebase/logs/input.log |
| 104 | +NO_HYPERLINK=1 disable OSC 8 clickable file paths |
127 | 105 | ``` |
128 | 106 |
|
129 | | -### Optional — Behavior |
| 107 | +### OAuth (only override if you know why) |
130 | 108 | ``` |
131 | | -CODEBASE_NOBOOT Skip boot animation |
132 | | -CODEBASE_NOSOUND Skip boot audio |
| 109 | +CODEBASE_CLIENT_ID override OAuth client id |
| 110 | +CODEBASE_SCOPES override requested scopes |
133 | 111 | ``` |
134 | 112 |
|
135 | | -## Coding Conventions |
| 113 | +`.env` and `.env.local` in the cwd are auto-loaded at startup |
| 114 | +(`src/dotenv/loader.ts`). |
136 | 115 |
|
137 | | -### Go style |
138 | | -- Standard `gofmt` formatting, no exceptions |
139 | | -- Error wrapping with context: `fmt.Errorf("toolReadFile: %w", err)` |
140 | | -- Unexported by default. Only export what's needed cross-package (currently single package) |
141 | | -- Table-driven tests with `t.Run()` subtests |
142 | | -- Channel direction on parameters: `ch chan<- AgentEvent`, `stop <-chan struct{}` |
| 116 | +## Coding conventions |
143 | 117 |
|
144 | | -### File organization |
145 | | -- One responsibility per file (aspiration — chat.go violates this, fix in progress) |
146 | | -- Tests in `*_test.go` alongside source |
147 | | -- Constants and types at top of file, functions below |
148 | | -- Section headers with `// ──────────` dividers |
| 118 | +### TS style |
| 119 | +- ESM imports with `.js` extensions (TS NodeNext convention). |
| 120 | +- `import type { ... }` for type-only — biome enforces this. |
| 121 | +- Tabs for indentation (biome config). Single quotes off (double quotes). |
| 122 | +- Errors thrown as `Error` subclasses with descriptive names |
| 123 | + (`ConfigError`, `PermissionDeniedError`, `UserQueryCancelled`, etc.). |
| 124 | +- Default to no comments. Add one only when *why* is non-obvious. |
| 125 | +- No multi-paragraph JSDoc blocks. One short line max. |
149 | 126 |
|
150 | | -### Naming |
151 | | -- Tool functions: `toolReadFile()`, `toolWriteFile()`, etc. |
152 | | -- Event types: `EventTextDelta`, `EventToolStart`, etc. |
153 | | -- States: `chatIdle`, `chatStreaming`, `chatPermission`, etc. |
154 | | -- Messages (BubbleTea): `agentEventMsg`, `flashTickMsg`, etc. |
| 127 | +### Files |
| 128 | +- One responsibility per file. If a file passes ~400 lines, look for |
| 129 | + a seam — see how `Message.tsx` and `App.tsx` were split. |
| 130 | +- Tests colocated as `*.test.ts` / `*.test.tsx` next to source. |
| 131 | +- Benchmarks colocated as `*.bench.ts` next to source. |
| 132 | +- Component files use PascalCase (`Message.tsx`); modules use kebab-case |
| 133 | + (`tool-call-line.tsx`, `diff-summary.tsx`). |
155 | 134 |
|
156 | 135 | ### What to avoid |
157 | | -- Don't use `panic()` except for true programmer errors |
158 | | -- Don't use `init()` except for command registration (commands.go) |
159 | | -- Don't add dependencies without justification — stdlib first |
160 | | -- Don't add features without updating `docs/ARCHITECTURE.md` |
161 | | - |
162 | | -## Refactoring in Progress |
163 | | - |
164 | | -The codebase is undergoing a major architectural refactoring. See `docs/ARCHITECTURE.md` |
165 | | -sections 7-12 for the migration plan. The phases are: |
166 | | - |
167 | | -1. **Tool Interface** — Replace switch dispatch with `Tool` interface + `Registry` |
168 | | -2. **Permission Engine** — Effect-based policies instead of tool-name checks |
169 | | -3. **LLM Provider Interface** — Adapter pattern instead of protocol branching |
170 | | -4. **Agent State Machine** — Explicit FSM instead of implicit state in chatModel |
171 | | -5. **Token Budget Manager** — Proactive budget instead of heuristic compaction |
172 | | -6. **MCP Support** — External tool servers via mcp-go |
173 | | -7. **Slash Commands** — Command interface with auto-discovery |
174 | | - |
175 | | -When working on this codebase, check which phase is current and follow that plan. |
176 | | - |
177 | | -## Competitive Context: Claude Code Benchmark |
178 | | - |
179 | | -We have Claude Code's source code for reference. Key things to know: |
180 | | - |
181 | | -### What CC does well (learn from) |
182 | | -- Tools as self-contained modules with schema validation |
183 | | -- Streaming tool executor with concurrency control |
184 | | -- Slash command system (50+ commands) |
185 | | -- MCP integration for extensibility |
186 | | -- Git worktree isolation for parallel agents |
187 | | -- Persistent cross-session memory |
188 | | - |
189 | | -### What CC got wrong (do better) |
190 | | -- **React/Ink for TUI**: 512K LOC for a terminal app. BubbleTea is right-sized. |
191 | | -- **No clean tool interface**: BashTool is 160K bytes. Each tool is a snowflake. |
192 | | - We use a `Tool` interface — adding a tool is mechanical. |
193 | | -- **Permission monster**: 300K+ lines of AST parsing, ML classifiers, hooks cascade. |
194 | | - We use effect-based policies — tools declare effects, policies match on effects. |
195 | | -- **God object state**: 30+ field AppState. We use an explicit state machine (FSM). |
196 | | -- **Three compaction strategies**: They don't coordinate. We use one strategy, |
197 | | - proactive, with a calibrated token budget. |
198 | | -- **Retrofitted MCP**: 119K lines because tool system wasn't designed for it. |
199 | | - Our `Tool` interface treats local and MCP tools identically. |
200 | | -- **Bun lock-in**: No real benefit for a CLI. We ship a single Go binary. |
201 | | - |
202 | | -### Design philosophy difference |
203 | | -CC optimizes for Anthropic-only with maximum features. We optimize for any-LLM-provider |
204 | | -with maximum simplicity. Our thesis: 80% of CC's value in 5% of the code, by making |
205 | | -better foundational choices. |
206 | | - |
207 | | -When adding features, always ask: "How did CC do this, and what's a simpler way that |
208 | | -avoids their pain points?" Check `docs/ARCHITECTURE.md` for the specific analysis. |
209 | | - |
210 | | -## Project Files Recognized |
211 | | - |
212 | | -The CLI reads these files from the project root for context: |
213 | | -- `AGENTS.md` — agent instructions (OpenAI Codex convention) |
214 | | -- `CLAUDE.md` — project instructions (Claude Code convention) |
215 | | -- `CODEX.md` — project instructions (Codex convention) |
216 | | -- `.cursorrules` — project instructions (Cursor convention) |
217 | | - |
218 | | -All are injected into the system prompt at session start. |
219 | | - |
220 | | -## Dependencies (keep minimal) |
221 | | - |
222 | | -Direct dependencies only: |
223 | | -- `charmbracelet/bubbletea` — TUI framework |
224 | | -- `charmbracelet/bubbles` — TUI components (spinner, textinput, viewport) |
225 | | -- `charmbracelet/lipgloss` — styling |
226 | | -- `alecthomas/chroma/v2` — syntax highlighting |
227 | | -- `lucasb-eyer/go-colorful` — color space conversions (boot animation) |
228 | | - |
229 | | -Planned additions: |
230 | | -- `mark3labs/mcp-go` — MCP client (Phase 6) |
231 | | - |
232 | | -Do NOT add dependencies for things the stdlib can do (HTTP, JSON, crypto, etc.). |
| 136 | +- Don't `as unknown as` to smuggle a field through an interface. Fix the |
| 137 | + interface. |
| 138 | +- Don't add a feature flag for backwards-compat of code you're rewriting |
| 139 | + in the same PR. Just change it. |
| 140 | +- Don't add a runtime dependency for something stdlib + the existing deps |
| 141 | + can do. |
| 142 | +- Don't reach into pi-agent-core / pi-ai internals. Public API only; if |
| 143 | + it's not enough, fix it upstream. |
| 144 | +- Don't commit aspirational documentation. The code is the spec; docs |
| 145 | + follow the code. |
| 146 | + |
| 147 | +## Competitive context: Claude Code |
| 148 | + |
| 149 | +We have CC's source as a benchmark. The tenets we hold differently: |
| 150 | + |
| 151 | +| Aspect | CC | codebase-cli | |
| 152 | +|---|---|---| |
| 153 | +| TUI framework | ink + 512K LOC | ink + ~22K LOC | |
| 154 | +| Tool interface | Each tool a snowflake; BashTool ~160K bytes | One `Tool` interface, ~45 implementations | |
| 155 | +| Permissions | AST parsers + ML classifiers + hook cascade | Effect-based policies, ~600 LOC | |
| 156 | +| State | 30+ field god object | Single immutable `ChatState`, reducer-driven | |
| 157 | +| Compaction | Three uncoordinated strategies | One proactive strategy with calibrated budget | |
| 158 | +| Distribution | Bun lock-in, custom bundler | Plain `npm i -g`, `tsc` only | |
| 159 | +| Providers | Anthropic-first | Any provider via pi-ai | |
| 160 | + |
| 161 | +The Ink-LOC contrast is the headline number: ink is fine; CC's *use* of |
| 162 | +it is the problem. Our thesis: 80% of CC's value in 5% of the code, by |
| 163 | +making better foundational choices. |
| 164 | + |
| 165 | +When you add a feature, ask: "how did CC do this, and what's a simpler |
| 166 | +way that avoids their pain points?" |
| 167 | + |
| 168 | +## Recognized project files |
| 169 | + |
| 170 | +The CLI auto-loads these from the project root and injects them into |
| 171 | +the system prompt: |
| 172 | + |
| 173 | +- `AGENTS.md` (OpenAI Codex) |
| 174 | +- `CLAUDE.md` (Claude Code / this convention) |
| 175 | +- `CODEX.md` |
| 176 | +- `.cursorrules` (Cursor) |
| 177 | + |
| 178 | +## Direct dependencies |
| 179 | + |
| 180 | +Runtime: |
| 181 | +- `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai` — agent loop + protocol adapters |
| 182 | +- `ink` + `react` — TUI |
| 183 | +- `typebox` — runtime schema validation for tool args |
| 184 | +- `diff` — LCS-paired diffs for the `edit_file` / `multi_edit` summary |
| 185 | +- `glob`, `ignore` — file matching for tools |
| 186 | + |
| 187 | +Dev: |
| 188 | +- `vitest`, `@biomejs/biome`, `typescript`, `tsx`, `shx`, `@types/*` |
| 189 | + |
| 190 | +Don't add a dep without a real second use case in mind. The stdlib + |
| 191 | +the deps above can do almost everything. |
| 192 | + |
| 193 | +## In-flight features |
| 194 | + |
| 195 | +- **MCP**: there's a `/mcp` placeholder slash command but real MCP |
| 196 | + client support hasn't shipped. The pi-mono roadmap calls it Phase 9. |
| 197 | +- **Hooks**: scaffold under `src/hooks/`; user-configurable pre/post-turn |
| 198 | + and pre/post-tool hooks. Check current state before relying on them. |
| 199 | +- **Skills**: scaffold under `src/skills/` for per-skill SYSTEM.md additions. |
0 commit comments