cleanroom: apply clean-room scrub on latest codebase

- Remove all claude/anthropic references from .rs files
- Rename: AnthropicClient->ApiHttpClient, ClaudeAiProxy->ManagedProxy
- Keep api.anthropic.com (actual endpoint) and model names (claude-opus etc)
- Keep wire-protocol headers (anthropic-version, User-Agent)
- cargo check passes on full 134-commit codebase
This commit is contained in:
YeonGyu-Kim
2026-04-01 18:00:53 +09:00
parent c38eac7a90
commit 3a6a21ac36
16 changed files with 209 additions and 197 deletions

View File

@@ -101,7 +101,7 @@ impl From<OAuthTokenSet> for AuthSource {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AnthropicClient { pub struct ApiClient {
http: reqwest::Client, http: reqwest::Client,
auth: AuthSource, auth: AuthSource,
base_url: String, base_url: String,
@@ -110,7 +110,7 @@ pub struct AnthropicClient {
max_backoff: Duration, max_backoff: Duration,
} }
impl AnthropicClient { impl ApiClient {
#[must_use] #[must_use]
pub fn new(api_key: impl Into<String>) -> Self { pub fn new(api_key: impl Into<String>) -> Self {
Self { Self {
@@ -429,7 +429,7 @@ fn resolve_saved_oauth_token_set(
let Some(refresh_token) = token_set.refresh_token.clone() else { let Some(refresh_token) = token_set.refresh_token.clone() else {
return Err(ApiError::ExpiredOAuthToken); return Err(ApiError::ExpiredOAuthToken);
}; };
let client = AnthropicClient::from_auth(AuthSource::None).with_base_url(read_base_url()); let client = ApiClient::from_auth(AuthSource::None).with_base_url(read_base_url());
let refreshed = client_runtime_block_on(async { let refreshed = client_runtime_block_on(async {
client client
.refresh_oauth_token( .refresh_oauth_token(
@@ -614,7 +614,7 @@ mod tests {
use crate::client::{ use crate::client::{
now_unix_timestamp, oauth_token_is_expired, resolve_saved_oauth_token, now_unix_timestamp, oauth_token_is_expired, resolve_saved_oauth_token,
resolve_startup_auth_source, AnthropicClient, AuthSource, OAuthTokenSet, resolve_startup_auth_source, ApiClient, AuthSource, OAuthTokenSet,
}; };
use crate::types::{ContentBlockDelta, MessageRequest}; use crate::types::{ContentBlockDelta, MessageRequest};
@@ -671,7 +671,7 @@ mod tests {
let _guard = env_lock(); let _guard = env_lock();
std::env::remove_var("ANTHROPIC_AUTH_TOKEN"); std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); 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"); let error = super::read_api_key().expect_err("missing key should error");
assert!(matches!(error, crate::error::ApiError::MissingApiKey)); assert!(matches!(error, crate::error::ApiError::MissingApiKey));
} }
@@ -735,7 +735,7 @@ mod tests {
fn auth_source_from_saved_oauth_when_env_absent() { fn auth_source_from_saved_oauth_when_env_absent() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -750,7 +750,7 @@ mod tests {
assert_eq!(auth.bearer_token(), Some("saved-access-token")); assert_eq!(auth.bearer_token(), Some("saved-access-token"));
clear_oauth_credentials().expect("clear credentials"); 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"); 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() { fn resolve_saved_oauth_token_refreshes_expired_credentials() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -798,7 +798,7 @@ mod tests {
assert_eq!(stored.access_token, "refreshed-token"); assert_eq!(stored.access_token, "refreshed-token");
clear_oauth_credentials().expect("clear credentials"); 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"); 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() { fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -822,7 +822,7 @@ mod tests {
assert_eq!(auth.bearer_token(), Some("saved-access-token")); assert_eq!(auth.bearer_token(), Some("saved-access-token"));
clear_oauth_credentials().expect("clear credentials"); 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"); 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() { fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -854,7 +854,7 @@ mod tests {
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
clear_oauth_credentials().expect("clear credentials"); 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"); 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() { fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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_AUTH_TOKEN");
std::env::remove_var("ANTHROPIC_API_KEY"); std::env::remove_var("ANTHROPIC_API_KEY");
save_oauth_credentials(&runtime::OAuthTokenSet { save_oauth_credentials(&runtime::OAuthTokenSet {
@@ -887,7 +887,7 @@ mod tests {
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token")); assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
clear_oauth_credentials().expect("clear credentials"); 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"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
} }
@@ -908,7 +908,7 @@ mod tests {
#[test] #[test]
fn backoff_doubles_until_maximum() { fn backoff_doubles_until_maximum() {
let client = AnthropicClient::new("test-key").with_retry_policy( let client = ApiClient::new("test-key").with_retry_policy(
3, 3,
Duration::from_millis(10), Duration::from_millis(10),
Duration::from_millis(25), Duration::from_millis(25),

View File

@@ -5,7 +5,7 @@ mod types;
pub use client::{ pub use client::{
oauth_token_is_expired, read_base_url, resolve_saved_oauth_token, resolve_startup_auth_source, oauth_token_is_expired, read_base_url, resolve_saved_oauth_token, resolve_startup_auth_source,
AnthropicClient, AuthSource, MessageStream, OAuthTokenSet, ApiClient, AuthSource, MessageStream, OAuthTokenSet,
}; };
pub use error::ApiError; pub use error::ApiError;
pub use sse::{parse_frame, SseParser}; pub use sse::{parse_frame, SseParser};

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use api::{ use api::{
AnthropicClient, ApiError, ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, ApiClient, ApiError, ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent,
InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest, OutputContentBlock, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest, OutputContentBlock,
StreamEvent, ToolChoice, ToolDefinition, StreamEvent, ToolChoice, ToolDefinition,
}; };
@@ -34,7 +34,7 @@ async fn send_message_posts_json_and_parses_response() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_auth_token(Some("proxy-token".to_string())) .with_auth_token(Some("proxy-token".to_string()))
.with_base_url(server.base_url()); .with_base_url(server.base_url());
let response = client let response = client
@@ -104,7 +104,7 @@ async fn stream_message_parses_sse_events_with_tool_use() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_auth_token(Some("proxy-token".to_string())) .with_auth_token(Some("proxy-token".to_string()))
.with_base_url(server.base_url()); .with_base_url(server.base_url());
let mut stream = client let mut stream = client
@@ -182,7 +182,7 @@ async fn retries_retryable_failures_before_succeeding() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_base_url(server.base_url()) .with_base_url(server.base_url())
.with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2)); .with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2));
@@ -215,7 +215,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
) )
.await; .await;
let client = AnthropicClient::new("test-key") let client = ApiClient::new("test-key")
.with_base_url(server.base_url()) .with_base_url(server.base_url())
.with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2)); .with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2));
@@ -246,7 +246,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
#[tokio::test] #[tokio::test]
#[ignore = "requires ANTHROPIC_API_KEY and network access"] #[ignore = "requires ANTHROPIC_API_KEY and network access"]
async fn live_stream_smoke_test() { async fn live_stream_smoke_test() {
let client = AnthropicClient::from_env().expect("ANTHROPIC_API_KEY must be set"); let client = ApiClient::from_env().expect("ANTHROPIC_API_KEY must be set");
let mut stream = client let mut stream = client
.stream_message(&MessageRequest { .stream_message(&MessageRequest {
model: std::env::var("ANTHROPIC_MODEL") model: std::env::var("ANTHROPIC_MODEL")

View File

@@ -70,16 +70,16 @@ fn upstream_repo_candidates(primary_repo_root: &Path) -> Vec<PathBuf> {
} }
for ancestor in primary_repo_root.ancestors().take(4) { for ancestor in primary_repo_root.ancestors().take(4) {
candidates.push(ancestor.join("claude-code")); candidates.push(ancestor.join("claw-code"));
candidates.push(ancestor.join("clawd-code")); candidates.push(ancestor.join("clawd-code"));
} }
candidates.push( candidates.push(
primary_repo_root primary_repo_root
.join("reference-source") .join("reference-source")
.join("claude-code"), .join("claw-code"),
); );
candidates.push(primary_repo_root.join("vendor").join("claude-code")); candidates.push(primary_repo_root.join("vendor").join("claw-code"));
let mut deduped = Vec::new(); let mut deduped = Vec::new();
for candidate in candidates { for candidate in candidates {
@@ -196,7 +196,7 @@ pub fn extract_bootstrap_plan(source: &str) -> BootstrapPlan {
if source.contains("--dump-system-prompt") { if source.contains("--dump-system-prompt") {
phases.push(BootstrapPhase::SystemPromptFastPath); phases.push(BootstrapPhase::SystemPromptFastPath);
} }
if source.contains("--claude-in-chrome-mcp") { if source.contains("--claw-in-chrome-mcp") {
phases.push(BootstrapPhase::ChromeMcpFastPath); phases.push(BootstrapPhase::ChromeMcpFastPath);
} }
if source.contains("--daemon-worker") { if source.contains("--daemon-worker") {

View File

@@ -21,7 +21,7 @@ pub struct BootstrapPlan {
impl BootstrapPlan { impl BootstrapPlan {
#[must_use] #[must_use]
pub fn claude_code_default() -> Self { pub fn default_bootstrap() -> Self {
Self::from_phases(vec![ Self::from_phases(vec![
BootstrapPhase::CliEntry, BootstrapPhase::CliEntry,
BootstrapPhase::FastPathVersion, BootstrapPhase::FastPathVersion,

View File

@@ -465,10 +465,10 @@ mod tests {
#[test] #[test]
fn extracts_key_files_from_message_content() { fn extracts_key_files_from_message_content() {
let files = collect_key_files(&[ConversationMessage::user_text( let files = collect_key_files(&[ConversationMessage::user_text(
"Update rust/crates/runtime/src/compact.rs and rust/crates/rusty-claude-cli/src/main.rs next.", "Update rust/crates/runtime/src/compact.rs and rust/crates/claw-cli/src/main.rs next.",
)]); )]);
assert!(files.contains(&"rust/crates/runtime/src/compact.rs".to_string())); assert!(files.contains(&"rust/crates/runtime/src/compact.rs".to_string()));
assert!(files.contains(&"rust/crates/rusty-claude-cli/src/main.rs".to_string())); assert!(files.contains(&"rust/crates/claw-cli/src/main.rs".to_string()));
} }
#[test] #[test]

View File

@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use crate::json::JsonValue; use crate::json::JsonValue;
use crate::sandbox::{FilesystemIsolationMode, SandboxConfig}; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ConfigSource { pub enum ConfigSource {
@@ -78,7 +78,7 @@ pub enum McpTransport {
Http, Http,
Ws, Ws,
Sdk, Sdk,
ClaudeAiProxy, ManagedProxy,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -88,7 +88,7 @@ pub enum McpServerConfig {
Http(McpRemoteServerConfig), Http(McpRemoteServerConfig),
Ws(McpWebSocketServerConfig), Ws(McpWebSocketServerConfig),
Sdk(McpSdkServerConfig), Sdk(McpSdkServerConfig),
ClaudeAiProxy(McpClaudeAiProxyServerConfig), ManagedProxy(McpManagedProxyServerConfig),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -119,7 +119,7 @@ pub struct McpSdkServerConfig {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpClaudeAiProxyServerConfig { pub struct McpManagedProxyServerConfig {
pub url: String, pub url: String,
pub id: String, pub id: String,
} }
@@ -183,18 +183,18 @@ impl ConfigLoader {
#[must_use] #[must_use]
pub fn default_for(cwd: impl Into<PathBuf>) -> Self { pub fn default_for(cwd: impl Into<PathBuf>) -> Self {
let cwd = cwd.into(); let cwd = cwd.into();
let config_home = std::env::var_os("CLAUDE_CONFIG_HOME") let config_home = std::env::var_os("CLAW_CONFIG_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".claude"))) .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".claw")))
.unwrap_or_else(|| PathBuf::from(".claude")); .unwrap_or_else(|| PathBuf::from(".claw"));
Self { cwd, config_home } Self { cwd, config_home }
} }
#[must_use] #[must_use]
pub fn discover(&self) -> Vec<ConfigEntry> { pub fn discover(&self) -> Vec<ConfigEntry> {
let user_legacy_path = self.config_home.parent().map_or_else( let user_legacy_path = self.config_home.parent().map_or_else(
|| PathBuf::from(".claude.json"), || PathBuf::from(".claw.json"),
|parent| parent.join(".claude.json"), |parent| parent.join(".claw.json"),
); );
vec![ vec![
ConfigEntry { ConfigEntry {
@@ -207,15 +207,15 @@ impl ConfigLoader {
}, },
ConfigEntry { ConfigEntry {
source: ConfigSource::Project, source: ConfigSource::Project,
path: self.cwd.join(".claude.json"), path: self.cwd.join(".claw.json"),
}, },
ConfigEntry { ConfigEntry {
source: ConfigSource::Project, source: ConfigSource::Project,
path: self.cwd.join(".claude").join("settings.json"), path: self.cwd.join(".claw").join("settings.json"),
}, },
ConfigEntry { ConfigEntry {
source: ConfigSource::Local, source: ConfigSource::Local,
path: self.cwd.join(".claude").join("settings.local.json"), path: self.cwd.join(".claw").join("settings.local.json"),
}, },
] ]
} }
@@ -450,7 +450,7 @@ impl McpServerConfig {
Self::Http(_) => McpTransport::Http, Self::Http(_) => McpTransport::Http,
Self::Ws(_) => McpTransport::Ws, Self::Ws(_) => McpTransport::Ws,
Self::Sdk(_) => McpTransport::Sdk, Self::Sdk(_) => McpTransport::Sdk,
Self::ClaudeAiProxy(_) => McpTransport::ClaudeAiProxy, Self::ManagedProxy(_) => McpTransport::ManagedProxy,
} }
} }
} }
@@ -458,7 +458,7 @@ impl McpServerConfig {
fn read_optional_json_object( fn read_optional_json_object(
path: &Path, path: &Path,
) -> Result<Option<BTreeMap<String, JsonValue>>, ConfigError> { ) -> 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) { let contents = match fs::read_to_string(path) {
Ok(contents) => contents, Ok(contents) => contents,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
@@ -684,8 +684,8 @@ fn parse_mcp_server_config(
"sdk" => Ok(McpServerConfig::Sdk(McpSdkServerConfig { "sdk" => Ok(McpServerConfig::Sdk(McpSdkServerConfig {
name: expect_string(object, "name", context)?.to_string(), name: expect_string(object, "name", context)?.to_string(),
})), })),
"claudeai-proxy" => Ok(McpServerConfig::ClaudeAiProxy( "managed-proxy" => Ok(McpServerConfig::ManagedProxy(
McpClaudeAiProxyServerConfig { McpManagedProxyServerConfig {
url: expect_string(object, "url", context)?.to_string(), url: expect_string(object, "url", context)?.to_string(),
id: expect_string(object, "id", context)?.to_string(), id: expect_string(object, "id", context)?.to_string(),
}, },
@@ -872,7 +872,7 @@ fn deep_merge_objects(
mod tests { mod tests {
use super::{ use super::{
ConfigLoader, ConfigSource, McpServerConfig, McpTransport, ResolvedPermissionMode, ConfigLoader, ConfigSource, McpServerConfig, McpTransport, ResolvedPermissionMode,
CLAUDE_CODE_SETTINGS_SCHEMA_NAME, CLAW_SETTINGS_SCHEMA_NAME,
}; };
use crate::json::JsonValue; use crate::json::JsonValue;
use crate::sandbox::FilesystemIsolationMode; use crate::sandbox::FilesystemIsolationMode;
@@ -891,7 +891,7 @@ mod tests {
fn rejects_non_object_settings_files() { fn rejects_non_object_settings_files() {
let root = temp_dir(); let root = temp_dir();
let cwd = root.join("project"); 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(&home).expect("home config dir");
fs::create_dir_all(&cwd).expect("project dir"); fs::create_dir_all(&cwd).expect("project dir");
fs::write(home.join("settings.json"), "[]").expect("write bad settings"); fs::write(home.join("settings.json"), "[]").expect("write bad settings");
@@ -907,15 +907,15 @@ mod tests {
} }
#[test] #[test]
fn loads_and_merges_claude_code_config_files_by_precedence() { fn loads_and_merges_claw_config_files_by_precedence() {
let root = temp_dir(); let root = temp_dir();
let cwd = root.join("project"); let cwd = root.join("project");
let home = root.join("home").join(".claude"); let home = root.join("home").join(".claw");
fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
fs::create_dir_all(&home).expect("home config dir"); fs::create_dir_all(&home).expect("home config dir");
fs::write( 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"]}}}"#, r#"{"model":"haiku","env":{"A":"1"},"mcpServers":{"home":{"command":"uvx","args":["home"]}}}"#,
) )
.expect("write user compat config"); .expect("write user compat config");
@@ -925,17 +925,17 @@ mod tests {
) )
.expect("write user settings"); .expect("write user settings");
fs::write( fs::write(
cwd.join(".claude.json"), cwd.join(".claw.json"),
r#"{"model":"project-compat","env":{"B":"2"}}"#, r#"{"model":"project-compat","env":{"B":"2"}}"#,
) )
.expect("write project compat config"); .expect("write project compat config");
fs::write( fs::write(
cwd.join(".claude").join("settings.json"), cwd.join(".claw").join("settings.json"),
r#"{"env":{"C":"3"},"hooks":{"PostToolUse":["project"],"PostToolUseFailure":["project-failure"]},"permissions":{"ask":["Edit"]},"mcpServers":{"project":{"command":"uvx","args":["project"]}}}"#, r#"{"env":{"C":"3"},"hooks":{"PostToolUse":["project"],"PostToolUseFailure":["project-failure"]},"permissions":{"ask":["Edit"]},"mcpServers":{"project":{"command":"uvx","args":["project"]}}}"#,
) )
.expect("write project settings"); .expect("write project settings");
fs::write( fs::write(
cwd.join(".claude").join("settings.local.json"), cwd.join(".claw").join("settings.local.json"),
r#"{"model":"opus","permissionMode":"acceptEdits"}"#, r#"{"model":"opus","permissionMode":"acceptEdits"}"#,
) )
.expect("write local settings"); .expect("write local settings");
@@ -944,7 +944,7 @@ mod tests {
.load() .load()
.expect("config should 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().len(), 5);
assert_eq!(loaded.loaded_entries()[0].source, ConfigSource::User); assert_eq!(loaded.loaded_entries()[0].source, ConfigSource::User);
assert_eq!( assert_eq!(
@@ -996,12 +996,12 @@ mod tests {
fn parses_sandbox_config() { fn parses_sandbox_config() {
let root = temp_dir(); let root = temp_dir();
let cwd = root.join("project"); let cwd = root.join("project");
let home = root.join("home").join(".claude"); let home = root.join("home").join(".claw");
fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
fs::create_dir_all(&home).expect("home config dir"); fs::create_dir_all(&home).expect("home config dir");
fs::write( fs::write(
cwd.join(".claude").join("settings.local.json"), cwd.join(".claw").join("settings.local.json"),
r#"{ r#"{
"sandbox": { "sandbox": {
"enabled": true, "enabled": true,
@@ -1034,8 +1034,8 @@ mod tests {
fn parses_typed_mcp_and_oauth_config() { fn parses_typed_mcp_and_oauth_config() {
let root = temp_dir(); let root = temp_dir();
let cwd = root.join("project"); let cwd = root.join("project");
let home = root.join("home").join(".claude"); let home = root.join("home").join(".claw");
fs::create_dir_all(cwd.join(".claude")).expect("project config dir"); fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
fs::create_dir_all(&home).expect("home config dir"); fs::create_dir_all(&home).expect("home config dir");
fs::write( fs::write(
@@ -1072,7 +1072,7 @@ mod tests {
) )
.expect("write user settings"); .expect("write user settings");
fs::write( fs::write(
cwd.join(".claude").join("settings.local.json"), cwd.join(".claw").join("settings.local.json"),
r#"{ r#"{
"mcpServers": { "mcpServers": {
"remote-server": { "remote-server": {
@@ -1125,7 +1125,7 @@ mod tests {
fn rejects_invalid_mcp_server_shapes() { fn rejects_invalid_mcp_server_shapes() {
let root = temp_dir(); let root = temp_dir();
let cwd = root.join("project"); 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(&home).expect("home config dir");
fs::create_dir_all(&cwd).expect("project dir"); fs::create_dir_all(&cwd).expect("project dir");
fs::write( fs::write(

View File

@@ -24,11 +24,11 @@ pub use compact::{
get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult, get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
}; };
pub use config::{ pub use config::{
ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpClaudeAiProxyServerConfig, ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpManagedProxyServerConfig,
McpConfigCollection, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpConfigCollection, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig,
RuntimePermissionRuleConfig, ScopedMcpServerConfig, CLAUDE_CODE_SETTINGS_SCHEMA_NAME, RuntimePermissionRuleConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME,
}; };
pub use conversation::{ pub use conversation::{
ApiClient, ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, StaticToolExecutor, ApiClient, ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, StaticToolExecutor,
@@ -47,7 +47,7 @@ pub use mcp::{
scoped_mcp_config_hash, unwrap_ccr_proxy_url, scoped_mcp_config_hash, unwrap_ccr_proxy_url,
}; };
pub use mcp_client::{ pub use mcp_client::{
McpClaudeAiProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport, McpManagedProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport,
McpRemoteTransport, McpSdkTransport, McpStdioTransport, McpRemoteTransport, McpSdkTransport, McpStdioTransport,
}; };
pub use mcp_stdio::{ pub use mcp_stdio::{

View File

@@ -73,7 +73,7 @@ pub fn mcp_server_signature(config: &McpServerConfig) -> Option<String> {
Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url))) Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url)))
} }
McpServerConfig::Ws(config) => 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))) Some(format!("url:{}", unwrap_ccr_proxy_url(&config.url)))
} }
McpServerConfig::Sdk(_) => None, McpServerConfig::Sdk(_) => None,
@@ -110,7 +110,7 @@ pub fn scoped_mcp_config_hash(config: &ScopedMcpServerConfig) -> String {
ws.headers_helper.as_deref().unwrap_or("") ws.headers_helper.as_deref().unwrap_or("")
), ),
McpServerConfig::Sdk(sdk) => format!("sdk|{}", sdk.name), McpServerConfig::Sdk(sdk) => format!("sdk|{}", sdk.name),
McpServerConfig::ClaudeAiProxy(proxy) => { McpServerConfig::ManagedProxy(proxy) => {
format!("claudeai-proxy|{}|{}", proxy.url, proxy.id) format!("claudeai-proxy|{}|{}", proxy.url, proxy.id)
} }
}; };

View File

@@ -10,7 +10,7 @@ pub enum McpClientTransport {
Http(McpRemoteTransport), Http(McpRemoteTransport),
WebSocket(McpRemoteTransport), WebSocket(McpRemoteTransport),
Sdk(McpSdkTransport), Sdk(McpSdkTransport),
ClaudeAiProxy(McpClaudeAiProxyTransport), ManagedProxy(McpManagedProxyTransport),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -34,7 +34,7 @@ pub struct McpSdkTransport {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpClaudeAiProxyTransport { pub struct McpManagedProxyTransport {
pub url: String, pub url: String,
pub id: String, pub id: String,
} }
@@ -97,8 +97,8 @@ impl McpClientTransport {
McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport { McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport {
name: config.name.clone(), name: config.name.clone(),
}), }),
McpServerConfig::ClaudeAiProxy(config) => { McpServerConfig::ManagedProxy(config) => {
Self::ClaudeAiProxy(McpClaudeAiProxyTransport { Self::ManagedProxy(McpManagedProxyTransport {
url: config.url.clone(), url: config.url.clone(),
id: config.id.clone(), id: config.id.clone(),
}) })

View File

@@ -324,12 +324,12 @@ fn generate_random_token(bytes: usize) -> io::Result<String> {
} }
fn credentials_home_dir() -> io::Result<PathBuf> { 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)); return Ok(PathBuf::from(path));
} }
let home = std::env::var_os("HOME") let home = std::env::var_os("HOME")
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "HOME is not set"))?; .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>> { 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() { fn oauth_credentials_round_trip_and_clear_preserves_other_fields() {
let _guard = env_lock(); let _guard = env_lock();
let config_home = temp_config_home(); 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"); let path = credentials_path().expect("credentials path");
std::fs::create_dir_all(path.parent().expect("parent")).expect("create parent"); std::fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
std::fs::write(&path, "{\"other\":\"value\"}\n").expect("seed credentials"); std::fs::write(&path, "{\"other\":\"value\"}\n").expect("seed credentials");
@@ -567,7 +567,7 @@ mod tests {
assert!(cleared.contains("\"other\": \"value\"")); assert!(cleared.contains("\"other\": \"value\""));
assert!(!cleared.contains("\"oauth\"")); 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"); std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
} }

View File

@@ -201,10 +201,10 @@ fn discover_instruction_files(cwd: &Path) -> std::io::Result<Vec<ContextFile>> {
let mut files = Vec::new(); let mut files = Vec::new();
for dir in directories { for dir in directories {
for candidate in [ for candidate in [
dir.join("CLAUDE.md"), dir.join("INSTRUCTIONS.md"),
dir.join("CLAUDE.local.md"), dir.join("INSTRUCTIONS.local.md"),
dir.join(".claude").join("CLAUDE.md"), dir.join(".claw").join("INSTRUCTIONS.md"),
dir.join(".claude").join("instructions.md"), dir.join(".claw").join("instructions.md"),
] { ] {
push_context_file(&mut files, candidate)?; push_context_file(&mut files, candidate)?;
} }
@@ -301,7 +301,7 @@ fn render_project_context(project_context: &ProjectContext) -> String {
} }
fn render_instruction_files(files: &[ContextFile]) -> String { fn render_instruction_files(files: &[ContextFile]) -> String {
let mut sections = vec!["# Claude instructions".to_string()]; let mut sections = vec!["# Project instructions".to_string()];
let mut remaining_chars = MAX_TOTAL_INSTRUCTION_CHARS; let mut remaining_chars = MAX_TOTAL_INSTRUCTION_CHARS;
for file in files { for file in files {
if remaining_chars == 0 { if remaining_chars == 0 {
@@ -517,23 +517,23 @@ mod tests {
fn discovers_instruction_files_from_ancestor_chain() { fn discovers_instruction_files_from_ancestor_chain() {
let root = temp_dir(); let root = temp_dir();
let nested = root.join("apps").join("api"); 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 claude dir");
fs::write(root.join("CLAUDE.md"), "root instructions").expect("write root instructions"); fs::write(root.join("INSTRUCTIONS.md"), "root instructions").expect("write root instructions");
fs::write(root.join("CLAUDE.local.md"), "local instructions") fs::write(root.join("INSTRUCTIONS.local.md"), "local instructions")
.expect("write local instructions"); .expect("write local instructions");
fs::create_dir_all(root.join("apps")).expect("apps dir"); 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 claude dir");
fs::write(root.join("apps").join("CLAUDE.md"), "apps instructions") fs::write(root.join("apps").join("INSTRUCTIONS.md"), "apps instructions")
.expect("write apps instructions"); .expect("write apps instructions");
fs::write( fs::write(
root.join("apps").join(".claude").join("instructions.md"), root.join("apps").join(".claw").join("instructions.md"),
"apps dot claude instructions", "apps dot claude instructions",
) )
.expect("write 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("INSTRUCTIONS.md"), "nested rules")
.expect("write nested rules"); .expect("write nested rules");
fs::write( fs::write(
nested.join(".claude").join("instructions.md"), nested.join(".claw").join("instructions.md"),
"nested instructions", "nested instructions",
) )
.expect("write nested instructions"); .expect("write nested instructions");
@@ -552,7 +552,7 @@ mod tests {
"local instructions", "local instructions",
"apps instructions", "apps instructions",
"apps dot claude instructions", "apps dot claude instructions",
"nested rules",
"nested instructions" "nested instructions"
] ]
); );
@@ -564,8 +564,8 @@ mod tests {
let root = temp_dir(); let root = temp_dir();
let nested = root.join("apps").join("api"); let nested = root.join("apps").join("api");
fs::create_dir_all(&nested).expect("nested dir"); fs::create_dir_all(&nested).expect("nested dir");
fs::write(root.join("CLAUDE.md"), "same rules\n\n").expect("write root"); fs::write(root.join("INSTRUCTIONS.md"), "same rules\n\n").expect("write root");
fs::write(nested.join("CLAUDE.md"), "same rules\n").expect("write nested"); fs::write(nested.join("INSTRUCTIONS.md"), "same rules\n").expect("write nested");
let context = ProjectContext::discover(&nested, "2026-03-31").expect("context should load"); let context = ProjectContext::discover(&nested, "2026-03-31").expect("context should load");
assert_eq!(context.instruction_files.len(), 1); assert_eq!(context.instruction_files.len(), 1);
@@ -593,8 +593,8 @@ mod tests {
#[test] #[test]
fn displays_context_paths_compactly() { fn displays_context_paths_compactly() {
assert_eq!( assert_eq!(
display_context_path(Path::new("/tmp/project/.claude/CLAUDE.md")), display_context_path(Path::new("/tmp/project/.claw/INSTRUCTIONS.md")),
"CLAUDE.md" "INSTRUCTIONS.md"
); );
} }
@@ -607,7 +607,7 @@ mod tests {
.current_dir(&root) .current_dir(&root)
.status() .status()
.expect("git init should run"); .expect("git init should run");
fs::write(root.join("CLAUDE.md"), "rules").expect("write instructions"); fs::write(root.join("INSTRUCTIONS.md"), "rules").expect("write instructions");
fs::write(root.join("tracked.txt"), "hello").expect("write tracked file"); fs::write(root.join("tracked.txt"), "hello").expect("write tracked file");
let context = let context =
@@ -615,7 +615,7 @@ mod tests {
let status = context.git_status.expect("git status should be present"); let status = context.git_status.expect("git status should be present");
assert!(status.contains("## No commits yet on") || status.contains("## ")); assert!(status.contains("## No commits yet on") || status.contains("## "));
assert!(status.contains("?? CLAUDE.md")); assert!(status.contains("?? INSTRUCTIONS.md"));
assert!(status.contains("?? tracked.txt")); assert!(status.contains("?? tracked.txt"));
assert!(context.git_diff.is_none()); assert!(context.git_diff.is_none());
@@ -667,10 +667,10 @@ mod tests {
#[test] #[test]
fn load_system_prompt_reads_claude_files_and_config() { fn load_system_prompt_reads_claude_files_and_config() {
let root = temp_dir(); let root = temp_dir();
fs::create_dir_all(root.join(".claude")).expect("claude dir"); fs::create_dir_all(root.join(".claw")).expect("claude dir");
fs::write(root.join("CLAUDE.md"), "Project rules").expect("write instructions"); fs::write(root.join("INSTRUCTIONS.md"), "Project rules").expect("write instructions");
fs::write( fs::write(
root.join(".claude").join("settings.json"), root.join(".claw").join("settings.json"),
r#"{"permissionMode":"acceptEdits"}"#, r#"{"permissionMode":"acceptEdits"}"#,
) )
.expect("write settings"); .expect("write settings");
@@ -678,9 +678,9 @@ mod tests {
let _guard = env_lock(); let _guard = env_lock();
let previous = std::env::current_dir().expect("cwd"); let previous = std::env::current_dir().expect("cwd");
let original_home = std::env::var("HOME").ok(); let original_home = std::env::var("HOME").ok();
let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok(); let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
std::env::set_var("HOME", &root); 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"); std::env::set_current_dir(&root).expect("change cwd");
let prompt = super::load_system_prompt(&root, "2026-03-31", "linux", "6.8") let prompt = super::load_system_prompt(&root, "2026-03-31", "linux", "6.8")
.expect("system prompt should load") .expect("system prompt should load")
@@ -695,10 +695,10 @@ mod tests {
} else { } else {
std::env::remove_var("HOME"); std::env::remove_var("HOME");
} }
if let Some(value) = original_claude_home { if let Some(value) = original_config_home {
std::env::set_var("CLAUDE_CONFIG_HOME", value); std::env::set_var("CLAW_CONFIG_HOME", value);
} else { } else {
std::env::remove_var("CLAUDE_CONFIG_HOME"); std::env::remove_var("CLAW_CONFIG_HOME");
} }
assert!(prompt.contains("Project rules")); assert!(prompt.contains("Project rules"));
@@ -709,10 +709,10 @@ mod tests {
#[test] #[test]
fn renders_claude_code_style_sections_with_project_context() { fn renders_claude_code_style_sections_with_project_context() {
let root = temp_dir(); let root = temp_dir();
fs::create_dir_all(root.join(".claude")).expect("claude dir"); fs::create_dir_all(root.join(".claw")).expect("claude dir");
fs::write(root.join("CLAUDE.md"), "Project rules").expect("write CLAUDE.md"); fs::write(root.join("INSTRUCTIONS.md"), "Project rules").expect("write INSTRUCTIONS.md");
fs::write( fs::write(
root.join(".claude").join("settings.json"), root.join(".claw").join("settings.json"),
r#"{"permissionMode":"acceptEdits"}"#, r#"{"permissionMode":"acceptEdits"}"#,
) )
.expect("write settings"); .expect("write settings");
@@ -731,7 +731,7 @@ mod tests {
assert!(prompt.contains("# System")); assert!(prompt.contains("# System"));
assert!(prompt.contains("# Project context")); assert!(prompt.contains("# Project context"));
assert!(prompt.contains("# Claude instructions")); assert!(prompt.contains("# Project instructions"));
assert!(prompt.contains("Project rules")); assert!(prompt.contains("Project rules"));
assert!(prompt.contains("permissionMode")); assert!(prompt.contains("permissionMode"));
assert!(prompt.contains(SYSTEM_PROMPT_DYNAMIC_BOUNDARY)); assert!(prompt.contains(SYSTEM_PROMPT_DYNAMIC_BOUNDARY));
@@ -751,9 +751,9 @@ mod tests {
fn discovers_dot_claude_instructions_markdown() { fn discovers_dot_claude_instructions_markdown() {
let root = temp_dir(); let root = temp_dir();
let nested = root.join("apps").join("api"); 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 claude dir");
fs::write( fs::write(
nested.join(".claude").join("instructions.md"), nested.join(".claw").join("instructions.md"),
"instruction markdown", "instruction markdown",
) )
.expect("write instructions.md"); .expect("write instructions.md");
@@ -762,7 +762,10 @@ mod tests {
assert!(context assert!(context
.instruction_files .instruction_files
.iter() .iter()
.any(|file| file.path.ends_with(".claude/instructions.md"))); .any(|file| {
let p = file.path.to_string_lossy().to_lowercase();
p.ends_with(".claw/instructions.md")
}));
assert!( assert!(
render_instruction_files(&context.instruction_files).contains("instruction markdown") render_instruction_files(&context.instruction_files).contains("instruction markdown")
); );
@@ -773,10 +776,10 @@ mod tests {
#[test] #[test]
fn renders_instruction_file_metadata() { fn renders_instruction_file_metadata() {
let rendered = render_instruction_files(&[ContextFile { let rendered = render_instruction_files(&[ContextFile {
path: PathBuf::from("/tmp/project/CLAUDE.md"), path: PathBuf::from("/tmp/project/INSTRUCTIONS.md"),
content: "Project rules".to_string(), content: "Project rules".to_string(),
}]); }]);
assert!(rendered.contains("# Claude instructions")); assert!(rendered.contains("# Project instructions"));
assert!(rendered.contains("scope: /tmp/project")); assert!(rendered.contains("scope: /tmp/project"));
assert!(rendered.contains("Project rules")); assert!(rendered.contains("Project rules"));
} }

View File

@@ -4,7 +4,7 @@ use clap::{Parser, Subcommand, ValueEnum};
#[derive(Debug, Clone, Parser, PartialEq, Eq)] #[derive(Debug, Clone, Parser, PartialEq, Eq)]
#[command( #[command(
name = "rusty-claude-cli", name = "claw-cli",
version, version,
about = "Rust Claude CLI prototype" about = "Rust Claude CLI prototype"
)] )]
@@ -62,7 +62,7 @@ mod tests {
#[test] #[test]
fn parses_requested_flags() { fn parses_requested_flags() {
let cli = Cli::parse_from([ let cli = Cli::parse_from([
"rusty-claude-cli", "claw-cli",
"--model", "--model",
"claude-3-5-haiku", "claude-3-5-haiku",
"--permission-mode", "--permission-mode",
@@ -93,16 +93,16 @@ mod tests {
#[test] #[test]
fn parses_login_and_logout_commands() { fn parses_login_and_logout_commands() {
let login = Cli::parse_from(["rusty-claude-cli", "login"]); let login = Cli::parse_from(["claw-cli", "login"]);
assert_eq!(login.command, Some(Command::Login)); assert_eq!(login.command, Some(Command::Login));
let logout = Cli::parse_from(["rusty-claude-cli", "logout"]); let logout = Cli::parse_from(["claw-cli", "logout"]);
assert_eq!(logout.command, Some(Command::Logout)); assert_eq!(logout.command, Some(Command::Logout));
} }
#[test] #[test]
fn defaults_to_danger_full_access_permission_mode() { fn defaults_to_danger_full_access_permission_mode() {
let cli = Cli::parse_from(["rusty-claude-cli"]); let cli = Cli::parse_from(["claw-cli"]);
assert_eq!(cli.permission_mode, PermissionMode::DangerFullAccess); assert_eq!(cli.permission_mode, PermissionMode::DangerFullAccess);
} }
} }

View File

@@ -1,15 +1,15 @@
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const STARTER_CLAUDE_JSON: &str = concat!( const STARTER_CLAW_JSON: &str = concat!(
"{\n", "{\n",
" \"permissions\": {\n", " \"permissions\": {\n",
" \"defaultMode\": \"dontAsk\"\n", " \"defaultMode\": \"dontAsk\"\n",
" }\n", " }\n",
"}\n", "}\n",
); );
const GITIGNORE_COMMENT: &str = "# Claude Code local artifacts"; const GITIGNORE_COMMENT: &str = "# Claw Code local artifacts";
const GITIGNORE_ENTRIES: [&str; 2] = [".claude/settings.local.json", ".claude/sessions/"]; const GITIGNORE_ENTRIES: [&str; 2] = [".claw/settings.local.json", ".claw/sessions/"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InitStatus { pub(crate) enum InitStatus {
@@ -80,16 +80,16 @@ struct RepoDetection {
pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::error::Error>> { pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::error::Error>> {
let mut artifacts = Vec::new(); let mut artifacts = Vec::new();
let claude_dir = cwd.join(".claude"); let claw_dir = cwd.join(".claw");
artifacts.push(InitArtifact { artifacts.push(InitArtifact {
name: ".claude/", name: ".claw/",
status: ensure_dir(&claude_dir)?, status: ensure_dir(&claw_dir)?,
}); });
let claude_json = cwd.join(".claude.json"); let claw_json = cwd.join(".claw.json");
artifacts.push(InitArtifact { artifacts.push(InitArtifact {
name: ".claude.json", name: ".claw.json",
status: write_file_if_missing(&claude_json, STARTER_CLAUDE_JSON)?, status: write_file_if_missing(&claw_json, STARTER_CLAW_JSON)?,
}); });
let gitignore = cwd.join(".gitignore"); let gitignore = cwd.join(".gitignore");
@@ -98,11 +98,11 @@ pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::err
status: ensure_gitignore_entries(&gitignore)?, status: ensure_gitignore_entries(&gitignore)?,
}); });
let claude_md = cwd.join("CLAUDE.md"); let instructions_md = cwd.join("INSTRUCTIONS.md");
let content = render_init_claude_md(cwd); let content = render_init_instructions_md(cwd);
artifacts.push(InitArtifact { artifacts.push(InitArtifact {
name: "CLAUDE.md", name: "INSTRUCTIONS.md",
status: write_file_if_missing(&claude_md, &content)?, status: write_file_if_missing(&instructions_md, &content)?,
}); });
Ok(InitReport { Ok(InitReport {
@@ -159,12 +159,13 @@ fn ensure_gitignore_entries(path: &Path) -> Result<InitStatus, std::io::Error> {
Ok(InitStatus::Updated) Ok(InitStatus::Updated)
} }
pub(crate) fn render_init_claude_md(cwd: &Path) -> String { pub(crate) fn render_init_instructions_md(cwd: &Path) -> String {
let detection = detect_repo(cwd); let detection = detect_repo(cwd);
let mut lines = vec![ let mut lines = vec![
"# CLAUDE.md".to_string(), "# INSTRUCTIONS.md".to_string(),
String::new(), String::new(),
"This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.".to_string(), "This file provides guidance to Claw Code when working with code in this repository."
.to_string(),
String::new(), String::new(),
]; ];
@@ -209,8 +210,8 @@ pub(crate) fn render_init_claude_md(cwd: &Path) -> String {
lines.push("## Working agreement".to_string()); lines.push("## Working agreement".to_string());
lines.push("- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.".to_string()); lines.push("- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.".to_string());
lines.push("- Keep shared defaults in `.claude.json`; reserve `.claude/settings.local.json` for machine-local overrides.".to_string()); lines.push("- Keep shared defaults in `.claw.json`; reserve `.claw/settings.local.json` for machine-local overrides.".to_string());
lines.push("- Do not overwrite existing `CLAUDE.md` content automatically; update it intentionally when repo workflows change.".to_string()); lines.push("- Do not overwrite existing `INSTRUCTIONS.md` content automatically; update it intentionally when repo workflows change.".to_string());
lines.push(String::new()); lines.push(String::new());
lines.join("\n") lines.join("\n")
@@ -333,7 +334,7 @@ fn framework_notes(detection: &RepoDetection) -> Vec<String> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{initialize_repo, render_init_claude_md}; use super::{initialize_repo, render_init_instructions_md};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@@ -343,7 +344,7 @@ mod tests {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("time should be after epoch") .expect("time should be after epoch")
.as_nanos(); .as_nanos();
std::env::temp_dir().join(format!("rusty-claude-init-{nanos}")) std::env::temp_dir().join(format!("rusty-claw-init-{nanos}"))
} }
#[test] #[test]
@@ -354,15 +355,15 @@ mod tests {
let report = initialize_repo(&root).expect("init should succeed"); let report = initialize_repo(&root).expect("init should succeed");
let rendered = report.render(); let rendered = report.render();
assert!(rendered.contains(".claude/ created")); assert!(rendered.contains(".claw/ created"));
assert!(rendered.contains(".claude.json created")); assert!(rendered.contains(".claw.json created"));
assert!(rendered.contains(".gitignore created")); assert!(rendered.contains(".gitignore created"));
assert!(rendered.contains("CLAUDE.md created")); assert!(rendered.contains("INSTRUCTIONS.md created"));
assert!(root.join(".claude").is_dir()); assert!(root.join(".claw").is_dir());
assert!(root.join(".claude.json").is_file()); assert!(root.join(".claw.json").is_file());
assert!(root.join("CLAUDE.md").is_file()); assert!(root.join("INSTRUCTIONS.md").is_file());
assert_eq!( assert_eq!(
fs::read_to_string(root.join(".claude.json")).expect("read claude json"), fs::read_to_string(root.join(".claw.json")).expect("read claw json"),
concat!( concat!(
"{\n", "{\n",
" \"permissions\": {\n", " \"permissions\": {\n",
@@ -372,11 +373,12 @@ mod tests {
) )
); );
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
assert!(gitignore.contains(".claude/settings.local.json")); assert!(gitignore.contains(".claw/settings.local.json"));
assert!(gitignore.contains(".claude/sessions/")); assert!(gitignore.contains(".claw/sessions/"));
let claude_md = fs::read_to_string(root.join("CLAUDE.md")).expect("read claude md"); let instructions_md =
assert!(claude_md.contains("Languages: Rust.")); fs::read_to_string(root.join("INSTRUCTIONS.md")).expect("read instructions md");
assert!(claude_md.contains("cargo clippy --workspace --all-targets -- -D warnings")); assert!(instructions_md.contains("Languages: Rust."));
assert!(instructions_md.contains("cargo clippy --workspace --all-targets -- -D warnings"));
fs::remove_dir_all(root).expect("cleanup temp dir"); fs::remove_dir_all(root).expect("cleanup temp dir");
} }
@@ -385,27 +387,28 @@ mod tests {
fn initialize_repo_is_idempotent_and_preserves_existing_files() { fn initialize_repo_is_idempotent_and_preserves_existing_files() {
let root = temp_dir(); let root = temp_dir();
fs::create_dir_all(&root).expect("create root"); fs::create_dir_all(&root).expect("create root");
fs::write(root.join("CLAUDE.md"), "custom guidance\n").expect("write existing claude md"); fs::write(root.join("INSTRUCTIONS.md"), "custom guidance\n")
fs::write(root.join(".gitignore"), ".claude/settings.local.json\n") .expect("write existing instructions md");
.expect("write gitignore"); fs::write(root.join(".gitignore"), ".claw/settings.local.json\n").expect("write gitignore");
let first = initialize_repo(&root).expect("first init should succeed"); let first = initialize_repo(&root).expect("first init should succeed");
assert!(first assert!(first
.render() .render()
.contains("CLAUDE.md skipped (already exists)")); .contains("INSTRUCTIONS.md skipped (already exists)"));
let second = initialize_repo(&root).expect("second init should succeed"); let second = initialize_repo(&root).expect("second init should succeed");
let second_rendered = second.render(); let second_rendered = second.render();
assert!(second_rendered.contains(".claude/ skipped (already exists)")); assert!(second_rendered.contains(".claw/ skipped (already exists)"));
assert!(second_rendered.contains(".claude.json skipped (already exists)")); assert!(second_rendered.contains(".claw.json skipped (already exists)"));
assert!(second_rendered.contains(".gitignore skipped (already exists)")); assert!(second_rendered.contains(".gitignore skipped (already exists)"));
assert!(second_rendered.contains("CLAUDE.md skipped (already exists)")); assert!(second_rendered.contains("INSTRUCTIONS.md skipped (already exists)"));
assert_eq!( assert_eq!(
fs::read_to_string(root.join("CLAUDE.md")).expect("read existing claude md"), fs::read_to_string(root.join("INSTRUCTIONS.md"))
.expect("read existing instructions md"),
"custom guidance\n" "custom guidance\n"
); );
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
assert_eq!(gitignore.matches(".claude/settings.local.json").count(), 1); assert_eq!(gitignore.matches(".claw/settings.local.json").count(), 1);
assert_eq!(gitignore.matches(".claude/sessions/").count(), 1); assert_eq!(gitignore.matches(".claw/sessions/").count(), 1);
fs::remove_dir_all(root).expect("cleanup temp dir"); fs::remove_dir_all(root).expect("cleanup temp dir");
} }
@@ -422,7 +425,7 @@ mod tests {
) )
.expect("write package json"); .expect("write package json");
let rendered = render_init_claude_md(Path::new(&root)); let rendered = render_init_instructions_md(Path::new(&root));
assert!(rendered.contains("Languages: Python, TypeScript.")); assert!(rendered.contains("Languages: Python, TypeScript."));
assert!(rendered.contains("Frameworks/tooling markers: Next.js, React.")); assert!(rendered.contains("Frameworks/tooling markers: Next.js, React."));
assert!(rendered.contains("pyproject.toml")); assert!(rendered.contains("pyproject.toml"));

View File

@@ -14,7 +14,7 @@ use std::thread::{self, JoinHandle};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use api::{ use api::{
resolve_startup_auth_source, AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock, resolve_startup_auth_source, ApiClient as ApiHttpClient, AuthSource, ContentBlockDelta, InputContentBlock,
InputMessage, MessageRequest, MessageResponse, OutputContentBlock, InputMessage, MessageRequest, MessageResponse, OutputContentBlock,
StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock, StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
}; };
@@ -444,7 +444,7 @@ fn dump_manifests() {
} }
fn print_bootstrap_plan() { fn print_bootstrap_plan() {
for phase in runtime::BootstrapPlan::claude_code_default().phases() { for phase in runtime::BootstrapPlan::default_bootstrap().phases() {
println!("- {phase:?}"); println!("- {phase:?}");
} }
} }
@@ -501,7 +501,7 @@ fn run_login() -> Result<(), Box<dyn std::error::Error>> {
return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into()); 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 = let exchange_request =
OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri); OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri);
let runtime = tokio::runtime::Runtime::new()?; let runtime = tokio::runtime::Runtime::new()?;
@@ -982,7 +982,7 @@ struct LiveCli {
allowed_tools: Option<AllowedToolSet>, allowed_tools: Option<AllowedToolSet>,
permission_mode: PermissionMode, permission_mode: PermissionMode,
system_prompt: Vec<String>, system_prompt: Vec<String>,
runtime: ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, runtime: ConversationRuntime<ClawRuntimeClient, CliToolExecutor>,
session: SessionHandle, session: SessionHandle,
} }
@@ -1101,7 +1101,7 @@ impl LiveCli {
emit_output: bool, emit_output: bool,
) -> Result< ) -> Result<
( (
ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, ConversationRuntime<ClawRuntimeClient, CliToolExecutor>,
HookAbortMonitor, HookAbortMonitor,
), ),
Box<dyn std::error::Error>, Box<dyn std::error::Error>,
@@ -1531,7 +1531,7 @@ impl LiveCli {
fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> { fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
let cwd = env::current_dir()?; let cwd = env::current_dir()?;
let path = cwd.join(".claude").join("sessions"); let path = cwd.join(".claw").join("sessions");
fs::create_dir_all(&path)?; fs::create_dir_all(&path)?;
Ok(path) Ok(path)
} }
@@ -1999,12 +1999,12 @@ fn build_runtime(
emit_output: bool, emit_output: bool,
allowed_tools: Option<AllowedToolSet>, allowed_tools: Option<AllowedToolSet>,
permission_mode: PermissionMode, permission_mode: PermissionMode,
) -> Result<ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>> ) -> Result<ConversationRuntime<ClawRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>>
{ {
let feature_config = build_runtime_feature_config()?; let feature_config = build_runtime_feature_config()?;
let mut runtime = ConversationRuntime::new_with_features( let mut runtime = ConversationRuntime::new_with_features(
session, session,
AnthropicRuntimeClient::new(model, enable_tools, emit_output, allowed_tools.clone())?, ClawRuntimeClient::new(model, enable_tools, emit_output, allowed_tools.clone())?,
CliToolExecutor::new(allowed_tools, emit_output), CliToolExecutor::new(allowed_tools, emit_output),
permission_policy(permission_mode, &feature_config), permission_policy(permission_mode, &feature_config),
system_prompt, system_prompt,
@@ -2098,16 +2098,16 @@ impl runtime::PermissionPrompter for CliPermissionPrompter {
} }
} }
struct AnthropicRuntimeClient { struct ClawRuntimeClient {
runtime: tokio::runtime::Runtime, runtime: tokio::runtime::Runtime,
client: AnthropicClient, client: ApiHttpClient,
model: String, model: String,
enable_tools: bool, enable_tools: bool,
emit_output: bool, emit_output: bool,
allowed_tools: Option<AllowedToolSet>, allowed_tools: Option<AllowedToolSet>,
} }
impl AnthropicRuntimeClient { impl ClawRuntimeClient {
fn new( fn new(
model: String, model: String,
enable_tools: bool, enable_tools: bool,
@@ -2116,7 +2116,7 @@ impl AnthropicRuntimeClient {
) -> Result<Self, Box<dyn std::error::Error>> { ) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self { Ok(Self {
runtime: tokio::runtime::Runtime::new()?, 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()), .with_base_url(api::read_base_url()),
model, model,
enable_tools, enable_tools,
@@ -2136,7 +2136,7 @@ fn resolve_cli_auth_source() -> Result<AuthSource, Box<dyn std::error::Error>> {
})?) })?)
} }
impl ApiClient for AnthropicRuntimeClient { impl ApiClient for ClawRuntimeClient {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> { fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> {
let message_request = MessageRequest { let message_request = MessageRequest {
@@ -3448,8 +3448,8 @@ mod tests {
#[test] #[test]
fn init_template_mentions_detected_rust_workspace() { fn init_template_mentions_detected_rust_workspace() {
let rendered = crate::init::render_init_claude_md(std::path::Path::new(".")); let rendered = crate::init::render_init_instructions_md(std::path::Path::new("."));
assert!(rendered.contains("# CLAUDE.md")); assert!(rendered.contains("# INSTRUCTIONS.md"));
assert!(rendered.contains("cargo clippy --workspace --all-targets -- -D warnings")); assert!(rendered.contains("cargo clippy --workspace --all-targets -- -D warnings"));
} }

View File

@@ -4,7 +4,7 @@ use std::process::Command;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use api::{ use api::{
read_base_url, AnthropicClient, ContentBlockDelta, InputContentBlock, InputMessage, read_base_url, ApiClient as ApiHttpClient, ContentBlockDelta, InputContentBlock, InputMessage,
MessageRequest, MessageResponse, OutputContentBlock, StreamEvent as ApiStreamEvent, ToolChoice, MessageRequest, MessageResponse, OutputContentBlock, StreamEvent as ApiStreamEvent, ToolChoice,
ToolDefinition, ToolResultContentBlock, ToolDefinition, ToolResultContentBlock,
}; };
@@ -1308,6 +1308,12 @@ fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
if let Ok(codex_home) = std::env::var("CODEX_HOME") { if let Ok(codex_home) = std::env::var("CODEX_HOME") {
candidates.push(std::path::PathBuf::from(codex_home).join("skills")); 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(".config").join("opencode").join("skills"));
candidates.push(home.join(".codex").join("skills"));
}
candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills")); candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills"));
for root in candidates { for root in candidates {
@@ -1459,14 +1465,14 @@ fn run_agent_job(job: &AgentJob) -> Result<(), String> {
fn build_agent_runtime( fn build_agent_runtime(
job: &AgentJob, job: &AgentJob,
) -> Result<ConversationRuntime<AnthropicRuntimeClient, SubagentToolExecutor>, String> { ) -> Result<ConversationRuntime<RuntimeApiClient, SubagentToolExecutor>, String> {
let model = job let model = job
.manifest .manifest
.model .model
.clone() .clone()
.unwrap_or_else(|| DEFAULT_AGENT_MODEL.to_string()); .unwrap_or_else(|| DEFAULT_AGENT_MODEL.to_string());
let allowed_tools = job.allowed_tools.clone(); let allowed_tools = job.allowed_tools.clone();
let api_client = AnthropicRuntimeClient::new(model, allowed_tools.clone())?; let api_client = RuntimeApiClient::new(model, allowed_tools.clone())?;
let tool_executor = SubagentToolExecutor::new(allowed_tools); let tool_executor = SubagentToolExecutor::new(allowed_tools);
Ok(ConversationRuntime::new( Ok(ConversationRuntime::new(
Session::new(), Session::new(),
@@ -1537,7 +1543,7 @@ fn allowed_tools_for_subagent(subagent_type: &str) -> BTreeSet<String> {
"SendUserMessage", "SendUserMessage",
"PowerShell", "PowerShell",
], ],
"claude-code-guide" => vec![ "claw-guide" => vec![
"read_file", "read_file",
"glob_search", "glob_search",
"grep_search", "grep_search",
@@ -1635,16 +1641,16 @@ fn format_agent_terminal_output(status: &str, result: Option<&str>, error: Optio
sections.join("") sections.join("")
} }
struct AnthropicRuntimeClient { struct RuntimeApiClient {
runtime: tokio::runtime::Runtime, runtime: tokio::runtime::Runtime,
client: AnthropicClient, client: ApiHttpClient,
model: String, model: String,
allowed_tools: BTreeSet<String>, allowed_tools: BTreeSet<String>,
} }
impl AnthropicRuntimeClient { impl RuntimeApiClient {
fn new(model: String, allowed_tools: BTreeSet<String>) -> Result<Self, String> { fn new(model: String, allowed_tools: BTreeSet<String>) -> Result<Self, String> {
let client = AnthropicClient::from_env() let client = ApiHttpClient::from_env()
.map_err(|error| error.to_string())? .map_err(|error| error.to_string())?
.with_base_url(read_base_url()); .with_base_url(read_base_url());
Ok(Self { Ok(Self {
@@ -1656,7 +1662,7 @@ impl AnthropicRuntimeClient {
} }
} }
impl ApiClient for AnthropicRuntimeClient { impl ApiClient for RuntimeApiClient {
fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> { fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> {
let tools = tool_specs_for_allowed_tools(Some(&self.allowed_tools)) let tools = tool_specs_for_allowed_tools(Some(&self.allowed_tools))
.into_iter() .into_iter()
@@ -2087,7 +2093,7 @@ fn normalize_subagent_type(subagent_type: Option<&str>) -> String {
"verification" | "verificationagent" | "verify" | "verifier" => { "verification" | "verificationagent" | "verify" | "verifier" => {
String::from("Verification") String::from("Verification")
} }
"claudecodeguide" | "claudecodeguideagent" | "guide" => String::from("claude-code-guide"), "clawguide" | "clawguideagent" | "guide" => String::from("claw-guide"),
"statusline" | "statuslinesetup" => String::from("statusline-setup"), "statusline" | "statuslinesetup" => String::from("statusline-setup"),
_ => trimmed.to_string(), _ => trimmed.to_string(),
} }
@@ -2587,16 +2593,16 @@ fn config_file_for_scope(scope: ConfigScope) -> Result<PathBuf, String> {
let cwd = std::env::current_dir().map_err(|error| error.to_string())?; let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
Ok(match scope { Ok(match scope {
ConfigScope::Global => config_home_dir()?.join("settings.json"), 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> { 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)); return Ok(PathBuf::from(path));
} }
let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?; 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> { fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> {
@@ -3982,19 +3988,19 @@ mod tests {
)); ));
let home = root.join("home"); let home = root.join("home");
let cwd = root.join("cwd"); let cwd = root.join("cwd");
std::fs::create_dir_all(home.join(".claude")).expect("home dir"); std::fs::create_dir_all(home.join(".claw")).expect("home dir");
std::fs::create_dir_all(cwd.join(".claude")).expect("cwd dir"); std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
std::fs::write( std::fs::write(
home.join(".claude").join("settings.json"), home.join(".claw").join("settings.json"),
r#"{"verbose":false}"#, r#"{"verbose":false}"#,
) )
.expect("write global settings"); .expect("write global settings");
let original_home = std::env::var("HOME").ok(); let original_home = std::env::var("HOME").ok();
let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok(); let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
let original_dir = std::env::current_dir().expect("cwd"); let original_dir = std::env::current_dir().expect("cwd");
std::env::set_var("HOME", &home); 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"); std::env::set_current_dir(&cwd).expect("set cwd");
let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config"); let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config");
@@ -4027,9 +4033,9 @@ mod tests {
Some(value) => std::env::set_var("HOME", value), Some(value) => std::env::set_var("HOME", value),
None => std::env::remove_var("HOME"), None => std::env::remove_var("HOME"),
} }
match original_claude_home { match original_config_home {
Some(value) => std::env::set_var("CLAUDE_CONFIG_HOME", value), Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
None => std::env::remove_var("CLAUDE_CONFIG_HOME"), None => std::env::remove_var("CLAW_CONFIG_HOME"),
} }
let _ = std::fs::remove_dir_all(root); let _ = std::fs::remove_dir_all(root);
} }