diff --git a/spec/13_rust_codebase.md b/spec/13_rust_codebase.md index 8b29a453..0b67f1ed 100644 --- a/spec/13_rust_codebase.md +++ b/spec/13_rust_codebase.md @@ -1,1537 +1,405 @@ -# Claude Code — Rust Codebase +# Coven Code — Rust Codebase ## Overview -The Rust codebase at `claude-code-rust/` is a **complete standalone rewrite** of the TypeScript Claude Code CLI in async Rust. It is not an FFI binding layer, not a partial port, and shares no runtime code with the TypeScript implementation. It re-implements the same tool names and semantics, permission model, CLAUDE.md discovery, auto-compact logic, MCP (Model Context Protocol) client, bridge protocol, and cron scheduler — all in async Rust using the Tokio runtime. +The active Coven Code implementation lives under `src-rust/`. It is a Rust +workspace for the `coven-code` binary, terminal UI, provider clients, tool +runtime, query loop, plugin system, MCP support, bridge support, and ACP server. -### Architecture +This document is the current architecture reference for the Rust codebase. The +other files in `spec/` mostly describe the upstream TypeScript Claude Code +system that Coven Code was originally modeled on; use this file, `docs/`, and +the Rust source for current Coven Code paths and names. -``` -claude-code-rust/ -├── Cargo.toml # Workspace root -└── crates/ - ├── core/ (cc-core) # Shared types, config, permissions, history, hooks - ├── api/ (cc-api) # API client + SSE streaming - ├── tools/ (cc-tools) # All tool implementations (33 tools) - ├── query/ (cc-query) # Agentic query loop, compact, cron scheduler - ├── tui/ (cc-tui) # ratatui terminal UI - ├── commands/ (cc-commands) # Slash command implementations - ├── mcp/ (cc-mcp) # MCP (Model Context Protocol) client - ├── bridge/ (cc-bridge) # Bridge to claude.ai web UI - └── cli/ (claude-code) # Binary entry point -``` +Important current identifiers: -**Dependency flow:** -``` -cli → query → tools → core - ↓ ↗ - api → core - ↓ - commands → core - ↓ - tui → core - ↓ - mcp → core - ↓ - bridge → core -``` +| Item | Current value | +|---|---| +| Workspace path | `src-rust/` | +| Workspace version | `0.0.24` | +| Binary package | `claurst` | +| Installed binary | `coven-code` | +| Runtime config dir | `~/.coven-code/` | +| Project instruction file | `AGENTS.md` | +| Workspace crate prefix | `claurst-*` | --- -## Workspace Root: `Cargo.toml` - -**Path:** `claude-code-rust/Cargo.toml` - -Cargo workspace with `resolver = "2"`, edition `2021`, version `1.0.0` across all member crates. +## Repository Layout -### Workspace Members -| Member Path | Package Name | Type | -|---|---|---| -| `crates/core` | `cc-core` | Library | -| `crates/api` | `cc-api` | Library | -| `crates/tools` | `cc-tools` | Library | -| `crates/query` | `cc-query` | Library | -| `crates/tui` | `cc-tui` | Library | -| `crates/commands` | `cc-commands` | Library | -| `crates/mcp` | `cc-mcp` | Library | -| `crates/bridge` | `cc-bridge` | Library | -| `crates/cli` | `claude-code` | Binary (`[[bin]] name = "claude"`) | - -### Key Shared Dependencies - -| Crate | Version | Features | -|---|---|---| -| `tokio` | 1.44 | `full` | -| `reqwest` | 0.12 | `json`, `stream`, `rustls-tls` | -| `ratatui` | 0.29 | default | -| `crossterm` | 0.28 | `event-stream` | -| `clap` | 4 | `derive`, `env`, `string` | -| `serde` | 1 | `derive` | -| `serde_json` | 1 | default | -| `anyhow` | 1 | default | -| `thiserror` | 2 | default | -| `tracing` | 0.1 | default | -| `tracing-subscriber` | 0.3 | `env-filter` | -| `uuid` | 1 | `v4` | -| `chrono` | 0.4 | `serde` | -| `regex` | 1 | default | -| `glob` | 0.3 | default | -| `walkdir` | 2 | default | -| `similar` | 2 | default (declared, not heavily used) | -| `once_cell` | 1 | default | -| `parking_lot` | 0.12 | default | -| `dashmap` | 6 | default | -| `tokio-util` | 0.7 | `codec`, `sync` | -| `async-trait` | 0.1 | default | -| `schemars` | 0.8 | `derive` | -| `nix` | 0.29 | `process`, `signal`, `user` | -| `base64` | 0.22 | default | -| `sha2` | 0.10 | default | -| `hex` | 0.4 | default | - ---- - -## Crate: `cc-core` - -**Path:** `crates/core/src/lib.rs` - -Central shared crate. Defines all types consumed by every other crate. Contains 9 inline submodules. - -### Module: `error` - -**`ClaudeError` enum** (implements `std::error::Error` via `thiserror`): -- `Api(String)` — Generic API error -- `ApiStatus { status_code: u16, message: String }` — HTTP status error -- `Auth(String)` — Authentication failure -- `PermissionDenied(String)` — Tool permission denied -- `Tool(String)` — Tool execution error -- `Io(#[from] std::io::Error)` — I/O error -- `Json(#[from] serde_json::Error)` — JSON parse error -- `Http(#[from] reqwest::Error)` — HTTP client error -- `RateLimit { retry_after: Option }` — 429 rate limit -- `ContextWindowExceeded` — Context window full -- `MaxTokensReached` — max_tokens hit -- `Cancelled` — User/signal cancellation -- `Config(String)` — Config load/save error -- `Mcp(String)` — MCP protocol error -- `Other(String)` — Catch-all - -**Key methods:** -- `is_retryable(&self) -> bool` — true for `RateLimit` and `ApiStatus` with code 529 -- `is_context_limit(&self) -> bool` — true for `ContextWindowExceeded` and `MaxTokensReached` - -### Module: `types` - -**`Role` enum:** `User`, `Assistant` - -**`ContentBlock` enum** (serde untagged): -- `Text { text: String }` -- `Image { source: ImageSource }` -- `ToolUse { id: String, name: String, input: Value }` -- `ToolResult { tool_use_id: String, content: ToolResultContent, is_error: Option }` -- `Thinking { thinking: String, signature: String }` -- `RedactedThinking { data: String }` -- `Document { source: DocumentSource, citations: Option }` - -**`MessageContent` enum** (serde untagged): -- `Text(String)` -- `Blocks(Vec)` - -**`Message` struct:** -- Fields: `role: Role`, `content: MessageContent` -- `Message::user(text)` — convenience constructor -- `Message::assistant(text)` — convenience constructor -- `Message::user_blocks(blocks: Vec)` — multi-block user message -- `Message::assistant_blocks(blocks)` — multi-block assistant message -- `get_text() -> Option<&str>` — first Text block or Text content -- `get_all_text() -> String` — concatenate all text blocks -- `get_tool_use_blocks() -> Vec` — filter ToolUse blocks -- `get_thinking_blocks() -> Vec` — filter Thinking blocks -- `has_tool_use() -> bool` -- `content_blocks() -> &[ContentBlock]` - -**`UsageInfo` struct:** -- Fields: `input_tokens: u32`, `output_tokens: u32`, `cache_creation_input_tokens: u32`, `cache_read_input_tokens: u32` -- `total_input() -> u32` — sum of input + cache tokens -- `total() -> u32` — sum of all tokens -- Implements `Default` - -**`ToolDefinition` struct:** `{ name: String, description: String, input_schema: Value }` - -**Supporting types:** `MessageCost`, `ImageSource { type, media_type, data }`, `DocumentSource`, `CitationsConfig`, `ToolResultContent` (Text/Blocks enum) - -### Module: `config` - -**`Config` struct** — runtime configuration: -- `api_key: Option` -- `api_base: Option` -- `model: String` -- `max_tokens: u32` -- `permission_mode: PermissionMode` -- `verbose: bool` -- `output_format: OutputFormat` -- `max_turns: u32` -- `system_prompt: Option` -- `append_system_prompt: Option` -- `no_claude_md: bool` -- `auto_compact: bool` -- `thinking_budget: Option` -- `mcp_servers: Vec` -- `hooks: HashMap>` - -**Key methods:** -- `resolve_api_key() -> Option` — checks `config.api_key` then `ANTHROPIC_API_KEY` env var -- `resolve_api_base() -> String` — checks `ANTHROPIC_BASE_URL` env var, falls back to constant -- `effective_model() -> &str` -- `effective_max_tokens() -> u32` - -**`PermissionMode` enum:** -- `Default` — allow read-only operations automatically -- `AcceptEdits` — allow all edits automatically -- `BypassPermissions` — allow everything without prompting -- `Plan` — read-only planning mode - -**`OutputFormat` enum:** `Text`, `Json`, `StreamJson` - -**`HookEvent` enum:** `PreToolUse`, `PostToolUse`, `Stop`, `UserPromptSubmit`, `Notification` - -**`HookEntry` struct:** `{ command: String, tool_filter: Option, blocking: bool }` - -**`McpServerConfig` struct:** `{ name: String, command: String, args: Vec, env: HashMap, url: Option, server_type: McpServerType }` - -**`Settings` struct** — persisted user preferences at `~/.claude/settings.json`: -- `async fn load() -> Result` — deserializes JSON, returns default on missing file -- `async fn save(&self) -> Result<()>` — serializes to JSON, creates parent dirs - -### Module: `constants` - -All constants are `pub const`: - -| Constant | Value | -|---|---| -| `APP_NAME` | `"claude"` | -| `DEFAULT_MODEL` | `"claude-opus-4-6"` | -| `SONNET_MODEL` | `"claude-sonnet-4-6"` | -| `HAIKU_MODEL` | `"claude-haiku-4-5-20251001"` | -| `DEFAULT_MAX_TOKENS` | `32_000` | -| `MAX_TOKENS_HARD_LIMIT` | `65_536` | -| `DEFAULT_COMPACT_THRESHOLD` | `0.9` | -| `MAX_TURNS_DEFAULT` | `10` | -| `ANTHROPIC_API_BASE` | `"https://api.anthropic.com"` | -| `ANTHROPIC_API_VERSION` | `"2023-06-01"` | -| `ANTHROPIC_BETA_HEADER` | `"interleaved-thinking-2025-05-14,token-efficient-tools-2025-02-19,files-api-2025-04-14"` | -| `CLAUDE_MD_FILENAME` | `"CLAUDE.md"` | -| `SETTINGS_FILENAME` | `"settings.json"` | -| `HISTORY_FILENAME` | `"history.json"` | -| `CONFIG_DIR_NAME` | `".claude"` | - -**Tool name constants:** -- `TOOL_NAME_BASH = "Bash"` -- `TOOL_NAME_FILE_EDIT = "Edit"` -- `TOOL_NAME_FILE_READ = "Read"` -- `TOOL_NAME_FILE_WRITE = "Write"` -- `TOOL_NAME_GLOB = "Glob"` -- `TOOL_NAME_GREP = "Grep"` -- `TOOL_NAME_WEB_FETCH = "WebFetch"` -- `TOOL_NAME_WEB_SEARCH = "WebSearch"` -- `TOOL_NAME_NOTEBOOK_EDIT = "NotebookEdit"` -- `TOOL_NAME_AGENT = "Task"` (sub-agent) -- `TOOL_NAME_TODO_WRITE = "TodoWrite"` -- `TOOL_NAME_ASK_USER = "AskUserQuestion"` -- `TOOL_NAME_ENTER_PLAN_MODE = "EnterPlanMode"` -- `TOOL_NAME_EXIT_PLAN_MODE = "ExitPlanMode"` -- `TOOL_NAME_POWERSHELL = "PowerShell"` -- `TOOL_NAME_SLEEP = "Sleep"` -- `TOOL_NAME_CRON_CREATE = "CronCreate"` -- `TOOL_NAME_CRON_DELETE = "CronDelete"` -- `TOOL_NAME_CRON_LIST = "CronList"` -- `TOOL_NAME_ENTER_WORKTREE = "EnterWorktree"` -- `TOOL_NAME_EXIT_WORKTREE = "ExitWorktree"` -- `TOOL_NAME_LIST_MCP_RESOURCES = "ListMcpResources"` -- `TOOL_NAME_READ_MCP_RESOURCE = "ReadMcpResource"` -- `TOOL_NAME_TOOL_SEARCH = "ToolSearch"` -- `TOOL_NAME_BRIEF = "Brief"` -- `TOOL_NAME_CONFIG = "Config"` -- `TOOL_NAME_SEND_MESSAGE = "SendMessage"` -- `TOOL_NAME_SKILL = "Skill"` - -### Module: `context` - -**`ContextBuilder`** — builds system context strings injected into the system prompt: - -- `build_system_context(working_dir: &Path) -> String` - - Platform (OS + architecture) - - Current working directory - - Git status (runs `git status --short`) - - Last 5 git commits (runs `git log --oneline -5`) - -- `build_user_context(working_dir: &Path, no_claude_md: bool) -> String` - - Current date/time (from `chrono::Local::now()`) - - CLAUDE.md discovery: walks from `working_dir` up to filesystem root, collecting any `CLAUDE.md` files; also reads `~/.claude/CLAUDE.md` - - Returns concatenated content of all discovered CLAUDE.md files - -### Module: `permissions` - -**`PermissionDecision` enum:** `Allow`, `AllowPermanently`, `Deny`, `DenyPermanently` - -**`PermissionRequest` struct:** `{ tool_name: String, description: String, details: Option, is_read_only: bool }` - -**`PermissionHandler` trait:** -- `check_permission(&self, tool_name: &str) -> PermissionDecision` -- `request_permission(&self, request: &PermissionRequest) -> PermissionDecision` - -**`AutoPermissionHandler`** — automatic non-interactive handler: -- `BypassPermissions` → `Allow` all requests -- `AcceptEdits` → `Allow` all requests -- `Plan` → `Allow` only if `is_read_only == true`, else `Deny` -- `Default` → `Allow` only if `is_read_only == true`, else `Deny` - -### Module: `history` - -**`ConversationSession` struct:** -``` -id: String (UUID v4) -created_at: DateTime -updated_at: DateTime -messages: Vec -model: String -title: Option -working_dir: String +```text +src-rust/ +├── Cargo.toml # Cargo workspace root +└── crates/ + ├── acp/ # Agent Client Protocol server + ├── api/ # Provider clients, model registry, streaming + ├── bridge/ # Bridge integration surface + ├── buddy/ # Buddy/familiar support crate + ├── cli/ # `coven-code` binary entry point + ├── commands/ # Slash/named command implementations + ├── core/ # Shared config, types, auth, history, constants + ├── mcp/ # Model Context Protocol client support + ├── plugins/ # Plugin manifests, loader, registry, hooks + ├── query/ # Agentic loop, compacting, tasks, sessions + ├── tools/ # Built-in model tools and tool dispatcher + └── tui/ # Ratatui/crossterm terminal UI ``` -**Functions:** -- `save_session(session: &ConversationSession) -> Result<()>` — writes to `~/.claude/conversations/.json` -- `load_session(id: &str) -> Result>` — reads from path above -- `list_sessions() -> Result>` — reads all `.json` files in `~/.claude/conversations/`, sorts by `updated_at` descending -- `delete_session(id: &str) -> Result<()>` — removes the file +The workspace uses Cargo resolver `2`, Rust edition `2021`, and a single +workspace package version stamped across the crates. -### Module: `cost` +--- -**`ModelPricing` struct:** `{ input_per_mtok: f64, output_per_mtok: f64, cache_creation_per_mtok: f64, cache_read_per_mtok: f64 }` +## Workspace Members -**Pricing constants:** -| Model | Input ($/MTok) | Output ($/MTok) | +| Member path | Package name | Primary role | |---|---|---| -| `OPUS` | $15.00 | $75.00 | -| `SONNET` | $3.00 | $15.00 | -| `HAIKU` | $0.80 | $4.00 | - -**`CostTracker` struct** — lock-free using `AtomicU64`: -- `input_tokens: AtomicU64` -- `output_tokens: AtomicU64` -- `cache_creation_tokens: AtomicU64` -- `cache_read_tokens: AtomicU64` -- `add_usage(input, output, cache_creation, cache_read)` — atomic adds -- `total_cost_usd(model: &str) -> f64` — loads atomics, looks up pricing by model substring match -- `summary(model: &str) -> String` — human-readable cost + token counts -- Implements `Default` - -### Module: `hooks` - -**`HookContext` struct:** `{ event: String, tool_name: Option, tool_input: Option, tool_output: Option, is_error: Option, session_id: Option }` - -**`HookOutcome` enum:** `Allowed`, `Blocked(String)`, `Modified(Value)` - -**`run_hooks(hooks, event, context, working_dir) -> HookOutcome`** (async): -- Iterates `Vec` for the given `HookEvent` -- Applies `tool_filter` (glob match against `tool_name`) -- Spawns shell command via `tokio::process::Command` -- Sends `HookContext` as JSON on stdin -- If `blocking: true` and exit code != 0, returns `HookOutcome::Blocked(stderr)` -- Otherwise returns `HookOutcome::Allowed` - -### Relationship to TypeScript - -`cc-core` corresponds to the scattered TypeScript files: `src/constants/`, `src/context.ts`, `src/history.ts`, `src/cost-tracker.ts`, `src/costHook.ts`, `src/schemas/hooks.ts`, and parts of `src/services/api/`. The permission modes, hook events, and config structure mirror the TypeScript `Config` type exactly. +| `crates/acp` | `claurst-acp` | JSON-RPC 2.0 ACP server over stdio, session/runtime plumbing | +| `crates/api` | `claurst-api` | LLM provider abstraction, provider registry, SSE/streaming, model registry | +| `crates/bridge` | `claurst-bridge` | Bridge integration between Coven Code sessions and external surfaces | +| `crates/buddy` | `claurst-buddy` | Buddy/familiar support primitives | +| `crates/cli` | `claurst` | Binary package; emits the `coven-code` executable | +| `crates/commands` | `claurst-commands` | Slash commands, named commands, stats commands | +| `crates/core` | `claurst-core` | Shared domain types, config, constants, auth store, context, history | +| `crates/mcp` | `claurst-mcp` | MCP server connections, resources, prompts, auth | +| `crates/plugins` | `claurst-plugins` | Plugin manifests, marketplace metadata, hooks, plugin registry | +| `crates/query` | `claurst-query` | Query execution loop, agent tool, compaction, cron, sessions, goals | +| `crates/tools` | `claurst-tools` | Built-in tool implementations and permission-aware dispatch | +| `crates/tui` | `claurst-tui` | Interactive terminal UI, dialogs, overlays, message rendering, key handling | + +Source file counts change often, but the current workspace has roughly 238 Rust +source files across these 12 crates. --- -## Crate: `cc-api` - -**Path:** `crates/api/src/lib.rs` - -Complete async Messages API client with SSE streaming support. - -### Module: `types` - -**`CreateMessageRequest` struct:** built via `CreateMessageRequestBuilder`: -- `model: String` -- `max_tokens: u32` -- `messages: Vec` -- `system: Option` -- `tools: Option>` -- `temperature: Option` -- `top_p: Option` -- `top_k: Option` -- `stop_sequences: Option>` -- `thinking: Option` -- `stream: bool` (always set to `true` internally) - -**`CreateMessageRequestBuilder`** — fluent builder: -- `CreateMessageRequest::builder(model, max_tokens) -> Self` -- `.messages(Vec)` -- `.system(SystemPrompt)` or `.system_text(String)` -- `.tools(Vec)` -- `.temperature(f32)`, `.top_p(f32)`, `.top_k(u32)` -- `.stop_sequences(Vec)` -- `.thinking(ThinkingConfig)` -- `.build() -> CreateMessageRequest` - -**`ThinkingConfig`:** `{ type: "enabled", budget_tokens: u32 }` -- `ThinkingConfig::enabled(budget: u32) -> Self` - -**`SystemPrompt` enum** (serde untagged): -- `Text(String)` — simple text system prompt -- `Blocks(Vec)` — structured blocks with cache control - -**`SystemBlock`:** `{ type: "text", text: String, cache_control: Option }` - -**`CacheControl`:** `{ type: "ephemeral" }` -- `CacheControl::ephemeral() -> Self` - -**`ApiMessage`:** `{ role: String, content: Value }` -- `From<&Message> for ApiMessage` — converts `cc_core::types::Message` to API format - -**`ApiToolDefinition`:** `{ name: String, description: String, input_schema: Value, cache_control: Option }` -- `From<&ToolDefinition> for ApiToolDefinition` -- Last tool in the list gets `cache_control: Some(CacheControl::ephemeral())` (prompt caching) - -**`CreateMessageResponse`:** `{ id, type, role, content, model, stop_reason, stop_sequence, usage }` - -**`ApiErrorResponse`:** `{ type: String, error: ApiErrorDetail }` - -### Module: `streaming` - -**`StreamEvent` enum** (serde `#[serde(tag = "type")]`): -- `MessageStart { message: CreateMessageResponse }` -- `MessageDelta { delta: MessageDeltaData, usage: Option }` -- `MessageStop` -- `ContentBlockStart { index: usize, content_block: PartialContentBlock }` -- `ContentBlockDelta { index: usize, delta: ContentDelta }` -- `ContentBlockStop { index: usize }` -- `Ping` -- `Error { error_type: String, message: String }` - -**`ContentDelta` enum:** -- `TextDelta { text: String }` -- `InputJsonDelta { partial_json: String }` -- `ThinkingDelta { thinking: String }` -- `SignatureDelta { signature: String }` - -**`StreamHandler` trait:** -- `fn on_event(&self, event: &StreamEvent)` — called for each SSE event - -**`NullStreamHandler`** — no-op implementation for headless mode - -**`StreamAccumulator`** — collects stream events into a complete message: -- `on_event(&mut self, event: &StreamEvent)` — processes all event types -- `finish(self) -> (Message, UsageInfo, Option)` — returns (assistant_message, usage, stop_reason) - -Internal `PartialBlock` enum during accumulation: -- `Text(String)` -- `ToolUse { id: String, name: String, json_buf: String }` -- `Thinking { thinking_buf: String, signature_buf: String }` - -### Module: `sse_parser` - -**`SseFrame` struct:** `{ event: Option, data: Option }` - -**`SseLineParser`** — stateful line-by-line SSE parser: -- `feed_line(&mut self, line: &str) -> Option` -- Handles `event:`, `data:`, and blank-line frame boundaries per SSE spec - -### Module: `client` - -**`ClientConfig` struct:** -- `api_key: String` -- `api_base: String` (default: `ANTHROPIC_API_BASE`) -- `timeout_secs: u64` (default: 600) -- `max_retries: u32` (default: 5) - -**`AnthropicClient` struct:** -- `AnthropicClient::new(config: ClientConfig) -> Result` — validates API key, builds `reqwest::Client` with rustls-tls, sets `anthropic-version` and `anthropic-beta` headers -- `AnthropicClient::from_config(cfg: &Config) -> Result` — resolves key/base from Config - -**`create_message(request) -> Result`** — non-streaming POST to `/v1/messages` - -**`create_message_stream(request, handler) -> Result>`** (async): -1. Sets `stream: true` on request -2. Spawns `tokio::spawn` background task calling `process_sse_stream()` -3. Returns `mpsc::Receiver` with channel buffer 256 -4. Background task reads response body line by line via `SseLineParser` -5. Calls `frame_to_event()` to parse each frame -6. Sends to channel + calls `handler.on_event()` - -**`send_with_retry(request_fn) -> Result`** — exponential backoff: -- Max 5 retries -- Initial delay: 1 second -- Multiplier: 2× per retry, capped at 60 seconds -- Honors `Retry-After` response header (overrides backoff delay) -- Retries on 429 (`RateLimit`) and 529 (`ApiStatus` overloaded) - -**`frame_to_event(frame: SseFrame) -> Option`** — dispatches by `frame.event`: -- `"ping"` → `StreamEvent::Ping` -- `"message_start"` → deserialize `data` into `StreamEvent::MessageStart` -- `"content_block_start"` → `StreamEvent::ContentBlockStart` -- `"content_block_delta"` → `StreamEvent::ContentBlockDelta` -- `"content_block_stop"` → `StreamEvent::ContentBlockStop` -- `"message_delta"` → `StreamEvent::MessageDelta` -- `"message_stop"` → `StreamEvent::MessageStop` -- `"error"` → `StreamEvent::Error` - -### Relationship to TypeScript +## Dependency Shape + +The dependency graph is intentionally layered around `claurst-core`. + +```text +cli +├── tui +├── query +│ ├── api +│ ├── tools +│ └── plugins +├── commands +├── acp +├── bridge +└── mcp + +tools ──> api, mcp, core +api ──> core +tui ──> api, tools, query, mcp, core +``` -Corresponds to `src/services/api/claude.ts`, `src/services/api/client.ts`, and `src/services/api/errorUtils.ts`. Implements the same SSE streaming protocol and retry logic. Prompt caching via `CacheControl::ephemeral()` mirrors the TypeScript cache control implementation. Beta header is identical. +`claurst-core` owns shared types and constants. Higher-level crates should not +redefine provider IDs, tool names, permission modes, or persisted config shapes +when a core type already exists. --- -## Crate: `cc-tools` - -**Path:** `crates/tools/src/` - -Implements all 33 built-in tools. Each tool is a zero-sized struct implementing the `Tool` trait. - -### Core Types (`lib.rs`) - -**`ToolResult` struct:** -- `content: String` -- `is_error: bool` -- `metadata: Option` — optional structured data for TUI rendering -- `ToolResult::success(content)` / `ToolResult::error(content)` / `.with_metadata(meta)` - -**`PermissionLevel` enum:** `None`, `ReadOnly`, `Write`, `Execute`, `Dangerous` - -**`ToolContext` struct:** -- `working_dir: PathBuf` -- `permission_mode: PermissionMode` -- `permission_handler: Arc` -- `cost_tracker: Arc` -- `session_id: String` -- `non_interactive: bool` -- `mcp_manager: Option>` -- `config: cc_core::config::Config` -- `resolve_path(&self, path: &str) -> PathBuf` — resolves relative paths against `working_dir` -- `check_permission(tool_name, description, is_read_only) -> Result<(), ClaudeError>` - -**`Tool` trait** (async_trait): -- `fn name(&self) -> &str` -- `fn description(&self) -> &str` -- `fn permission_level(&self) -> PermissionLevel` -- `fn input_schema(&self) -> Value` — JSON Schema for tool parameters -- `async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult` -- `fn to_definition(&self) -> ToolDefinition` — default impl from above methods - -**`all_tools() -> Vec>`** — returns all 33 tools - -**`find_tool(name: &str) -> Option>`** — finds by exact name match - -### Tool: `BashTool` (`bash.rs`) - -**Name:** `"Bash"` -**Permission level:** `Execute` - -Input schema: `{ command: string, timeout: optional u64 (seconds) }` - -**Algorithm:** -1. Checks permission via `ctx.check_permission()` -2. On Windows: `cmd /C `. On Unix: `bash -c ` -3. Default timeout: 120 seconds. Maximum: 600 seconds -4. Collects stdout and stderr with `tokio::io::BufReader` -5. Truncates output >100,000 characters with notice -6. Non-zero exit → `ToolResult::error` with combined stdout+stderr+exit_code -7. Zero exit → `ToolResult::success` with stdout (stderr appended if non-empty) - -### Tool: `FileReadTool` (`file_read.rs`) - -**Name:** `"Read"` -**Permission level:** `ReadOnly` - -Input schema: `{ file_path: string, offset: optional u32 (1-based line), limit: optional u32 }` - -**Algorithm:** -1. Resolves path via `ctx.resolve_path()` -2. Default limit: 2000 lines -3. Reads entire file, splits on newlines -4. Applies offset (1-based) and limit -5. Formats output as `{line_number}\t{content}` -6. Returns error on binary files (detected via `std::io::ErrorKind::InvalidData`) -7. Returns stub message for images and PDFs - -### Tool: `FileEditTool` (`file_edit.rs`) - -**Name:** `"Edit"` -**Permission level:** `Write` - -Input schema: `{ file_path: string, old_string: string, new_string: string, replace_all: optional bool }` - -**Algorithm:** -1. Validates `old_string != new_string` -2. Reads current file content -3. Counts occurrences of `old_string` -4. If `replace_all == false` (default) and count > 1: returns error (ambiguous) -5. If `replace_all == true`: uses `str::replace()` (replaces all) -6. If `replace_all == false` and count == 1: uses `str::replacen(old, new, 1)` -7. Writes updated content back to file - -### Tool: `FileWriteTool` (`file_write.rs`) - -**Name:** `"Write"` -**Permission level:** `Write` - -Input schema: `{ file_path: string, content: string }` - -**Algorithm:** -1. Resolves path -2. Creates parent directories via `tokio::fs::create_dir_all()` -3. Writes content to file -4. Reports line count and byte count in success message - -### Tool: `GlobTool` (`glob_tool.rs`) - -**Name:** `"Glob"` -**Permission level:** `ReadOnly` - -Input schema: `{ pattern: string, path: optional string }` - -**Algorithm:** -1. Resolves base path (defaults to `working_dir`) -2. Constructs full glob pattern by joining base + pattern -3. Uses `glob::glob()` crate for pattern matching -4. Sorts results by modification time (most recent first) -5. Returns max 250 results -6. Returns newline-separated list of relative paths - -### Tool: `GrepTool` (`grep_tool.rs`) - -**Name:** `"Grep"` -**Permission level:** `ReadOnly` - -Input schema: `{ pattern: string, path: optional string, glob: optional string, type: optional string, output_mode: optional enum, context: optional u32, head_limit: optional u32, offset: optional u32, -i: optional bool, -n: optional bool, -A: optional u32, -B: optional u32, -C: optional u32, multiline: optional bool }` - -**Algorithm:** -1. Compiles `RegexBuilder` with `case_insensitive` and `multi_line` flags -2. Uses `walkdir::WalkDir` to traverse directory tree -3. Skips hidden directories, `node_modules/`, `target/`, `__pycache__/`, `.git/` -4. Filters by glob pattern or file type extension mapping -5. Three output modes: - - `files_with_matches` — list of file paths (default) - - `content` — matching lines with optional context (-A/-B/-C) - - `count` — match counts per file -6. Applies `head_limit` and `offset` pagination - -**Type shortcuts** (e.g., `type="js"` → `["js", "jsx", "mjs", "cjs"]`): -- `js`, `ts`, `py`, `rs`, `go`, `java`, `rb`, `cpp`, `c`, `cs`, `php`, `swift`, `kt`, `html`, `css`, `json`, `yaml`, `md` - -### Tool: `WebFetchTool` (`web_fetch.rs`) - -**Name:** `"WebFetch"` -**Permission level:** `ReadOnly` - -Input schema: `{ url: string, prompt: optional string }` - -**Algorithm:** -1. `reqwest` GET with 30-second timeout, 10 redirect limit -2. User-Agent: `"Claude-Code/1.0"` -3. If HTML content-type: runs `strip_html()` — manual state machine removing tags, scripts, styles; converts `&`, `<`, `>`, ` ` entities -4. Truncates content >100,000 characters -5. Returns text content - -### Tool: `WebSearchTool` (`web_search.rs`) - -**Name:** `"WebSearch"` -**Permission level:** `ReadOnly` - -Input schema: `{ query: string, num_results: optional u32 (default 5) }` - -**Algorithm:** -1. Checks `BRAVE_SEARCH_API_KEY` env var: - - If set: calls Brave Search API at `https://api.search.brave.com/res/v1/web/search` - - Returns title + URL + description for each result -2. Fallback: DuckDuckGo Instant Answer API at `https://api.duckduckgo.com/?q=...&format=json` -3. Returns up to `num_results` formatted results - -### Tool: `NotebookEditTool` (`notebook_edit.rs`) - -**Name:** `"NotebookEdit"` -**Permission level:** `Write` - -Input schema: `{ notebook_path: string, cell_id: optional string, cell_index: optional u32, source: optional string, cell_type: optional string, mode: string (replace|insert|delete) }` - -**Algorithm:** -1. Parses `.ipynb` JSON with `serde_json` -2. Cell lookup: by UUID string OR by `cell-N` index pattern -3. **replace mode:** updates `source`, resets `outputs = []`, `execution_count = null` -4. **insert mode:** inserts new cell at given index or after cell_id; generates 8-char hex cell ID from `timestamp XOR random` -5. **delete mode:** removes cell by id/index -6. Writes updated notebook back to file - -### Tool: `TaskCreateTool`, `TaskGetTool`, `TaskUpdateTool`, `TaskListTool`, `TaskStopTool`, `TaskOutputTool` (`tasks.rs`) - -Global store: `TASK_STORE: Lazy>>` - -**`Task` struct:** `{ id: String, subject: String, description: String, status: TaskStatus, owner: Option, blocks: Vec, blocked_by: Vec, metadata: HashMap, output: Vec, created_at: DateTime, updated_at: DateTime }` - -**`TaskStatus` enum:** `Pending`, `InProgress`, `Completed`, `Deleted`, `Running`, `Failed` - -| Tool | Name | Description | -|---|---|---| -| `TaskCreateTool` | `"TaskCreate"` | Creates task with UUID, stores in `TASK_STORE` | -| `TaskGetTool` | `"TaskGet"` | Returns task JSON by ID | -| `TaskUpdateTool` | `"TaskUpdate"` | Updates task fields; `status=deleted` removes from store | -| `TaskListTool` | `"TaskList"` | Lists all non-deleted tasks with optional status filter | -| `TaskStopTool` | `"TaskStop"` | Sets task status to `Failed` | -| `TaskOutputTool` | `"TaskOutput"` | Appends text to task `output` vector | - -### Tool: `CronCreateTool`, `CronDeleteTool`, `CronListTool` (`cron.rs`) - -Global store: `CRON_STORE: Lazy>>>` - -**`CronTask` struct:** `{ id: String, cron: String, prompt: String, recurring: bool, durable: bool, created_at: DateTime }` - -| Tool | Name | Description | -|---|---|---| -| `CronCreateTool` | `"CronCreate"` | Creates scheduled task; validates cron expression; if `durable=true`, persists to `.claude/scheduled_tasks.json`; max 50 jobs | -| `CronDeleteTool` | `"CronDelete"` | Removes task by ID from store (and disk if durable) | -| `CronListTool` | `"CronList"` | Lists all scheduled tasks with human-readable schedule | - -**`cron_matches(cron: &str, now: &DateTime) -> bool`:** -- Parses 5-field cron: minute, hour, day-of-month, month, day-of-week -- Supports: `*`, `*/N` (step), `N-M` (range), `N,M,...` (list) - -**`validate_cron(cron: &str) -> Result<()>`** — validates field ranges (minute 0–59, hour 0–23, etc.) - -**`cron_to_human(cron: &str) -> String`** — describes schedule in plain English - -**`pop_due_tasks() -> Vec`** — returns tasks matching current time, removes non-recurring tasks from store - -### Tool: `TodoWriteTool` (`todo_write.rs`) - -**Name:** `"TodoWrite"` -**Permission level:** `None` - -Input schema: `{ todos: Array<{ id: string, content: string, status: string, priority: string }> }` - -Replaces entire todo list. Returns summary with counts of pending/in_progress/completed items. - -### Tool: `AskUserQuestionTool` (`ask_user.rs`) - -**Name:** `"AskUserQuestion"` -**Permission level:** `None` - -Input schema: `{ question: string, options: optional Array }` - -In `non_interactive` mode: returns error "Cannot prompt user in non-interactive mode". -Otherwise: returns `ToolResult::success("")` with metadata `{ type: "ask_user", question, options }` for TUI layer to handle. +## Workspace Dependencies -### Tool: `EnterPlanModeTool` (`enter_plan_mode.rs`) +The workspace root centralizes common dependencies: -**Name:** `"EnterPlanMode"` -**Permission level:** `None` - -Returns `ToolResult::success` with metadata `{ type: "enter_plan_mode" }`. Signals the session to switch to Plan permission mode. - -### Tool: `ExitPlanModeTool` (`exit_plan_mode.rs`) - -**Name:** `"ExitPlanMode"` -**Permission level:** `None` - -Input schema: `{ summary: optional string }` - -Returns success with metadata `{ type: "exit_plan_mode", summary }`. Signals return from Plan mode. - -### Tool: `PowerShellTool` (`powershell.rs`) - -**Name:** `"PowerShell"` -**Permission level:** `Execute` - -Input schema: `{ command: string, timeout: optional u64 }` - -Same execution pattern as `BashTool`. On Windows uses `powershell -NoProfile -NonInteractive -Command`. On other platforms uses `pwsh`. - -### Tool: `EnterWorktreeTool`, `ExitWorktreeTool` (`worktree.rs`) - -Global: `WORKTREE_SESSION: Lazy>>>` - -**`WorktreeSession`:** `{ branch: String, path: PathBuf, original_dir: PathBuf }` - -**`EnterWorktreeTool`** (`"EnterWorktree"`): -- Input: `{ branch: string, path: optional string }` -- Runs `git worktree add -b ` -- Saves session to `WORKTREE_SESSION` - -**`ExitWorktreeTool`** (`"ExitWorktree"`): -- Input: `{ action: "keep" | "remove", discard_changes: optional bool }` -- `keep`: locks worktree, clears session -- `remove`: checks for uncommitted changes (requires `discard_changes=true` to override), runs `git worktree remove --force `, then `git branch -D ` - -### Tool: `SendMessageTool` (`send_message.rs`) - -**Name:** `"SendMessage"` -**Permission level:** `None` - -Global: `INBOX: Lazy>>` - -Input schema: `{ to: string, message: string, metadata: optional Value }` - -- Delivers message to named recipient in `INBOX` -- `to = "*"` broadcasts to all existing keys -- `drain_inbox(recipient: &str) -> Vec` — removes and returns all messages -- `peek_inbox(recipient: &str) -> Vec` — returns without removing - -### Tool: `SkillTool` (`skill_tool.rs`) - -**Name:** `"Skill"` -**Permission level:** `None` - -Input schema: `{ skill: string, arguments: optional string }` - -**Algorithm:** -1. `skill = "list"` → enumerates `.claude/commands/*.md` and `~/.claude/commands/*.md`, extracts description from YAML frontmatter or first heading -2. Otherwise: resolves `.md` file from project then user commands directory -3. Strips YAML frontmatter (`---` block) -4. Substitutes `$ARGUMENTS` with provided arguments string -5. Returns file content as `ToolResult::success` - -### Tool: `SleepTool` (`sleep.rs`) - -**Name:** `"Sleep"` -**Permission level:** `None` - -Input schema: `{ duration: f64 (seconds) }` - -Calls `tokio::time::sleep(Duration::from_secs_f64(duration))`. Maximum 300 seconds. - -### Tool: `ToolSearchTool` (`tool_search.rs`) - -**Name:** `"ToolSearch"` -**Permission level:** `None` - -Input schema: `{ query: string, max_results: optional u32 (default 5) }` - -Static `TOOL_CATALOG: &[(&str, &str, &[&str])]` — 32 entries of `(name, description, keywords)`. - -**Scoring algorithm:** -- `select:Name` syntax → score 100 for exact name match -- Otherwise for each catalog entry: - - exact name match: +20 - - name contains query: +10 - - description contains query: +5 - - keyword exact match: +8 - - keyword contains query: +3 -- Returns top `max_results` entries with non-zero score - -### Tool: `BriefTool` (`brief.rs`) - -**Name:** `"Brief"` -**Permission level:** `None` - -Input schema: `{ message: string, status: optional string, attachments: optional Array (file paths) }` - -Resolves attachment metadata (file size, is_image flag from extension). Returns `ToolResult::success("")` with metadata `{ message, status, sentAt, attachments: [{ path, size, isImage }] }`. - -### Tool: `ConfigTool` (`config_tool.rs`) - -**Name:** `"Config"` -**Permission level:** `None` - -Input schema: `{ action: "get" | "set", key: string, value: optional Value }` - -Reads/writes `~/.claude/settings.json`. Supported keys: `model`, `max_tokens`, `verbose`, `permission_mode`, `auto_compact`. Returns current value on `get`, writes and confirms on `set`. - -### Tool: `ListMcpResourcesTool`, `ReadMcpResourceTool` (`mcp_resources.rs`) - -| Tool | Name | Description | -|---|---|---| -| `ListMcpResourcesTool` | `"ListMcpResources"` | Calls `ctx.mcp_manager.list_all_resources()`, returns JSON | -| `ReadMcpResourceTool` | `"ReadMcpResource"` | Input: `{ uri: string }`. Calls `ctx.mcp_manager.read_resource(uri)` | - -Both return error if `ctx.mcp_manager` is `None`. - -### Relationship to TypeScript - -`cc-tools` corresponds to the TypeScript tool implementations in `src/` (e.g., bash is in the tool system, file operations in ReadTool/EditTool/WriteTool, etc.). Tool names are identical to the TypeScript constants. The `ToolContext` mirrors the TypeScript `ToolUseContext`. - ---- - -## Crate: `cc-query` - -**Path:** `crates/query/src/` - -The core agentic query loop crate. Contains 4 source files. - -### Module: `lib.rs` — Main Query Loop - -**`QueryOutcome` enum:** -- `EndTurn { message: Message, usage: UsageInfo }` — model issued `end_turn` -- `MaxTokens { partial_message: Message, usage: UsageInfo }` — hit token limit -- `Cancelled` — cancellation token fired -- `Error(ClaudeError)` — unrecoverable error - -**`QueryConfig` struct:** -- `model: String` -- `max_tokens: u32` -- `max_turns: u32` (default: `MAX_TURNS_DEFAULT = 10`) -- `system_prompt: Option` -- `append_system_prompt: Option` -- `thinking_budget: Option` -- `temperature: Option` -- `QueryConfig::default()` uses `DEFAULT_MODEL` + `DEFAULT_MAX_TOKENS` -- `QueryConfig::from_config(cfg: &Config)` — reads model + max_tokens from Config - -**`QueryEvent` enum:** -- `Stream(StreamEvent)` — raw API stream event -- `ToolStart { tool_name, tool_id }` — tool beginning execution -- `ToolEnd { tool_name, tool_id, result, is_error }` — tool completed -- `TurnComplete { turn: u32, stop_reason: String }` — model turn finished -- `Status(String)` — informational message -- `Error(String)` — error notification - -**`run_query_loop(client, messages, tools, tool_ctx, config, cost_tracker, event_tx, cancel_token) -> QueryOutcome`** (async): - -Main agentic loop: -1. Increments turn counter; returns `EndTurn` if `> max_turns` -2. Checks `cancel_token.is_cancelled()` → `Cancelled` -3. Converts `messages` → `Vec`, tools → `Vec` -4. Calls `build_system_prompt(config)` to construct `SystemPrompt` -5. Builds `CreateMessageRequest` (with thinking config if `budget` provided) -6. Creates `ChannelStreamHandler` or `NullStreamHandler` -7. Calls `client.create_message_stream()`, receives `mpsc::Receiver` -8. Inner loop: `tokio::select!` on cancellation or stream events; feeds `StreamAccumulator` -9. On `MessageStop` or channel close: calls `accumulator.finish()` -10. Tracks costs via `cost_tracker.add_usage()` -11. Appends assistant message to `messages` -12. Calls `auto_compact_if_needed()` if stop reason is `end_turn` or `tool_use` -13. Dispatches on `stop_reason`: - - `"end_turn"` / `"stop_sequence"` / unknown → fires `Stop` hook → returns `EndTurn` - - `"max_tokens"` → returns `MaxTokens` - - `"tool_use"` → executes all tool_use blocks (see below), appends results, `continue` - -**Tool execution in `tool_use` turn:** -1. For each `ContentBlock::ToolUse { id, name, input }`: -2. Emits `QueryEvent::ToolStart` -3. Fires `PreToolUse` hooks via `cc_core::hooks::run_hooks()`; if `HookOutcome::Blocked` → `ToolResult::error("Blocked by hook: ...")` -4. Otherwise calls `execute_tool(name, input, tools, ctx)` -5. Fires `PostToolUse` hooks -6. Emits `QueryEvent::ToolEnd` -7. Pushes `ContentBlock::ToolResult` to result_blocks -8. Appends `Message::user_blocks(result_blocks)` to conversation - -**`execute_tool(name, input, tools, ctx) -> ToolResult`** (async): -- Finds tool by name in slice, calls `tool.execute(input, ctx)` -- Unknown tool → `ToolResult::error("Unknown tool: {name}")` - -**`build_system_prompt(config) -> SystemPrompt`:** -- Joins `system_prompt` and `append_system_prompt` with `\n\n` -- Empty → default `"You are Claude, an AI assistant by Anthropic."` - -**`run_single_query(client, messages, config) -> Result`** (async): -- Single API call, no tool loop, `NullStreamHandler` -- Returns complete assistant message - -**`ChannelStreamHandler`** — implements `StreamHandler`: -- `on_event(&self, event)` forwards to `mpsc::UnboundedSender` - -### Module: `compact.rs` — Auto-Compact - -**Constants:** -- `AUTOCOMPACT_BUFFER_TOKENS = 13_000` -- `WARNING_THRESHOLD_BUFFER_TOKENS = 20_000` -- `AUTOCOMPACT_TRIGGER_FRACTION = 0.90` -- `KEEP_RECENT_MESSAGES = 10` -- `MAX_CONSECUTIVE_FAILURES = 3` - -**`AutoCompactState` struct:** -- `compaction_count: u32` -- `consecutive_failures: u32` -- `disabled: bool` — circuit breaker; set after 3 consecutive failures - -**`TokenWarningState` enum:** `Ok`, `Warning`, `Critical` - -**`context_window_for_model(model: &str) -> u32`:** -- `200_000` for models matching "opus-4", "sonnet-4", "haiku-4", "claude-3-5" -- `100_000` otherwise - -**`calculate_token_warning_state(input_tokens, model) -> TokenWarningState`:** -- Uses `WARNING_THRESHOLD_BUFFER_TOKENS` to determine Warning vs Critical - -**`should_auto_compact(state, input_tokens, model) -> bool`:** -- Returns false if `state.disabled` -- Returns true if `input_tokens / context_window > AUTOCOMPACT_TRIGGER_FRACTION` - -**`summarise_head(client, messages_to_summarize, model) -> Result`** (async): -- Calls API with prompt asking to summarize the provided conversation -- Returns summary wrapped in `...` XML tags - -**`compact_conversation(client, messages, model) -> Result>`** (async): -- Splits conversation: head = `messages[0..total-KEEP_RECENT_MESSAGES]`, tail = last 10 messages -- Calls `summarise_head()` on head -- Returns `[Message::user(summary)] + tail` - -**`auto_compact_if_needed(client, messages, input_tokens, model, state) -> Option>`** (async): -- Checks `should_auto_compact()`, calls `compact_conversation()` -- On success: resets `consecutive_failures`, increments `compaction_count` -- On failure: increments `consecutive_failures`; disables if `>= MAX_CONSECUTIVE_FAILURES` -- Returns `Some(new_messages)` on success, `None` if not needed or failed - -### Module: `agent_tool.rs` — Sub-Agent Tool - -**`AgentTool`** implements `Tool`: -- **Name:** `"Task"` (constant `TOOL_NAME_AGENT`) -- **Permission level:** `Execute` - -Input schema: `{ description: string, prompt: string, tools: optional Array, system_prompt: optional string, max_turns: optional u32, model: optional string }` - -**Algorithm:** -1. Creates dedicated `AnthropicClient` from `ANTHROPIC_API_KEY` env var -2. Filters tool list: if `tools` field provided, uses that subset; always excludes `TOOL_NAME_AGENT` (prevents recursion) -3. Calls `run_query_loop()` with: - - `event_tx = None` (no TUI forwarding for sub-agent) - - New `ToolContext` with same working_dir, permission_mode, etc. -4. Returns final assistant message text as `ToolResult::success()` - -### Module: `cron_scheduler.rs` — Background Cron - -**`start_cron_scheduler(tools, tool_ctx, cancel_token) -> JoinHandle<()>`:** -- Spawns `tokio::spawn(run_scheduler_loop(...))` - -**`run_scheduler_loop(tools, tool_ctx, cancel_token)`** (async loop): -1. Computes seconds until next minute boundary: `sleep(60 - now.second() + 1)` -2. Calls `cc_tools::cron::pop_due_tasks()` to get matching tasks -3. For each due task: spawns `run_query_loop()` with: - - Single user message from `task.prompt` - - `event_tx = None` (background, no UI) - - `cancel_token` clone -4. Loop continues until cancellation - -### Relationship to TypeScript - -`cc-query` corresponds to `src/query.ts`, `src/query/`, `src/services/compact/autoCompact.ts`, `src/coordinator/`, and parts of `src/services/autoDream/`. The `AgentTool` corresponds to the TypeScript `Task` tool. +| Area | Dependencies | +|---|---| +| Async/runtime | `tokio`, `tokio-stream`, `futures`, `async-trait`, `async-stream` | +| HTTP/streaming | `reqwest`, `tokio-tungstenite`, `tower-http`, `sse-stream` | +| Serialization/config | `serde`, `serde_json`, `toml`, `schemars` | +| CLI/TUI | `clap`, `ratatui`, `crossterm` | +| Persistence/utilities | `rusqlite`, `dirs`, `uuid`, `chrono`, `dashmap`, `parking_lot` | +| Text/files | `regex`, `glob`, `walkdir`, `similar`, `syntect`, `unicode-width` | +| Process/system | `nix`, `portable-pty`, `which`, `open` | +| Media/terminal images | `image`, `icy_sixel`, `qrcode` | + +The `claurst` CLI package has the default feature `voice`, which enables voice +support in `claurst-core` and `claurst-tui`. --- -## Crate: `cc-tui` - -**Path:** `crates/tui/src/lib.rs` +## CLI Surface -Terminal UI built on `ratatui` + `crossterm`. Replaces the TypeScript `ink`/React rendering layer. +The binary is declared in `src-rust/crates/cli/Cargo.toml`: -### `App` struct - -``` -config: Config -cost_tracker: Arc -messages: Vec<(Role, String)> -input: String -input_history: Vec -history_index: Option -scroll_offset: u16 -is_streaming: bool -streaming_text: String -status_message: Option -should_quit: bool -show_help: bool +```toml +[[bin]] +name = "coven-code" +path = "src/main.rs" ``` -### Key Methods - -**`handle_key_event(&mut self, key: KeyEvent) -> Option`:** -- `Ctrl+C`: if streaming → cancels; if input empty → quits; else clears input -- `Ctrl+D`: if input empty → quits -- Character input: appended to `self.input` -- `Backspace`: removes last char from `self.input` -- `Enter`: returns `Some(input)` for caller to process (empty input ignored) -- `Up`/`Down`: navigates `input_history` -- `PageUp`/`PageDown`: adjusts `scroll_offset` -- `F1` / `?`: toggles help overlay - -**`handle_query_event(&mut self, event: QueryEvent)`:** -- `Stream(ContentBlockDelta::TextDelta)` → appends to `streaming_text` -- `ToolStart { tool_name, .. }` → sets `status_message = "Running {tool_name}..."` -- `ToolEnd { .. }` → clears `status_message` -- `TurnComplete { .. }` → moves `streaming_text` into `messages`, clears streaming state -- `Status(msg)` → sets `status_message` -- `Error(msg)` → sets `status_message` with error prefix +The `clap` entry point in `crates/cli/src/main.rs` defines the interactive and +headless surfaces. Key flags include: -**`take_input(&mut self) -> String`:** -- Returns and clears `self.input` -- Pushes to `input_history` (dedup at head) - -**`add_message(&mut self, role: Role, text: String)`** — appends to messages vec +| Flag | Purpose | +|---|---| +| `--print`, `-p` | Headless prompt mode: send prompt and exit | +| `--model`, `-m` | Select model | +| `--provider` | Select provider (`COVEN_CODE_PROVIDER`) | +| `--api-base` | Override selected provider base URL (`COVEN_CODE_API_BASE`) | +| `--permission-mode` | Select `default`, `accept-edits`, `bypass-permissions`, or `plan` | +| `--resume` | Resume a session by ID, or most recent when omitted | +| `--continue`, `-c` | Continue the most recent conversation | +| `--max-turns` | Cap agentic turn count | +| `--system-prompt`, `--system-prompt-file` | Override the system prompt | +| `--append-system-prompt` | Append extra system instructions | +| `--no-claude-md` | Disable instruction-file loading; retained as a compatibility flag | +| `--cwd` | Run from a specific working directory | +| `--dangerously-skip-permissions`, `--yolo` | Bypass permission checks | +| `--mcp-config` | Inline MCP server config JSON | +| `--no-auto-compact` | Disable automatic compaction | +| `--auto-commits` | Enable shadow-git snapshots | +| `--add-dir` | Grant access to additional directories | +| `--input-format stream-json` | Read newline-delimited JSON messages in print mode | +| `--output-format stream-json` | Emit stream JSON in print mode | +| `--agent`, `-A` | Select a named agent/familiar mode | +| `--context`, `--output` | Headless GitHub App session brief/result envelope | + +The public command name is `coven-code`, even though several internal package +names still use the historical `claurst` prefix. -### Module: `render` +--- -**`render_app(f: &mut Frame, app: &App)`:** -- Splits terminal into 3 vertical chunks via `Layout::vertical`: - 1. Messages area (flex fill) - 2. Input area (3 rows) - 3. Status bar (1 row) -- **Messages:** renders each `(role, text)` pair — `Role::User` in Cyan, `Role::Assistant` in Green. If streaming, appends partial `streaming_text` in Yellow italic -- **Input area:** bordered `Block` titled "Input"; shows `self.input` with cursor `_` appended -- **Status bar:** shows `{model} | {cost_summary}` in Dark Gray +## Core Crate -### Module: `widgets` +`claurst-core` is the shared foundation. It includes: -**`render_permission_dialog(f: &mut Frame, question: &str, options: &[String])`:** -- Centered popup dialog -- Shows question text -- Lists numbered options -- Renders as `Clear` + `Block` + `Paragraph` overlay +- `Message`, `ContentBlock`, `Role`, usage/cost, and tool definition types. +- `Config`, `Settings`, `PermissionMode`, `OutputFormat`, MCP config, hooks, and + persisted UI/config settings. +- Provider and model identifiers. +- Auth store helpers for provider keys and OAuth-style integrations. +- Context and instruction-file discovery. +- Session/conversation history and cost tracking. +- Shared constants such as default models, tool names, config directory names, + and beta headers. -**`render_spinner(f: &mut Frame, area: Rect, frame_count: u64)`:** -- Cycles through braille spinner characters: `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` -- Indexed by `frame_count % 10` +Current constants include: -### Module: `input` +| Constant | Current value | +|---|---| +| `DEFAULT_MODEL` | `claude-opus-4-8` | +| `SONNET_MODEL` | `claude-sonnet-4-6` | +| `HAIKU_MODEL` | `claude-haiku-4-5-20251001` | +| `OPUS_MODEL` | `claude-opus-4-8` | +| `FABLE_MODEL` | `claude-fable-5` | +| `CONFIG_DIR_NAME` | `.coven-code` | +| `CLAUDE_MD_FILENAME` | `AGENTS.md` | +| `HISTORY_FILENAME` | `conversations` | + +The `--no-claude-md` CLI flag is still present for compatibility, but new +documentation should treat `AGENTS.md` as the current instruction filename. -**`is_slash_command(input: &str) -> bool`** — returns true if starts with `"/"` +--- -**`parse_slash_command(input: &str) -> (&str, &str)`** — splits `"/name args"` → `("name", "args")` +## API and Provider Runtime -### Terminal Setup +`claurst-api` defines the provider abstraction and the model registry. -**`setup_terminal() -> Result>>`:** -1. `enable_raw_mode()` (crossterm) -2. `execute!(stdout, EnterAlternateScreen)` (crossterm) -3. Creates `Terminal::new(CrosstermBackend::new(stdout))` +Provider modules currently include: -**`restore_terminal(terminal: &mut Terminal<...>)`:** -1. `disable_raw_mode()` -2. `execute!(stdout, LeaveAlternateScreen)` -3. `terminal.show_cursor()` +- `anthropic` +- `azure` +- `bedrock` +- `codex` +- `cohere` +- `copilot` +- `free` +- `google` +- `minimax` +- `openai` +- `openai_compat` +- `openai_compat_providers` -### Relationship to TypeScript +The registry chooses runtime providers from explicit configuration, environment +variables, the auth store, and provider-specific constructors. It supports +Anthropic, OpenAI, Google, GitHub Copilot, Codex/OpenAI Codex, Cohere, Minimax, +Azure, Bedrock, the free provider chain, and OpenAI-compatible providers such as +OpenRouter. -`cc-tui` replaces the entire TypeScript `src/ink/` rendering system, `src/components/`, and React/Ink component tree. The ratatui approach is fundamentally different (immediate-mode rendering vs React reconciler), but provides equivalent visual functionality: message history, streaming text, input box, status bar, permission dialogs. +`model_registry.rs` owns provider/model metadata and resolution. Bare model +names are resolved through heuristics for well-known families (`claude-*`, +`gpt-*`, `gemini*`, DeepSeek, Mistral, xAI, Cohere, Perplexity, Z.ai), while +`provider/model` strings resolve directly. --- -## Crate: `cc-commands` - -**Path:** `crates/commands/src/lib.rs` - -Slash command implementations for the interactive REPL. - -### Core Types - -**`CommandContext` struct:** -``` -config: Config -cost_tracker: Arc -messages: Vec -working_dir: PathBuf +## Tools Runtime + +`claurst-tools` owns built-in model tools, tool schemas, permissions, execution, +and lookup. `claurst-query` owns the agent-style `Agent` tool because it needs +query-loop orchestration. + +Current built-in tool names include: + +```text +Bash +Read +Edit +Write +BatchEdit +ApplyPatch +Glob +Grep +WebFetch +WebSearch +NotebookEdit +TaskCreate +TaskGet +TaskUpdate +TaskList +TaskStop +TaskOutput +TodoWrite +AskUserQuestion +EnterPlanMode +ExitPlanMode +PowerShell +Sleep +CronCreate +CronDelete +CronList +EnterWorktree +ExitWorktree +ListMcpResources +ReadMcpResource +ToolSearch +Brief +Config +SendMessage +Skill +LSP +REPL +TeamCreate +TeamDelete +StructuredOutput +mcp__auth +RemoteTrigger +monitor +GoalComplete +ComputerUse (feature-gated) ``` -**`CommandResult` enum:** -- `Message(String)` — display text to user -- `UserMessage(String)` — inject as user message into conversation -- `ConfigChange(Config)` — update running config -- `ClearConversation` — clear message history -- `SetMessages(Vec)` — replace message history -- `Exit` — terminate session -- `Silent` — no output -- `Error(String)` — display error - -**`SlashCommand` trait** (async_trait): -- `fn name(&self) -> &str` -- `fn aliases(&self) -> Vec<&str>` (default: empty) -- `fn description(&self) -> &str` -- `fn help(&self) -> &str` -- `fn hidden(&self) -> bool` (default: false) -- `async fn execute(&self, args: &str, ctx: &CommandContext) -> CommandResult` - -### Command Registry - -**`all_commands() -> Vec>`** — returns all built-in commands - -**`find_command(name: &str) -> Option>`** — matches by name or alias - -**`execute_command(input: &str, ctx: &CommandContext) -> Option`** (async): -- Parses slash command from input -- Finds matching command -- Returns `None` if no match (pass-through to query loop) - -### Implemented Commands - -| Struct | Name | Aliases | Description | -|---|---|---|---| -| `HelpCommand` | `help` | `h`, `?` | List available slash commands | -| `ClearCommand` | `clear` | `cls` | Clear conversation history | -| `CompactCommand` | `compact` | — | Manually compact conversation | -| `CostCommand` | `cost` | — | Show current session cost | -| `ExitCommand` | `exit` | `quit`, `q` | Exit the REPL | -| `ModelCommand` | `model` | — | Show/change current model | -| `ConfigCommand` | `config` | — | Show/update configuration | -| `VersionCommand` | `version` | — | Show version information | -| `ResumeCommand` | `resume` | — | Resume previous conversation | -| `StatusCommand` | `status` | — | Show session status | -| `DiffCommand` | `diff` | — | Show file diffs | -| `MemoryCommand` | `memory` | — | Manage CLAUDE.md memories | -| `BugCommand` | `bug` | — | File a bug report | -| `DoctorCommand` | `doctor` | — | Run diagnostics | -| `LoginCommand` | `login` | — | Authenticate | -| `LogoutCommand` | `logout` | — | Clear authentication | -| `InitCommand` | `init` | — | Initialize project CLAUDE.md | -| `ReviewCommand` | `review` | — | Code review workflow | -| `HooksCommand` | `hooks` | — | Manage event hooks | -| `McpCommand` | `mcp` | — | Manage MCP servers | -| `PermissionsCommand` | `permissions` | — | Show/edit permissions | -| `PlanCommand` | `plan` | — | Enter/exit plan mode | -| `TasksCommand` | `tasks` | — | View background tasks | -| `SessionCommand` | `session` | — | Session management | -| `ThinkingCommand` | `thinking` | — | Toggle extended thinking | -| `ExportCommand` | `export` | — | Export conversation | -| `SkillsCommand` | `skills` | — | List/manage skills | -| `RewindCommand` | `rewind` | — | Rewind conversation state | -| `StatsCommand` | `stats` | — | Show usage statistics | -| `FilesCommand` | `files` | — | List context files | -| `RenameCommand` | `rename` | — | Rename current session | -| `EffortCommand` | `effort` | — | Set effort/thinking level | -| `SummaryCommand` | `summary` | — | Summarize conversation | -| `CommitCommand` | `commit` | — | Run git commit workflow | - -### Relationship to TypeScript - -`cc-commands` corresponds to the TypeScript `src/commands/` directory (150+ files). Each TypeScript command module (e.g., `src/commands/compact/`, `src/commands/model/`) maps to a struct in this crate. The slash command names and behaviors are preserved. +Tool definitions are generated from each tool's `Tool` implementation and sent +to providers through the API/query layers. Permission checks and tool context +flow through `ToolContext` and the configured permission mode/rules. --- -## Crate: `cc-mcp` - -**Path:** `crates/mcp/src/lib.rs` - -Full MCP (Model Context Protocol) client implementation. Uses JSON-RPC 2.0 over stdio subprocess transport. - -### JSON-RPC Types - -**`JsonRpcRequest`:** `{ jsonrpc: "2.0", method: String, params: Option, id: Option }` -- `JsonRpcRequest::new(method, params, id)` — regular request -- `JsonRpcRequest::notification(method, params)` — no id - -**`JsonRpcResponse`:** `{ jsonrpc: "2.0", id: Option, result: Option, error: Option }` - -**`JsonRpcError`:** `{ code: i32, message: String, data: Option }` - -### MCP Protocol Types - -**`InitializeParams`:** `{ protocol_version: "2024-11-05", capabilities: ClientCapabilities, client_info: ClientInfo }` - -**`ClientCapabilities`:** `{ roots: Option }` - -**`InitializeResult`:** `{ protocol_version: String, capabilities: ServerCapabilities, server_info: ServerInfo }` - -**`ServerCapabilities`:** `{ tools: Option, resources: Option, prompts: Option }` - -**`McpTool`:** `{ name: String, description: Option, input_schema: Value }` -- `From<&McpTool> for ToolDefinition` — converts to cc-core ToolDefinition - -**`CallToolParams`:** `{ name: String, arguments: Option }` - -**`CallToolResult`:** `{ content: Vec, is_error: Option }` - -**`McpContent` enum** (serde tagged): -- `Text { type: "text", text: String }` -- `Image { type: "image", data: String, mime_type: String }` -- `Resource { type: "resource", resource: ResourceContents }` - -**`McpResource`:** `{ uri: String, name: String, description: Option, mime_type: Option }` - -**`McpPrompt`:** `{ name: String, description: Option, arguments: Option> }` - -### Transport - -**`McpTransport` trait** (async_trait): -- `async fn send(&mut self, request: &JsonRpcRequest) -> Result<()>` -- `async fn recv(&mut self) -> Result>` -- `async fn close(&mut self)` - -**`StdioTransport`:** -- `StdioTransport::spawn(command: &str, args: &[String], env: &HashMap) -> Result` - - Spawns subprocess with piped stdin/stdout - - Spawns background reader task forwarding lines to `mpsc::UnboundedReceiver` -- `send()` — serializes to JSON + newline on stdin -- `recv()` — receives from channel, deserializes JSON-RPC response - -### `McpClient` - -**`McpClient::connect_stdio(config: &McpServerConfig) -> Result`** (async): -1. Calls `StdioTransport::spawn()` -2. Calls `initialize()` — sends `initialize` request, receives `InitializeResult` -3. Sends `notifications/initialized` notification -4. If `capabilities.tools` present: calls `tools/list`, stores in `self.tools` -5. If `capabilities.resources` present: calls `resources/list`, stores -6. If `capabilities.prompts` present: calls `prompts/list`, stores -7. Returns connected client - -**`call(&mut self, method, params) -> Result`:** -- Sequential request/response: sends request with incrementing ID -- Calls `transport.recv()` in loop until response ID matches -- Deserializes `result` field +## Query Loop and Sessions -**`call_tool(&mut self, name: &str, arguments: Option) -> Result`:** -- Calls `tools/call` with `CallToolParams` +`claurst-query` coordinates the agentic loop: -**`list_resources(&mut self) -> Result>`** — `resources/list` +- Prompt execution and streaming event handling. +- Tool calls and tool-result feedback. +- Compaction and context analysis. +- Session memory and conversation state. +- Cron scheduling and away summaries. +- Goal loop support. +- Managed agents and coordinator/orchestrator paths. +- Skill prefetch and command queues. -**`read_resource(&mut self, uri: &str) -> Result`** — `resources/read` - -### `McpManager` - -Manages multiple named MCP server connections. - -**`McpManager::connect_all(configs: &[McpServerConfig]) -> Result`** (async): -- Attempts to connect each server; logs warnings on failure (doesn't abort) - -**`all_tool_definitions(&self) -> Vec`:** -- Prefixes each tool name with `"{server_name}_"` to namespace tools - -**`call_tool(&self, prefixed_name: &str, arguments: Option) -> Result`:** -- Strips server prefix to identify server -- Routes to correct `McpClient` - -**`list_all_resources(&self) -> Result>`:** -- Aggregates resources from all connected servers - -**`read_resource(&self, uri: &str) -> Result`:** -- Tries each server until one returns a result - -**`server_count(&self) -> usize`**, **`server_names(&self) -> Vec`** - -**`mcp_result_to_string(result: &CallToolResult) -> String`:** -- Converts `McpContent::Text` → text, `McpContent::Image` → `[image: mime_type]`, `McpContent::Resource` → URI/text - -### Relationship to TypeScript - -`cc-mcp` corresponds to `src/services/mcpClient.ts` (TypeScript MCP implementation). Implements the same MCP protocol version (`2024-11-05`), stdio transport, and tool namespacing convention. +This crate is where the high-level "assistant turn" behavior lives. UI and CLI +surfaces drive it rather than duplicating the loop. --- -## Crate: `cc-bridge` - -**Path:** `crates/bridge/src/lib.rs` - -Implements the bridge protocol connecting the local Claude Code CLI to the claude.ai web UI. Enables remote control of the CLI from a browser session. - -### Configuration - -**`BridgeConfig` struct:** -- `enabled: bool` -- `server_url: String` -- `device_id: String` -- `session_token: Option` -- `polling_interval_ms: u64` (default: 1000) -- `max_reconnect_attempts: u32` (default: 10) +## Terminal UI -### Protocol Types +`claurst-tui` is the interactive terminal experience. It uses `ratatui` and +`crossterm`, and contains: -**`BridgeMessage` enum** (serde tagged — messages from server to client): -- `UserMessage { content: String, attachments: Vec }` -- `PermissionResponse { tool_use_id: String, decision: PermissionDecision }` -- `Cancel` -- `Ping` +- Main `App` state and key handling. +- Message rendering, markdown rendering, thinking/tool blocks, and snapshots. +- Prompt input, slash suggestions, history search, and file references. +- Permission dialogs and tool approval flows. +- Model picker, settings, theme screen, stats, onboarding, help, and overlays. +- Familiar/mascot card rendering and familiar switcher. +- MCP view, plugin views, agents view, task overlays, and diff viewer. +- Voice recording/transcription UI when the `voice` feature is enabled. -**`BridgeEvent` enum** (serde tagged — events from client to server): -- `TextDelta { text: String }` -- `ToolStart { tool_name: String, tool_id: String }` -- `ToolEnd { tool_name: String, tool_id: String, result: String, is_error: bool }` -- `PermissionRequest { tool_use_id: String, tool_name: String, description: String }` -- `TurnComplete { stop_reason: String }` -- `Error { message: String }` -- `Pong` +The TUI should treat `claurst-core` constants and `claurst-tools` tool names as +canonical rather than hardcoding divergent names. -**`PermissionDecision` enum:** `Allow`, `AllowPermanently`, `Deny`, `DenyPermanently` - -**`BridgeState` enum:** `Connecting`, `Connected`, `Reconnecting { attempt: u32 }`, `Disconnected` - -### Session Management - -**`BridgeSession::new(config: BridgeConfig) -> (Self, mpsc::Receiver, mpsc::Sender)`:** -- Creates channel pair for bidirectional communication - -**`BridgeManager::start(config, msg_tx, event_rx) -> Self`:** -- Spawns `run_poll_loop()` background task -- Returns manager with `JoinHandle` +--- -### Polling Loop +## Commands -**`run_poll_loop(config, msg_tx, event_rx)`** (async): -1. Long-polls `{server_url}/sessions/{id}/poll` with `reqwest` GET -2. On response: deserializes `BridgeMessage` array, sends each to `msg_tx` -3. Drains `event_rx`: sends accumulated `BridgeEvent` items to `{server_url}/sessions/{id}/events` via POST -4. On network error: exponential backoff up to `max_reconnect_attempts` -5. On 401/403: sets state to `Disconnected`, exits loop +`claurst-commands` implements visible slash commands and named command helpers. +The user-facing command reference lives in `docs/commands.md`; implementation +lives primarily in: -### Module: `jwt` +- `crates/commands/src/lib.rs` +- `crates/commands/src/named_commands.rs` +- `crates/commands/src/stats.rs` -**`JwtClaims` struct:** `{ sub: String, exp: u64, iat: u64, device_id: String }` +Commands cover model/provider selection, configuration, permissions, hooks, +MCP, plugins, skills, agents, goals, session/history operations, stats, and +developer workflows. Some CLI-only named commands such as stats and ultraplan +are exposed through the binary rather than only the interactive slash-command +surface. -**`decode_payload(token: &str) -> Result`:** -- Splits token by `"."`, takes index 1 (payload segment) -- Base64 decodes (URL-safe, no padding) via `base64` crate -- Deserializes JSON to `JwtClaims` +--- -**`is_expired(claims: &JwtClaims) -> bool`:** -- Compares `claims.exp` against `SystemTime::now()` Unix timestamp +## MCP, Plugins, Bridge, and ACP -### Module: `trusted_device` +`claurst-mcp` handles Model Context Protocol client behavior: server +connections, resources, prompts, and auth flows. -**`device_fingerprint() -> String`:** -- Collects: `hostname()` (from `hostname` crate), `USER` env var, home directory path -- SHA-256 hash of concatenated string via `sha2` crate -- Returns lowercase hex string (first 16 chars) via `hex` crate +`claurst-plugins` loads plugins from `~/.coven-code/plugins/`. Plugins can +provide command markdown, agents, skills, hooks, MCP server definitions, LSP +server definitions, and marketplace metadata. -### Relationship to TypeScript +`claurst-bridge` contains bridge integration used by external surfaces that +need to connect into Coven Code sessions. -`cc-bridge` corresponds to `src/bridge/` (31 TypeScript files including `bridgeMain.ts`, `bridgeMessaging.ts`, `replBridge.ts`, `jwtUtils.ts`, `trustedDevice.ts`, etc.). Implements the same polling-based bridge protocol and JWT handling. +`claurst-acp` implements the Agent Client Protocol server over stdio using +JSON-RPC 2.0. It wires ACP prompts, sessions, permissions, and runtime handling +onto the same core/query/tool pieces rather than creating a separate agent. --- -## Crate: `claude-code` (CLI Binary) +## Persistence and Local Files -**Path:** `crates/cli/src/main.rs` +Current Coven Code runtime state is rooted in `~/.coven-code/`. -Binary entry point. Produces the `claude` executable. Wires all crates together. +Common persisted surfaces include: -### CLI Arguments (`Cli` struct via clap derive) +| Surface | Location | +|---|---| +| Settings | `~/.coven-code/settings.json` | +| Conversations | `~/.coven-code/conversations/` | +| Plugins | `~/.coven-code/plugins/` | +| User agents | `~/.coven-code/agents/` | +| Keybindings | `~/.coven-code/keybindings.json` | +| UI settings | `~/.coven-code/ui-settings.json` | -| Flag | Type | Description | -|---|---|---| -| `prompt` | `Option` (positional) | Non-interactive prompt | -| `-p, --print` | `bool` | Print mode (alias for non-interactive) | -| `-m, --model` | `Option` | Override model | -| `--permission-mode` | `Option` | Permission mode | -| `--resume` | `Option` | Resume session by ID | -| `--max-turns` | `u32` (default: 10) | Max conversation turns | -| `-s, --system-prompt` | `Option` | Override system prompt | -| `--append-system-prompt` | `Option` | Append to system prompt | -| `--no-claude-md` | `bool` | Skip CLAUDE.md loading | -| `--output-format` | `Option` | Output format | -| `-v, --verbose` | `bool` | Enable verbose logging | -| `--api-key` | `Option` | API key | -| `--max-tokens` | `Option` | Override max tokens | -| `--cwd` | `Option` | Working directory | -| `--dangerously-skip-permissions` | `bool` | BypassPermissions mode | -| `--dump-system-prompt` | `bool` | Print system prompt and exit | -| `--mcp-config` | `Option` | MCP server config JSON file | -| `--no-auto-compact` | `bool` | Disable auto-compact | - -**`CliPermissionMode` enum** (clap ValueEnum): `Default`, `AcceptEdits`, `BypassPermissions`, `Plan` - -**`CliOutputFormat` enum** (clap ValueEnum): `Text`, `Json`, `StreamJson` - -### `McpToolWrapper` - -Implements `Tool` for tools provided by MCP servers: -- `permission_level()` → `Execute` -- `execute()` strips server prefix from tool name, calls `McpManager::call_tool()`, converts result via `mcp_result_to_string()` - -### `main()` Function - -1. Parses `Cli` args with clap -2. Sets up `tracing_subscriber` (verbose → DEBUG, default → WARN) -3. Loads `Settings` from `~/.claude/settings.json` -4. Builds `Config` by layering: settings → CLI overrides -5. Determines `working_dir` (from `--cwd` or `std::env::current_dir()`) -6. Creates `Arc` -7. Builds system context strings: - - Reads `crates/cli/src/system_prompt.txt` (embedded at compile time via `include_str!`) - - Calls `ContextBuilder::build_system_context()` - - Calls `ContextBuilder::build_user_context()` (unless `--no-claude-md`) - - Joins all parts -8. If `--dump-system-prompt`: prints and exits -9. Creates `AnthropicClient::from_config()` -10. Creates `ToolContext` with `AutoPermissionHandler` -11. Calls `McpManager::connect_all()` if MCP config provided -12. Builds tool list: `cc_tools::all_tools()` + `AgentTool` + `McpToolWrapper` for each MCP tool -13. Creates `CancellationToken`, starts cron scheduler with `start_cron_scheduler()` -14. If prompt provided or `--print`: calls `run_headless()` -15. Otherwise: calls `run_interactive()` - -### `run_headless(prompt, client, messages, tools, tool_ctx, config, cost_tracker, output_format)` - -1. Reads prompt from arg or stdin (if no positional arg) -2. Pushes `Message::user(prompt)` to messages -3. Spawns `run_query_loop()` with `mpsc::unbounded_channel()` for events -4. Drains event channel: - - `Text` output format: prints `QueryEvent::Stream(TextDelta)` text directly; prints tool names - - `Json` format: collects full response, outputs as single JSON object - - `StreamJson` format: outputs each `QueryEvent` as NDJSON line -5. Returns on `QueryOutcome::EndTurn` or error - -### `run_interactive()` - -Interactive TUI REPL: -1. Sets up terminal via `cc_tui::setup_terminal()` -2. Restores terminal on exit (via `defer`-pattern) -3. Handles session resume if `--resume` provided -4. Main event loop at 16ms poll interval (`EventStream` from crossterm): - - Processes `crossterm::event::KeyEvent` via `app.handle_key_event()` - - On Enter: if slash command (`is_slash_command()`), calls `execute_command()` from `cc-commands` - - Regular message: pushes to `messages`, spawns `run_query_loop()` as `tokio::spawn` - - Shares `Arc>>` between main and spawned task for result sync - - Drains query events via `event_rx.try_recv()` - - Calls `app.handle_query_event()` to update TUI state - - Re-renders via `terminal.draw(|f| render_app(f, &app))` -5. Saves session to `cc_core::history::save_session()` after each completed turn - -### System Prompt (`system_prompt.txt`) - -Embedded in binary at compile time. Content: -> You are Claude Code, an AI coding assistant by Anthropic. - -Guidelines: -- Read files before editing them -- Prefer editing existing files over creating new ones -- Write clean, idiomatic code -- Run tests after making changes -- Use git log/diff for codebase context -- Be concise in responses -- Produce production-quality code -- Never introduce security vulnerabilities - -### Relationship to TypeScript - -`claude-code` CLI corresponds to `src/entrypoints/cli.tsx` (the main TypeScript CLI entry point), `src/main.tsx`, `src/screens/REPL.tsx`, and `src/cli/` directory. The CLI flag names and behaviors are preserved, including `--print`, `--output-format`, `--permission-mode`, and `--resume`. +Project instructions use `AGENTS.md`; new Coven Code documentation should use +that name for the current instruction-file surface. --- -## Cross-Cutting Architecture Notes - -### Async Runtime -All async code uses `tokio` with `"full"` features. The `#[tokio::main]` macro is on `main()` in `crates/cli/src/main.rs`. All tools use `async fn execute()` via `async_trait`. - -### Cancellation -`tokio_util::sync::CancellationToken` is threaded through `run_query_loop()`, the cron scheduler, and TUI event loop. `Ctrl+C` fires the token. - -### Global State -Three `DashMap`/`RwLock` singletons using `once_cell::sync::Lazy`: -- `TASK_STORE` (cc-tools/tasks.rs) — task management -- `INBOX` (cc-tools/send_message.rs) — inter-agent messaging -- `CRON_STORE` (cc-tools/cron.rs) — scheduled tasks -- `WORKTREE_SESSION` (cc-tools/worktree.rs) — active git worktree +## Release and Versioning -### Error Handling -- Libraries use `thiserror` for typed `ClaudeError` -- CLI binary uses `anyhow` for ergonomic error propagation -- Tool errors never panic; always return `ToolResult::error()` +Coven Code uses a single workspace version. The release/bump flow stamps the +Cargo workspace, internal crates, npm package metadata, docs/badges, and related +templates together. Do not manually edit generated lockfile/version surfaces +when the release script owns them. -### Prompt Caching -`cc-api` automatically applies `CacheControl::ephemeral()` to: -- System prompt blocks (when using `SystemPrompt::Blocks`) -- The last tool definition in the tools list +The root `AGENTS.md` release section is the current operational reference for +version stamping and release workflow constraints. -### Logging -`tracing` + `tracing-subscriber` with `EnvFilter`. Default level WARN; `--verbose` enables DEBUG. Structured fields on all log calls. - -### TypeScript Parity Summary +--- -| TypeScript Area | Rust Crate | -|---|---| -| `src/entrypoints/cli.tsx`, `src/main.tsx` | `crates/cli` | -| `src/services/api/` | `crates/api` | -| `src/query.ts`, `src/query/` | `crates/query` | -| `src/components/`, `src/ink/` | `crates/tui` | -| `src/commands/` | `crates/commands` | -| `src/constants/`, `src/context.ts`, etc. | `crates/core` | -| Tool implementations (Bash, Read, Edit, etc.) | `crates/tools` | -| MCP client (`src/services/mcpClient.ts`) | `crates/mcp` | -| `src/bridge/` | `crates/bridge` | +## Maintenance Notes + +- Prefer `src-rust/Cargo.toml` and crate `Cargo.toml` files as the source of + truth for workspace membership. +- Prefer `docs/commands.md`, `docs/tools.md`, `docs/providers.md`, + `docs/plugins.md`, and `docs/mcp.md` for current user-facing behavior. +- Prefer `claurst-core` constants for tool names, model defaults, config paths, + and instruction filenames. +- Keep this file focused on the Rust workspace. Upstream TypeScript behavior + belongs in the earlier `spec/` files unless it has been verified against + current Coven Code code. diff --git a/spec/INDEX.md b/spec/INDEX.md index 6c56ef01..eb88a9f7 100644 --- a/spec/INDEX.md +++ b/spec/INDEX.md @@ -4,8 +4,8 @@ > that Claurst (and therefore Coven Code) was modeled on. They do **not** describe this Rust > codebase — crate names, paths, versions, and the command/tool surface differ. For current > Coven Code documentation see [`docs/`](../docs/index.md); for the audit of this codebase see -> [`docs/AUDIT-2026-06.md`](../docs/AUDIT-2026-06.md). `13_rust_codebase.md` is also stale and -> pending a rewrite. +> [`docs/AUDIT-2026-06.md`](../docs/AUDIT-2026-06.md). `13_rust_codebase.md` has been rewritten +> as the current Rust workspace reference. > Quick-reference index across all spec documents. > Total spec coverage: ~990 KB across 15 markdown files. @@ -29,7 +29,7 @@ | 10 | [10_utils.md](10_utils.md) | 60 KB | ~564 utility files organized by category | | 11 | [11_special_systems.md](11_special_systems.md) | 64 KB | Buddy/Tamagotchi, memdir, keybindings, skills, voice, plugins, migrations | | 12 | [12_constants_types.md](12_constants_types.md) | 83 KB | Every constant, type, OAuth config, system prompts, tool limits, beta headers | -| 13 | [13_rust_codebase.md](13_rust_codebase.md) | 63 KB | Complete Rust rewrite: all 9 crates, 33 tools, query loop, TUI, bridge | +| 13 | [13_rust_codebase.md](13_rust_codebase.md) | 14 KB | Current Coven Code Rust workspace: 12 crates, `coven-code` binary, tools, query loop, TUI, ACP, bridge | --- @@ -92,11 +92,12 @@ | Cyber risk instruction | 12 | §cyberRisk | | Tool name constants | 12 | §tools | | All TypeScript types | 12 | §types | -| Rust rewrite overview | 13 | §1 | -| Rust tool implementations | 13 | §cc-tools | -| Rust query loop | 13 | §cc-query | -| Rust TUI | 13 | §cc-tui | -| Rust bridge | 13 | §cc-bridge | +| Rust workspace overview | 13 | §Overview | +| Rust crate map | 13 | §Workspace Members | +| Rust tool implementations | 13 | §Tools Runtime | +| Rust query loop | 13 | §Query Loop and Sessions | +| Rust TUI | 13 | §Terminal UI | +| Rust ACP / bridge / MCP / plugins | 13 | §MCP, Plugins, Bridge, and ACP | --- @@ -114,15 +115,15 @@ | Number of utility files | ~564 | | Ink terminal framework files | 96 | | Bridge protocol files | 31 | -| Rust crates | 9 | -| Rust source files | 47 | +| Rust crates | 12 | +| Rust source files | ~238 | | Spec documentation size | ~990 KB | --- ## Architecture in One Paragraph -Claude Code is a terminal AI coding assistant built as a React application running in a custom terminal UI framework (Ink, a React reconciler targeting terminal output with Yoga flexbox layout). The main loop (`query.ts` + `QueryEngine.ts`) streams responses from the Claude API, executes tools with user permission, and manages a 200K-token context window with automatic compaction. It has 100+ slash commands, 40+ tools (file I/O, shell, web, agents, MCP), a multi-agent system for parallel task execution, a memory system for long-term context, voice input, IDE integration via a bridge protocol (WebSocket/SSE), and a plugin/skills marketplace. The codebase is being rewritten in Rust (`claude-code-rust/`) as a complete standalone reimplementation. +The upstream Claude Code specs describe a terminal AI coding assistant built as a React application running in a custom terminal UI framework. Coven Code's active implementation is the Rust workspace under `src-rust/`, with the `coven-code` binary, `claurst-*` crates, `~/.coven-code` persistence, and `AGENTS.md` instruction discovery. Use the earlier spec files for upstream TypeScript reference material, and use `13_rust_codebase.md` for the current Coven Code Rust workspace. ---