diff --git a/src/channels/email_channel.rs b/src/channels/email_channel.rs index 5e4034b..68a5f03 100644 --- a/src/channels/email_channel.rs +++ b/src/channels/email_channel.rs @@ -8,8 +8,8 @@ #![allow(clippy::too_many_lines)] #![allow(clippy::unnecessary_map_or)] -use async_trait::async_trait; use anyhow::{anyhow, Result}; +use async_trait::async_trait; use lettre::transport::smtp::authentication::Credentials; use lettre::{Message, SmtpTransport, Transport}; use mail_parser::{MessageParser, MimeHeaders}; @@ -59,11 +59,21 @@ pub struct EmailConfig { pub allowed_senders: Vec, } -fn default_imap_port() -> u16 { 993 } -fn default_smtp_port() -> u16 { 587 } -fn default_imap_folder() -> String { "INBOX".into() } -fn default_poll_interval() -> u64 { 60 } -fn default_true() -> bool { true } +fn default_imap_port() -> u16 { + 993 +} +fn default_smtp_port() -> u16 { + 587 +} +fn default_imap_folder() -> String { + "INBOX".into() +} +fn default_poll_interval() -> u64 { + 60 +} +fn default_true() -> bool { + true +} impl Default for EmailConfig { fn default() -> Self { @@ -137,7 +147,8 @@ impl EmailChannel { /// Extract the sender address from a parsed email fn extract_sender(parsed: &mail_parser::Message) -> String { - parsed.from() + parsed + .from() .and_then(|addr| addr.first()) .and_then(|a| a.address()) .map(|s| s.to_string()) @@ -185,32 +196,31 @@ impl EmailChannel { .with_root_certificates(root_store) .with_no_client_auth(), ); - let server_name: ServerName<'_> = - ServerName::try_from(config.imap_host.clone())?; - let conn = - rustls::ClientConnection::new(tls_config, server_name)?; + let server_name: ServerName<'_> = ServerName::try_from(config.imap_host.clone())?; + let conn = rustls::ClientConnection::new(tls_config, server_name)?; let mut tls = rustls::StreamOwned::new(conn, tcp); - let read_line = |tls: &mut rustls::StreamOwned| -> Result { - let mut buf = Vec::new(); - loop { - let mut byte = [0u8; 1]; - match std::io::Read::read(tls, &mut byte) { - Ok(0) => return Err(anyhow!("IMAP connection closed")), - Ok(_) => { - buf.push(byte[0]); - if buf.ends_with(b"\r\n") { - return Ok(String::from_utf8_lossy(&buf).to_string()); + let read_line = + |tls: &mut rustls::StreamOwned| -> Result { + let mut buf = Vec::new(); + loop { + let mut byte = [0u8; 1]; + match std::io::Read::read(tls, &mut byte) { + Ok(0) => return Err(anyhow!("IMAP connection closed")), + Ok(_) => { + buf.push(byte[0]); + if buf.ends_with(b"\r\n") { + return Ok(String::from_utf8_lossy(&buf).to_string()); + } } + Err(e) => return Err(e.into()), } - Err(e) => return Err(e.into()), } - } - }; + }; let send_cmd = |tls: &mut rustls::StreamOwned, - tag: &str, - cmd: &str| + tag: &str, + cmd: &str| -> Result> { let full = format!("{} {}\r\n", tag, cmd); IoWrite::write_all(tls, full.as_bytes())?; @@ -241,7 +251,11 @@ impl EmailChannel { } // Select folder - let _select = send_cmd(&mut tls, "A2", &format!("SELECT \"{}\"", config.imap_folder))?; + let _select = send_cmd( + &mut tls, + "A2", + &format!("SELECT \"{}\"", config.imap_folder), + )?; // Search unseen let search_resp = send_cmd(&mut tls, "A3", "SEARCH UNSEEN")?; @@ -285,8 +299,17 @@ impl EmailChannel { .date() .map(|d| { let naive = chrono::NaiveDate::from_ymd_opt( - d.year as i32, u32::from(d.month), u32::from(d.day) - ).and_then(|date| date.and_hms_opt(u32::from(d.hour), u32::from(d.minute), u32::from(d.second))); + d.year as i32, + u32::from(d.month), + u32::from(d.day), + ) + .and_then(|date| { + date.and_hms_opt( + u32::from(d.hour), + u32::from(d.minute), + u32::from(d.second), + ) + }); naive.map_or(0, |n| n.and_utc().timestamp() as u64) }) .unwrap_or_else(|| { @@ -302,7 +325,11 @@ impl EmailChannel { // Mark as seen with unique tag let store_tag = format!("A{tag_counter}"); tag_counter += 1; - let _ = send_cmd(&mut tls, &store_tag, &format!("STORE {uid} +FLAGS (\\Seen)")); + let _ = send_cmd( + &mut tls, + &store_tag, + &format!("STORE {uid} +FLAGS (\\Seen)"), + ); } // Logout with unique tag diff --git a/src/channels/mod.rs b/src/channels/mod.rs index d876519..fe451d3 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -5,8 +5,8 @@ pub mod imessage; pub mod matrix; pub mod slack; pub mod telegram; -pub mod whatsapp; pub mod traits; +pub mod whatsapp; pub use cli::CliChannel; pub use discord::DiscordChannel; @@ -14,8 +14,8 @@ pub use imessage::IMessageChannel; pub use matrix::MatrixChannel; pub use slack::SlackChannel; pub use telegram::TelegramChannel; -pub use whatsapp::WhatsAppChannel; pub use traits::Channel; +pub use whatsapp::WhatsAppChannel; use crate::config::Config; use crate::memory::{self, Memory}; @@ -189,7 +189,7 @@ pub fn build_system_prompt( } } -/// Inject OpenClaw (markdown) identity files into the prompt +/// Inject `OpenClaw` (markdown) identity files into the prompt fn inject_openclaw_identity(prompt: &mut String, workspace_dir: &std::path::Path) { #[allow(unused_imports)] use std::fmt::Write; diff --git a/src/channels/whatsapp.rs b/src/channels/whatsapp.rs index bc038f0..e739239 100644 --- a/src/channels/whatsapp.rs +++ b/src/channels/whatsapp.rs @@ -2,7 +2,7 @@ use super::traits::{Channel, ChannelMessage}; use async_trait::async_trait; use uuid::Uuid; -/// WhatsApp channel — uses WhatsApp Business Cloud API +/// `WhatsApp` channel — uses `WhatsApp` Business Cloud API /// /// This channel operates in webhook mode (push-based) rather than polling. /// Messages are received via the gateway's `/whatsapp` webhook endpoint. diff --git a/src/config/schema.rs b/src/config/schema.rs index 872a600..e6c2c62 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -89,6 +89,12 @@ impl Default for IdentityConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GatewayConfig { + /// Gateway port (default: 8080) + #[serde(default = "default_gateway_port")] + pub port: u16, + /// Gateway host (default: 127.0.0.1) + #[serde(default = "default_gateway_host")] + pub host: String, /// Require pairing before accepting requests (default: true) #[serde(default = "default_true")] pub require_pairing: bool, @@ -100,6 +106,14 @@ pub struct GatewayConfig { pub paired_tokens: Vec, } +fn default_gateway_port() -> u16 { + 3000 +} + +fn default_gateway_host() -> String { + "127.0.0.1".into() +} + fn default_true() -> bool { true } @@ -107,6 +121,8 @@ fn default_true() -> bool { impl Default for GatewayConfig { fn default() -> Self { Self { + port: default_gateway_port(), + host: default_gateway_host(), require_pairing: true, allow_public_bind: false, paired_tokens: Vec::new(), @@ -649,6 +665,65 @@ impl Config { } } + /// Apply environment variable overrides to config + pub fn apply_env_overrides(&mut self) { + // API Key: ZEROCLAW_API_KEY or API_KEY + if let Ok(key) = std::env::var("ZEROCLAW_API_KEY").or_else(|_| std::env::var("API_KEY")) { + if !key.is_empty() { + self.api_key = Some(key); + } + } + + // Provider: ZEROCLAW_PROVIDER or PROVIDER + if let Ok(provider) = + std::env::var("ZEROCLAW_PROVIDER").or_else(|_| std::env::var("PROVIDER")) + { + if !provider.is_empty() { + self.default_provider = Some(provider); + } + } + + // Model: ZEROCLAW_MODEL + if let Ok(model) = std::env::var("ZEROCLAW_MODEL") { + if !model.is_empty() { + self.default_model = Some(model); + } + } + + // Workspace directory: ZEROCLAW_WORKSPACE + if let Ok(workspace) = std::env::var("ZEROCLAW_WORKSPACE") { + if !workspace.is_empty() { + self.workspace_dir = PathBuf::from(workspace); + } + } + + // Gateway port: ZEROCLAW_GATEWAY_PORT or PORT + if let Ok(port_str) = + std::env::var("ZEROCLAW_GATEWAY_PORT").or_else(|_| std::env::var("PORT")) + { + if let Ok(port) = port_str.parse::() { + self.gateway.port = port; + } + } + + // Gateway host: ZEROCLAW_GATEWAY_HOST or HOST + if let Ok(host) = std::env::var("ZEROCLAW_GATEWAY_HOST").or_else(|_| std::env::var("HOST")) + { + if !host.is_empty() { + self.gateway.host = host; + } + } + + // Temperature: ZEROCLAW_TEMPERATURE + if let Ok(temp_str) = std::env::var("ZEROCLAW_TEMPERATURE") { + if let Ok(temp) = temp_str.parse::() { + if (0.0..=2.0).contains(&temp) { + self.default_temperature = temp; + } + } + } + } + pub fn save(&self) -> Result<()> { let toml_str = toml::to_string_pretty(self).context("Failed to serialize config")?; fs::write(&self.config_path, toml_str).context("Failed to write config file")?; @@ -1191,6 +1266,8 @@ channel_id = "C123" #[test] fn checklist_gateway_serde_roundtrip() { let g = GatewayConfig { + port: 3000, + host: "127.0.0.1".into(), require_pairing: true, allow_public_bind: false, paired_tokens: vec!["zc_test_token".into()], @@ -1364,4 +1441,187 @@ default_temperature = 0.7 assert!(!parsed.browser.enabled); assert!(parsed.browser.allowed_domains.is_empty()); } + + // ── Environment variable overrides (Docker support) ───────── + + #[test] + fn env_override_api_key() { + let mut config = Config::default(); + assert!(config.api_key.is_none()); + + std::env::set_var("ZEROCLAW_API_KEY", "sk-test-env-key"); + config.apply_env_overrides(); + assert_eq!(config.api_key.as_deref(), Some("sk-test-env-key")); + + std::env::remove_var("ZEROCLAW_API_KEY"); + } + + #[test] + fn env_override_api_key_fallback() { + let mut config = Config::default(); + + std::env::remove_var("ZEROCLAW_API_KEY"); + std::env::set_var("API_KEY", "sk-fallback-key"); + config.apply_env_overrides(); + assert_eq!(config.api_key.as_deref(), Some("sk-fallback-key")); + + std::env::remove_var("API_KEY"); + } + + #[test] + fn env_override_provider() { + let mut config = Config::default(); + + std::env::set_var("ZEROCLAW_PROVIDER", "anthropic"); + config.apply_env_overrides(); + assert_eq!(config.default_provider.as_deref(), Some("anthropic")); + + std::env::remove_var("ZEROCLAW_PROVIDER"); + } + + #[test] + fn env_override_provider_fallback() { + let mut config = Config::default(); + + std::env::remove_var("ZEROCLAW_PROVIDER"); + std::env::set_var("PROVIDER", "openai"); + config.apply_env_overrides(); + assert_eq!(config.default_provider.as_deref(), Some("openai")); + + std::env::remove_var("PROVIDER"); + } + + #[test] + fn env_override_model() { + let mut config = Config::default(); + + std::env::set_var("ZEROCLAW_MODEL", "gpt-4o"); + config.apply_env_overrides(); + assert_eq!(config.default_model.as_deref(), Some("gpt-4o")); + + std::env::remove_var("ZEROCLAW_MODEL"); + } + + #[test] + fn env_override_workspace() { + let mut config = Config::default(); + + std::env::set_var("ZEROCLAW_WORKSPACE", "/custom/workspace"); + config.apply_env_overrides(); + assert_eq!(config.workspace_dir, PathBuf::from("/custom/workspace")); + + std::env::remove_var("ZEROCLAW_WORKSPACE"); + } + + #[test] + fn env_override_empty_values_ignored() { + let mut config = Config::default(); + let original_provider = config.default_provider.clone(); + + std::env::set_var("ZEROCLAW_PROVIDER", ""); + config.apply_env_overrides(); + assert_eq!(config.default_provider, original_provider); + + std::env::remove_var("ZEROCLAW_PROVIDER"); + } + + #[test] + fn env_override_gateway_port() { + let mut config = Config::default(); + assert_eq!(config.gateway.port, 3000); + + std::env::set_var("ZEROCLAW_GATEWAY_PORT", "8080"); + config.apply_env_overrides(); + assert_eq!(config.gateway.port, 8080); + + std::env::remove_var("ZEROCLAW_GATEWAY_PORT"); + } + + #[test] + fn env_override_port_fallback() { + let mut config = Config::default(); + + std::env::remove_var("ZEROCLAW_GATEWAY_PORT"); + std::env::set_var("PORT", "9000"); + config.apply_env_overrides(); + assert_eq!(config.gateway.port, 9000); + + std::env::remove_var("PORT"); + } + + #[test] + fn env_override_gateway_host() { + let mut config = Config::default(); + assert_eq!(config.gateway.host, "127.0.0.1"); + + std::env::set_var("ZEROCLAW_GATEWAY_HOST", "0.0.0.0"); + config.apply_env_overrides(); + assert_eq!(config.gateway.host, "0.0.0.0"); + + std::env::remove_var("ZEROCLAW_GATEWAY_HOST"); + } + + #[test] + fn env_override_host_fallback() { + let mut config = Config::default(); + + std::env::remove_var("ZEROCLAW_GATEWAY_HOST"); + std::env::set_var("HOST", "0.0.0.0"); + config.apply_env_overrides(); + assert_eq!(config.gateway.host, "0.0.0.0"); + + std::env::remove_var("HOST"); + } + + #[test] + fn env_override_temperature() { + let mut config = Config::default(); + + std::env::set_var("ZEROCLAW_TEMPERATURE", "0.5"); + config.apply_env_overrides(); + assert!((config.default_temperature - 0.5).abs() < f64::EPSILON); + + std::env::remove_var("ZEROCLAW_TEMPERATURE"); + } + + #[test] + fn env_override_temperature_out_of_range_ignored() { + // Clean up any leftover env vars from other tests + std::env::remove_var("ZEROCLAW_TEMPERATURE"); + + let mut config = Config::default(); + let original_temp = config.default_temperature; + + // Temperature > 2.0 should be ignored + std::env::set_var("ZEROCLAW_TEMPERATURE", "3.0"); + config.apply_env_overrides(); + assert!( + (config.default_temperature - original_temp).abs() < f64::EPSILON, + "Temperature 3.0 should be ignored (out of range)" + ); + + std::env::remove_var("ZEROCLAW_TEMPERATURE"); + } + + #[test] + fn env_override_invalid_port_ignored() { + let mut config = Config::default(); + let original_port = config.gateway.port; + + std::env::set_var("PORT", "not_a_number"); + config.apply_env_overrides(); + assert_eq!(config.gateway.port, original_port); + + std::env::remove_var("PORT"); + } + + #[test] + fn gateway_config_default_values() { + let g = GatewayConfig::default(); + assert_eq!(g.port, 3000); + assert_eq!(g.host, "127.0.0.1"); + assert!(g.require_pairing); + assert!(!g.allow_public_bind); + assert!(g.paired_tokens.is_empty()); + } } diff --git a/src/cron/mod.rs b/src/cron/mod.rs index 572670d..4de03ce 100644 --- a/src/cron/mod.rs +++ b/src/cron/mod.rs @@ -18,6 +18,7 @@ pub struct CronJob { pub last_status: Option, } +#[allow(clippy::needless_pass_by_value)] pub fn handle_command(command: super::CronCommands, config: Config) -> Result<()> { match command { super::CronCommands::List => { @@ -33,8 +34,7 @@ pub fn handle_command(command: super::CronCommands, config: Config) -> Result<() for job in jobs { let last_run = job .last_run - .map(|d| d.to_rfc3339()) - .unwrap_or_else(|| "never".into()); + .map_or_else(|| "never".into(), |d| d.to_rfc3339()); let last_status = job.last_status.unwrap_or_else(|| "n/a".into()); println!( "- {} | {} | next={} | last={} ({})\n cmd: {}", diff --git a/src/cron/scheduler.rs b/src/cron/scheduler.rs index 973fbee..dce5891 100644 --- a/src/cron/scheduler.rs +++ b/src/cron/scheduler.rs @@ -66,7 +66,7 @@ async fn execute_job_with_retry( } if attempt < retries { - let jitter_ms = (Utc::now().timestamp_subsec_millis() % 250) as u64; + let jitter_ms = u64::from(Utc::now().timestamp_subsec_millis() % 250); time::sleep(Duration::from_millis(backoff_ms + jitter_ms)).await; backoff_ms = (backoff_ms.saturating_mul(2)).min(30_000); } diff --git a/src/doctor/mod.rs b/src/doctor/mod.rs index 62417ea..e858f7c 100644 --- a/src/doctor/mod.rs +++ b/src/doctor/mod.rs @@ -52,25 +52,21 @@ pub fn run(config: &Config) -> Result<()> { let scheduler_ok = scheduler .get("status") .and_then(serde_json::Value::as_str) - .map(|s| s == "ok") - .unwrap_or(false); + .is_some_and(|s| s == "ok"); let scheduler_last_ok = scheduler .get("last_ok") .and_then(serde_json::Value::as_str) .and_then(parse_rfc3339) - .map(|dt| Utc::now().signed_duration_since(dt).num_seconds()) - .unwrap_or(i64::MAX); + .map_or(i64::MAX, |dt| { + Utc::now().signed_duration_since(dt).num_seconds() + }); if scheduler_ok && scheduler_last_ok <= SCHEDULER_STALE_SECONDS { - println!( - " ✅ scheduler healthy (last ok {}s ago)", - scheduler_last_ok - ); + println!(" ✅ scheduler healthy (last ok {scheduler_last_ok}s ago)"); } else { println!( - " ❌ scheduler unhealthy/stale (status_ok={}, age={}s)", - scheduler_ok, scheduler_last_ok + " ❌ scheduler unhealthy/stale (status_ok={scheduler_ok}, age={scheduler_last_ok}s)" ); } } else { @@ -86,14 +82,14 @@ pub fn run(config: &Config) -> Result<()> { let status_ok = component .get("status") .and_then(serde_json::Value::as_str) - .map(|s| s == "ok") - .unwrap_or(false); + .is_some_and(|s| s == "ok"); let age = component .get("last_ok") .and_then(serde_json::Value::as_str) .and_then(parse_rfc3339) - .map(|dt| Utc::now().signed_duration_since(dt).num_seconds()) - .unwrap_or(i64::MAX); + .map_or(i64::MAX, |dt| { + Utc::now().signed_duration_since(dt).num_seconds() + }); if status_ok && age <= CHANNEL_STALE_SECONDS { println!(" ✅ {name} fresh (last ok {age}s ago)"); @@ -107,10 +103,7 @@ pub fn run(config: &Config) -> Result<()> { if channel_count == 0 { println!(" ℹ️ no channel components tracked in state yet"); } else { - println!( - " Channel summary: {} total, {} stale", - channel_count, stale_channels - ); + println!(" Channel summary: {channel_count} total, {stale_channels} stale"); } Ok(()) diff --git a/src/health/mod.rs b/src/health/mod.rs index 4fcd8b2..f3f35d8 100644 --- a/src/health/mod.rs +++ b/src/health/mod.rs @@ -67,6 +67,7 @@ pub fn mark_component_ok(component: &str) { }); } +#[allow(clippy::needless_pass_by_value)] pub fn mark_component_error(component: &str, error: impl ToString) { let err = error.to_string(); upsert_component(component, move |entry| { diff --git a/src/main.rs b/src/main.rs index 46fb1d8..9ce3910 100644 --- a/src/main.rs +++ b/src/main.rs @@ -169,9 +169,9 @@ enum Commands { #[derive(Subcommand, Debug)] enum MigrateCommands { - /// Import memory from an OpenClaw workspace into this ZeroClaw workspace + /// Import memory from an `OpenClaw` workspace into this `ZeroClaw` workspace Openclaw { - /// Optional path to OpenClaw workspace (defaults to ~/.openclaw/workspace) + /// Optional path to `OpenClaw` workspace (defaults to ~/.openclaw/workspace) #[arg(long)] source: Option, diff --git a/src/migration.rs b/src/migration.rs index ed160c7..2ce29ba 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -250,6 +250,7 @@ fn read_openclaw_markdown_entries(source_workspace: &Path) -> Result Option<(&str, &str)> { fn parse_category(raw: &str) -> MemoryCategory { match raw.trim().to_ascii_lowercase().as_str() { - "core" => MemoryCategory::Core, + "core" | "" => MemoryCategory::Core, "daily" => MemoryCategory::Daily, "conversation" => MemoryCategory::Conversation, - "" => MemoryCategory::Core, other => MemoryCategory::Custom(other.to_string()), } } @@ -350,7 +350,7 @@ fn pick_optional_column_expr(columns: &[String], candidates: &[&str]) -> Option< candidates .iter() .find(|candidate| columns.iter().any(|c| c == *candidate)) - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) } fn pick_column_expr(columns: &[String], candidates: &[&str], fallback: &str) -> String { diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 6f5ba40..da551b0 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -451,7 +451,10 @@ fn setup_provider() -> Result<(String, String, String)> { ("mistral", "Mistral — Large & Codestral"), ("xai", "xAI — Grok 3 & 4"), ("perplexity", "Perplexity — search-augmented AI"), - ("gemini", "Google Gemini — Gemini 2.0 Flash & Pro (supports CLI auth)"), + ( + "gemini", + "Google Gemini — Gemini 2.0 Flash & Pro (supports CLI auth)", + ), ], 1 => vec![ ("groq", "Groq — ultra-fast LPU inference"), @@ -534,7 +537,10 @@ fn setup_provider() -> Result<(String, String, String)> { let api_key = if provider_name == "ollama" { print_bullet("Ollama runs locally — no API key needed!"); String::new() - } else if provider_name == "gemini" || provider_name == "google" || provider_name == "google-gemini" { + } else if provider_name == "gemini" + || provider_name == "google" + || provider_name == "google-gemini" + { // Special handling for Gemini: check for CLI auth first if crate::providers::gemini::GeminiProvider::has_cli_credentials() { print_bullet(&format!( @@ -741,7 +747,10 @@ fn setup_provider() -> Result<(String, String, String)> { ], "gemini" | "google" | "google-gemini" => vec![ ("gemini-2.0-flash", "Gemini 2.0 Flash (fast, recommended)"), - ("gemini-2.0-flash-lite", "Gemini 2.0 Flash Lite (fastest, cheapest)"), + ( + "gemini-2.0-flash-lite", + "Gemini 2.0 Flash Lite (fastest, cheapest)", + ), ("gemini-1.5-pro", "Gemini 1.5 Pro (best quality)"), ("gemini-1.5-flash", "Gemini 1.5 Flash (balanced)"), ], diff --git a/src/providers/gemini.rs b/src/providers/gemini.rs index 89bbd88..1b64af0 100644 --- a/src/providers/gemini.rs +++ b/src/providers/gemini.rs @@ -95,7 +95,7 @@ struct GeminiCliSettings { impl GeminiProvider { /// Create a new Gemini provider. - /// + /// /// Authentication priority: /// 1. Explicit API key passed in /// 2. `GEMINI_API_KEY` environment variable diff --git a/src/security/secrets.rs b/src/security/secrets.rs index 6022ebe..3940843 100644 --- a/src/security/secrets.rs +++ b/src/security/secrets.rs @@ -194,7 +194,10 @@ impl SecretStore { let _ = std::process::Command::new("icacls") .arg(&self.key_path) .args(["/inheritance:r", "/grant:r"]) - .arg(format!("{}:F", std::env::var("USERNAME").unwrap_or_default())) + .arg(format!( + "{}:F", + std::env::var("USERNAME").unwrap_or_default() + )) .output(); } diff --git a/src/service/mod.rs b/src/service/mod.rs index fc6bf51..3c5064f 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -6,6 +6,7 @@ use std::process::Command; const SERVICE_LABEL: &str = "com.zeroclaw.daemon"; +#[allow(clippy::needless_pass_by_value)] pub fn handle_command(command: super::ServiceCommands, config: &Config) -> Result<()> { match command { super::ServiceCommands::Install => install(config), diff --git a/src/skills/mod.rs b/src/skills/mod.rs index 0b108fc..34e15d8 100644 --- a/src/skills/mod.rs +++ b/src/skills/mod.rs @@ -239,6 +239,7 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<()> { } /// Handle the `skills` CLI command +#[allow(clippy::too_many_lines)] pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Result<()> { match command { super::SkillCommands::List => { diff --git a/src/tools/file_write.rs b/src/tools/file_write.rs index f147497..0760a29 100644 --- a/src/tools/file_write.rs +++ b/src/tools/file_write.rs @@ -69,15 +69,12 @@ impl Tool for FileWriteTool { tokio::fs::create_dir_all(parent).await?; } - let parent = match full_path.parent() { - Some(p) => p, - None => { - return Ok(ToolResult { - success: false, - output: String::new(), - error: Some("Invalid path: missing parent directory".into()), - }); - } + let Some(parent) = full_path.parent() else { + return Ok(ToolResult { + success: false, + output: String::new(), + error: Some("Invalid path: missing parent directory".into()), + }); }; // Resolve parent before writing to block symlink escapes. @@ -103,15 +100,12 @@ impl Tool for FileWriteTool { }); } - let file_name = match full_path.file_name() { - Some(name) => name, - None => { - return Ok(ToolResult { - success: false, - output: String::new(), - error: Some("Invalid path: missing file name".into()), - }); - } + let Some(file_name) = full_path.file_name() else { + return Ok(ToolResult { + success: false, + output: String::new(), + error: Some("Invalid path: missing file name".into()), + }); }; let resolved_target = resolved_parent.join(file_name);