diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index af152d9..5b971db 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -137,6 +137,44 @@ pub async fn run() -> Result<()> { } async fn run_interactive(mut agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> { + // Display the flying goose ASCII art logo + println!(r#" + ___ + .-' `'. + / \ + | | + | ; + | / + _.._ '. .' + .-' `'-.__`\ /` + / | `"` + | | + | | + ___..--.. | | + _.--""` `"-. | | + .-' `. | | + .' `. | | + / ___... \| | + / _.-"" `\ | | + | _.-' | | | + | .-' | | | + | .-' | '. .' + | .-' / |`'--...--'`| + |-' / | | + / / | G3 | + / ___ / | | + | __..--' `'-. | | CODING | + | __.--' `| | AGENT | + | .--' | | | + |' '. | | + | | `'--...--'--' + | | + | | + '. / + `'--..__ __.-' + ``''------''`` + "#); + println!(); println!("🤖 G3 AI Coding Agent - Interactive Mode"); println!( "I solve problems by writing and executing code. Tell me what you need to accomplish!" @@ -479,4 +517,4 @@ Keep your response concise and focused on actionable items.", Ok(()) } -use std::io::Write; +use std::io::Write; \ No newline at end of file diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index bf8699b..847d819 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -625,8 +625,8 @@ The tool will execute immediately and you'll receive the result (success or erro - Example: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"src/lib.rs\", \"content\": \"pub fn hello() {}\"}} - **edit_file**: Edit a specific range of lines in a file - - Format: {\"tool\": \"edit_file\", \"args\": {\"file_path\": \"path/to/file\", \"start_line\": 1, \"end_line\": 3, \"new_text\": \"replacement text\"}} - - Example: {\"tool\": \"edit_file\", \"args\": {\"file_path\": \"src/main.rs\", \"start_line\": 5, \"end_line\": 7, \"new_text\": \"println!(\\\"Hello, world!\\\");\"}} + - Format: {\"tool\": \"edit_file\", \"args\": {\"file_path\": \"path/to/file\", \"content\": \"replacement text\", \"start_of_range\": 1, \"end_of_range\": 3}} + - Example: {\"tool\": \"edit_file\", \"args\": {\"file_path\": \"src/main.rs\", \"content\": \"println!(\\\"Hello, world!\\\");\", \"start_of_range\": 5, \"end_of_range\": 7}} - **final_output**: Signal task completion with a detailed summary of work done in markdown format - Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"}} @@ -884,6 +884,32 @@ The tool will execute immediately and you'll receive the result (success or erro "required": ["file_path", "content"] }), }, + Tool { + name: "edit_file".to_string(), + description: "Edit a specific range of lines in a file. Replaces lines from start_of_range to end_of_range (inclusive, 1-indexed) with new content.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "The path to the file to edit" + }, + "content": { + "type": "string", + "description": "The new content to replace the specified range" + }, + "start_of_range": { + "type": "integer", + "description": "The starting line number (1-indexed, inclusive)" + }, + "end_of_range": { + "type": "integer", + "description": "The ending line number (1-indexed, inclusive)" + } + }, + "required": ["file_path", "content", "start_of_range", "end_of_range"] + }), + }, Tool { name: "final_output".to_string(), description: "Signal task completion with a detailed summary".to_string(), @@ -1575,6 +1601,100 @@ The tool will execute immediately and you'll receive the result (success or erro )) } } + "edit_file" => { + debug!("Processing edit_file tool call"); + + // Extract arguments + let args_obj = tool_call.args.as_object(); + if args_obj.is_none() { + return Ok("❌ Invalid arguments: expected object".to_string()); + } + let args_obj = args_obj.unwrap(); + + // Get file_path + let file_path = args_obj.get("file_path") + .and_then(|v| v.as_str()); + if file_path.is_none() { + return Ok("❌ Missing file_path argument".to_string()); + } + let file_path = file_path.unwrap(); + + // Get content + let content = args_obj.get("content") + .and_then(|v| v.as_str()); + if content.is_none() { + return Ok("❌ Missing content argument".to_string()); + } + let content = content.unwrap(); + + // Get start_of_range + let start_of_range = args_obj.get("start_of_range") + .and_then(|v| v.as_i64()) + .map(|v| v as usize); + if start_of_range.is_none() { + return Ok("❌ Missing or invalid start_of_range argument".to_string()); + } + let start_of_range = start_of_range.unwrap(); + + // Get end_of_range + let end_of_range = args_obj.get("end_of_range") + .and_then(|v| v.as_i64()) + .map(|v| v as usize); + if end_of_range.is_none() { + return Ok("❌ Missing or invalid end_of_range argument".to_string()); + } + let end_of_range = end_of_range.unwrap(); + + // Validate range + if start_of_range < 1 { + return Ok("❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string()); + } + if end_of_range < start_of_range { + return Ok("❌ end_of_range must be >= start_of_range".to_string()); + } + + // Read the existing file + let existing_content = match std::fs::read_to_string(file_path) { + Ok(content) => content, + Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", file_path, e)), + }; + + // Split into lines + let mut lines: Vec = existing_content.lines().map(|s| s.to_string()).collect(); + + // Check if range is valid + if start_of_range > lines.len() + 1 { + return Ok(format!("❌ start_of_range {} exceeds file length ({} lines)", start_of_range, lines.len())); + } + + // Prepare new content lines + let new_lines: Vec = content.lines().map(|s| s.to_string()).collect(); + + // Calculate the actual end of range (capped at file length) + let actual_end = end_of_range.min(lines.len()); + + // Replace the range with new content + // Convert to 0-indexed for vector operations + let start_idx = start_of_range - 1; + let end_idx = actual_end; + + // Remove old lines and insert new ones + lines.splice(start_idx..end_idx, new_lines.clone()); + + // Write back to file + let new_content = lines.join("\n"); + match std::fs::write(file_path, &new_content) { + Ok(()) => { + let lines_replaced = end_idx - start_idx; + let lines_added = new_lines.len(); + Ok(format!( + "✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines", + file_path, lines_replaced, start_of_range, actual_end, lines_added, lines.len() + )) + } + Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)), + } + } "final_output" => { if let Some(summary) = tool_call.args.get("summary") { if let Some(summary_str) = summary.as_str() {