steer_tools/tools/
glob.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::path::Path;
4use steer_macros::tool;
5
6use crate::result::GlobResult;
7use crate::{ExecutionContext, ToolError};
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
10pub struct GlobParams {
11    /// The glob pattern to match files against
12    pub pattern: String,
13    /// Optional directory to search in. Defaults to the current working directory.
14    pub path: Option<String>,
15}
16
17tool! {
18    GlobTool {
19        params: GlobParams,
20        output: GlobResult,
21        variant: Glob,
22        description: r#"Fast file pattern matching tool that works with any codebase size.
23- Supports glob patterns like "**/*.js" or "src/**/*.ts"
24- Returns matching file paths sorted by modification time
25- Use this tool when you need to find files by name patterns"#,
26        name: "glob",
27        require_approval: false
28    }
29
30    async fn run(
31        _tool: &GlobTool,
32        params: GlobParams,
33        context: &ExecutionContext,
34    ) -> Result<GlobResult, ToolError> {
35        if context.is_cancelled() {
36            return Err(ToolError::Cancelled(GLOB_TOOL_NAME.to_string()));
37        }
38
39        let search_path = params.path.as_deref().unwrap_or(".");
40        let base_path = if Path::new(search_path).is_absolute() {
41            Path::new(search_path).to_path_buf()
42        } else {
43            context.working_directory.join(search_path)
44        };
45
46        let glob_pattern = if base_path.to_string_lossy() == "." {
47            params.pattern.clone()
48        } else {
49            format!("{}/{}", base_path.display(), params.pattern)
50        };
51
52        let mut results = Vec::new();
53        match glob::glob(&glob_pattern) {
54            Ok(paths) => {
55                for entry in paths {
56                    if context.is_cancelled() {
57                        return Err(ToolError::Cancelled(GLOB_TOOL_NAME.to_string()));
58                    }
59
60                    match entry {
61                        Ok(path) => {
62                            results.push(path.display().to_string());
63                        }
64                        Err(e) => {
65                            return Err(ToolError::execution(
66                                GLOB_TOOL_NAME,
67                                format!("Error matching glob pattern '{glob_pattern}': {e}"),
68                            ));
69                        }
70                    }
71                }
72            }
73            Err(e) => {
74                return Err(ToolError::execution(
75                    GLOB_TOOL_NAME,
76                    format!("Invalid glob pattern '{glob_pattern}': {e}"),
77                ));
78            }
79        }
80
81        // Sort results for consistent output
82        results.sort();
83        Ok(GlobResult {
84            matches: results,
85            pattern: params.pattern,
86        })
87    }
88}