diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 698d3ff..5e6aa0e 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -44,7 +44,8 @@ use runtime::{ ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource, ContentBlock, ConversationMessage, ConversationRuntime, MessageRole, OAuthAuthorizationRequest, OAuthConfig, OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, PromptCacheEvent, - RuntimeError, Session, TokenUsage, ToolError, ToolExecutor, UsageTracker, + ResolvedPermissionMode, RuntimeError, Session, TokenUsage, ToolError, ToolExecutor, + UsageTracker, }; use serde_json::json; use tools::GlobalToolRegistry; @@ -618,12 +619,32 @@ fn permission_mode_from_label(mode: &str) -> PermissionMode { } } +fn permission_mode_from_resolved(mode: ResolvedPermissionMode) -> PermissionMode { + match mode { + ResolvedPermissionMode::ReadOnly => PermissionMode::ReadOnly, + ResolvedPermissionMode::WorkspaceWrite => PermissionMode::WorkspaceWrite, + ResolvedPermissionMode::DangerFullAccess => PermissionMode::DangerFullAccess, + } +} + fn default_permission_mode() -> PermissionMode { env::var("RUSTY_CLAUDE_PERMISSION_MODE") .ok() .as_deref() .and_then(normalize_permission_mode) - .map_or(PermissionMode::DangerFullAccess, permission_mode_from_label) + .map(permission_mode_from_label) + .or_else(config_permission_mode_for_current_dir) + .unwrap_or(PermissionMode::DangerFullAccess) +} + +fn config_permission_mode_for_current_dir() -> Option { + let cwd = env::current_dir().ok()?; + let loader = ConfigLoader::default_for(&cwd); + loader + .load() + .ok()? + .permission_mode() + .map(permission_mode_from_resolved) } fn filter_tool_specs( @@ -5190,6 +5211,74 @@ mod tests { ); } + #[test] + fn default_permission_mode_uses_project_config_when_env_is_unset() { + let _guard = env_lock(); + let root = temp_dir(); + let cwd = root.join("project"); + let config_home = root.join("config-home"); + std::fs::create_dir_all(cwd.join(".claw")).expect("project config dir should exist"); + std::fs::create_dir_all(&config_home).expect("config home should exist"); + std::fs::write( + cwd.join(".claw").join("settings.json"), + r#"{"permissionMode":"acceptEdits"}"#, + ) + .expect("project config should write"); + + let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok(); + let original_permission_mode = std::env::var("RUSTY_CLAUDE_PERMISSION_MODE").ok(); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); + std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE"); + + let resolved = with_current_dir(&cwd, super::default_permission_mode); + + match original_config_home { + Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value), + None => std::env::remove_var("CLAW_CONFIG_HOME"), + } + match original_permission_mode { + Some(value) => std::env::set_var("RUSTY_CLAUDE_PERMISSION_MODE", value), + None => std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE"), + } + std::fs::remove_dir_all(root).expect("temp config root should clean up"); + + assert_eq!(resolved, PermissionMode::WorkspaceWrite); + } + + #[test] + fn env_permission_mode_overrides_project_config_default() { + let _guard = env_lock(); + let root = temp_dir(); + let cwd = root.join("project"); + let config_home = root.join("config-home"); + std::fs::create_dir_all(cwd.join(".claw")).expect("project config dir should exist"); + std::fs::create_dir_all(&config_home).expect("config home should exist"); + std::fs::write( + cwd.join(".claw").join("settings.json"), + r#"{"permissionMode":"acceptEdits"}"#, + ) + .expect("project config should write"); + + let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok(); + let original_permission_mode = std::env::var("RUSTY_CLAUDE_PERMISSION_MODE").ok(); + std::env::set_var("CLAW_CONFIG_HOME", &config_home); + std::env::set_var("RUSTY_CLAUDE_PERMISSION_MODE", "read-only"); + + let resolved = with_current_dir(&cwd, super::default_permission_mode); + + match original_config_home { + Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value), + None => std::env::remove_var("CLAW_CONFIG_HOME"), + } + match original_permission_mode { + Some(value) => std::env::set_var("RUSTY_CLAUDE_PERMISSION_MODE", value), + None => std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE"), + } + std::fs::remove_dir_all(root).expect("temp config root should clean up"); + + assert_eq!(resolved, PermissionMode::ReadOnly); + } + #[test] fn parses_prompt_subcommand() { let args = vec![