1package tools
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "github.com/charmbracelet/crush/internal/fsext"
10)
11
12const (
13 GlobToolName = "glob"
14 globDescription = `Fast file pattern matching tool that finds files by name and pattern, returning matching paths sorted by modification time (newest first).
15
16WHEN TO USE THIS TOOL:
17- Use when you need to find files by name patterns or extensions
18- Great for finding specific file types across a directory structure
19- Useful for discovering files that match certain naming conventions
20
21HOW TO USE:
22- Provide a glob pattern to match against file paths
23- Optionally specify a starting directory (defaults to current working directory)
24- Results are sorted with most recently modified files first
25
26GLOB PATTERN SYNTAX:
27- '*' matches any sequence of non-separator characters
28- '**' matches any sequence of characters, including separators
29- '?' matches any single non-separator character
30- '[...]' matches any character in the brackets
31- '[!...]' matches any character not in the brackets
32
33COMMON PATTERN EXAMPLES:
34- '*.js' - Find all JavaScript files in the current directory
35- '**/*.js' - Find all JavaScript files in any subdirectory
36- 'src/**/*.{ts,tsx}' - Find all TypeScript files in the src directory
37- '*.{html,css,js}' - Find all HTML, CSS, and JS files
38
39LIMITATIONS:
40- Results are limited to 100 files (newest first)
41- Does not search file contents (use Grep tool for that)
42- Hidden files (starting with '.') are skipped
43
44WINDOWS NOTES:
45- Path separators are handled automatically (both / and \ work)
46- Uses built-in Go implementation and bmatcuk/doublestar/v4 for globbing
47
48TIPS:
49- Patterns should use forward slashes (/) for cross-platform compatibility
50- For the most useful results, combine with the Grep tool: first find files with Glob, then search their contents with Grep
51- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
52- Always check if results are truncated and refine your search pattern if needed`
53)
54
55type GlobParams struct {
56 Pattern string `json:"pattern"`
57 Path string `json:"path"`
58}
59
60type GlobResponseMetadata struct {
61 NumberOfFiles int `json:"number_of_files"`
62 Truncated bool `json:"truncated"`
63}
64
65type globTool struct {
66 workingDir string
67}
68
69func NewGlobTool(workingDir string) BaseTool {
70 return &globTool{
71 workingDir: workingDir,
72 }
73}
74
75func (g *globTool) Name() string {
76 return GlobToolName
77}
78
79func (g *globTool) Info() ToolInfo {
80 return ToolInfo{
81 Name: GlobToolName,
82 Description: globDescription,
83 Parameters: map[string]any{
84 "pattern": map[string]any{
85 "type": "string",
86 "description": "The glob pattern to match files against",
87 },
88 "path": map[string]any{
89 "type": "string",
90 "description": "The directory to search in. Defaults to the current working directory.",
91 },
92 },
93 Required: []string{"pattern"},
94 }
95}
96
97func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
98 var params GlobParams
99 if err := json.Unmarshal([]byte(call.Input), ¶ms); err != nil {
100 return NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
101 }
102
103 if params.Pattern == "" {
104 return NewTextErrorResponse("pattern is required"), nil
105 }
106
107 searchPath := params.Path
108 if searchPath == "" {
109 searchPath = g.workingDir
110 }
111
112 files, truncated, err := globFiles(params.Pattern, searchPath, 100)
113 if err != nil {
114 return ToolResponse{}, fmt.Errorf("error finding files: %w", err)
115 }
116
117 var output string
118 if len(files) == 0 {
119 output = "No files found"
120 } else {
121 output = strings.Join(files, "\n")
122 if truncated {
123 output += "\n\n(Results are truncated. Consider using a more specific path or pattern.)"
124 }
125 }
126
127 return WithResponseMetadata(
128 NewTextResponse(output),
129 GlobResponseMetadata{
130 NumberOfFiles: len(files),
131 Truncated: truncated,
132 },
133 ), nil
134}
135
136func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
137 return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
138}