Procházet zdrojové kódy

fix: post-plugins-merge cleanroom fixes and workspace deps

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
YeonGyu-Kim před 2 měsíci
rodič
revize
c2e41ba205

+ 1 - 0
rust/Cargo.lock

@@ -113,6 +113,7 @@ version = "0.1.0"
 dependencies = [
  "plugins",
  "runtime",
+ "serde_json",
 ]
 
 [[package]]

+ 3 - 0
rust/Cargo.toml

@@ -8,6 +8,9 @@ edition = "2021"
 license = "MIT"
 publish = false
 
+[workspace.dependencies]
+serde_json = "1"
+
 [workspace.lints.rust]
 unsafe_code = "forbid"
 

+ 16 - 16
rust/crates/api/src/client.rs

@@ -101,7 +101,7 @@ impl From<OAuthTokenSet> 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<String>) -> 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),

+ 1 - 1
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};

+ 8 - 8
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")

+ 1 - 0
rust/crates/commands/Cargo.toml

@@ -11,3 +11,4 @@ workspace = true
 [dependencies]
 plugins = { path = "../plugins" }
 runtime = { path = "../runtime" }
+serde_json = "1"

+ 246 - 7
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("<session-path>"),
         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("<symbol-or-path>"),
         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 <session-id>]"),
         resume_supported: false,
     },
     SlashCommandSpec {
         name: "plugins",
+        aliases: &[],
         summary: "List or manage plugins",
         argument_hint: Some(
             "[list|install <path>|enable <name>|disable <name>|uninstall <id>|update <id>]",
@@ -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<Vec<AgentDef>, String> {
+    let mut agents = Vec::new();
+    let mut seen: std::collections::HashMap<String, DefinitionSource> =
+        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<Vec<SkillDef>, String> {
+    let mut skills = Vec::new();
+    let mut seen: std::collections::HashMap<String, DefinitionSource> =
+        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"));

+ 38 - 40
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<ConfigEntry> {
         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<Option<BTreeMap<String, JsonValue>>, 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<String>, 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(

+ 3 - 3
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::{

+ 2 - 2
rust/crates/runtime/src/mcp.rs

@@ -73,7 +73,7 @@ pub fn mcp_server_signature(config: &McpServerConfig) -> Option<String> {
             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)
         }
     };

+ 4 - 4
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(),
                 })

+ 4 - 4
rust/crates/runtime/src/oauth.rs

@@ -324,12 +324,12 @@ fn generate_random_token(bytes: usize) -> io::Result<String> {
 }
 
 fn credentials_home_dir() -> io::Result<PathBuf> {
-    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<Map<String, Value>> {
@@ -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");
     }
 

+ 20 - 20
rust/crates/runtime/src/prompt.rs

@@ -203,8 +203,8 @@ fn discover_instruction_files(cwd: &Path) -> std::io::Result<Vec<ContextFile>> {
         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")
         );

+ 12 - 6
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<Option<AllowedToolSet>, 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<GlobalToolRegistry, String> {
@@ -473,7 +479,7 @@ fn run_login() -> Result<(), Box<dyn std::error::Error>> {
         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<PathBuf, Box<dyn std::error::Error>> {
     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<Self, Box<dyn std::error::Error>> {
         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,

+ 19 - 14
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<std::path::PathBuf, String> {
     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<String>,
     tool_registry: GlobalToolRegistry,
@@ -1887,7 +1892,7 @@ impl AnthropicRuntimeClient {
         allowed_tools: BTreeSet<String>,
         tool_registry: GlobalToolRegistry,
     ) -> Result<Self, String> {
-        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<PathBuf, String> {
     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<PathBuf, String> {
-    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<serde_json::Map<String, Value>, 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);
     }