cersei_tools/
grep_tool.rs1use super::*;
4use crate::tool_primitives::search as psearch;
5use serde::Deserialize;
6
7pub struct GrepTool;
8
9#[async_trait]
10impl Tool for GrepTool {
11 fn name(&self) -> &str {
12 "Grep"
13 }
14 fn description(&self) -> &str {
15 "Search file contents using regex patterns."
16 }
17 fn permission_level(&self) -> PermissionLevel {
18 PermissionLevel::ReadOnly
19 }
20 fn category(&self) -> ToolCategory {
21 ToolCategory::FileSystem
22 }
23
24 fn input_schema(&self) -> Value {
25 serde_json::json!({
26 "type": "object",
27 "properties": {
28 "pattern": { "type": "string", "description": "Regex pattern to search for" },
29 "path": { "type": "string", "description": "File or directory to search in" },
30 "glob": { "type": "string", "description": "Glob pattern to filter files" }
31 },
32 "required": ["pattern"]
33 })
34 }
35
36 async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {
37 #[derive(Deserialize)]
38 struct Input {
39 pattern: String,
40 path: Option<String>,
41 glob: Option<String>,
42 }
43
44 let input: Input = match serde_json::from_value(input) {
45 Ok(i) => i,
46 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
47 };
48
49 let search_path = input
50 .path
51 .unwrap_or_else(|| ctx.working_dir.display().to_string());
52
53 let opts = psearch::GrepOptions {
54 glob_filter: input.glob,
55 max_results: Some(250),
56 case_insensitive: false,
57 };
58
59 match psearch::grep(&input.pattern, std::path::Path::new(&search_path), opts).await {
60 Ok(matches) => {
61 if matches.is_empty() {
62 ToolResult::success("No matches found.")
63 } else {
64 let output: Vec<String> = matches
65 .iter()
66 .map(|m| {
67 format!("{}:{}:{}", m.file.display(), m.line_number, m.line_content)
68 })
69 .collect();
70 ToolResult::success(output.join("\n"))
71 }
72 }
73 Err(e) => ToolResult::error(format!("Search failed: {}", e)),
74 }
75 }
76}