1use anyhow::{Context, Result};
2use serde_json::Value;
3use std::path::Path;
4
5use super::Tool;
6
7pub struct GlobTool;
8
9impl Tool for GlobTool {
10 fn name(&self) -> &str {
11 "glob"
12 }
13
14 fn description(&self) -> &str {
15 "Find files matching a glob pattern. Returns file paths sorted by modification time (newest first). Use for locating files by name."
16 }
17
18 fn input_schema(&self) -> Value {
19 serde_json::json!({
20 "type": "object",
21 "properties": {
22 "pattern": {
23 "type": "string",
24 "description": "Glob pattern (e.g. '**/*.rs', 'src/**/*.ts', '*.json')"
25 },
26 "path": {
27 "type": "string",
28 "description": "Base directory to search from (defaults to current directory)"
29 }
30 },
31 "required": ["pattern"]
32 })
33 }
34
35 fn execute(&self, input: Value) -> Result<String> {
36 let pattern = input["pattern"]
37 .as_str()
38 .context("Missing required parameter 'pattern'")?;
39 let base = input["path"].as_str().unwrap_or(".");
40 tracing::debug!("glob: {} in {}", pattern, base);
41
42 let full = if Path::new(pattern).is_absolute() {
43 pattern.to_string()
44 } else {
45 format!("{}/{}", base, pattern)
46 };
47
48 let mut entries: Vec<(String, std::time::SystemTime)> = Vec::new();
49 for path in glob::glob(&full).with_context(|| format!("invalid glob: {}", pattern))? {
50 if entries.len() >= 200 {
51 break;
52 }
53 if let Ok(p) = path {
54 let mtime = p
55 .metadata()
56 .and_then(|m| m.modified())
57 .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
58 entries.push((p.display().to_string(), mtime));
59 }
60 }
61
62 entries.sort_by(|a, b| b.1.cmp(&a.1));
63
64 if entries.is_empty() {
65 Ok(format!("No files matching '{}'", pattern))
66 } else {
67 let count = entries.len();
68 let paths: Vec<String> = entries.into_iter().map(|(p, _)| p).collect();
69 let mut output = paths.join("\n");
70 if count >= 200 {
71 output.push_str("\n... (truncated at 200 results)");
72 }
73 Ok(output)
74 }
75 }
76}