diff --git a/lib/crewai-tools/src/crewai_tools/__init__.py b/lib/crewai-tools/src/crewai_tools/__init__.py index 5aded17a7..1ecf814fd 100644 --- a/lib/crewai-tools/src/crewai_tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/__init__.py @@ -77,6 +77,7 @@ from crewai_tools.tools.generate_crewai_automation_tool.generate_crewai_automati GenerateCrewaiAutomationTool, ) from crewai_tools.tools.github_search_tool.github_search_tool import GithubSearchTool +from crewai_tools.tools.grep_tool.grep_tool import GrepTool from crewai_tools.tools.hyperbrowser_load_tool.hyperbrowser_load_tool import ( HyperbrowserLoadTool, ) @@ -230,6 +231,7 @@ __all__ = [ "FirecrawlSearchTool", "GenerateCrewaiAutomationTool", "GithubSearchTool", + "GrepTool", "HyperbrowserLoadTool", "InvokeCrewAIAutomationTool", "JSONSearchTool", diff --git a/lib/crewai-tools/src/crewai_tools/tools/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/__init__.py index 51d32ddc2..1dfc614c5 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/tools/__init__.py @@ -66,6 +66,7 @@ from crewai_tools.tools.generate_crewai_automation_tool.generate_crewai_automati GenerateCrewaiAutomationTool, ) from crewai_tools.tools.github_search_tool.github_search_tool import GithubSearchTool +from crewai_tools.tools.grep_tool.grep_tool import GrepTool from crewai_tools.tools.hyperbrowser_load_tool.hyperbrowser_load_tool import ( HyperbrowserLoadTool, ) @@ -214,6 +215,7 @@ __all__ = [ "FirecrawlSearchTool", "GenerateCrewaiAutomationTool", "GithubSearchTool", + "GrepTool", "HyperbrowserLoadTool", "InvokeCrewAIAutomationTool", "JSONSearchTool", diff --git a/lib/crewai-tools/src/crewai_tools/tools/grep_tool/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/grep_tool/__init__.py new file mode 100644 index 000000000..bd5d132f6 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/grep_tool/__init__.py @@ -0,0 +1,3 @@ +from crewai_tools.tools.grep_tool.grep_tool import GrepTool + +__all__ = ["GrepTool"] diff --git a/lib/crewai/src/crewai/tools/agent_tools/grep_tool.py b/lib/crewai-tools/src/crewai_tools/tools/grep_tool/grep_tool.py similarity index 88% rename from lib/crewai/src/crewai/tools/agent_tools/grep_tool.py rename to lib/crewai-tools/src/crewai_tools/tools/grep_tool/grep_tool.py index 56a45b38e..1b863f69e 100644 --- a/lib/crewai/src/crewai/tools/agent_tools/grep_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/grep_tool/grep_tool.py @@ -2,33 +2,33 @@ from __future__ import annotations -from dataclasses import dataclass, field import os -from pathlib import Path import re +from dataclasses import dataclass, field +from pathlib import Path from typing import Literal +from crewai.tools import BaseTool from pydantic import BaseModel, Field -from crewai.tools.base_tool import BaseTool - - MAX_OUTPUT_CHARS = 50_000 MAX_FILES = 10_000 MAX_MATCHES_PER_FILE = 200 MAX_LINE_LENGTH = 500 BINARY_CHECK_SIZE = 8192 -SKIP_DIRS = frozenset({ - ".git", - "__pycache__", - "node_modules", - ".venv", - "venv", - ".tox", - ".mypy_cache", - ".pytest_cache", -}) +SKIP_DIRS = frozenset( + { + ".git", + "__pycache__", + "node_modules", + ".venv", + "venv", + ".tox", + ".mypy_cache", + ".pytest_cache", + } +) @dataclass @@ -52,7 +52,9 @@ class FileSearchResult: class GrepToolSchema(BaseModel): """Schema for grep tool arguments.""" - pattern: str = Field(..., description="Regex pattern to search for in file contents") + pattern: str = Field( + ..., description="Regex pattern to search for in file contents" + ) path: str | None = Field( default=None, description="File or directory to search in. Defaults to current working directory.", @@ -84,13 +86,23 @@ class GrepTool(BaseTool): Recursively searches files in a directory for lines matching a regex pattern. Supports glob filtering, context lines, and multiple output modes. + + Example: + >>> tool = GrepTool() + >>> result = tool.run(pattern="def.*main", path="/path/to/project") + >>> result = tool.run( + ... pattern="TODO", + ... path="/path/to/project", + ... glob_pattern="*.py", + ... context_lines=2, + ... ) """ - name: str = "grep" + name: str = "Search file contents" description: str = ( - "Search file contents on disk using regex patterns. " + "A tool that searches file contents on disk using regex patterns. " "Recursively searches files in a directory for matching lines. " - "Returns matching content, file paths, or match counts." + "Returns matching content with line numbers, file paths only, or match counts." ) args_schema: type[BaseModel] = GrepToolSchema @@ -154,7 +166,10 @@ class GrepTool(BaseTool): # Truncate if needed if len(output) > MAX_OUTPUT_CHARS: - output = output[:MAX_OUTPUT_CHARS] + "\n\n... Output truncated. Try a narrower search pattern or glob filter." + output = ( + output[:MAX_OUTPUT_CHARS] + + "\n\n... Output truncated. Try a narrower search pattern or glob filter." + ) return output @@ -255,11 +270,13 @@ class GrepTool(BaseTool): text = lines[i].rstrip("\n\r") if len(text) > MAX_LINE_LENGTH: text = text[:MAX_LINE_LENGTH] + "..." - current_group.append(MatchLine( - line_number=i + 1, # 1-indexed - text=text, - is_match=(i in match_line_nums), - )) + current_group.append( + MatchLine( + line_number=i + 1, # 1-indexed + text=text, + is_match=(i in match_line_nums), + ) + ) prev_end = end diff --git a/lib/crewai/tests/tools/agent_tools/test_grep_tool.py b/lib/crewai-tools/tests/tools/grep_tool_test.py similarity index 98% rename from lib/crewai/tests/tools/agent_tools/test_grep_tool.py rename to lib/crewai-tools/tests/tools/grep_tool_test.py index 8422ddcbc..410b31fd7 100644 --- a/lib/crewai/tests/tools/agent_tools/test_grep_tool.py +++ b/lib/crewai-tools/tests/tools/grep_tool_test.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest -from crewai.tools.agent_tools.grep_tool import GrepTool +from crewai_tools import GrepTool @pytest.fixture @@ -69,7 +69,7 @@ class TestGrepTool: def test_tool_metadata(self) -> None: """Test tool has correct name and description.""" - assert self.tool.name == "grep" + assert self.tool.name == "Search file contents" assert "search" in self.tool.description.lower() or "Search" in self.tool.description def test_args_schema(self) -> None: diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index 1e32b2d6c..ff4029ca8 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -8360,6 +8360,126 @@ "type": "object" } }, + { + "description": "A tool that searches file contents on disk using regex patterns. Recursively searches files in a directory for matching lines. Returns matching content with line numbers, file paths only, or match counts.", + "env_vars": [], + "humanized_name": "Search file contents", + "init_params_schema": { + "$defs": { + "EnvVar": { + "properties": { + "default": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Default" + }, + "description": { + "title": "Description", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "required": { + "default": true, + "title": "Required", + "type": "boolean" + } + }, + "required": [ + "name", + "description" + ], + "title": "EnvVar", + "type": "object" + } + }, + "description": "Tool for searching file contents on disk using regex patterns.\n\nRecursively searches files in a directory for lines matching a regex pattern.\nSupports glob filtering, context lines, and multiple output modes.\n\nExample:\n >>> tool = GrepTool()\n >>> result = tool.run(pattern=\"def.*main\", path=\"/path/to/project\")\n >>> result = tool.run(\n ... pattern=\"TODO\",\n ... path=\"/path/to/project\",\n ... glob_pattern=\"*.py\",\n ... context_lines=2,\n ... )", + "properties": {}, + "title": "GrepTool", + "type": "object" + }, + "name": "GrepTool", + "package_dependencies": [], + "run_params_schema": { + "description": "Schema for grep tool arguments.", + "properties": { + "case_insensitive": { + "default": false, + "description": "Whether to perform case-insensitive matching", + "title": "Case Insensitive", + "type": "boolean" + }, + "context_lines": { + "default": 0, + "description": "Number of lines to show before and after each match", + "title": "Context Lines", + "type": "integer" + }, + "glob_pattern": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Glob pattern to filter files (e.g. '*.py', '*.{ts,tsx}')", + "title": "Glob Pattern" + }, + "include_line_numbers": { + "default": true, + "description": "Whether to prefix matching lines with line numbers", + "title": "Include Line Numbers", + "type": "boolean" + }, + "output_mode": { + "default": "content", + "description": "Output mode: 'content' shows matching lines, 'files_with_matches' shows only file paths, 'count' shows match counts per file", + "enum": [ + "content", + "files_with_matches", + "count" + ], + "title": "Output Mode", + "type": "string" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "File or directory to search in. Defaults to current working directory.", + "title": "Path" + }, + "pattern": { + "description": "Regex pattern to search for in file contents", + "title": "Pattern", + "type": "string" + } + }, + "required": [ + "pattern" + ], + "title": "GrepToolSchema", + "type": "object" + } + }, { "description": "Scrape or crawl a website using Hyperbrowser and return the contents in properly formatted markdown or html", "env_vars": [