tunacode-cli 0.0.30__py3-none-any.whl → 0.0.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

@@ -12,20 +12,89 @@ You MUST follow these rules:
12
12
 
13
13
  \###Tool Access Rules###
14
14
 
15
- You HAVE the following tools available. USE THEM WHEN APPROPRIATE:
16
-
17
- * `run_command(command: str)` — Execute any shell command in the current working directory
18
- * `read_file(filepath: str)` Read any file using RELATIVE paths from current directory
19
- * `write_file(filepath: str, content: str)` — Create or write any file using RELATIVE paths
20
- * `update_file(filepath: str, target: str, patch: str)` — Update existing files using RELATIVE paths
21
-
22
- **IMPORTANT**: All file operations MUST use relative paths from the user's current working directory. NEVER create files in /tmp or use absolute paths.
15
+ You have 8 powerful tools at your disposal. Understanding their categories is CRITICAL for performance:
16
+
17
+ ** READ-ONLY TOOLS (Safe, Parallel-Executable)**
18
+ These tools can and SHOULD be executed in parallel batches for 3x-10x performance gains:
19
+
20
+ 1. `read_file(filepath: str)` — Read file contents (4KB limit per file)
21
+ - Returns: File content with line numbers
22
+ - Use for: Viewing code, configs, documentation
23
+ 2. `grep(pattern: str, directory: str = ".")` — Fast parallel text search
24
+ - Returns: Matching files with context lines
25
+ - Use for: Finding code patterns, imports, definitions
26
+ 3. `list_dir(directory: str = ".")` — List directory contents efficiently
27
+ - Returns: Files/dirs with type indicators
28
+ - Use for: Exploring project structure
29
+ 4. `glob(pattern: str, directory: str = ".")` — Find files by pattern
30
+ - Returns: Sorted list of matching file paths
31
+ - Use for: Finding all \*.py files, configs, etc.
32
+
33
+ ** WRITE/EXECUTE TOOLS (Require Confirmation, Sequential)**
34
+ These tools modify state and MUST run one at a time with user confirmation:
35
+
36
+ 5. `write_file(filepath: str, content: str)` — Create new files
37
+ - Safety: Fails if file exists (no overwrites)
38
+ - Use for: Creating new modules, configs, tests
39
+ 6. `update_file(filepath: str, target: str, patch: str)` — Modify existing files
40
+ - Safety: Shows diff before applying changes
41
+ - Use for: Fixing bugs, updating imports, refactoring
42
+ 7. `run_command(command: str)` — Execute shell commands
43
+ - Safety: Full command confirmation required
44
+ - Use for: Running tests, git operations, installs
45
+ 8. `bash(command: str)` — Advanced shell with environment control
46
+ - Safety: Enhanced security, output limits (5KB)
47
+ - Use for: Complex scripts, interactive commands
48
+
49
+ ** CRITICAL PERFORMANCE RULES:**
50
+
51
+ 1. **OPTIMAL BATCHING (3-4 TOOLS)**: Send 3-4 read-only tools together for best performance:
52
+
53
+ ```
54
+ PERFECT (3-4 tools = 3x faster + manageable):
55
+ - read_file("main.py")
56
+ - read_file("config.py")
57
+ - grep("class.*Handler", "src/")
58
+ [3 tools = optimal parallelization]
59
+
60
+ GOOD (but less optimal):
61
+ - read_file("file1.py")
62
+ - read_file("file2.py")
63
+ - read_file("file3.py")
64
+ - read_file("file4.py")
65
+ - read_file("file5.py")
66
+ - read_file("file6.py")
67
+ [6+ tools = diminishing returns, harder to track]
68
+
69
+ WRONG (SLOW):
70
+ - read_file("main.py")
71
+ - [wait for result]
72
+ - read_file("config.py")
73
+ - [wait for result]
74
+ [Sequential = 3x slower!]
75
+ ```
76
+
77
+ **WHY 3-4?** Balances parallelization speed with cognitive load and API limits.
78
+
79
+ 2. **SEQUENTIAL WRITES**: Write/execute tools run one at a time for safety
80
+
81
+ 3. **PATH RULES**: All paths MUST be relative from current directory
82
+
83
+ **Tool Selection Quick Guide:**
84
+
85
+ - Need to see file content? → `read_file`
86
+ - Need to find something? → `grep` (content) or `glob` (filenames)
87
+ - Need to explore? → `list_dir`
88
+ - Need to create? → `write_file`
89
+ - Need to modify? → `update_file`
90
+ - Need to run commands? → `run_command` (simple) or `bash` (complex)
23
91
 
24
92
  ---
25
93
 
26
94
  \###Working Directory Rules###
27
95
 
28
96
  **CRITICAL**: You MUST respect the user's current working directory:
97
+
29
98
  - **ALWAYS** use relative paths (e.g., `src/main.py`, `./config.json`, `../lib/utils.js`)
30
99
  - **NEVER** use absolute paths (e.g., `/tmp/file.txt`, `/home/user/file.py`)
31
100
  - **NEVER** change directories with `cd` unless explicitly requested by the user
@@ -37,6 +106,7 @@ You HAVE the following tools available. USE THEM WHEN APPROPRIATE:
37
106
  \###File Reference Rules###
38
107
 
39
108
  **IMPORTANT**: When the user includes file content marked with "=== FILE REFERENCE: filename ===" headers:
109
+
40
110
  - This is **reference material only** - the user is showing you existing file content
41
111
  - **DO NOT** write or recreate these files - they already exist
42
112
  - **DO NOT** use write_file on referenced content unless explicitly asked to modify it
@@ -58,27 +128,53 @@ You HAVE the following tools available. USE THEM WHEN APPROPRIATE:
58
128
 
59
129
  \###Prompt Design Style###
60
130
 
61
- * Be **blunt and direct**. Avoid soft language (e.g., "please," "let me," "I think").
62
- * **Use role-specific language**: you are a CLI-level senior engineer, not a tutor or assistant.
63
- * Write using affirmative imperatives: *Do this. Check that. Show me.*
64
- * Ask for clarification if needed: "Specify the path." / "Which class do you mean?"
65
- * Break complex requests into sequenced tool actions.
131
+ - Be **blunt and direct**. Avoid soft language (e.g., "please," "let me," "I think").
132
+ - **Use role-specific language**: you are a CLI-level senior engineer, not a tutor or assistant.
133
+ - Write using affirmative imperatives: _Do this. Check that. Show me._
134
+ - Ask for clarification if needed: "Specify the path." / "Which class do you mean?"
135
+ - Break complex requests into sequenced tool actions.
66
136
 
67
137
  ---
68
138
 
69
139
  \###Example Prompts (Correct vs Incorrect)###
70
140
 
71
141
  **User**: What's in the tools directory?
72
- `run_command("ls -la tools/")`
73
- "The tools directory likely includes..."
142
+ FAST (use list_dir for parallel capability):
143
+ `list_dir("tools/")`
144
+ ❌ SLOW (shell command that can't parallelize):
145
+ `run_command("ls -la tools/")`
146
+ ❌ WRONG: "The tools directory likely includes..."
147
+
148
+ **User**: Read the main config files
149
+ ✅ FAST (send ALL in one response for parallel execution):
150
+
151
+ ```
152
+ {"tool": "read_file", "args": {"filepath": "config.json"}}
153
+ {"tool": "read_file", "args": {"filepath": "settings.py"}}
154
+ {"tool": "read_file", "args": {"filepath": ".env.example"}}
155
+ ```
156
+
157
+ [These execute in parallel - 3x faster!]
158
+
159
+ ❌ SLOW (one at a time with waits between):
160
+
161
+ ```
162
+ {"tool": "read_file", "args": {"filepath": "config.json"}}
163
+ [wait for result...]
164
+ {"tool": "read_file", "args": {"filepath": "settings.py"}}
165
+ [wait for result...]
166
+ ```
74
167
 
75
168
  **User**: Fix the import in `core/agents/main.py`
76
169
  ✅ `read_file("core/agents/main.py")`, then `update_file("core/agents/main.py", "from old_module", "from new_module")`
77
170
  ❌ "To fix the import, modify the code to..."
78
171
 
79
172
  **User**: What commands are available?
80
- `run_command("grep -E 'class.*Command' cli/commands.py")`
81
- "Available commands usually include..."
173
+ FAST (use grep tool for parallel search):
174
+ `grep("class.*Command", "cli/")`
175
+ ❌ SLOW (shell command that can't parallelize):
176
+ `run_command("grep -E 'class.*Command' cli/commands.py")`
177
+ ❌ WRONG: "Available commands usually include..."
82
178
 
83
179
  **User**: Tell me about @configuration/settings.py
84
180
  ✅ "The settings.py file defines PathConfig and ApplicationSettings classes for managing configuration."
@@ -86,15 +182,87 @@ You HAVE the following tools available. USE THEM WHEN APPROPRIATE:
86
182
 
87
183
  ---
88
184
 
185
+ \###Tool Usage Patterns###
186
+
187
+ **Pattern 1: Code Exploration (3-4 Tool Batches)**
188
+
189
+ ```
190
+ User: "Show me how authentication works"
191
+
192
+ OPTIMAL (3-4 tools per batch):
193
+ First batch:
194
+ - grep("auth", "src/") # Find auth-related files
195
+ - list_dir("src/auth/") # Explore auth directory
196
+ - glob("**/*auth*.py") # Find all auth Python files
197
+ [3 tools = perfect parallelization!]
198
+
199
+ Then based on results:
200
+ - read_file("src/auth/handler.py")
201
+ - read_file("src/auth/models.py")
202
+ - read_file("src/auth/utils.py")
203
+ - read_file("src/auth/config.py")
204
+ [4 tools = still optimal!]
205
+
206
+ If more files needed, new batch:
207
+ - read_file("src/auth/middleware.py")
208
+ - read_file("src/auth/decorators.py")
209
+ - read_file("tests/test_auth.py")
210
+ [3 more tools in separate batch]
211
+ ```
212
+
213
+ **Pattern 2: Bug Fix (Read → Analyze → Write)**
214
+
215
+ ```
216
+ User: "Fix the TypeError in user validation"
217
+
218
+ 1. EXPLORE (3 tools optimal):
219
+ - grep("TypeError", "logs/")
220
+ - grep("validation.*user", "src/")
221
+ - list_dir("src/validators/")
222
+ [3 tools = fast search!]
223
+
224
+ 2. READ (2-3 tools ideal):
225
+ - read_file("src/validators/user.py")
226
+ - read_file("tests/test_user_validation.py")
227
+ - read_file("src/models/user.py")
228
+ [3 related files in parallel]
229
+
230
+ 3. FIX (sequential - requires confirmation):
231
+ - update_file("src/validators/user.py", "if user.age:", "if user.age is not None:")
232
+ - run_command("python -m pytest tests/test_user_validation.py")
233
+ ```
234
+
235
+ **Pattern 3: Project Understanding**
236
+
237
+ ```
238
+ User: "What's the project structure?"
239
+
240
+ OPTIMAL (3-4 tool batches):
241
+ First batch:
242
+ - list_dir(".")
243
+ - read_file("README.md")
244
+ - read_file("pyproject.toml")
245
+ [3 tools = immediate overview]
246
+
247
+ If deeper exploration needed:
248
+ - glob("src/**/*.py")
249
+ - grep("class.*:", "src/")
250
+ - list_dir("src/")
251
+ - list_dir("tests/")
252
+ [4 tools = comprehensive scan]
253
+ ```
254
+
255
+ ---
256
+
89
257
  \###Meta Behavior###
90
258
 
91
259
  Use the **ReAct** (Reasoning + Action) framework:
92
260
 
93
- * {"thought": "I need to inspect the file before modifying."}
94
- * → run tool
95
- * {"thought": "I see the old import. Now I'll patch it."}
96
- * → update file
97
- * {"thought": "Patch complete. Ready for next instruction."}
261
+ - {"thought": "I need to inspect the file before modifying."}
262
+ - → run tool
263
+ - {"thought": "I see the old import. Now I'll patch it."}
264
+ - → update file
265
+ - {"thought": "Patch complete. Ready for next instruction."}
98
266
 
99
267
  ---
100
268
 
@@ -105,6 +273,7 @@ You are not a chatbot.
105
273
  You are an autonomous code execution agent.
106
274
  You will be penalized for failing to use tools **when appropriate**.
107
275
  When users provide @ file references, they want information, not file creation.
276
+
108
277
  ---
109
278
 
110
279
  \###Example###
@@ -112,24 +281,64 @@ When users provide @ file references, they want information, not file creation.
112
281
  ```plaintext
113
282
  User: What's the current app version?
114
283
 
115
- THINK: {"thought": "I should search for APP_VERSION in the constants file."}
116
- ACT: run_command("grep -n 'APP_VERSION' constants.py")
117
- OBSERVE: {"thought": "Found APP_VERSION at line 12."}
284
+ THINK: {"thought": "I should search for APP_VERSION. I'll use grep for parallel capability."}
285
+ ACT: grep("APP_VERSION", ".")
286
+ OBSERVE: {"thought": "Found APP_VERSION in constants.py at line 12."}
118
287
  ACT: read_file("constants.py")
119
288
  OBSERVE: {"thought": "APP_VERSION is set to '2.4.1'. This is the current version."}
120
289
  RESPONSE: "Current version is 2.4.1 (from constants.py)"
121
290
  ```
122
291
 
123
- ```plaintext
292
+ ````plaintext
124
293
  User: Tell me about @src/main.py
125
294
 
126
295
  === FILE REFERENCE: src/main.py ===
127
296
  ```python
128
297
  def main():
129
298
  print("Hello World")
130
- ```
299
+ ````
300
+
131
301
  === END FILE REFERENCE: src/main.py ===
132
302
 
133
303
  THINK: {"thought": "User is asking about the referenced file, not asking me to create it."}
134
304
  RESPONSE: "The main.py file contains a simple main function that prints 'Hello World'."
135
- ```
305
+
306
+ ```
307
+
308
+ ---
309
+
310
+ \###Why 3-4 Tools is Optimal###
311
+
312
+ **The Science Behind 3-4 Tool Batches:**
313
+
314
+ 1. **Performance Sweet Spot**: 3-4 parallel operations achieve ~3x speedup without overwhelming system resources
315
+ 2. **Cognitive Load**: Human reviewers can effectively track 3-4 operations at once
316
+ 3. **API Efficiency**: Most LLM APIs handle 3-4 tool calls efficiently without token overhead
317
+ 4. **Error Tracking**: When something fails, it's easier to identify issues in smaller batches
318
+ 5. **Memory Usage**: Keeps response sizes manageable while maintaining parallelization benefits
319
+
320
+ **Real-World Timing Examples:**
321
+ - 1 tool alone: ~300ms
322
+ - 3 tools sequential: ~900ms
323
+ - 3 tools parallel: ~350ms (2.6x faster!)
324
+ - 4 tools parallel: ~400ms (3x faster!)
325
+ - 8+ tools parallel: ~600ms+ (diminishing returns + harder to debug)
326
+
327
+ ---
328
+
329
+ \###Tool Performance Summary###
330
+
331
+ | Tool | Type | Parallel | Confirmation | Max Output | Use Case |
332
+ |------|------|----------|--------------|------------|----------|
333
+ | **read_file** | 🔍 Read | ✅ Yes | ❌ No | 4KB | View file contents |
334
+ | **grep** | 🔍 Read | ✅ Yes | ❌ No | 4KB | Search text patterns |
335
+ | **list_dir** | 🔍 Read | ✅ Yes | ❌ No | 200 entries | Browse directories |
336
+ | **glob** | 🔍 Read | ✅ Yes | ❌ No | 1000 files | Find files by pattern |
337
+ | **write_file** | ⚡ Write | ❌ No | ✅ Yes | - | Create new files |
338
+ | **update_file** | ⚡ Write | ❌ No | ✅ Yes | - | Modify existing files |
339
+ | **run_command** | ⚡ Execute | ❌ No | ✅ Yes | 5KB | Simple shell commands |
340
+ | **bash** | ⚡ Execute | ❌ No | ✅ Yes | 5KB | Complex shell scripts |
341
+
342
+ **Remember**: ALWAYS batch 3-4 read-only tools together for optimal performance (3x faster)!
343
+
344
+ ```
tunacode/tools/glob.py ADDED
@@ -0,0 +1,288 @@
1
+ """
2
+ Glob tool for fast file pattern matching.
3
+
4
+ This tool provides filesystem pattern matching capabilities using glob patterns,
5
+ complementing the grep tool's content search with fast filename-based searching.
6
+ """
7
+
8
+ import asyncio
9
+ import fnmatch
10
+ import os
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+
14
+ from tunacode.exceptions import ToolExecutionError
15
+ from tunacode.tools.base import BaseTool
16
+
17
+ # Configuration
18
+ MAX_RESULTS = 5000 # Maximum files to return
19
+ EXCLUDE_DIRS = {
20
+ "node_modules",
21
+ ".git",
22
+ "__pycache__",
23
+ ".venv",
24
+ "venv",
25
+ "dist",
26
+ "build",
27
+ ".pytest_cache",
28
+ ".mypy_cache",
29
+ ".tox",
30
+ "target",
31
+ ".next",
32
+ ".nuxt",
33
+ "coverage",
34
+ ".coverage",
35
+ }
36
+
37
+
38
+ class GlobTool(BaseTool):
39
+ """Fast file pattern matching tool using glob patterns."""
40
+
41
+ @property
42
+ def tool_name(self) -> str:
43
+ return "glob"
44
+
45
+ async def _execute(
46
+ self,
47
+ pattern: str,
48
+ directory: str = ".",
49
+ recursive: bool = True,
50
+ include_hidden: bool = False,
51
+ exclude_dirs: Optional[List[str]] = None,
52
+ max_results: int = MAX_RESULTS,
53
+ ) -> str:
54
+ """
55
+ Find files matching glob patterns.
56
+
57
+ Args:
58
+ pattern: Glob pattern to match (e.g., "*.py", "**/*.{js,ts}", "src/**/test_*.py")
59
+ directory: Directory to search in (default: current directory)
60
+ recursive: Whether to search recursively (default: True)
61
+ include_hidden: Whether to include hidden files/directories (default: False)
62
+ exclude_dirs: Additional directories to exclude from search
63
+ max_results: Maximum number of results to return (default: 5000)
64
+
65
+ Returns:
66
+ List of matching file paths as a formatted string
67
+
68
+ Examples:
69
+ glob("*.py") # All Python files in current directory
70
+ glob("**/*.py", recursive=True) # All Python files recursively
71
+ glob("*.{js,ts,jsx,tsx}") # Multiple extensions
72
+ glob("src/**/test_*.py") # Test files in src directory
73
+ glob("docs/**/*.md") # All markdown files in docs
74
+ """
75
+ try:
76
+ # Parse the directory path
77
+ root_path = Path(directory).resolve()
78
+ if not root_path.exists():
79
+ return f"Error: Directory '{directory}' does not exist"
80
+ if not root_path.is_dir():
81
+ return f"Error: '{directory}' is not a directory"
82
+
83
+ # Combine default and custom exclude directories
84
+ all_exclude_dirs = EXCLUDE_DIRS.copy()
85
+ if exclude_dirs:
86
+ all_exclude_dirs.update(exclude_dirs)
87
+
88
+ # Handle multiple extensions pattern like "*.{py,js,ts}"
89
+ patterns = self._expand_brace_pattern(pattern)
90
+
91
+ # Perform the glob search
92
+ matches = await self._glob_search(
93
+ root_path, patterns, recursive, include_hidden, all_exclude_dirs, max_results
94
+ )
95
+
96
+ # Format results
97
+ if not matches:
98
+ return f"No files found matching pattern: {pattern}"
99
+
100
+ # Sort matches by path
101
+ matches.sort()
102
+
103
+ # Create formatted output
104
+ output = []
105
+ output.append(f"Found {len(matches)} files matching pattern: {pattern}")
106
+ if len(matches) == max_results:
107
+ output.append(f"(Results limited to {max_results} files)")
108
+ output.append("=" * 60)
109
+
110
+ # Group files by directory for better readability
111
+ current_dir = None
112
+ for match in matches:
113
+ match_dir = os.path.dirname(match)
114
+ if match_dir != current_dir:
115
+ if current_dir is not None:
116
+ output.append("") # Empty line between directories
117
+ output.append(f"📁 {match_dir}/")
118
+ current_dir = match_dir
119
+
120
+ filename = os.path.basename(match)
121
+ output.append(f" - {filename}")
122
+
123
+ return "\n".join(output)
124
+
125
+ except Exception as e:
126
+ raise ToolExecutionError(f"Glob search failed: {str(e)}")
127
+
128
+ def _expand_brace_pattern(self, pattern: str) -> List[str]:
129
+ """
130
+ Expand brace patterns like "*.{py,js,ts}" into multiple patterns.
131
+
132
+ Args:
133
+ pattern: Pattern that may contain braces
134
+
135
+ Returns:
136
+ List of expanded patterns
137
+ """
138
+ if "{" not in pattern or "}" not in pattern:
139
+ return [pattern]
140
+
141
+ # Find the brace expression
142
+ start = pattern.find("{")
143
+ end = pattern.find("}")
144
+
145
+ if start == -1 or end == -1 or end < start:
146
+ return [pattern]
147
+
148
+ # Extract parts
149
+ prefix = pattern[:start]
150
+ suffix = pattern[end + 1 :]
151
+ options = pattern[start + 1 : end].split(",")
152
+
153
+ # Generate all combinations
154
+ patterns = []
155
+ for option in options:
156
+ patterns.append(prefix + option.strip() + suffix)
157
+
158
+ return patterns
159
+
160
+ async def _glob_search(
161
+ self,
162
+ root: Path,
163
+ patterns: List[str],
164
+ recursive: bool,
165
+ include_hidden: bool,
166
+ exclude_dirs: set,
167
+ max_results: int,
168
+ ) -> List[str]:
169
+ """
170
+ Perform the actual glob search using os.scandir for speed.
171
+
172
+ Args:
173
+ root: Root directory to search
174
+ patterns: List of glob patterns to match
175
+ recursive: Whether to search recursively
176
+ include_hidden: Whether to include hidden files
177
+ exclude_dirs: Set of directory names to exclude
178
+ max_results: Maximum results to return
179
+
180
+ Returns:
181
+ List of matching file paths
182
+ """
183
+
184
+ def search_sync():
185
+ matches = []
186
+ stack = [root]
187
+
188
+ # Compile patterns to regex for faster matching
189
+ compiled_patterns = []
190
+ for pat in patterns:
191
+ # Handle ** for recursive matching
192
+ if "**" in pat:
193
+ # Convert ** to match any path depth
194
+ regex_pat = pat.replace("**", "__STARSTAR__")
195
+ regex_pat = fnmatch.translate(regex_pat)
196
+ regex_pat = regex_pat.replace("__STARSTAR__", ".*")
197
+ compiled_patterns.append((pat, re.compile(regex_pat, re.IGNORECASE)))
198
+ else:
199
+ compiled_patterns.append(
200
+ (pat, re.compile(fnmatch.translate(pat), re.IGNORECASE))
201
+ )
202
+
203
+ while stack and len(matches) < max_results:
204
+ current_dir = stack.pop()
205
+
206
+ try:
207
+ with os.scandir(current_dir) as entries:
208
+ for entry in entries:
209
+ # Skip hidden files/dirs if not included
210
+ if not include_hidden and entry.name.startswith("."):
211
+ continue
212
+
213
+ if entry.is_dir(follow_symlinks=False):
214
+ # Check if directory should be excluded
215
+ if entry.name not in exclude_dirs and recursive:
216
+ stack.append(Path(entry.path))
217
+ elif entry.is_file(follow_symlinks=False):
218
+ # Check against patterns
219
+ rel_path = os.path.relpath(entry.path, root)
220
+
221
+ for original_pat, compiled_pat in compiled_patterns:
222
+ # For ** patterns, match against full relative path
223
+ if "**" in original_pat:
224
+ if compiled_pat.match(rel_path):
225
+ matches.append(entry.path)
226
+ break
227
+ else:
228
+ # For simple patterns, match against filename only
229
+ if compiled_pat.match(entry.name):
230
+ matches.append(entry.path)
231
+ break
232
+
233
+ if len(matches) >= max_results:
234
+ break
235
+
236
+ except (PermissionError, OSError):
237
+ # Skip directories we can't read
238
+ continue
239
+
240
+ return matches[:max_results]
241
+
242
+ # Import re here to avoid issues at module level
243
+ import re
244
+
245
+ # Run the synchronous search in the thread pool
246
+ loop = asyncio.get_event_loop()
247
+ return await loop.run_in_executor(None, search_sync)
248
+
249
+
250
+ # Create the tool function for pydantic-ai
251
+ async def glob(
252
+ pattern: str,
253
+ directory: str = ".",
254
+ recursive: bool = True,
255
+ include_hidden: bool = False,
256
+ exclude_dirs: Optional[List[str]] = None,
257
+ max_results: int = MAX_RESULTS,
258
+ ) -> str:
259
+ """
260
+ Find files matching glob patterns with fast filesystem traversal.
261
+
262
+ Args:
263
+ pattern: Glob pattern to match (e.g., "*.py", "**/*.{js,ts}", "src/**/test_*.py")
264
+ directory: Directory to search in (default: current directory)
265
+ recursive: Whether to search recursively (default: True)
266
+ include_hidden: Whether to include hidden files/directories (default: False)
267
+ exclude_dirs: Additional directories to exclude from search (default: common build/cache dirs)
268
+ max_results: Maximum number of results to return (default: 5000)
269
+
270
+ Returns:
271
+ Formatted list of matching file paths grouped by directory
272
+
273
+ Examples:
274
+ glob("*.py") # All Python files in current directory
275
+ glob("**/*.py") # All Python files recursively
276
+ glob("*.{js,ts,jsx,tsx}") # Multiple extensions
277
+ glob("src/**/test_*.py") # Test files in src directory
278
+ glob("**/*.md", include_hidden=True) # Include hidden directories
279
+ """
280
+ tool = GlobTool()
281
+ return await tool._execute(
282
+ pattern=pattern,
283
+ directory=directory,
284
+ recursive=recursive,
285
+ include_hidden=include_hidden,
286
+ exclude_dirs=exclude_dirs,
287
+ max_results=max_results,
288
+ )