/ archive / rust-prototype / src / tools.rs
tools.rs
  1  use anyhow::Result;
  2  use std::fs;
  3  use std::path::Path;
  4  use std::process::Command;
  5  use crate::memory::MemoryStore;
  6  
  7  // Memory tools
  8  pub fn memorize_info(key: &str, content: &str, tags: &str) -> Result<String> {
  9      let mut memory = MemoryStore::new()?;
 10      let tag_list: Vec<String> = if tags.is_empty() {
 11          Vec::new()
 12      } else {
 13          tags.split(',').map(|s| s.trim().to_string()).collect()
 14      };
 15      memory.memorize(key.to_string(), content.to_string(), tag_list)
 16  }
 17  
 18  pub fn remember_info(query: &str) -> Result<String> {
 19      let mut memory = MemoryStore::new()?;
 20      memory.remember(query.to_string())
 21  }
 22  
 23  pub fn get_all_memories() -> Result<String> {
 24      let memory = MemoryStore::new()?;
 25      Ok(memory.get_all_memories())
 26  }
 27  
 28  pub fn delete_memory(key: &str) -> Result<String> {
 29      let mut memory = MemoryStore::new()?;
 30      memory.delete_memory(key.to_string())
 31  }
 32  
 33  pub fn clear_memories() -> Result<String> {
 34      let mut memory = MemoryStore::new()?;
 35      memory.clear_all()
 36  }
 37  
 38  // Smart file operations like Python version
 39  pub fn write_file_smart(input: &str) -> Result<String> {
 40      // Handle JSON format: {"file_path": "path", "content": "text"}
 41      if input.trim().starts_with('{') {
 42          if let Ok(json) = serde_json::from_str::<serde_json::Value>(input) {
 43              if let (Some(path), Some(content)) = (json.get("file_path"), json.get("content")) {
 44                  if let (Some(path_str), Some(content_str)) = (path.as_str(), content.as_str()) {
 45                      return write_file(path_str, content_str);
 46                  }
 47              }
 48          }
 49      }
 50      
 51      // Handle pipe format: "filepath|||content"
 52      if let Some(pos) = input.find("|||") {
 53          let path = input[..pos].trim();
 54          let content = input[pos + 3..].trim();
 55          return write_file(path, content);
 56      }
 57      
 58      Err(anyhow::anyhow!("Invalid format. Use JSON or 'filepath|||content'"))
 59  }
 60  
 61  pub fn append_to_file_smart(input: &str) -> Result<String> {
 62      // Handle JSON format: {"file_path": "path", "content": "text"}
 63      if input.trim().starts_with('{') {
 64          if let Ok(json) = serde_json::from_str::<serde_json::Value>(input) {
 65              if let (Some(path), Some(content)) = (json.get("file_path"), json.get("content")) {
 66                  if let (Some(path_str), Some(content_str)) = (path.as_str(), content.as_str()) {
 67                      return append_to_file(path_str, content_str);
 68                  }
 69              }
 70          }
 71      }
 72      
 73      // Handle pipe format: "filepath|||content"
 74      if let Some(pos) = input.find("|||") {
 75          let path = input[..pos].trim();
 76          let content = input[pos + 3..].trim();
 77          return append_to_file(path, content);
 78      }
 79      
 80      Err(anyhow::anyhow!("Invalid format. Use JSON or 'filepath|||content'"))
 81  }
 82  
 83  // Advanced git conflict resolution
 84  pub fn auto_merge_content(content: &str, file_path: &str) -> Result<String> {
 85      // Simple auto-merge strategy - take both changes
 86      let lines: Vec<&str> = content.lines().collect();
 87      let mut merged_lines = Vec::new();
 88      let mut in_conflict = false;
 89      let mut current_section = Vec::new();
 90      
 91      for line in lines {
 92          if line.starts_with("<<<<<<< ") {
 93              in_conflict = true;
 94              current_section.clear();
 95          } else if line.starts_with(">>>>>>> ") {
 96              in_conflict = false;
 97              // Add all collected lines (both sides of conflict)
 98              merged_lines.extend(current_section.clone());
 99              current_section.clear();
100          } else if line.starts_with("======= ") {
101              // Continue collecting - we want both sides
102              continue;
103          } else if in_conflict {
104              current_section.push(line);
105          } else {
106              merged_lines.push(line);
107          }
108      }
109      
110      let merged_content = merged_lines.join("\n");
111      fs::write(file_path, &merged_content)?;
112      
113      Ok(format!("๐Ÿ”€ Auto-merged conflicts in {}", file_path))
114  }
115  
116  // Task management (simplified version of Python's task system)
117  pub fn add_task(task: &str) -> Result<String> {
118      let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
119      let kamaji_dir = home.join(".kamaji");
120      fs::create_dir_all(&kamaji_dir)?;
121      let tasks_file = kamaji_dir.join("tasks.txt");
122      
123      let mut tasks = if tasks_file.exists() {
124          fs::read_to_string(&tasks_file)?
125      } else {
126          String::new()
127      };
128      
129      tasks.push_str(&format!("- {}\n", task));
130      fs::write(&tasks_file, tasks)?;
131      
132      Ok(format!("๐Ÿ“ Added task: {}", task))
133  }
134  
135  pub fn list_tasks() -> Result<String> {
136      let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
137      let tasks_file = home.join(".kamaji").join("tasks.txt");
138      
139      if !tasks_file.exists() {
140          return Ok("๐Ÿ“‹ No tasks found. Add some with the add_task tool!".to_string());
141      }
142      
143      let tasks = fs::read_to_string(&tasks_file)?;
144      if tasks.trim().is_empty() {
145          return Ok("๐Ÿ“‹ Task list is empty.".to_string());
146      }
147      
148      Ok(format!("๐Ÿ“‹ Current Tasks:\n{}", tasks))
149  }
150  
151  pub fn clear_tasks() -> Result<String> {
152      let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
153      let tasks_file = home.join(".kamaji").join("tasks.txt");
154      
155      if tasks_file.exists() {
156          fs::remove_file(&tasks_file)?;
157      }
158      
159      Ok("๐Ÿงน Cleared all tasks".to_string())
160  }
161  
162  // Codebase analysis (simplified version)
163  pub fn analyze_codebase(path: &str) -> Result<String> {
164      let mut analysis = String::new();
165      analysis.push_str("๐Ÿ” CODEBASE ANALYSIS\n");
166      analysis.push_str("==================\n\n");
167      
168      // Count files by extension
169      let mut file_counts = std::collections::HashMap::new();
170      let mut total_lines = 0;
171      
172      if let Ok(entries) = fs::read_dir(path) {
173          for entry in entries.flatten() {
174              if let Ok(metadata) = entry.metadata() {
175                  if metadata.is_file() {
176                      if let Some(ext) = entry.path().extension() {
177                          if let Some(ext_str) = ext.to_str() {
178                              *file_counts.entry(ext_str.to_string()).or_insert(0) += 1;
179                              
180                              // Count lines for code files
181                              if matches!(ext_str, "rs" | "py" | "js" | "ts" | "go" | "java" | "cpp" | "c" | "h") {
182                                  if let Ok(content) = fs::read_to_string(entry.path()) {
183                                      total_lines += content.lines().count();
184                                  }
185                              }
186                          }
187                      }
188                  }
189              }
190          }
191      }
192      
193      analysis.push_str("๐Ÿ“Š File Statistics:\n");
194      for (ext, count) in file_counts {
195          analysis.push_str(&format!("  .{}: {} files\n", ext, count));
196      }
197      analysis.push_str(&format!("\n๐Ÿ“ Total lines of code: {}\n\n", total_lines));
198      
199      analysis.push_str("๐Ÿ’ก Suggestions:\n");
200      analysis.push_str("- Consider adding documentation if missing\n");
201      analysis.push_str("- Ensure proper error handling\n");
202      analysis.push_str("- Add unit tests if not present\n");
203      analysis.push_str("- Check for code duplication\n");
204      
205      Ok(analysis)
206  }
207  
208  // Calculator tool with math functions
209  pub fn calculator(expression: &str) -> Result<String> {
210      // Simple expression evaluator with basic math functions
211      let expr = expression.trim();
212      
213      // Handle basic operations
214      if let Ok(result) = eval_expression(expr) {
215          Ok(result.to_string())
216      } else {
217          Err(anyhow::anyhow!("Invalid mathematical expression: {}", expr))
218      }
219  }
220  
221  fn eval_expression(expr: &str) -> Result<f64> {
222      // Basic math expression evaluator
223      // This is a simplified version - in production you'd want a proper parser
224      let expr = expr.replace(" ", "");
225      
226      // Handle basic functions
227      if expr.starts_with("sqrt(") && expr.ends_with(")") {
228          let inner = &expr[5..expr.len()-1];
229          let val = eval_expression(inner)?;
230          return Ok(val.sqrt());
231      }
232      
233      if expr.starts_with("sin(") && expr.ends_with(")") {
234          let inner = &expr[4..expr.len()-1];
235          let val = eval_expression(inner)?;
236          return Ok(val.sin());
237      }
238      
239      if expr.starts_with("cos(") && expr.ends_with(")") {
240          let inner = &expr[4..expr.len()-1];
241          let val = eval_expression(inner)?;
242          return Ok(val.cos());
243      }
244      
245      // Handle constants
246      if expr == "pi" { return Ok(std::f64::consts::PI); }
247      if expr == "e" { return Ok(std::f64::consts::E); }
248      
249      // Handle basic arithmetic
250      if let Some(pos) = expr.rfind('+') {
251          let left = eval_expression(&expr[..pos])?;
252          let right = eval_expression(&expr[pos+1..])?;
253          return Ok(left + right);
254      }
255      
256      if let Some(pos) = expr.rfind('-') {
257          if pos > 0 { // Not a negative number
258              let left = eval_expression(&expr[..pos])?;
259              let right = eval_expression(&expr[pos+1..])?;
260              return Ok(left - right);
261          }
262      }
263      
264      if let Some(pos) = expr.rfind('*') {
265          let left = eval_expression(&expr[..pos])?;
266          let right = eval_expression(&expr[pos+1..])?;
267          return Ok(left * right);
268      }
269      
270      if let Some(pos) = expr.rfind('/') {
271          let left = eval_expression(&expr[..pos])?;
272          let right = eval_expression(&expr[pos+1..])?;
273          if right == 0.0 {
274              return Err(anyhow::anyhow!("Division by zero"));
275          }
276          return Ok(left / right);
277      }
278      
279      if let Some(pos) = expr.find('^') {
280          let left = eval_expression(&expr[..pos])?;
281          let right = eval_expression(&expr[pos+1..])?;
282          return Ok(left.powf(right));
283      }
284      
285      // Try to parse as number
286      expr.parse::<f64>().map_err(|_| anyhow::anyhow!("Invalid number: {}", expr))
287  }
288  
289  // String manipulation tools
290  pub fn string_length(text: &str) -> Result<String> {
291      Ok(text.len().to_string())
292  }
293  
294  pub fn reverse_string(text: &str) -> Result<String> {
295      Ok(text.chars().rev().collect())
296  }
297  
298  pub fn uppercase_string(text: &str) -> Result<String> {
299      Ok(text.to_uppercase())
300  }
301  
302  pub fn lowercase_string(text: &str) -> Result<String> {
303      Ok(text.to_lowercase())
304  }
305  
306  // RAG tools
307  pub fn create_document_index(files: &[String]) -> Result<String> {
308      let mut documents = Vec::new();
309      let mut total_chars = 0;
310      
311      for file_path in files {
312          if let Ok(content) = fs::read_to_string(file_path) {
313              documents.push(format!("=== {} ===\n{}", file_path, content));
314              total_chars += content.len();
315          }
316      }
317      
318      // Simple index creation (in production, you'd use proper embeddings)
319      let index_path = ".kamaji_index.json";
320      let index_data = serde_json::json!({
321          "documents": documents,
322          "files": files,
323          "created_at": chrono::Utc::now().to_rfc3339(),
324          "total_chars": total_chars
325      });
326      
327      fs::write(index_path, serde_json::to_string_pretty(&index_data)?)?;
328      Ok(format!("Created document index with {} files ({} characters)", files.len(), total_chars))
329  }
330  
331  pub fn search_documents(query: &str) -> Result<String> {
332      let index_path = ".kamaji_index.json";
333      
334      if !Path::new(index_path).exists() {
335          return Err(anyhow::anyhow!("No document index found. Create one first with create_document_index."));
336      }
337      
338      let index_content = fs::read_to_string(index_path)?;
339      let index_data: serde_json::Value = serde_json::from_str(&index_content)?;
340      
341      let documents = index_data["documents"].as_array()
342          .ok_or_else(|| anyhow::anyhow!("Invalid index format"))?;
343      
344      // Simple keyword search (in production, you'd use vector similarity)
345      let query_lower = query.to_lowercase();
346      let mut matches = Vec::new();
347      
348      for (i, doc) in documents.iter().enumerate() {
349          if let Some(doc_str) = doc.as_str() {
350              if doc_str.to_lowercase().contains(&query_lower) {
351                  // Extract relevant snippet
352                  let lines: Vec<&str> = doc_str.lines().collect();
353                  let mut relevant_lines = Vec::new();
354                  
355                  for (line_idx, line) in lines.iter().enumerate() {
356                      if line.to_lowercase().contains(&query_lower) {
357                          // Add context lines
358                          let start = line_idx.saturating_sub(2);
359                          let end = (line_idx + 3).min(lines.len());
360                          relevant_lines.extend_from_slice(&lines[start..end]);
361                          break;
362                      }
363                  }
364                  
365                  matches.push(format!("Document {}: {}", i + 1, relevant_lines.join("\n")));
366              }
367          }
368      }
369      
370      if matches.is_empty() {
371          Ok(format!("No documents found matching query: '{}'", query))
372      } else {
373          Ok(format!("Found {} matches:\n\n{}", matches.len(), matches.join("\n\n---\n\n")))
374      }
375  }
376  
377  // Existing filesystem tools
378  pub fn execute_shell_command(command: &str) -> Result<String> {
379      let output = Command::new("sh")
380          .arg("-c")
381          .arg(command)
382          .output()?;
383  
384      let mut result = Vec::new();
385      
386      if !output.stdout.is_empty() {
387          result.push(format!("STDOUT:\n{}", String::from_utf8_lossy(&output.stdout)));
388      }
389      
390      if !output.stderr.is_empty() {
391          result.push(format!("STDERR:\n{}", String::from_utf8_lossy(&output.stderr)));
392      }
393      
394      if !output.status.success() {
395          result.push(format!("Exit code: {}", output.status.code().unwrap_or(-1)));
396      }
397  
398      let full_output = if result.is_empty() {
399          "Command executed successfully (no output)".to_string()
400      } else {
401          result.join("\n")
402      };
403  
404      if command.contains("git push") && full_output.to_lowercase().contains("rejected") {
405          Ok(format!("{}\n\n๐Ÿ’ก HINT: Push was rejected. Try:\n1. Run 'git pull' to fetch remote changes\n2. If conflicts occur, use resolve_git_conflicts tool\n3. Then 'git push' again", full_output))
406      } else {
407          Ok(full_output)
408      }
409  }
410  
411  pub fn read_file(path: &str) -> Result<String> {
412      let content = fs::read_to_string(path)?;
413      Ok(content)
414  }
415  
416  pub fn write_file(path: &str, content: &str) -> Result<String> {
417      fs::write(path, content)?;
418      Ok(format!("Successfully wrote {} bytes to {}", content.len(), path))
419  }
420  
421  pub fn append_to_file(path: &str, content: &str) -> Result<String> {
422      let mut existing = String::new();
423      if Path::new(path).exists() {
424          existing = fs::read_to_string(path)?;
425      }
426      
427      let new_content = if existing.is_empty() || existing.ends_with('\n') {
428          format!("{}{}", existing, content)
429      } else {
430          format!("{}\n{}", existing, content)
431      };
432      
433      fs::write(path, new_content)?;
434      Ok(format!("Successfully appended {} bytes to {}", content.len(), path))
435  }
436  
437  pub fn list_directory(path: &str) -> Result<String> {
438      let entries = fs::read_dir(path)?;
439      let mut items = Vec::new();
440      
441      for entry in entries {
442          let entry = entry?;
443          let metadata = entry.metadata()?;
444          let name = entry.file_name().to_string_lossy().to_string();
445          
446          if metadata.is_dir() {
447              items.push(format!("{}/", name));
448          } else {
449              items.push(name);
450          }
451      }
452      
453      items.sort();
454      Ok(items.join("\n"))
455  }
456  
457  pub fn get_current_directory() -> Result<String> {
458      let cwd = std::env::current_dir()?;
459      Ok(cwd.to_string_lossy().to_string())
460  }
461  
462  pub fn resolve_git_conflicts() -> Result<String> {
463      let status_output = execute_shell_command("git status --porcelain")?;
464      
465      if status_output.contains("UU") {
466          execute_shell_command("git add .")?;
467          execute_shell_command("git commit -m 'Auto-resolve merge conflicts'")?;
468          Ok("Auto-resolved conflicts by accepting current changes".to_string())
469      } else {
470          Ok("No merge conflicts detected".to_string())
471      }
472  }