code-puppy 0.0.150__py3-none-any.whl → 0.0.151__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.
- code_puppy/agents/agent_code_puppy.py +1 -1
- code_puppy/tools/file_operations.py +126 -75
- code_puppy/tools/tools_content.py +1 -1
- {code_puppy-0.0.150.dist-info → code_puppy-0.0.151.dist-info}/METADATA +2 -1
- {code_puppy-0.0.150.dist-info → code_puppy-0.0.151.dist-info}/RECORD +9 -9
- {code_puppy-0.0.150.data → code_puppy-0.0.151.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.150.dist-info → code_puppy-0.0.151.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.150.dist-info → code_puppy-0.0.151.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.150.dist-info → code_puppy-0.0.151.dist-info}/licenses/LICENSE +0 -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
|
|
|
@@ -380,8 +380,15 @@ def _read_file(
|
|
|
380
380
|
|
|
381
381
|
|
|
382
382
|
def _grep(context: RunContext, search_string: str, directory: str = ".") -> GrepOutput:
|
|
383
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
639
|
+
"""Recursively search for text patterns across files using ripgrep (rg).
|
|
594
640
|
|
|
595
|
-
This tool
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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.
|
|
603
|
-
|
|
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
|
-
>>> #
|
|
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
|
-
>>> #
|
|
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
|
-
>>> #
|
|
626
|
-
>>> result = grep(ctx, "
|
|
627
|
-
>>>
|
|
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
|
-
-
|
|
632
|
-
-
|
|
633
|
-
-
|
|
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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.151
|
|
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
|
|
@@ -16,7 +16,7 @@ code_puppy/summarization_agent.py,sha256=-e6yUGZ22ahSaF0y7QhgVcQBfx5ktNUkPxBIWQf
|
|
|
16
16
|
code_puppy/token_utils.py,sha256=inLo-S2YERGA-JmV-nrSFN7KMswSfHxpawAuK6YiDHc,1982
|
|
17
17
|
code_puppy/version_checker.py,sha256=bjLDmgGPrl7XnYwX1u13O8uFlsfikV90PK6nbA9Z9QU,1150
|
|
18
18
|
code_puppy/agents/__init__.py,sha256=SwtHGNG1GIgDBv7y3EGIXOXEWrG_Ou7fEknNgFbrHv8,594
|
|
19
|
-
code_puppy/agents/agent_code_puppy.py,sha256=
|
|
19
|
+
code_puppy/agents/agent_code_puppy.py,sha256=es4fsm-T_Ba1v-j1YKnkzC9d9zS3sO6z0usokJjd4HM,8074
|
|
20
20
|
code_puppy/agents/agent_creator_agent.py,sha256=IUPLJDhhqIQ8NAjNTFQ_3Z5kZYYxVAUE0RPkcz90reY,20450
|
|
21
21
|
code_puppy/agents/agent_manager.py,sha256=nXvro6fpX8KA-NedRoVJuhJW966trrePOrH4eAnqq40,17034
|
|
22
22
|
code_puppy/agents/agent_orchestrator.json,sha256=Iqnc0p6ICoAlUTMkEsi1XXMXJi4pdxVnWZUMaih6s5o,1267
|
|
@@ -82,9 +82,9 @@ code_puppy/tools/agent_tools.py,sha256=tG11PMmjuU-pYG1MFCgqsYiC1Q8C-zPsitAYXxl3m
|
|
|
82
82
|
code_puppy/tools/command_runner.py,sha256=GVNsgwjTFD9tkNlycgMNmMoVPdmMkZkbAcHH5y6iMww,26070
|
|
83
83
|
code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
|
|
84
84
|
code_puppy/tools/file_modifications.py,sha256=oeNEQItqwMhGOeEN2TzGR7TjmgLsfFFdPaVMzWbfXIQ,30398
|
|
85
|
-
code_puppy/tools/file_operations.py,sha256
|
|
85
|
+
code_puppy/tools/file_operations.py,sha256=rH0pOH0u7ZsQ7bUxaIH38nAiYF9RHmtR_mBNFYBe4ws,27334
|
|
86
86
|
code_puppy/tools/token_check.py,sha256=cNrGOOKahXsnWsvh5xnMkL1NS9FjYur9QIRZGQFW-pE,1189
|
|
87
|
-
code_puppy/tools/tools_content.py,sha256=
|
|
87
|
+
code_puppy/tools/tools_content.py,sha256=bsBqW-ppd1XNAS_g50B3UHDQBWEALC1UneH6-afz1zo,2365
|
|
88
88
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
|
89
89
|
code_puppy/tui/app.py,sha256=10FDM1suc5gDiTD3rj9z3crh2wMYb7W1VUa5QHCs3ss,39373
|
|
90
90
|
code_puppy/tui/messages.py,sha256=zQoToWI0eWdT36NEsY6RdCFzcDfAmfvoPlHv8jiCbgo,720
|
|
@@ -126,9 +126,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
|
|
|
126
126
|
code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
|
|
127
127
|
code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
|
|
128
128
|
code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
|
|
129
|
-
code_puppy-0.0.
|
|
130
|
-
code_puppy-0.0.
|
|
131
|
-
code_puppy-0.0.
|
|
132
|
-
code_puppy-0.0.
|
|
133
|
-
code_puppy-0.0.
|
|
134
|
-
code_puppy-0.0.
|
|
129
|
+
code_puppy-0.0.151.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
|
|
130
|
+
code_puppy-0.0.151.dist-info/METADATA,sha256=hzCciidzNul88w5yYFT3jhOIjRQvx3cwkuzLcww9QsE,19516
|
|
131
|
+
code_puppy-0.0.151.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
132
|
+
code_puppy-0.0.151.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
133
|
+
code_puppy-0.0.151.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
134
|
+
code_puppy-0.0.151.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|