tunacode-cli 0.0.55__py3-none-any.whl → 0.0.78.6__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.

Files changed (114) hide show
  1. tunacode/cli/commands/__init__.py +2 -2
  2. tunacode/cli/commands/implementations/__init__.py +2 -3
  3. tunacode/cli/commands/implementations/command_reload.py +48 -0
  4. tunacode/cli/commands/implementations/debug.py +2 -2
  5. tunacode/cli/commands/implementations/development.py +10 -8
  6. tunacode/cli/commands/implementations/model.py +357 -29
  7. tunacode/cli/commands/implementations/quickstart.py +43 -0
  8. tunacode/cli/commands/implementations/system.py +96 -3
  9. tunacode/cli/commands/implementations/template.py +0 -2
  10. tunacode/cli/commands/registry.py +139 -5
  11. tunacode/cli/commands/slash/__init__.py +32 -0
  12. tunacode/cli/commands/slash/command.py +157 -0
  13. tunacode/cli/commands/slash/loader.py +135 -0
  14. tunacode/cli/commands/slash/processor.py +294 -0
  15. tunacode/cli/commands/slash/types.py +93 -0
  16. tunacode/cli/commands/slash/validator.py +400 -0
  17. tunacode/cli/main.py +23 -2
  18. tunacode/cli/repl.py +217 -190
  19. tunacode/cli/repl_components/command_parser.py +38 -4
  20. tunacode/cli/repl_components/error_recovery.py +85 -4
  21. tunacode/cli/repl_components/output_display.py +12 -1
  22. tunacode/cli/repl_components/tool_executor.py +1 -1
  23. tunacode/configuration/defaults.py +12 -3
  24. tunacode/configuration/key_descriptions.py +284 -0
  25. tunacode/configuration/settings.py +0 -1
  26. tunacode/constants.py +12 -40
  27. tunacode/core/agents/__init__.py +43 -2
  28. tunacode/core/agents/agent_components/__init__.py +7 -0
  29. tunacode/core/agents/agent_components/agent_config.py +249 -55
  30. tunacode/core/agents/agent_components/agent_helpers.py +43 -13
  31. tunacode/core/agents/agent_components/node_processor.py +179 -139
  32. tunacode/core/agents/agent_components/response_state.py +123 -6
  33. tunacode/core/agents/agent_components/state_transition.py +116 -0
  34. tunacode/core/agents/agent_components/streaming.py +296 -0
  35. tunacode/core/agents/agent_components/task_completion.py +19 -6
  36. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  37. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  38. tunacode/core/agents/main.py +522 -370
  39. tunacode/core/agents/main_legact.py +538 -0
  40. tunacode/core/agents/prompts.py +66 -0
  41. tunacode/core/agents/utils.py +29 -121
  42. tunacode/core/code_index.py +83 -29
  43. tunacode/core/setup/__init__.py +0 -2
  44. tunacode/core/setup/config_setup.py +110 -20
  45. tunacode/core/setup/config_wizard.py +230 -0
  46. tunacode/core/setup/coordinator.py +14 -5
  47. tunacode/core/state.py +16 -20
  48. tunacode/core/token_usage/usage_tracker.py +5 -3
  49. tunacode/core/tool_authorization.py +352 -0
  50. tunacode/core/tool_handler.py +67 -40
  51. tunacode/exceptions.py +119 -5
  52. tunacode/prompts/system.xml +751 -0
  53. tunacode/services/mcp.py +125 -7
  54. tunacode/setup.py +5 -25
  55. tunacode/tools/base.py +163 -0
  56. tunacode/tools/bash.py +110 -1
  57. tunacode/tools/glob.py +332 -34
  58. tunacode/tools/grep.py +179 -82
  59. tunacode/tools/grep_components/result_formatter.py +98 -4
  60. tunacode/tools/list_dir.py +132 -2
  61. tunacode/tools/prompts/bash_prompt.xml +72 -0
  62. tunacode/tools/prompts/glob_prompt.xml +45 -0
  63. tunacode/tools/prompts/grep_prompt.xml +98 -0
  64. tunacode/tools/prompts/list_dir_prompt.xml +31 -0
  65. tunacode/tools/prompts/react_prompt.xml +23 -0
  66. tunacode/tools/prompts/read_file_prompt.xml +54 -0
  67. tunacode/tools/prompts/run_command_prompt.xml +64 -0
  68. tunacode/tools/prompts/update_file_prompt.xml +53 -0
  69. tunacode/tools/prompts/write_file_prompt.xml +37 -0
  70. tunacode/tools/react.py +153 -0
  71. tunacode/tools/read_file.py +91 -0
  72. tunacode/tools/run_command.py +114 -0
  73. tunacode/tools/schema_assembler.py +167 -0
  74. tunacode/tools/update_file.py +94 -0
  75. tunacode/tools/write_file.py +86 -0
  76. tunacode/tools/xml_helper.py +83 -0
  77. tunacode/tutorial/__init__.py +9 -0
  78. tunacode/tutorial/content.py +98 -0
  79. tunacode/tutorial/manager.py +182 -0
  80. tunacode/tutorial/steps.py +124 -0
  81. tunacode/types.py +20 -27
  82. tunacode/ui/completers.py +434 -50
  83. tunacode/ui/config_dashboard.py +585 -0
  84. tunacode/ui/console.py +63 -11
  85. tunacode/ui/input.py +20 -3
  86. tunacode/ui/keybindings.py +7 -4
  87. tunacode/ui/model_selector.py +395 -0
  88. tunacode/ui/output.py +40 -19
  89. tunacode/ui/panels.py +212 -43
  90. tunacode/ui/path_heuristics.py +91 -0
  91. tunacode/ui/prompt_manager.py +5 -1
  92. tunacode/ui/tool_ui.py +33 -10
  93. tunacode/utils/api_key_validation.py +93 -0
  94. tunacode/utils/config_comparator.py +340 -0
  95. tunacode/utils/json_utils.py +206 -0
  96. tunacode/utils/message_utils.py +14 -4
  97. tunacode/utils/models_registry.py +593 -0
  98. tunacode/utils/ripgrep.py +332 -9
  99. tunacode/utils/text_utils.py +18 -1
  100. tunacode/utils/user_configuration.py +45 -0
  101. tunacode_cli-0.0.78.6.dist-info/METADATA +260 -0
  102. tunacode_cli-0.0.78.6.dist-info/RECORD +158 -0
  103. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +1 -2
  104. tunacode/cli/commands/implementations/todo.py +0 -217
  105. tunacode/context.py +0 -71
  106. tunacode/core/setup/git_safety_setup.py +0 -182
  107. tunacode/prompts/system.md +0 -731
  108. tunacode/tools/read_file_async_poc.py +0 -196
  109. tunacode/tools/todo.py +0 -349
  110. tunacode_cli-0.0.55.dist-info/METADATA +0 -322
  111. tunacode_cli-0.0.55.dist-info/RECORD +0 -126
  112. tunacode_cli-0.0.55.dist-info/top_level.txt +0 -1
  113. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  114. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
tunacode/tools/glob.py CHANGED
@@ -8,11 +8,16 @@ complementing the grep tool's content search with fast filename-based searching.
8
8
  import asyncio
9
9
  import fnmatch
10
10
  import os
11
+ import re
12
+ from enum import Enum
13
+ from functools import lru_cache
11
14
  from pathlib import Path
12
- from typing import List, Optional
15
+ from typing import Any, Dict, List, Optional, Set, Union
13
16
 
17
+ from tunacode.core.code_index import CodeIndex
14
18
  from tunacode.exceptions import ToolExecutionError
15
19
  from tunacode.tools.base import BaseTool
20
+ from tunacode.tools.xml_helper import load_parameters_schema_from_xml, load_prompt_from_xml
16
21
 
17
22
  # Configuration
18
23
  MAX_RESULTS = 5000 # Maximum files to return
@@ -35,13 +40,124 @@ EXCLUDE_DIRS = {
35
40
  }
36
41
 
37
42
 
43
+ class SortOrder(Enum):
44
+ """Sorting options for glob results."""
45
+
46
+ MODIFIED = "modified" # Sort by modification time (newest first)
47
+ SIZE = "size" # Sort by file size (largest first)
48
+ ALPHABETICAL = "alphabetical" # Sort alphabetically
49
+ DEPTH = "depth" # Sort by path depth (shallow first)
50
+
51
+
38
52
  class GlobTool(BaseTool):
39
53
  """Fast file pattern matching tool using glob patterns."""
40
54
 
55
+ def __init__(self):
56
+ """Initialize the glob tool."""
57
+ super().__init__()
58
+ self._code_index: Optional[CodeIndex] = None
59
+ self._gitignore_patterns: Optional[Set[str]] = None
60
+
41
61
  @property
42
62
  def tool_name(self) -> str:
43
63
  return "glob"
44
64
 
65
+ @lru_cache(maxsize=1)
66
+ def _get_base_prompt(self) -> str:
67
+ """Load and return the base prompt from XML file.
68
+
69
+ Returns:
70
+ str: The loaded prompt from XML or a default prompt
71
+ """
72
+ # Try to load from XML helper
73
+ prompt = load_prompt_from_xml("glob")
74
+ if prompt:
75
+ return prompt
76
+
77
+ # Fallback to default prompt
78
+ return """Fast file pattern matching tool
79
+
80
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
81
+ - Returns matching file paths sorted by modification time
82
+ - Use this tool when you need to find files by name patterns"""
83
+
84
+ @lru_cache(maxsize=1)
85
+ def _get_parameters_schema(self) -> Dict[str, Any]:
86
+ """Get the parameters schema for the glob tool."""
87
+ # Try to load from XML helper
88
+ schema = load_parameters_schema_from_xml("glob")
89
+ if schema:
90
+ return schema
91
+
92
+ # Fallback to hardcoded schema
93
+ return {
94
+ "type": "object",
95
+ "properties": {
96
+ "pattern": {
97
+ "type": "string",
98
+ "description": "Glob pattern to match (e.g., '*.py', '**/*.{js,ts}')",
99
+ },
100
+ "directory": {
101
+ "type": "string",
102
+ "description": "Directory to search in",
103
+ "default": ".",
104
+ },
105
+ "recursive": {
106
+ "type": "boolean",
107
+ "description": "Whether to search recursively",
108
+ "default": True,
109
+ },
110
+ "include_hidden": {
111
+ "type": "boolean",
112
+ "description": "Whether to include hidden files/directories",
113
+ "default": False,
114
+ },
115
+ "exclude_dirs": {
116
+ "type": "array",
117
+ "items": {"type": "string"},
118
+ "description": "Additional directories to exclude from search",
119
+ },
120
+ "max_results": {
121
+ "type": "integer",
122
+ "description": "Maximum number of results to return",
123
+ "default": 5000,
124
+ },
125
+ "sort_by": {
126
+ "type": "string",
127
+ "enum": ["modified", "size", "alphabetical", "depth"],
128
+ "description": "How to sort results",
129
+ "default": "modified",
130
+ },
131
+ "case_sensitive": {
132
+ "type": "boolean",
133
+ "description": "Whether pattern matching is case-sensitive",
134
+ "default": False,
135
+ },
136
+ "use_gitignore": {
137
+ "type": "boolean",
138
+ "description": "Whether to respect .gitignore patterns",
139
+ "default": True,
140
+ },
141
+ },
142
+ "required": ["pattern"],
143
+ }
144
+
145
+ def _get_code_index(self, directory: str) -> Optional[CodeIndex]:
146
+ """Get the CodeIndex instance if available and appropriate."""
147
+ # Only use CodeIndex if we're searching from the project root
148
+ if directory != "." and directory != os.getcwd():
149
+ return None
150
+
151
+ if self._code_index is None:
152
+ try:
153
+ self._code_index = CodeIndex.get_instance()
154
+ # Ensure index is built
155
+ self._code_index.build_index()
156
+ except Exception:
157
+ # CodeIndex not available, fall back to filesystem traversal
158
+ self._code_index = None
159
+ return self._code_index
160
+
45
161
  async def _execute(
46
162
  self,
47
163
  pattern: str,
@@ -50,6 +166,9 @@ class GlobTool(BaseTool):
50
166
  include_hidden: bool = False,
51
167
  exclude_dirs: Optional[List[str]] = None,
52
168
  max_results: int = MAX_RESULTS,
169
+ sort_by: Union[str, SortOrder] = SortOrder.MODIFIED,
170
+ case_sensitive: bool = False,
171
+ use_gitignore: bool = True,
53
172
  ) -> str:
54
173
  """
55
174
  Find files matching glob patterns.
@@ -61,6 +180,9 @@ class GlobTool(BaseTool):
61
180
  include_hidden: Whether to include hidden files/directories (default: False)
62
181
  exclude_dirs: Additional directories to exclude from search
63
182
  max_results: Maximum number of results to return (default: 5000)
183
+ sort_by: How to sort results (modified/size/alphabetical/depth)
184
+ case_sensitive: Whether pattern matching is case-sensitive (default: False)
185
+ use_gitignore: Whether to respect .gitignore patterns (default: True)
64
186
 
65
187
  Returns:
66
188
  List of matching file paths as a formatted string
@@ -85,20 +207,45 @@ class GlobTool(BaseTool):
85
207
  if exclude_dirs:
86
208
  all_exclude_dirs.update(exclude_dirs)
87
209
 
210
+ # Convert sort_by to enum if string
211
+ if isinstance(sort_by, str):
212
+ try:
213
+ sort_by = SortOrder(sort_by)
214
+ except ValueError:
215
+ sort_by = SortOrder.MODIFIED
216
+
88
217
  # Handle multiple extensions pattern like "*.{py,js,ts}"
89
218
  patterns = self._expand_brace_pattern(pattern)
90
219
 
91
- # Perform the glob search
92
- matches = await self._glob_search(
93
- root_path, patterns, recursive, include_hidden, all_exclude_dirs, max_results
94
- )
220
+ # Load gitignore patterns if requested
221
+ if use_gitignore:
222
+ await self._load_gitignore_patterns(root_path)
223
+
224
+ # Try to use CodeIndex for faster lookup if available
225
+ code_index = self._get_code_index(directory)
226
+ if code_index and not include_hidden and recursive:
227
+ # Use CodeIndex for common cases
228
+ matches = await self._glob_search_with_index(
229
+ code_index, patterns, root_path, all_exclude_dirs, max_results, case_sensitive
230
+ )
231
+ else:
232
+ # Fall back to filesystem traversal
233
+ matches = await self._glob_search(
234
+ root_path,
235
+ patterns,
236
+ recursive,
237
+ include_hidden,
238
+ all_exclude_dirs,
239
+ max_results,
240
+ case_sensitive,
241
+ )
95
242
 
96
243
  # Format results
97
244
  if not matches:
98
245
  return f"No files found matching pattern: {pattern}"
99
246
 
100
- # Sort matches by path
101
- matches.sort()
247
+ # Sort matches based on sort_by parameter
248
+ matches = await self._sort_matches(matches, sort_by)
102
249
 
103
250
  # Create formatted output
104
251
  output = []
@@ -128,6 +275,7 @@ class GlobTool(BaseTool):
128
275
  def _expand_brace_pattern(self, pattern: str) -> List[str]:
129
276
  """
130
277
  Expand brace patterns like "*.{py,js,ts}" into multiple patterns.
278
+ Also supports extended patterns like "?(pattern)" for optional matching.
131
279
 
132
280
  Args:
133
281
  pattern: Pattern that may contain braces
@@ -138,24 +286,129 @@ class GlobTool(BaseTool):
138
286
  if "{" not in pattern or "}" not in pattern:
139
287
  return [pattern]
140
288
 
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
289
+ # Handle nested braces recursively
290
+ expanded = []
291
+ stack = [pattern]
292
+
293
+ while stack:
294
+ current = stack.pop()
295
+
296
+ # Find the innermost brace expression
297
+ start = -1
298
+ depth = 0
299
+ for i, char in enumerate(current):
300
+ if char == "{":
301
+ if depth == 0:
302
+ start = i
303
+ depth += 1
304
+ elif char == "}":
305
+ depth -= 1
306
+ if depth == 0 and start != -1:
307
+ # Found a complete brace expression
308
+ prefix = current[:start]
309
+ suffix = current[i + 1 :]
310
+ options = current[start + 1 : i].split(",")
311
+
312
+ # Generate all combinations
313
+ for option in options:
314
+ new_pattern = prefix + option.strip() + suffix
315
+ if "{" in new_pattern:
316
+ stack.append(new_pattern)
317
+ else:
318
+ expanded.append(new_pattern)
319
+ break
320
+ else:
321
+ # No more braces to expand
322
+ expanded.append(current)
323
+
324
+ return expanded
325
+
326
+ async def _load_gitignore_patterns(self, root: Path) -> None:
327
+ """Load .gitignore patterns from the repository."""
328
+ if self._gitignore_patterns is not None:
329
+ return
330
+
331
+ self._gitignore_patterns = set()
332
+
333
+ # Look for .gitignore, .ignore, and .rgignore files
334
+ ignore_files = [".gitignore", ".ignore", ".rgignore"]
335
+
336
+ for ignore_file in ignore_files:
337
+ ignore_path = root / ignore_file
338
+ if ignore_path.exists():
339
+ try:
340
+ with open(ignore_path, "r", encoding="utf-8") as f:
341
+ for line in f:
342
+ line = line.strip()
343
+ if line and not line.startswith("#"):
344
+ self._gitignore_patterns.add(line)
345
+ except Exception:
346
+ pass
347
+
348
+ async def _glob_search_with_index(
349
+ self,
350
+ code_index: CodeIndex,
351
+ patterns: List[str],
352
+ root: Path,
353
+ exclude_dirs: set,
354
+ max_results: int,
355
+ case_sensitive: bool,
356
+ ) -> List[str]:
357
+ """Use CodeIndex for faster file matching."""
358
+ # Get all files from index
359
+ all_files = code_index.get_all_files()
360
+
361
+ matches = []
362
+ for file_path in all_files:
363
+ # Convert to absolute path
364
+ abs_path = code_index.root_dir / file_path
365
+
366
+ # Check against patterns
367
+ for pattern in patterns:
368
+ if self._match_pattern(str(file_path), pattern, case_sensitive):
369
+ # Check if in excluded directories
370
+ skip = False
371
+ for exclude_dir in exclude_dirs:
372
+ if exclude_dir in file_path.parts:
373
+ skip = True
374
+ break
375
+
376
+ if not skip:
377
+ matches.append(str(abs_path))
378
+ if len(matches) >= max_results:
379
+ return matches
380
+ break
381
+
382
+ return matches
383
+
384
+ def _match_pattern(self, path: str, pattern: str, case_sensitive: bool) -> bool:
385
+ """Match a path against a glob pattern."""
386
+ # Handle ** for recursive matching
387
+ if "**" in pattern:
388
+ # Special case: **/*.ext should match both root files and nested files
389
+ if pattern.startswith("**/"):
390
+ # Match the pattern after **/ directly and also with any prefix
391
+ suffix_pattern = pattern[3:] # Remove **/
392
+ if case_sensitive:
393
+ # Check if path matches the suffix directly (root files)
394
+ if fnmatch.fnmatch(path, suffix_pattern):
395
+ return True
396
+ else:
397
+ if fnmatch.fnmatch(path.lower(), suffix_pattern.lower()):
398
+ return True
399
+
400
+ # Full recursive pattern matching
401
+ regex_pat = pattern.replace("**", "__STARSTAR__")
402
+ regex_pat = fnmatch.translate(regex_pat)
403
+ regex_pat = regex_pat.replace("__STARSTAR__", ".*")
404
+ flags = 0 if case_sensitive else re.IGNORECASE
405
+ return bool(re.match(regex_pat, path, flags))
406
+ else:
407
+ # Simple pattern matching
408
+ if case_sensitive:
409
+ return fnmatch.fnmatch(path, pattern)
410
+ else:
411
+ return fnmatch.fnmatch(path.lower(), pattern.lower())
159
412
 
160
413
  async def _glob_search(
161
414
  self,
@@ -165,6 +418,7 @@ class GlobTool(BaseTool):
165
418
  include_hidden: bool,
166
419
  exclude_dirs: set,
167
420
  max_results: int,
421
+ case_sensitive: bool = False,
168
422
  ) -> List[str]:
169
423
  """
170
424
  Perform the actual glob search using os.scandir for speed.
@@ -182,14 +436,13 @@ class GlobTool(BaseTool):
182
436
  """
183
437
 
184
438
  def search_sync():
185
- # Import re here to avoid issues at module level
186
- import re
187
-
188
439
  matches = []
189
440
  stack = [root]
190
441
 
191
442
  # Compile patterns to regex for faster matching
192
443
  compiled_patterns = []
444
+ flags = 0 if case_sensitive else re.IGNORECASE
445
+
193
446
  for pat in patterns:
194
447
  # Handle ** for recursive matching
195
448
  if "**" in pat:
@@ -197,11 +450,9 @@ class GlobTool(BaseTool):
197
450
  regex_pat = pat.replace("**", "__STARSTAR__")
198
451
  regex_pat = fnmatch.translate(regex_pat)
199
452
  regex_pat = regex_pat.replace("__STARSTAR__", ".*")
200
- compiled_patterns.append((pat, re.compile(regex_pat, re.IGNORECASE)))
453
+ compiled_patterns.append((pat, re.compile(regex_pat, flags)))
201
454
  else:
202
- compiled_patterns.append(
203
- (pat, re.compile(fnmatch.translate(pat), re.IGNORECASE))
204
- )
455
+ compiled_patterns.append((pat, re.compile(fnmatch.translate(pat), flags)))
205
456
 
206
457
  while stack and len(matches) < max_results:
207
458
  current_dir = stack.pop()
@@ -224,9 +475,22 @@ class GlobTool(BaseTool):
224
475
  for original_pat, compiled_pat in compiled_patterns:
225
476
  # For ** patterns, match against full relative path
226
477
  if "**" in original_pat:
227
- if compiled_pat.match(rel_path):
478
+ # Special handling for **/*.ext patterns
479
+ if original_pat.startswith("**/") and not recursive:
480
+ # In non-recursive mode, match only filename
481
+ suffix_pat = original_pat[3:]
482
+ if fnmatch.fnmatch(entry.name, suffix_pat):
483
+ matches.append(entry.path)
484
+ break
485
+ elif compiled_pat.match(rel_path):
228
486
  matches.append(entry.path)
229
487
  break
488
+ # Also check if filename matches the pattern after **/
489
+ elif original_pat.startswith("**/"):
490
+ suffix_pat = original_pat[3:]
491
+ if fnmatch.fnmatch(entry.name, suffix_pat):
492
+ matches.append(entry.path)
493
+ break
230
494
  else:
231
495
  # For simple patterns, match against filename only
232
496
  if compiled_pat.match(entry.name):
@@ -246,6 +510,28 @@ class GlobTool(BaseTool):
246
510
  loop = asyncio.get_event_loop()
247
511
  return await loop.run_in_executor(None, search_sync)
248
512
 
513
+ async def _sort_matches(self, matches: List[str], sort_by: SortOrder) -> List[str]:
514
+ """Sort matches based on the specified order."""
515
+ if not matches:
516
+ return matches
517
+
518
+ def sort_sync():
519
+ if sort_by == SortOrder.MODIFIED:
520
+ # Sort by modification time (newest first)
521
+ return sorted(matches, key=lambda p: os.path.getmtime(p), reverse=True)
522
+ elif sort_by == SortOrder.SIZE:
523
+ # Sort by file size (largest first)
524
+ return sorted(matches, key=lambda p: os.path.getsize(p), reverse=True)
525
+ elif sort_by == SortOrder.DEPTH:
526
+ # Sort by path depth (shallow first), then alphabetically
527
+ return sorted(matches, key=lambda p: (p.count(os.sep), p))
528
+ else: # SortOrder.ALPHABETICAL
529
+ # Sort alphabetically
530
+ return sorted(matches)
531
+
532
+ loop = asyncio.get_event_loop()
533
+ return await loop.run_in_executor(None, sort_sync)
534
+
249
535
 
250
536
  # Create the tool function for pydantic-ai
251
537
  async def glob(
@@ -255,6 +541,9 @@ async def glob(
255
541
  include_hidden: bool = False,
256
542
  exclude_dirs: Optional[List[str]] = None,
257
543
  max_results: int = MAX_RESULTS,
544
+ sort_by: str = "modified",
545
+ case_sensitive: bool = False,
546
+ use_gitignore: bool = True,
258
547
  ) -> str:
259
548
  """
260
549
  Find files matching glob patterns with fast filesystem traversal.
@@ -264,8 +553,13 @@ async def glob(
264
553
  directory: Directory to search in (default: current directory)
265
554
  recursive: Whether to search recursively (default: True)
266
555
  include_hidden: Whether to include hidden files/directories (default: False)
267
- exclude_dirs: Additional directories to exclude from search (default: common build/cache dirs)
556
+ exclude_dirs: Additional directories to exclude from search
557
+ (default: common build/cache dirs)
268
558
  max_results: Maximum number of results to return (default: 5000)
559
+ sort_by: How to sort results - "modified", "size", "alphabetical", or "depth"
560
+ (default: "modified")
561
+ case_sensitive: Whether pattern matching is case-sensitive (default: False)
562
+ use_gitignore: Whether to respect .gitignore patterns (default: True)
269
563
 
270
564
  Returns:
271
565
  Formatted list of matching file paths grouped by directory
@@ -276,6 +570,7 @@ async def glob(
276
570
  glob("*.{js,ts,jsx,tsx}") # Multiple extensions
277
571
  glob("src/**/test_*.py") # Test files in src directory
278
572
  glob("**/*.md", include_hidden=True) # Include hidden directories
573
+ glob("*.py", sort_by="size") # Sort by file size
279
574
  """
280
575
  tool = GlobTool()
281
576
  return await tool._execute(
@@ -285,4 +580,7 @@ async def glob(
285
580
  include_hidden=include_hidden,
286
581
  exclude_dirs=exclude_dirs,
287
582
  max_results=max_results,
583
+ sort_by=sort_by,
584
+ case_sensitive=case_sensitive,
585
+ use_gitignore=use_gitignore,
288
586
  )