Skip to main content

cersei_tools/
grep_tool.rs

1//! Grep tool: search file contents with regex.
2
3use 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}