From 409073c10c001873c98de917640830c35bcd9019 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 1 Apr 2026 18:48:39 +0900 Subject: [PATCH] fix: post-plugins-merge cleanroom fixes and workspace deps Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- rust/Cargo.lock | 1 + rust/Cargo.toml | 3 + rust/crates/api/src/client.rs | 32 +-- rust/crates/api/src/lib.rs | 2 +- rust/crates/api/tests/client_integration.rs | 16 +- rust/crates/commands/Cargo.toml | 1 + rust/crates/commands/src/lib.rs | 253 +++++++++++++++++++- rust/crates/runtime/src/config.rs | 78 +++--- rust/crates/runtime/src/lib.rs | 6 +- rust/crates/runtime/src/mcp.rs | 4 +- rust/crates/runtime/src/mcp_client.rs | 8 +- rust/crates/runtime/src/oauth.rs | 8 +- rust/crates/runtime/src/prompt.rs | 40 ++-- rust/crates/rusty-claude-cli/src/main.rs | 18 +- rust/crates/tools/src/lib.rs | 33 +-- 15 files changed, 378 insertions(+), 125 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index a182255..c64d4d0 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -113,6 +113,7 @@ version = "0.1.0" dependencies = [ "plugins", "runtime", + "serde_json", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 4a2f4d4..f4e4fef 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,6 +8,9 @@ edition = "2021" license = "MIT" publish = false +[workspace.dependencies] +serde_json = "1" + [workspace.lints.rust] unsafe_code = "forbid" diff --git a/rust/crates/api/src/client.rs b/rust/crates/api/src/client.rs index 7ef7e83..1881de8 100644 --- a/rust/crates/api/src/client.rs +++ b/rust/crates/api/src/client.rs @@ -101,7 +101,7 @@ impl From for AuthSource { } #[derive(Debug, Clone)] -pub struct AnthropicClient { +pub struct ApiHttpClient { http: reqwest::Client, auth: AuthSource, base_url: String, @@ -110,7 +110,7 @@ pub struct AnthropicClient { max_backoff: Duration, } -impl AnthropicClient { +impl ApiHttpClient { #[must_use] pub fn new(api_key: impl Into) -> Self { Self { @@ -429,7 +429,7 @@ fn resolve_saved_oauth_token_set( let Some(refresh_token) = token_set.refresh_token.clone() else { return Err(ApiError::ExpiredOAuthToken); }; - let client = AnthropicClient::from_auth(AuthSource::None).with_base_url(read_base_url()); + let client = ApiHttpClient::from_auth(AuthSource::None).with_base_url(read_base_url()); let refreshed = client_runtime_block_on(async { client .refresh_oauth_token( @@ -614,7 +614,7 @@ mod tests { use crate::client::{ now_unix_timestamp, oauth_token_is_expired, resolve_saved_oauth_token, - resolve_startup_auth_source, AnthropicClient, AuthSource, OAuthTokenSet, + resolve_startup_auth_source, ApiHttpClient, AuthSource, OAuthTokenSet, }; use crate::types::{ContentBlockDelta, MessageRequest}; @@ -671,7 +671,7 @@ mod tests { let _guard = env_lock(); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); let error = super::read_api_key().expect_err("missing key should error"); assert!(matches!(error, crate::error::ApiError::MissingApiKey)); } @@ -735,7 +735,7 @@ mod tests { fn auth_source_from_saved_oauth_when_env_absent() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); save_oauth_credentials(&runtime::OAuthTokenSet { @@ -750,7 +750,7 @@ mod tests { assert_eq!(auth.bearer_token(), Some("saved-access-token")); clear_oauth_credentials().expect("clear credentials"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } @@ -774,7 +774,7 @@ mod tests { fn resolve_saved_oauth_token_refreshes_expired_credentials() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); save_oauth_credentials(&runtime::OAuthTokenSet { @@ -798,7 +798,7 @@ mod tests { assert_eq!(stored.access_token, "refreshed-token"); clear_oauth_credentials().expect("clear credentials"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } @@ -806,7 +806,7 @@ mod tests { fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); save_oauth_credentials(&runtime::OAuthTokenSet { @@ -822,7 +822,7 @@ mod tests { assert_eq!(auth.bearer_token(), Some("saved-access-token")); clear_oauth_credentials().expect("clear credentials"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } @@ -830,7 +830,7 @@ mod tests { fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); save_oauth_credentials(&runtime::OAuthTokenSet { @@ -854,7 +854,7 @@ mod tests { assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); clear_oauth_credentials().expect("clear credentials"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } @@ -862,7 +862,7 @@ mod tests { fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_API_KEY"); save_oauth_credentials(&runtime::OAuthTokenSet { @@ -887,7 +887,7 @@ mod tests { assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); clear_oauth_credentials().expect("clear credentials"); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } @@ -908,7 +908,7 @@ mod tests { #[test] fn backoff_doubles_until_maximum() { - let client = AnthropicClient::new("test-key").with_retry_policy( + let client = ApiHttpClient::new("test-key").with_retry_policy( 3, Duration::from_millis(10), Duration::from_millis(25), diff --git a/rust/crates/api/src/lib.rs b/rust/crates/api/src/lib.rs index 4108187..4684af7 100644 --- a/rust/crates/api/src/lib.rs +++ b/rust/crates/api/src/lib.rs @@ -5,7 +5,7 @@ mod types; pub use client::{ oauth_token_is_expired, read_base_url, resolve_saved_oauth_token, resolve_startup_auth_source, - AnthropicClient, AuthSource, MessageStream, OAuthTokenSet, + ApiHttpClient, AuthSource, MessageStream, OAuthTokenSet, }; pub use error::ApiError; pub use sse::{parse_frame, SseParser}; diff --git a/rust/crates/api/tests/client_integration.rs b/rust/crates/api/tests/client_integration.rs index be4abca..e2eaef6 100644 --- a/rust/crates/api/tests/client_integration.rs +++ b/rust/crates/api/tests/client_integration.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::Duration; use api::{ - AnthropicClient, ApiError, ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, + ApiHttpClient, ApiError, ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest, OutputContentBlock, StreamEvent, ToolChoice, ToolDefinition, }; @@ -34,7 +34,7 @@ async fn send_message_posts_json_and_parses_response() { ) .await; - let client = AnthropicClient::new("test-key") + let client = ApiHttpClient::new("test-key") .with_auth_token(Some("proxy-token".to_string())) .with_base_url(server.base_url()); let response = client @@ -99,7 +99,7 @@ async fn send_message_parses_response_with_thinking_blocks() { ) .await; - let client = AnthropicClient::new("test-key").with_base_url(server.base_url()); + let client = ApiHttpClient::new("test-key").with_base_url(server.base_url()); let response = client .send_message(&sample_request(false)) .await @@ -146,7 +146,7 @@ async fn stream_message_parses_sse_events_with_tool_use() { ) .await; - let client = AnthropicClient::new("test-key") + let client = ApiHttpClient::new("test-key") .with_auth_token(Some("proxy-token".to_string())) .with_base_url(server.base_url()); let mut stream = client @@ -234,7 +234,7 @@ async fn stream_message_parses_sse_events_with_thinking_blocks() { ) .await; - let client = AnthropicClient::new("test-key").with_base_url(server.base_url()); + let client = ApiHttpClient::new("test-key").with_base_url(server.base_url()); let mut stream = client .stream_message(&sample_request(false)) .await @@ -303,7 +303,7 @@ async fn retries_retryable_failures_before_succeeding() { ) .await; - let client = AnthropicClient::new("test-key") + let client = ApiHttpClient::new("test-key") .with_base_url(server.base_url()) .with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2)); @@ -336,7 +336,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() { ) .await; - let client = AnthropicClient::new("test-key") + let client = ApiHttpClient::new("test-key") .with_base_url(server.base_url()) .with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2)); @@ -367,7 +367,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() { #[tokio::test] #[ignore = "requires ANTHROPIC_API_KEY and network access"] async fn live_stream_smoke_test() { - let client = AnthropicClient::from_env().expect("ANTHROPIC_API_KEY must be set"); + let client = ApiHttpClient::from_env().expect("ANTHROPIC_API_KEY must be set"); let mut stream = client .stream_message(&MessageRequest { model: std::env::var("ANTHROPIC_MODEL") diff --git a/rust/crates/commands/Cargo.toml b/rust/crates/commands/Cargo.toml index b3a68b6..71da51c 100644 --- a/rust/crates/commands/Cargo.toml +++ b/rust/crates/commands/Cargo.toml @@ -11,3 +11,4 @@ workspace = true [dependencies] plugins = { path = "../plugins" } runtime = { path = "../runtime" } +serde_json = "1" diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index b1aa69c..4c3358c 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -48,138 +48,161 @@ pub struct SlashCommandSpec { const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[ SlashCommandSpec { name: "help", + aliases: &[], summary: "Show available slash commands", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "status", + aliases: &[], summary: "Show current session status", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "compact", + aliases: &[], summary: "Compact local session history", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "model", + aliases: &[], summary: "Show or switch the active model", argument_hint: Some("[model]"), resume_supported: false, }, SlashCommandSpec { name: "permissions", + aliases: &[], summary: "Show or switch the active permission mode", argument_hint: Some("[read-only|workspace-write|danger-full-access]"), resume_supported: false, }, SlashCommandSpec { name: "clear", + aliases: &[], summary: "Start a fresh local session", argument_hint: Some("[--confirm]"), resume_supported: true, }, SlashCommandSpec { name: "cost", + aliases: &[], summary: "Show cumulative token usage for this session", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "resume", + aliases: &[], summary: "Load a saved session into the REPL", argument_hint: Some(""), resume_supported: false, }, SlashCommandSpec { name: "config", + aliases: &[], summary: "Inspect Claude config files or merged sections", argument_hint: Some("[env|hooks|model|plugins]"), resume_supported: true, }, SlashCommandSpec { name: "memory", + aliases: &[], summary: "Inspect loaded Claude instruction memory files", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "init", + aliases: &[], summary: "Create a starter CLAUDE.md for this repo", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "diff", + aliases: &[], summary: "Show git diff for current workspace changes", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "version", + aliases: &[], summary: "Show CLI version and build information", argument_hint: None, resume_supported: true, }, SlashCommandSpec { name: "bughunter", + aliases: &[], summary: "Inspect the codebase for likely bugs", argument_hint: Some("[scope]"), resume_supported: false, }, SlashCommandSpec { name: "commit", + aliases: &[], summary: "Generate a commit message and create a git commit", argument_hint: None, resume_supported: false, }, SlashCommandSpec { name: "pr", + aliases: &[], summary: "Draft or create a pull request from the conversation", argument_hint: Some("[context]"), resume_supported: false, }, SlashCommandSpec { name: "issue", + aliases: &[], summary: "Draft or create a GitHub issue from the conversation", argument_hint: Some("[context]"), resume_supported: false, }, SlashCommandSpec { name: "ultraplan", + aliases: &[], summary: "Run a deep planning prompt with multi-step reasoning", argument_hint: Some("[task]"), resume_supported: false, }, SlashCommandSpec { name: "teleport", + aliases: &[], summary: "Jump to a file or symbol by searching the workspace", argument_hint: Some(""), resume_supported: false, }, SlashCommandSpec { name: "debug-tool-call", + aliases: &[], summary: "Replay the last tool call with debug details", argument_hint: None, resume_supported: false, }, SlashCommandSpec { name: "export", + aliases: &[], summary: "Export the current conversation to a file", argument_hint: Some("[file]"), resume_supported: true, }, SlashCommandSpec { name: "session", + aliases: &[], summary: "List or switch managed local sessions", argument_hint: Some("[list|switch ]"), resume_supported: false, }, SlashCommandSpec { name: "plugins", + aliases: &[], summary: "List or manage plugins", argument_hint: Some( "[list|install |enable |disable |uninstall |update ]", @@ -584,6 +607,210 @@ pub fn handle_slash_command( } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DefinitionSource { + ProjectCodex, + UserCodex, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AgentDef { + pub name: String, + pub description: String, + pub model: String, + pub temperature: String, + pub source: DefinitionSource, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SkillDef { + pub name: String, + pub description: String, + pub source: DefinitionSource, +} + +pub fn load_agents_from_roots( + roots: &[(DefinitionSource, std::path::PathBuf)], +) -> Result, String> { + let mut agents = Vec::new(); + let mut seen: std::collections::HashMap = + std::collections::HashMap::new(); + + for (source, path) in roots { + if !path.exists() { + continue; + } + for entry in std::fs::read_dir(path).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let file_path = entry.path(); + if file_path.extension().map_or(false, |ext| ext == "json") { + let name = file_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("unknown") + .to_string(); + let content = std::fs::read_to_string(&file_path).map_err(|e| e.to_string())?; + let json: serde_json::Value = + serde_json::from_str(&content).map_err(|e| e.to_string())?; + let description = json + .get("description") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + let model = json + .get("model") + .and_then(|v| v.as_str()) + .unwrap_or("unknown") + .to_string(); + let temperature = json + .get("temperature") + .and_then(|v| v.as_str()) + .unwrap_or("medium") + .to_string(); + + let shadowed_by = seen.get(&name).copied(); + seen.insert(name.clone(), *source); + let final_name = if let Some(shadow_source) = shadowed_by { + let source_name = match shadow_source { + DefinitionSource::ProjectCodex => "Project (.codex)", + DefinitionSource::UserCodex => "User (~/.codex)", + }; + format!("(shadowed by {}) {}", source_name, name) + } else { + name + }; + agents.push(AgentDef { + name: final_name, + description, + model, + temperature, + source: *source, + }); + } + } + } + Ok(agents) +} + +pub fn load_skills_from_roots( + roots: &[(DefinitionSource, std::path::PathBuf)], +) -> Result, String> { + let mut skills = Vec::new(); + let mut seen: std::collections::HashMap = + std::collections::HashMap::new(); + + for (source, path) in roots { + if !path.exists() { + continue; + } + for entry in std::fs::read_dir(path).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let file_path = entry.path(); + if file_path.extension().map_or(false, |ext| ext == "md") { + let name = file_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("unknown") + .to_string(); + let content = std::fs::read_to_string(&file_path).map_err(|e| e.to_string())?; + let description = content.lines().next().unwrap_or("").to_string(); + + let shadowed_by = seen.get(&name).copied(); + seen.insert(name.clone(), *source); + let final_name = if let Some(shadow_source) = shadowed_by { + let source_name = match shadow_source { + DefinitionSource::ProjectCodex => "Project (.codex)", + DefinitionSource::UserCodex => "User (~/.codex)", + }; + format!("(shadowed by {}) {}", source_name, name) + } else { + name + }; + skills.push(SkillDef { + name: final_name, + description, + source: *source, + }); + } + } + } + Ok(skills) +} + +pub fn render_agents_report(agents: &[AgentDef]) -> String { + let mut lines = vec!["Agents".to_string()]; + let project_agents: Vec<_> = agents + .iter() + .filter(|a| matches!(a.source, DefinitionSource::ProjectCodex)) + .collect(); + let user_agents: Vec<_> = agents + .iter() + .filter(|a| matches!(a.source, DefinitionSource::UserCodex)) + .collect(); + + let unique_count = agents + .iter() + .filter(|a| !a.name.starts_with("(shadowed")) + .count(); + lines.push(format!("{} active agents", unique_count)); + + if !project_agents.is_empty() { + lines.push("Project (.codex):".to_string()); + for agent in project_agents { + lines.push(format!( + "{} · {} · {} · {}", + agent.name, agent.description, agent.model, agent.temperature + )); + } + } + + if !user_agents.is_empty() { + lines.push("User (~/.codex):".to_string()); + for agent in user_agents { + lines.push(format!( + "{} · {} · {} · {}", + agent.name, agent.description, agent.model, agent.temperature + )); + } + } + + lines.join("\n") +} + +pub fn render_skills_report(skills: &[SkillDef]) -> String { + let mut lines = vec!["Skills".to_string()]; + let project_skills: Vec<_> = skills + .iter() + .filter(|s| matches!(s.source, DefinitionSource::ProjectCodex)) + .collect(); + let user_skills: Vec<_> = skills + .iter() + .filter(|s| matches!(s.source, DefinitionSource::UserCodex)) + .collect(); + + let unique_count = skills + .iter() + .filter(|s| !s.name.starts_with("(shadowed")) + .count(); + lines.push(format!("{} available skills", unique_count)); + + if !project_skills.is_empty() { + lines.push("Project (.codex):".to_string()); + for skill in project_skills { + lines.push(format!("{} · {}", skill.name, skill.description)); + } + } + + if !user_skills.is_empty() { + lines.push("User (~/.codex):".to_string()); + for skill in user_skills { + lines.push(format!("{} · {}", skill.name, skill.description)); + } + } + + lines.join("\n") +} + #[cfg(test)] mod tests { use super::{ @@ -622,13 +849,27 @@ mod tests { fs::write( root.join(".claude-plugin").join("plugin.json"), format!( - "{{\n \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"description\": \"bundled commands plugin\",\n \"defaultEnabled\": {}\n}}", + "{{\n \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"description\": \"bundled commands plugin\",\n \"defaultEnabled\": {}}}", if default_enabled { "true" } else { "false" } ), ) .expect("write bundled manifest"); } + fn write_agent(dir: &Path, name: &str, description: &str, model: &str, temperature: &str) { + fs::create_dir_all(dir).expect("agent dir"); + let json = format!( + "{{\n \"name\": \"{name}\",\n \"description\": \"{description}\",\n \"model\": \"{model}\",\n \"temperature\": \"{temperature}\"\n}}" + ); + fs::write(dir.join(format!("{name}.json")), json).expect("write agent"); + } + + fn write_skill(dir: &Path, name: &str, description: &str) { + fs::create_dir_all(dir).expect("skill dir"); + let content = format!("{description}\n\nSkill content here.\n"); + fs::write(dir.join(format!("{name}.md")), content).expect("write skill"); + } + #[allow(clippy::too_many_lines)] #[test] fn parses_supported_slash_commands() { @@ -961,9 +1202,8 @@ mod tests { (DefinitionSource::ProjectCodex, project_agents), (DefinitionSource::UserCodex, user_agents), ]; - let report = render_agents_report( - &load_agents_from_roots(&roots).expect("agent roots should load"), - ); + let report = + render_agents_report(&load_agents_from_roots(&roots).expect("agent roots should load")); assert!(report.contains("Agents")); assert!(report.contains("2 active agents")); @@ -992,9 +1232,8 @@ mod tests { (DefinitionSource::ProjectCodex, project_skills), (DefinitionSource::UserCodex, user_skills), ]; - let report = render_skills_report( - &load_skills_from_roots(&roots).expect("skill roots should load"), - ); + let report = + render_skills_report(&load_skills_from_roots(&roots).expect("skill roots should load")); assert!(report.contains("Skills")); assert!(report.contains("2 available skills")); diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index dfc4d1a..d41fefb 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use crate::json::JsonValue; use crate::sandbox::{FilesystemIsolationMode, SandboxConfig}; -pub const CLAUDE_CODE_SETTINGS_SCHEMA_NAME: &str = "SettingsSchema"; +pub const CLAW_SETTINGS_SCHEMA_NAME: &str = "SettingsSchema"; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum ConfigSource { @@ -79,7 +79,7 @@ pub enum McpTransport { Http, Ws, Sdk, - ClaudeAiProxy, + ManagedProxy, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -89,7 +89,7 @@ pub enum McpServerConfig { Http(McpRemoteServerConfig), Ws(McpWebSocketServerConfig), Sdk(McpSdkServerConfig), - ClaudeAiProxy(McpClaudeAiProxyServerConfig), + ManagedProxy(McpManagedProxyServerConfig), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -120,7 +120,7 @@ pub struct McpSdkServerConfig { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct McpClaudeAiProxyServerConfig { +pub struct McpManagedProxyServerConfig { pub url: String, pub id: String, } @@ -196,8 +196,8 @@ impl ConfigLoader { #[must_use] pub fn discover(&self) -> Vec { let user_legacy_path = self.config_home.parent().map_or_else( - || PathBuf::from(".claude.json"), - |parent| parent.join(".claude.json"), + || PathBuf::from(".claw.json"), + |parent| parent.join(".claw.json"), ); vec![ ConfigEntry { @@ -210,15 +210,15 @@ impl ConfigLoader { }, ConfigEntry { source: ConfigSource::Project, - path: self.cwd.join(".claude.json"), + path: self.cwd.join(".claw.json"), }, ConfigEntry { source: ConfigSource::Project, - path: self.cwd.join(".claude").join("settings.json"), + path: self.cwd.join(".claw").join("settings.json"), }, ConfigEntry { source: ConfigSource::Local, - path: self.cwd.join(".claude").join("settings.local.json"), + path: self.cwd.join(".claw").join("settings.local.json"), }, ] } @@ -420,10 +420,10 @@ impl RuntimePluginConfig { #[must_use] pub fn default_config_home() -> PathBuf { - std::env::var_os("CLAUDE_CONFIG_HOME") + std::env::var_os("CLAW_CONFIG_HOME") .map(PathBuf::from) - .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".claude"))) - .unwrap_or_else(|| PathBuf::from(".claude")) + .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".claw"))) + .unwrap_or_else(|| PathBuf::from(".claw")) } impl RuntimeHookConfig { @@ -486,7 +486,7 @@ impl McpServerConfig { Self::Http(_) => McpTransport::Http, Self::Ws(_) => McpTransport::Ws, Self::Sdk(_) => McpTransport::Sdk, - Self::ClaudeAiProxy(_) => McpTransport::ClaudeAiProxy, + Self::ManagedProxy(_) => McpTransport::ManagedProxy, } } } @@ -494,7 +494,7 @@ impl McpServerConfig { fn read_optional_json_object( path: &Path, ) -> Result>, ConfigError> { - let is_legacy_config = path.file_name().and_then(|name| name.to_str()) == Some(".claude.json"); + let is_legacy_config = path.file_name().and_then(|name| name.to_str()) == Some(".claw.json"); let contents = match fs::read_to_string(path) { Ok(contents) => contents, Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), @@ -724,12 +724,10 @@ fn parse_mcp_server_config( "sdk" => Ok(McpServerConfig::Sdk(McpSdkServerConfig { name: expect_string(object, "name", context)?.to_string(), })), - "claudeai-proxy" => Ok(McpServerConfig::ClaudeAiProxy( - McpClaudeAiProxyServerConfig { - url: expect_string(object, "url", context)?.to_string(), - id: expect_string(object, "id", context)?.to_string(), - }, - )), + "claudeai-proxy" => Ok(McpServerConfig::ManagedProxy(McpManagedProxyServerConfig { + url: expect_string(object, "url", context)?.to_string(), + id: expect_string(object, "id", context)?.to_string(), + })), other => Err(ConfigError::Parse(format!( "{context}: unsupported MCP server type for {server_name}: {other}" ))), @@ -942,7 +940,7 @@ fn push_unique(target: &mut Vec, value: String) { mod tests { use super::{ ConfigLoader, ConfigSource, McpServerConfig, McpTransport, ResolvedPermissionMode, - CLAUDE_CODE_SETTINGS_SCHEMA_NAME, + CLAW_SETTINGS_SCHEMA_NAME, }; use crate::json::JsonValue; use crate::sandbox::FilesystemIsolationMode; @@ -961,7 +959,7 @@ mod tests { fn rejects_non_object_settings_files() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); + let home = root.join("home").join(".claw"); fs::create_dir_all(&home).expect("home config dir"); fs::create_dir_all(&cwd).expect("project dir"); fs::write(home.join("settings.json"), "[]").expect("write bad settings"); @@ -980,12 +978,12 @@ mod tests { fn loads_and_merges_claude_code_config_files_by_precedence() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); - fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(cwd.join(".claw")).expect("project config dir"); fs::create_dir_all(&home).expect("home config dir"); fs::write( - home.parent().expect("home parent").join(".claude.json"), + home.parent().expect("home parent").join(".claw.json"), r#"{"model":"haiku","env":{"A":"1"},"mcpServers":{"home":{"command":"uvx","args":["home"]}}}"#, ) .expect("write user compat config"); @@ -995,17 +993,17 @@ mod tests { ) .expect("write user settings"); fs::write( - cwd.join(".claude.json"), + cwd.join(".claw.json"), r#"{"model":"project-compat","env":{"B":"2"}}"#, ) .expect("write project compat config"); fs::write( - cwd.join(".claude").join("settings.json"), + cwd.join(".claw").join("settings.json"), r#"{"env":{"C":"3"},"hooks":{"PostToolUse":["project"]},"mcpServers":{"project":{"command":"uvx","args":["project"]}}}"#, ) .expect("write project settings"); fs::write( - cwd.join(".claude").join("settings.local.json"), + cwd.join(".claw").join("settings.local.json"), r#"{"model":"opus","permissionMode":"acceptEdits"}"#, ) .expect("write local settings"); @@ -1014,7 +1012,7 @@ mod tests { .load() .expect("config should load"); - assert_eq!(CLAUDE_CODE_SETTINGS_SCHEMA_NAME, "SettingsSchema"); + assert_eq!(CLAW_SETTINGS_SCHEMA_NAME, "SettingsSchema"); assert_eq!(loaded.loaded_entries().len(), 5); assert_eq!(loaded.loaded_entries()[0].source, ConfigSource::User); assert_eq!( @@ -1056,12 +1054,12 @@ mod tests { fn parses_sandbox_config() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); - fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(cwd.join(".claw")).expect("project config dir"); fs::create_dir_all(&home).expect("home config dir"); fs::write( - cwd.join(".claude").join("settings.local.json"), + cwd.join(".claw").join("settings.local.json"), r#"{ "sandbox": { "enabled": true, @@ -1094,8 +1092,8 @@ mod tests { fn parses_typed_mcp_and_oauth_config() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); - fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(cwd.join(".claw")).expect("project config dir"); fs::create_dir_all(&home).expect("home config dir"); fs::write( @@ -1132,7 +1130,7 @@ mod tests { ) .expect("write user settings"); fs::write( - cwd.join(".claude").join("settings.local.json"), + cwd.join(".claw").join("settings.local.json"), r#"{ "mcpServers": { "remote-server": { @@ -1185,8 +1183,8 @@ mod tests { fn parses_plugin_config_from_enabled_plugins() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); - fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(cwd.join(".claw")).expect("project config dir"); fs::create_dir_all(&home).expect("home config dir"); fs::write( @@ -1223,8 +1221,8 @@ mod tests { fn parses_plugin_config() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); - fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(cwd.join(".claw")).expect("project config dir"); fs::create_dir_all(&home).expect("home config dir"); fs::write( @@ -1275,7 +1273,7 @@ mod tests { fn rejects_invalid_mcp_server_shapes() { let root = temp_dir(); let cwd = root.join("project"); - let home = root.join("home").join(".claude"); + let home = root.join("home").join(".claw"); fs::create_dir_all(&home).expect("home config dir"); fs::create_dir_all(&cwd).expect("project dir"); fs::write( diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index edac666..5206fa9 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -24,11 +24,11 @@ pub use compact::{ get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult, }; pub use config::{ - ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpClaudeAiProxyServerConfig, + ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpManagedProxyServerConfig, McpConfigCollection, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig, - RuntimePluginConfig, ScopedMcpServerConfig, CLAUDE_CODE_SETTINGS_SCHEMA_NAME, + RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME, }; pub use conversation::{ auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent, @@ -45,7 +45,7 @@ pub use mcp::{ scoped_mcp_config_hash, unwrap_ccr_proxy_url, }; pub use mcp_client::{ - McpClaudeAiProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport, + McpManagedProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport, McpRemoteTransport, McpSdkTransport, McpStdioTransport, }; pub use mcp_stdio::{ diff --git a/rust/crates/runtime/src/mcp.rs b/rust/crates/runtime/src/mcp.rs index 103fbe4..b37ea33 100644 --- a/rust/crates/runtime/src/mcp.rs +++ b/rust/crates/runtime/src/mcp.rs @@ -73,7 +73,7 @@ pub fn mcp_server_signature(config: &McpServerConfig) -> Option { Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url))) } McpServerConfig::Ws(config) => Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url))), - McpServerConfig::ClaudeAiProxy(config) => { + McpServerConfig::ManagedProxy(config) => { Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url))) } McpServerConfig::Sdk(_) => None, @@ -110,7 +110,7 @@ pub fn scoped_mcp_config_hash(config: &ScopedMcpServerConfig) -> String { ws.headers_helper.as_deref().unwrap_or("") ), McpServerConfig::Sdk(sdk) => format!("sdk|{}", sdk.name), - McpServerConfig::ClaudeAiProxy(proxy) => { + McpServerConfig::ManagedProxy(proxy) => { format!("claudeai-proxy|{}|{}", proxy.url, proxy.id) } }; diff --git a/rust/crates/runtime/src/mcp_client.rs b/rust/crates/runtime/src/mcp_client.rs index 23ccb95..0e94391 100644 --- a/rust/crates/runtime/src/mcp_client.rs +++ b/rust/crates/runtime/src/mcp_client.rs @@ -10,7 +10,7 @@ pub enum McpClientTransport { Http(McpRemoteTransport), WebSocket(McpRemoteTransport), Sdk(McpSdkTransport), - ClaudeAiProxy(McpClaudeAiProxyTransport), + ManagedProxy(McpManagedProxyTransport), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -34,7 +34,7 @@ pub struct McpSdkTransport { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct McpClaudeAiProxyTransport { +pub struct McpManagedProxyTransport { pub url: String, pub id: String, } @@ -97,8 +97,8 @@ impl McpClientTransport { McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport { name: config.name.clone(), }), - McpServerConfig::ClaudeAiProxy(config) => { - Self::ClaudeAiProxy(McpClaudeAiProxyTransport { + McpServerConfig::ManagedProxy(config) => { + Self::ManagedProxy(McpManagedProxyTransport { url: config.url.clone(), id: config.id.clone(), }) diff --git a/rust/crates/runtime/src/oauth.rs b/rust/crates/runtime/src/oauth.rs index 3f30a00..82e13d0 100644 --- a/rust/crates/runtime/src/oauth.rs +++ b/rust/crates/runtime/src/oauth.rs @@ -324,12 +324,12 @@ fn generate_random_token(bytes: usize) -> io::Result { } fn credentials_home_dir() -> io::Result { - if let Some(path) = std::env::var_os("CLAUDE_CONFIG_HOME") { + if let Some(path) = std::env::var_os("CLAW_CONFIG_HOME") { return Ok(PathBuf::from(path)); } let home = std::env::var_os("HOME") .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "HOME is not set"))?; - Ok(PathBuf::from(home).join(".claude")) + Ok(PathBuf::from(home).join(".claw")) } fn read_credentials_root(path: &PathBuf) -> io::Result> { @@ -541,7 +541,7 @@ mod tests { fn oauth_credentials_round_trip_and_clear_preserves_other_fields() { let _guard = env_lock(); let config_home = temp_config_home(); - std::env::set_var("CLAUDE_CONFIG_HOME", &config_home); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); let path = credentials_path().expect("credentials path"); std::fs::create_dir_all(path.parent().expect("parent")).expect("create parent"); std::fs::write(&path, "{\"other\":\"value\"}\n").expect("seed credentials"); @@ -567,7 +567,7 @@ mod tests { assert!(cleared.contains("\"other\": \"value\"")); assert!(!cleared.contains("\"oauth\"")); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir"); } diff --git a/rust/crates/runtime/src/prompt.rs b/rust/crates/runtime/src/prompt.rs index 91a3afc..8952682 100644 --- a/rust/crates/runtime/src/prompt.rs +++ b/rust/crates/runtime/src/prompt.rs @@ -203,8 +203,8 @@ fn discover_instruction_files(cwd: &Path) -> std::io::Result> { for candidate in [ dir.join("CLAUDE.md"), dir.join("CLAUDE.local.md"), - dir.join(".claude").join("CLAUDE.md"), - dir.join(".claude").join("instructions.md"), + dir.join(".claw").join("CLAUDE.md"), + dir.join(".claw").join("instructions.md"), ] { push_context_file(&mut files, candidate)?; } @@ -517,23 +517,23 @@ mod tests { fn discovers_instruction_files_from_ancestor_chain() { let root = temp_dir(); let nested = root.join("apps").join("api"); - fs::create_dir_all(nested.join(".claude")).expect("nested claude dir"); + fs::create_dir_all(nested.join(".claw")).expect("nested claw dir"); fs::write(root.join("CLAUDE.md"), "root instructions").expect("write root instructions"); fs::write(root.join("CLAUDE.local.md"), "local instructions") .expect("write local instructions"); fs::create_dir_all(root.join("apps")).expect("apps dir"); - fs::create_dir_all(root.join("apps").join(".claude")).expect("apps claude dir"); + fs::create_dir_all(root.join("apps").join(".claw")).expect("apps claw dir"); fs::write(root.join("apps").join("CLAUDE.md"), "apps instructions") .expect("write apps instructions"); fs::write( - root.join("apps").join(".claude").join("instructions.md"), + root.join("apps").join(".claw").join("instructions.md"), "apps dot claude instructions", ) .expect("write apps dot claude instructions"); - fs::write(nested.join(".claude").join("CLAUDE.md"), "nested rules") + fs::write(nested.join(".claw").join("CLAUDE.md"), "nested rules") .expect("write nested rules"); fs::write( - nested.join(".claude").join("instructions.md"), + nested.join(".claw").join("instructions.md"), "nested instructions", ) .expect("write nested instructions"); @@ -593,7 +593,7 @@ mod tests { #[test] fn displays_context_paths_compactly() { assert_eq!( - display_context_path(Path::new("/tmp/project/.claude/CLAUDE.md")), + display_context_path(Path::new("/tmp/project/.claw/CLAUDE.md")), "CLAUDE.md" ); } @@ -667,10 +667,10 @@ mod tests { #[test] fn load_system_prompt_reads_claude_files_and_config() { let root = temp_dir(); - fs::create_dir_all(root.join(".claude")).expect("claude dir"); + fs::create_dir_all(root.join(".claw")).expect("claw dir"); fs::write(root.join("CLAUDE.md"), "Project rules").expect("write instructions"); fs::write( - root.join(".claude").join("settings.json"), + root.join(".claw").join("settings.json"), r#"{"permissionMode":"acceptEdits"}"#, ) .expect("write settings"); @@ -678,9 +678,9 @@ mod tests { let _guard = env_lock(); let previous = std::env::current_dir().expect("cwd"); let original_home = std::env::var("HOME").ok(); - let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok(); + let original_claw_home = std::env::var("CLAW_CONFIG_HOME").ok(); std::env::set_var("HOME", &root); - std::env::set_var("CLAUDE_CONFIG_HOME", root.join("missing-home")); + std::env::set_var("CLAW_CONFIG_HOME", root.join("missing-home")); std::env::set_current_dir(&root).expect("change cwd"); let prompt = super::load_system_prompt(&root, "2026-03-31", "linux", "6.8") .expect("system prompt should load") @@ -695,10 +695,10 @@ mod tests { } else { std::env::remove_var("HOME"); } - if let Some(value) = original_claude_home { - std::env::set_var("CLAUDE_CONFIG_HOME", value); + if let Some(value) = original_claw_home { + std::env::set_var("CLAW_CONFIG_HOME", value); } else { - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); } assert!(prompt.contains("Project rules")); @@ -709,10 +709,10 @@ mod tests { #[test] fn renders_claude_code_style_sections_with_project_context() { let root = temp_dir(); - fs::create_dir_all(root.join(".claude")).expect("claude dir"); + fs::create_dir_all(root.join(".claw")).expect("claw dir"); fs::write(root.join("CLAUDE.md"), "Project rules").expect("write CLAUDE.md"); fs::write( - root.join(".claude").join("settings.json"), + root.join(".claw").join("settings.json"), r#"{"permissionMode":"acceptEdits"}"#, ) .expect("write settings"); @@ -751,9 +751,9 @@ mod tests { fn discovers_dot_claude_instructions_markdown() { let root = temp_dir(); let nested = root.join("apps").join("api"); - fs::create_dir_all(nested.join(".claude")).expect("nested claude dir"); + fs::create_dir_all(nested.join(".claw")).expect("nested claw dir"); fs::write( - nested.join(".claude").join("instructions.md"), + nested.join(".claw").join("instructions.md"), "instruction markdown", ) .expect("write instructions.md"); @@ -762,7 +762,7 @@ mod tests { assert!(context .instruction_files .iter() - .any(|file| file.path.ends_with(".claude/instructions.md"))); + .any(|file| file.path.ends_with(".claw/instructions.md"))); assert!( render_instruction_files(&context.instruction_files).contains("instruction markdown") ); diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 7c3179c..3c96284 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -16,7 +16,7 @@ use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use api::{ - resolve_startup_auth_source, AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock, + resolve_startup_auth_source, ApiHttpClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage, MessageRequest, MessageResponse, OutputContentBlock, StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock, }; @@ -305,7 +305,13 @@ fn resolve_model_alias(model: &str) -> &str { } fn normalize_allowed_tools(values: &[String]) -> Result, String> { - current_tool_registry()?.normalize_allowed_tools(values) + if values.is_empty() { + return Ok(None); + } + match current_tool_registry() { + Ok(registry) => registry.normalize_allowed_tools(values), + Err(_) => GlobalToolRegistry::builtin().normalize_allowed_tools(values), + } } fn current_tool_registry() -> Result { @@ -473,7 +479,7 @@ fn run_login() -> Result<(), Box> { return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into()); } - let client = AnthropicClient::from_auth(AuthSource::None).with_base_url(api::read_base_url()); + let client = ApiHttpClient::from_auth(AuthSource::None).with_base_url(api::read_base_url()); let exchange_request = OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri); let runtime = tokio::runtime::Runtime::new()?; @@ -1697,7 +1703,7 @@ impl LiveCli { fn sessions_dir() -> Result> { let cwd = env::current_dir()?; - let path = cwd.join(".claude").join("sessions"); + let path = cwd.join(".claw").join("sessions"); fs::create_dir_all(&path)?; Ok(path) } @@ -2811,7 +2817,7 @@ impl runtime::PermissionPrompter for CliPermissionPrompter { struct AnthropicRuntimeClient { runtime: tokio::runtime::Runtime, - client: AnthropicClient, + client: ApiHttpClient, model: String, enable_tools: bool, emit_output: bool, @@ -2831,7 +2837,7 @@ impl AnthropicRuntimeClient { ) -> Result> { Ok(Self { runtime: tokio::runtime::Runtime::new()?, - client: AnthropicClient::from_auth(resolve_cli_auth_source()?) + client: ApiHttpClient::from_auth(resolve_cli_auth_source()?) .with_base_url(api::read_base_url()), model, enable_tools, diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index 38fafe9..a6e1ba4 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -4,7 +4,7 @@ use std::process::Command; use std::time::{Duration, Instant}; use api::{ - read_base_url, AnthropicClient, ContentBlockDelta, InputContentBlock, InputMessage, + read_base_url, ApiHttpClient, ContentBlockDelta, InputContentBlock, InputMessage, MessageRequest, MessageResponse, OutputContentBlock, StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock, }; @@ -1542,6 +1542,11 @@ fn resolve_skill_path(skill: &str) -> Result { if let Ok(codex_home) = std::env::var("CODEX_HOME") { candidates.push(std::path::PathBuf::from(codex_home).join("skills")); } + if let Ok(home) = std::env::var("HOME") { + let home = std::path::PathBuf::from(home); + candidates.push(home.join(".agents").join("skills")); + candidates.push(home.join(".codex").join("skills")); + } candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills")); for root in candidates { @@ -1875,7 +1880,7 @@ fn format_agent_terminal_output(status: &str, result: Option<&str>, error: Optio struct AnthropicRuntimeClient { runtime: tokio::runtime::Runtime, - client: AnthropicClient, + client: ApiHttpClient, model: String, allowed_tools: BTreeSet, tool_registry: GlobalToolRegistry, @@ -1887,7 +1892,7 @@ impl AnthropicRuntimeClient { allowed_tools: BTreeSet, tool_registry: GlobalToolRegistry, ) -> Result { - let client = AnthropicClient::from_env() + let client = ApiHttpClient::from_env() .map_err(|error| error.to_string())? .with_base_url(read_base_url()); Ok(Self { @@ -2877,16 +2882,16 @@ fn config_file_for_scope(scope: ConfigScope) -> Result { let cwd = std::env::current_dir().map_err(|error| error.to_string())?; Ok(match scope { ConfigScope::Global => config_home_dir()?.join("settings.json"), - ConfigScope::Settings => cwd.join(".claude").join("settings.local.json"), + ConfigScope::Settings => cwd.join(".claw").join("settings.local.json"), }) } fn config_home_dir() -> Result { - if let Ok(path) = std::env::var("CLAUDE_CONFIG_HOME") { + if let Ok(path) = std::env::var("CLAW_CONFIG_HOME") { return Ok(PathBuf::from(path)); } let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?; - Ok(PathBuf::from(home).join(".claude")) + Ok(PathBuf::from(home).join(".claw")) } fn read_json_object(path: &Path) -> Result, String> { @@ -4490,19 +4495,19 @@ mod tests { )); let home = root.join("home"); let cwd = root.join("cwd"); - std::fs::create_dir_all(home.join(".claude")).expect("home dir"); - std::fs::create_dir_all(cwd.join(".claude")).expect("cwd dir"); + std::fs::create_dir_all(home.join(".claw")).expect("home dir"); + std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir"); std::fs::write( - home.join(".claude").join("settings.json"), + home.join(".claw").join("settings.json"), r#"{"verbose":false}"#, ) .expect("write global settings"); let original_home = std::env::var("HOME").ok(); - let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok(); + let original_claw_home = std::env::var("CLAW_CONFIG_HOME").ok(); let original_dir = std::env::current_dir().expect("cwd"); std::env::set_var("HOME", &home); - std::env::remove_var("CLAUDE_CONFIG_HOME"); + std::env::remove_var("CLAW_CONFIG_HOME"); std::env::set_current_dir(&cwd).expect("set cwd"); let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config"); @@ -4535,9 +4540,9 @@ mod tests { Some(value) => std::env::set_var("HOME", value), None => std::env::remove_var("HOME"), } - match original_claude_home { - Some(value) => std::env::set_var("CLAUDE_CONFIG_HOME", value), - None => std::env::remove_var("CLAUDE_CONFIG_HOME"), + match original_claw_home { + Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value), + None => std::env::remove_var("CLAW_CONFIG_HOME"), } let _ = std::fs::remove_dir_all(root); }