diff --git a/Cargo.lock b/Cargo.lock index 4a90898..be51b95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,6 +1305,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "shellexpand", "thiserror 1.0.69", "tokio", "tokio-stream", diff --git a/crates/g3-core/Cargo.toml b/crates/g3-core/Cargo.toml index 91ea8e6..0e86851 100644 --- a/crates/g3-core/Cargo.toml +++ b/crates/g3-core/Cargo.toml @@ -24,3 +24,4 @@ futures-util = "0.3" chrono = { version = "0.4", features = ["serde"] } rand = "0.8" regex = "1.0" +shellexpand = "3.1" diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index abc5498..9caab70 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -12,6 +12,9 @@ mod fixed_filter_json; #[cfg(test)] mod fixed_filter_tests; +#[cfg(test)] +mod tilde_expansion_tests; + #[cfg(test)] mod error_handling_test; use anyhow::Result; @@ -2211,6 +2214,10 @@ The tool will execute immediately and you'll receive the result (success or erro debug!("Processing read_file tool call"); if let Some(file_path) = tool_call.args.get("file_path") { if let Some(path_str) = file_path.as_str() { + // Expand tilde (~) to home directory + let expanded_path = shellexpand::tilde(path_str); + let path_str = expanded_path.as_ref(); + // Check if this is an image file let is_image = path_str.to_lowercase().ends_with(".png") || path_str.to_lowercase().ends_with(".jpg") @@ -2472,6 +2479,10 @@ The tool will execute immediately and you'll receive the result (success or erro ); if let (Some(path), Some(content)) = (path_str, content_str) { + // Expand tilde (~) to home directory + let expanded_path = shellexpand::tilde(path); + let path = expanded_path.as_ref(); + debug!("Writing to file: {}", path); // Create parent directories if they don't exist @@ -2519,7 +2530,11 @@ The tool will execute immediately and you'll receive the result (success or erro }; let file_path = match args_obj.get("file_path").and_then(|v| v.as_str()) { - Some(path) => path, + Some(path) => { + // Expand tilde (~) to home directory + let expanded_path = shellexpand::tilde(path); + expanded_path.into_owned() + } None => return Ok("❌ Missing or invalid file_path argument".to_string()), }; @@ -2544,7 +2559,7 @@ The tool will execute immediately and you'll receive the result (success or erro ); // Read the existing file - let file_content = match std::fs::read_to_string(file_path) { + let file_content = match std::fs::read_to_string(&file_path) { Ok(content) => content, Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", file_path, e)), }; @@ -2557,7 +2572,7 @@ The tool will execute immediately and you'll receive the result (success or erro }; // Write the result back to the file - match std::fs::write(file_path, &result) { + match std::fs::write(&file_path, &result) { Ok(()) => Ok(format!("✅ Successfully applied unified diff")), Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)), } diff --git a/crates/g3-core/src/tilde_expansion_tests.rs b/crates/g3-core/src/tilde_expansion_tests.rs new file mode 100644 index 0000000..e3a0ebc --- /dev/null +++ b/crates/g3-core/src/tilde_expansion_tests.rs @@ -0,0 +1,36 @@ +#[cfg(test)] +mod tilde_expansion_tests { + use std::env; + + #[test] + fn test_tilde_expansion() { + // Test that shellexpand works + let path_with_tilde = "~/test.txt"; + let expanded = shellexpand::tilde(path_with_tilde); + + // Get the actual home directory + let home = env::var("HOME").expect("HOME environment variable not set"); + + // Verify expansion happened + assert_eq!(expanded.as_ref(), format!("{}/test.txt", home)); + assert!(!expanded.contains("~")); + } + + #[test] + fn test_tilde_expansion_with_subdirs() { + let path_with_tilde = "~/Documents/test.txt"; + let expanded = shellexpand::tilde(path_with_tilde); + + let home = env::var("HOME").expect("HOME environment variable not set"); + + assert_eq!(expanded.as_ref(), format!("{}/Documents/test.txt", home)); + } + + #[test] + fn test_no_tilde_unchanged() { + let path_without_tilde = "/absolute/path/test.txt"; + let expanded = shellexpand::tilde(path_without_tilde); + + assert_eq!(expanded.as_ref(), path_without_tilde); + } +}