Skip to main content

cersei_tools/
glob_tool.rs

1//! Glob tool: find files by pattern.
2
3use super::*;
4use crate::tool_primitives::search as psearch;
5use serde::Deserialize;
6
7pub struct GlobTool;
8
9#[async_trait]
10impl Tool for GlobTool {
11    fn name(&self) -> &str {
12        "Glob"
13    }
14    fn description(&self) -> &str {
15        "Find files matching a glob pattern."
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": "Glob pattern (e.g. **/*.rs)" },
29                "path": { "type": "string", "description": "Directory to search in" },
30                "limit": { "type": "integer", "description": "Max results to return (default 200)" }
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            limit: Option<usize>,
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 max_results = input.limit.unwrap_or(200);
50
51        let base_dir = input
52            .path
53            .as_ref()
54            .map(PathBuf::from)
55            .unwrap_or_else(|| ctx.working_dir.clone());
56
57        match psearch::glob(&input.pattern, &base_dir).await {
58            Ok(mut paths) => {
59                paths.sort();
60                if paths.is_empty() {
61                    ToolResult::success("No files matched the pattern.")
62                } else {
63                    let total = paths.len();
64                    let truncated = total > max_results;
65                    let output: Vec<String> = paths
66                        .iter()
67                        .take(max_results)
68                        .map(|p| p.display().to_string())
69                        .collect();
70                    let mut result = output.join("\n");
71                    if truncated {
72                        result.push_str(&format!(
73                            "\n\n[Showing {max_results} of {total} matches. Use a more specific pattern to narrow results.]"
74                        ));
75                    }
76                    ToolResult::success(result)
77                }
78            }
79            Err(e) => ToolResult::error(format!("Glob failed: {}", e)),
80        }
81    }
82}