code-puppy 0.0.150__tar.gz → 0.0.152__tar.gz

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.
Files changed (133) hide show
  1. {code_puppy-0.0.150 → code_puppy-0.0.152}/PKG-INFO +2 -1
  2. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/agent_code_puppy.py +1 -6
  3. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/file_operations.py +126 -75
  4. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/tools_content.py +1 -1
  5. {code_puppy-0.0.150 → code_puppy-0.0.152}/pyproject.toml +2 -2
  6. {code_puppy-0.0.150 → code_puppy-0.0.152}/.gitignore +0 -0
  7. {code_puppy-0.0.150 → code_puppy-0.0.152}/LICENSE +0 -0
  8. {code_puppy-0.0.150 → code_puppy-0.0.152}/README.md +0 -0
  9. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/__main__.py +0 -0
  11. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agent.py +0 -0
  12. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/__init__.py +0 -0
  13. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/agent_creator_agent.py +0 -0
  14. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/agent_manager.py +0 -0
  15. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/agent_orchestrator.json +0 -0
  16. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/base_agent.py +0 -0
  17. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/json_agent.py +0 -0
  18. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/agents/runtime_manager.py +0 -0
  19. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/callbacks.py +0 -0
  20. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/__init__.py +0 -0
  21. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/command_handler.py +0 -0
  22. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/file_path_completion.py +0 -0
  23. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/load_context_completion.py +0 -0
  24. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/__init__.py +0 -0
  25. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/add_command.py +0 -0
  26. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/base.py +0 -0
  27. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/handler.py +0 -0
  28. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/help_command.py +0 -0
  29. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/install_command.py +0 -0
  30. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/list_command.py +0 -0
  31. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/logs_command.py +0 -0
  32. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/remove_command.py +0 -0
  33. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/restart_command.py +0 -0
  34. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/search_command.py +0 -0
  35. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  36. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/start_command.py +0 -0
  37. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/status_command.py +0 -0
  38. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  39. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/stop_command.py +0 -0
  40. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/test_command.py +0 -0
  41. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/utils.py +0 -0
  42. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  43. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/meta_command_handler.py +0 -0
  44. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/model_picker_completion.py +0 -0
  45. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/motd.py +0 -0
  46. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  47. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/command_line/utils.py +0 -0
  48. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/config.py +0 -0
  49. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/http_utils.py +0 -0
  50. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/main.py +0 -0
  51. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/__init__.py +0 -0
  52. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/async_lifecycle.py +0 -0
  53. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/blocking_startup.py +0 -0
  54. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/captured_stdio_server.py +0 -0
  55. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/circuit_breaker.py +0 -0
  56. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/config_wizard.py +0 -0
  57. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/dashboard.py +0 -0
  58. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/error_isolation.py +0 -0
  59. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/examples/retry_example.py +0 -0
  60. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/health_monitor.py +0 -0
  61. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/managed_server.py +0 -0
  62. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/manager.py +0 -0
  63. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/registry.py +0 -0
  64. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/retry_manager.py +0 -0
  65. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/server_registry_catalog.py +0 -0
  66. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/status_tracker.py +0 -0
  67. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/mcp/system_tools.py +0 -0
  68. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/message_history_processor.py +0 -0
  69. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/__init__.py +0 -0
  70. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/message_queue.py +0 -0
  71. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/queue_console.py +0 -0
  72. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/renderers.py +0 -0
  73. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/spinner/__init__.py +0 -0
  74. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  75. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  76. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
  77. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/model_factory.py +0 -0
  78. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/models.json +0 -0
  79. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/plugins/__init__.py +0 -0
  80. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/reopenable_async_client.py +0 -0
  81. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/round_robin_model.py +0 -0
  82. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/state_management.py +0 -0
  83. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/status_display.py +0 -0
  84. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/summarization_agent.py +0 -0
  85. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/token_utils.py +0 -0
  86. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/__init__.py +0 -0
  87. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/agent_tools.py +0 -0
  88. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/command_runner.py +0 -0
  89. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/common.py +0 -0
  90. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/file_modifications.py +0 -0
  91. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tools/token_check.py +0 -0
  92. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/__init__.py +0 -0
  93. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/app.py +0 -0
  94. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/__init__.py +0 -0
  95. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/chat_view.py +0 -0
  96. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/command_history_modal.py +0 -0
  97. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/copy_button.py +0 -0
  98. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/custom_widgets.py +0 -0
  99. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/human_input_modal.py +0 -0
  100. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/input_area.py +0 -0
  101. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/sidebar.py +0 -0
  102. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/components/status_bar.py +0 -0
  103. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/messages.py +0 -0
  104. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/models/__init__.py +0 -0
  105. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/models/chat_message.py +0 -0
  106. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/models/command_history.py +0 -0
  107. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/models/enums.py +0 -0
  108. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/screens/__init__.py +0 -0
  109. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/screens/help.py +0 -0
  110. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
  111. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/screens/settings.py +0 -0
  112. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/screens/tools.py +0 -0
  113. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/__init__.py +0 -0
  114. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_agent_command.py +0 -0
  115. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_chat_message.py +0 -0
  116. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_chat_view.py +0 -0
  117. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_command_history.py +0 -0
  118. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_copy_button.py +0 -0
  119. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_custom_widgets.py +0 -0
  120. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_disclaimer.py +0 -0
  121. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_enums.py +0 -0
  122. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_file_browser.py +0 -0
  123. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_help.py +0 -0
  124. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_history_file_reader.py +0 -0
  125. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_input_area.py +0 -0
  126. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_settings.py +0 -0
  127. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_sidebar.py +0 -0
  128. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_sidebar_history.py +0 -0
  129. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -0
  130. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_status_bar.py +0 -0
  131. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_timestamped_history.py +0 -0
  132. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/tui/tests/test_tools.py +0 -0
  133. {code_puppy-0.0.150 → code_puppy-0.0.152}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.150
3
+ Version: 0.0.152
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -30,6 +30,7 @@ Requires-Dist: pytest-cov>=6.1.1
30
30
  Requires-Dist: python-dotenv>=1.0.0
31
31
  Requires-Dist: rapidfuzz>=3.13.0
32
32
  Requires-Dist: rich>=13.4.2
33
+ Requires-Dist: ripgrep>=14.1.0
33
34
  Requires-Dist: ruff>=0.11.11
34
35
  Requires-Dist: termcolor>=3.1.0
35
36
  Requires-Dist: textual-dev>=1.7.0
@@ -68,7 +68,7 @@ File Operations:
68
68
  - read_file(file_path: str, start_line: int | None = None, num_lines: int | None = None): ALWAYS use this to read existing files before modifying them. By default, read the entire file. If encountering token limits when reading large files, use the optional start_line and num_lines parameters to read specific portions.
69
69
  - edit_file(payload): Swiss-army file editor powered by Pydantic payloads (ContentPayload, ReplacementsPayload, DeleteSnippetPayload).
70
70
  - delete_file(file_path): Use this to remove files when needed
71
- - grep(search_string, directory="."): Use this to recursively search for a string across files starting from the specified directory, capping results at 200 matches.
71
+ - grep(search_string, directory="."): Use this to recursively search for a string across files starting from the specified directory, capping results at 200 matches. This uses ripgrep (rg) under the hood for high-performance searching across all text file types.
72
72
 
73
73
  Tool Usage Instructions:
74
74
 
@@ -99,17 +99,12 @@ edit_file(
99
99
  payload={{file_path="example.py", "delete_snippet": "# TODO: remove this line"}}
100
100
  )
101
101
  ```
102
-
103
- NEVER output an entire file – this is very expensive.
104
- You may not edit file extensions: [.ipynb]
105
-
106
102
  Best-practice guidelines for `edit_file`:
107
103
  • Keep each diff small – ideally between 100-300 lines.
108
104
  • Apply multiple sequential `edit_file` calls when you need to refactor large files instead of sending one massive diff.
109
105
  • Never paste an entire file inside `old_str`; target only the minimal snippet you want changed.
110
106
  • If the resulting file would grow beyond 600 lines, split logic into additional files and create them with separate `edit_file` calls.
111
107
 
112
-
113
108
  System Operations:
114
109
  - run_shell_command(command, cwd=None, timeout=60): Use this to execute commands, run tests, or start services
115
110
 
@@ -380,8 +380,15 @@ def _read_file(
380
380
 
381
381
 
382
382
  def _grep(context: RunContext, search_string: str, directory: str = ".") -> GrepOutput:
383
- matches: List[MatchInfo] = []
383
+ import subprocess
384
+ import json
385
+ import tempfile
386
+ import os
387
+ import shutil
388
+ import sys
389
+
384
390
  directory = os.path.abspath(directory)
391
+ matches: List[MatchInfo] = []
385
392
 
386
393
  # Generate group_id for this tool execution
387
394
  group_id = generate_group_id("grep", f"{directory}_{search_string}")
@@ -392,67 +399,106 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
392
399
  )
393
400
  emit_divider(message_group=group_id)
394
401
 
395
- for root, dirs, files in os.walk(directory, topdown=True):
396
- # Filter out ignored directories
397
- dirs[:] = [d for d in dirs if not should_ignore_path(os.path.join(root, d))]
398
-
399
- for f_name in files:
400
- file_path = os.path.join(root, f_name)
401
-
402
- if should_ignore_path(file_path):
402
+ # Create a temporary ignore file with our ignore patterns
403
+ ignore_file = None
404
+ try:
405
+ # Use ripgrep to search for the string
406
+ # Use absolute path to ensure it works from any directory
407
+ # --json for structured output
408
+ # --max-count 50 to limit results
409
+ # --max-filesize 5M to avoid huge files (increased from 1M)
410
+ # --type=all to search across all recognized text file types
411
+ # --ignore-file to obey our ignore list
412
+
413
+ # Find ripgrep executable - first check system PATH, then virtual environment
414
+ rg_path = shutil.which("rg")
415
+ if not rg_path:
416
+ # Try to find it in the virtual environment
417
+ # Use sys.executable to determine the Python environment path
418
+ python_dir = os.path.dirname(sys.executable)
419
+ # Check both 'bin' (Unix) and 'Scripts' (Windows) directories
420
+ for rg_dir in ["bin", "Scripts"]:
421
+ venv_rg_path = os.path.join(python_dir, "rg")
422
+ if os.path.exists(venv_rg_path):
423
+ rg_path = venv_rg_path
424
+ break
425
+ # Also check with .exe extension for Windows
426
+ venv_rg_exe_path = os.path.join(python_dir, "rg.exe")
427
+ if os.path.exists(venv_rg_exe_path):
428
+ rg_path = venv_rg_exe_path
429
+ break
430
+
431
+ if not rg_path:
432
+ emit_error(f"ripgrep (rg) not found. Please install ripgrep to use this tool.", message_group=group_id)
433
+ return GrepOutput(matches=[])
434
+
435
+ cmd = [rg_path, "--json", "--max-count", "50", "--max-filesize", "5M", "--type=all"]
436
+
437
+ # Add ignore patterns to the command via a temporary file
438
+ from code_puppy.tools.common import IGNORE_PATTERNS
439
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ignore') as f:
440
+ ignore_file = f.name
441
+ for pattern in IGNORE_PATTERNS:
442
+ f.write(f"{pattern}\n")
443
+
444
+ cmd.extend(["--ignore-file", ignore_file])
445
+ cmd.extend([search_string, directory])
446
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
447
+
448
+ # Parse the JSON output from ripgrep
449
+ for line in result.stdout.strip().split('\n'):
450
+ if not line:
403
451
  continue
404
-
405
452
  try:
406
- with open(file_path, "r", encoding="utf-8", errors="ignore") as fh:
407
- for line_number, line_content in enumerate(fh, 1):
408
- if search_string in line_content:
409
- match_info = MatchInfo(
410
- **{
411
- "file_path": file_path,
412
- "line_number": line_number,
413
- "line_content": line_content.rstrip("\n\r")[512:],
414
- }
415
- )
416
- matches.append(match_info)
417
- emit_system_message(
418
- f"[green]Match:[/green] {file_path}:{line_number} - {line_content.strip()}",
419
- message_group=group_id,
420
- )
421
- if len(matches) >= 50:
422
- emit_warning(
423
- "Limit of 50 matches reached. Stopping search.",
424
- message_group=group_id,
425
- )
426
- return GrepOutput(matches=matches)
427
- except FileNotFoundError:
428
- emit_warning(
429
- f"File not found (possibly a broken symlink): {file_path}",
430
- message_group=group_id,
431
- )
432
- continue
433
- except UnicodeDecodeError:
434
- emit_warning(
435
- f"Cannot decode file (likely binary): {file_path}",
436
- message_group=group_id,
437
- )
453
+ match_data = json.loads(line)
454
+ # Only process match events, not context or summary
455
+ if match_data.get('type') == 'match':
456
+ data = match_data.get('data', {})
457
+ path_data = data.get('path', {})
458
+ file_path = path_data.get('text', '') if path_data.get('text') else ''
459
+ line_number = data.get('line_number', None)
460
+ line_content = data.get('lines', {}).get('text', '') if data.get('lines', {}).get('text') else ''
461
+
462
+ if file_path and line_number:
463
+ match_info = MatchInfo(
464
+ file_path=file_path,
465
+ line_number=line_number,
466
+ line_content=line_content.strip()
467
+ )
468
+ matches.append(match_info)
469
+ # Limit to 50 matches total, same as original implementation
470
+ if len(matches) >= 50:
471
+ break
472
+ emit_system_message(
473
+ f"[green]Match:[/green] {file_path}:{line_number} - {line_content.strip()}",
474
+ message_group=group_id,
475
+ )
476
+ except json.JSONDecodeError:
477
+ # Skip lines that aren't valid JSON
438
478
  continue
439
- except Exception as e:
440
- emit_error(
441
- f"Error processing file {file_path}: {e}", message_group=group_id
442
- )
443
- continue
444
-
445
- if not matches:
446
- emit_warning(
447
- f"No matches found for '{search_string}' in {directory}",
448
- message_group=group_id,
449
- )
450
- else:
451
- emit_success(
452
- f"Found {len(matches)} match(es) for '{search_string}' in {directory}",
453
- message_group=group_id,
454
- )
455
-
479
+
480
+ if not matches:
481
+ emit_warning(
482
+ f"No matches found for '{search_string}' in {directory}",
483
+ message_group=group_id,
484
+ )
485
+ else:
486
+ emit_success(
487
+ f"Found {len(matches)} match(es) for '{search_string}' in {directory}",
488
+ message_group=group_id,
489
+ )
490
+
491
+ except subprocess.TimeoutExpired:
492
+ emit_error(f"Grep command timed out after 30 seconds", message_group=group_id)
493
+ except FileNotFoundError:
494
+ emit_error(f"ripgrep (rg) not found. Please install ripgrep to use this tool.", message_group=group_id)
495
+ except Exception as e:
496
+ emit_error(f"Error during grep operation: {e}", message_group=group_id)
497
+ finally:
498
+ # Clean up the temporary ignore file
499
+ if ignore_file and os.path.exists(ignore_file):
500
+ os.unlink(ignore_file)
501
+
456
502
  return GrepOutput(matches=matches)
457
503
 
458
504
 
@@ -590,17 +636,22 @@ def register_grep(agent):
590
636
  def grep(
591
637
  context: RunContext, search_string: str = "", directory: str = "."
592
638
  ) -> GrepOutput:
593
- """Recursively search for text patterns across files with intelligent filtering.
639
+ """Recursively search for text patterns across files using ripgrep (rg).
594
640
 
595
- This tool provides powerful text searching across directory trees with
596
- automatic filtering of irrelevant files, binary detection, and match limiting
597
- for performance. It's essential for code exploration and finding specific
598
- patterns or references.
641
+ This tool leverages the high-performance ripgrep utility for fast text
642
+ searching across directory trees. It searches across all recognized text file
643
+ types (Python, JavaScript, HTML, CSS, Markdown, etc.) while automatically
644
+ filtering binary files and limiting results for performance.
645
+
646
+ The search_string parameter supports ripgrep's full flag syntax, allowing
647
+ advanced searches including regex patterns, case-insensitive matching,
648
+ and other ripgrep features.
599
649
 
600
650
  Args:
601
651
  context (RunContext): The PydanticAI runtime context for the agent.
602
- search_string (str): The text pattern to search for. Performs exact
603
- string matching (not regex). Cannot be empty.
652
+ search_string (str): The text pattern to search for. Can include ripgrep
653
+ flags like '--ignore-case', '-w' (word boundaries), etc.
654
+ Cannot be empty.
604
655
  directory (str, optional): Root directory to start the recursive search.
605
656
  Can be relative or absolute. Defaults to "." (current directory).
606
657
 
@@ -613,23 +664,23 @@ def register_grep(agent):
613
664
  - line_content (str | None): Full line content containing the match
614
665
 
615
666
  Examples:
616
- >>> # Search for function definitions
667
+ >>> # Simple text search
617
668
  >>> result = grep(ctx, "def my_function")
618
669
  >>> for match in result.matches:
619
670
  ... print(f"{match.file_path}:{match.line_number}: {match.line_content}")
620
671
 
621
- >>> # Search in specific directory
622
- >>> result = grep(ctx, "TODO", "/path/to/project/src")
672
+ >>> # Case-insensitive search
673
+ >>> result = grep(ctx, "--ignore-case TODO", "/path/to/project/src")
623
674
  >>> print(f"Found {len(result.matches)} TODO items")
624
675
 
625
- >>> # Search for imports
626
- >>> result = grep(ctx, "import pandas")
627
- >>> files_using_pandas = {match.file_path for match in result.matches}
676
+ >>> # Word boundary search (regex)
677
+ >>> result = grep(ctx, "-w \\w+State\\b")
678
+ >>> files_with_state = {match.file_path for match in result.matches}
628
679
 
629
680
  Best Practices:
630
681
  - Use specific search terms to avoid too many results
631
- - Search is case-sensitive; try variations if needed
632
- - Combine with read_file to examine matches in detail
633
- - For case-insensitive search, try multiple variants manually
682
+ - Leverage ripgrep's powerful regex and flag features for advanced searches
683
+ - ripgrep is much faster than naive implementations
684
+ - Results are capped at 50 matches for performance
634
685
  """
635
686
  return _grep(context, search_string, directory)
@@ -12,7 +12,7 @@ Woof! 🐶 Here's my complete toolkit! I'm like a Swiss Army knife but way more
12
12
  - **`delete_file(file_path)`** - Remove files when needed (use with caution!)
13
13
 
14
14
  # **Search & Analysis**
15
- - **`grep(search_string, directory)`** - Search for text across files recursively (up to 200 matches)
15
+ - **`grep(search_string, directory)`** - Search for text across files recursively using ripgrep (rg) for high-performance searching (up to 200 matches). Searches across all text file types, not just Python files. Supports ripgrep flags in the search string.
16
16
 
17
17
  # 💻 **System Operations**
18
18
  - **`agent_run_shell_command(command, cwd, timeout)`** - Execute shell commands with full output capture (stdout, stderr, exit codes)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.150"
7
+ version = "0.0.152"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "termcolor>=3.1.0",
33
33
  "textual-dev>=1.7.0",
34
34
  "openai>=1.99.1",
35
-
35
+ "ripgrep>=14.1.0",
36
36
  ]
37
37
  dev-dependencies = [
38
38
  "pytest>=8.3.4",
File without changes
File without changes
File without changes