code-puppy 0.0.154__py3-none-any.whl → 0.0.156__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/agent.py +26 -5
- code_puppy/agents/agent_creator_agent.py +65 -13
- code_puppy/agents/json_agent.py +8 -0
- code_puppy/agents/runtime_manager.py +12 -4
- code_puppy/command_line/command_handler.py +83 -0
- code_puppy/command_line/mcp/install_command.py +50 -1
- code_puppy/command_line/mcp/wizard_utils.py +88 -17
- code_puppy/command_line/prompt_toolkit_completion.py +18 -2
- code_puppy/config.py +8 -2
- code_puppy/main.py +17 -4
- code_puppy/mcp/__init__.py +2 -2
- code_puppy/mcp/config_wizard.py +1 -1
- code_puppy/messaging/spinner/console_spinner.py +1 -1
- code_puppy/model_factory.py +13 -12
- code_puppy/models.json +26 -0
- code_puppy/round_robin_model.py +35 -18
- code_puppy/summarization_agent.py +1 -3
- code_puppy/tools/agent_tools.py +41 -138
- code_puppy/tools/file_operations.py +116 -96
- code_puppy/tui/app.py +1 -1
- {code_puppy-0.0.154.data → code_puppy-0.0.156.data}/data/code_puppy/models.json +26 -0
- {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/METADATA +4 -3
- {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/RECORD +26 -48
- code_puppy/token_utils.py +0 -67
- code_puppy/tools/token_check.py +0 -32
- code_puppy/tui/tests/__init__.py +0 -1
- code_puppy/tui/tests/test_agent_command.py +0 -79
- code_puppy/tui/tests/test_chat_message.py +0 -28
- code_puppy/tui/tests/test_chat_view.py +0 -88
- code_puppy/tui/tests/test_command_history.py +0 -89
- code_puppy/tui/tests/test_copy_button.py +0 -191
- code_puppy/tui/tests/test_custom_widgets.py +0 -27
- code_puppy/tui/tests/test_disclaimer.py +0 -27
- code_puppy/tui/tests/test_enums.py +0 -15
- code_puppy/tui/tests/test_file_browser.py +0 -60
- code_puppy/tui/tests/test_help.py +0 -38
- code_puppy/tui/tests/test_history_file_reader.py +0 -107
- code_puppy/tui/tests/test_input_area.py +0 -33
- code_puppy/tui/tests/test_settings.py +0 -44
- code_puppy/tui/tests/test_sidebar.py +0 -33
- code_puppy/tui/tests/test_sidebar_history.py +0 -153
- code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -132
- code_puppy/tui/tests/test_status_bar.py +0 -54
- code_puppy/tui/tests/test_timestamped_history.py +0 -52
- code_puppy/tui/tests/test_tools.py +0 -82
- {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,23 +18,7 @@ from code_puppy.messaging import (
|
|
|
18
18
|
emit_system_message,
|
|
19
19
|
emit_warning,
|
|
20
20
|
)
|
|
21
|
-
from code_puppy.tools.common import generate_group_id
|
|
22
|
-
|
|
23
|
-
# Add token checking functionality
|
|
24
|
-
try:
|
|
25
|
-
from code_puppy.token_utils import get_tokenizer
|
|
26
|
-
from code_puppy.tools.token_check import token_guard
|
|
27
|
-
except ImportError:
|
|
28
|
-
# Fallback for when token checking modules aren't available
|
|
29
|
-
def get_tokenizer():
|
|
30
|
-
# Simple token estimation - no longer using tiktoken
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
def token_guard(num_tokens):
|
|
34
|
-
if num_tokens > 10000:
|
|
35
|
-
raise ValueError(
|
|
36
|
-
f"Token count {num_tokens} exceeds safety limit of 10,000 tokens"
|
|
37
|
-
)
|
|
21
|
+
from code_puppy.tools.common import generate_group_id
|
|
38
22
|
|
|
39
23
|
|
|
40
24
|
# Pydantic models for tool return types
|
|
@@ -127,7 +111,6 @@ def _list_files(
|
|
|
127
111
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
128
112
|
) -> ListFileOutput:
|
|
129
113
|
import subprocess
|
|
130
|
-
import tempfile
|
|
131
114
|
import shutil
|
|
132
115
|
import sys
|
|
133
116
|
|
|
@@ -136,26 +119,30 @@ def _list_files(
|
|
|
136
119
|
|
|
137
120
|
# Build string representation
|
|
138
121
|
output_lines = []
|
|
139
|
-
|
|
140
|
-
directory_listing_header =
|
|
122
|
+
|
|
123
|
+
directory_listing_header = (
|
|
124
|
+
"\n[bold white on blue] DIRECTORY LISTING [/bold white on blue]"
|
|
125
|
+
)
|
|
141
126
|
output_lines.append(directory_listing_header)
|
|
142
|
-
|
|
127
|
+
|
|
143
128
|
directory_info = f"\U0001f4c2 [bold cyan]{directory}[/bold cyan] [dim](recursive={recursive})[/dim]\n"
|
|
144
129
|
output_lines.append(directory_info)
|
|
145
|
-
|
|
130
|
+
|
|
146
131
|
divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
|
|
147
132
|
output_lines.append(divider)
|
|
148
|
-
|
|
133
|
+
|
|
149
134
|
if not os.path.exists(directory):
|
|
150
|
-
error_msg =
|
|
135
|
+
error_msg = (
|
|
136
|
+
f"[red bold]Error:[/red bold] Directory '{directory}' does not exist"
|
|
137
|
+
)
|
|
151
138
|
output_lines.append(error_msg)
|
|
152
|
-
|
|
139
|
+
|
|
153
140
|
output_lines.append(divider)
|
|
154
141
|
return ListFileOutput(content="\n".join(output_lines))
|
|
155
142
|
if not os.path.isdir(directory):
|
|
156
143
|
error_msg = f"[red bold]Error:[/red bold] '{directory}' is not a directory"
|
|
157
144
|
output_lines.append(error_msg)
|
|
158
|
-
|
|
145
|
+
|
|
159
146
|
output_lines.append(divider)
|
|
160
147
|
return ListFileOutput(content="\n".join(output_lines))
|
|
161
148
|
|
|
@@ -165,11 +152,11 @@ def _list_files(
|
|
|
165
152
|
if not is_project_directory(directory):
|
|
166
153
|
warning_msg = "[yellow bold]Warning:[/yellow bold] 🏠 Detected home directory - limiting to non-recursive listing for performance"
|
|
167
154
|
output_lines.append(warning_msg)
|
|
168
|
-
|
|
155
|
+
|
|
169
156
|
info_msg = f"[dim]💡 To force recursive listing in home directory, use list_files('{directory}', recursive=True) explicitly[/dim]"
|
|
170
157
|
output_lines.append(info_msg)
|
|
171
158
|
recursive = False
|
|
172
|
-
|
|
159
|
+
|
|
173
160
|
# Create a temporary ignore file with our ignore patterns
|
|
174
161
|
ignore_file = None
|
|
175
162
|
try:
|
|
@@ -190,50 +177,51 @@ def _list_files(
|
|
|
190
177
|
if os.path.exists(venv_rg_exe_path):
|
|
191
178
|
rg_path = venv_rg_exe_path
|
|
192
179
|
break
|
|
193
|
-
|
|
180
|
+
|
|
194
181
|
if not rg_path:
|
|
195
|
-
error_msg =
|
|
182
|
+
error_msg = "[red bold]Error:[/red bold] ripgrep (rg) not found. Please install ripgrep to use this tool."
|
|
196
183
|
output_lines.append(error_msg)
|
|
197
184
|
return ListFileOutput(content="\n".join(output_lines))
|
|
198
|
-
|
|
185
|
+
|
|
199
186
|
# Build command for ripgrep --files
|
|
200
187
|
cmd = [rg_path, "--files"]
|
|
201
|
-
|
|
188
|
+
|
|
202
189
|
# For non-recursive mode, we'll limit depth after getting results
|
|
203
190
|
if not recursive:
|
|
204
191
|
cmd.extend(["--max-depth", "1"])
|
|
205
|
-
|
|
192
|
+
|
|
206
193
|
# Add ignore patterns to the command via a temporary file
|
|
207
194
|
from code_puppy.tools.common import IGNORE_PATTERNS
|
|
208
|
-
|
|
195
|
+
|
|
196
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".ignore") as f:
|
|
209
197
|
ignore_file = f.name
|
|
210
198
|
for pattern in IGNORE_PATTERNS:
|
|
211
199
|
f.write(f"{pattern}\n")
|
|
212
|
-
|
|
200
|
+
|
|
213
201
|
cmd.extend(["--ignore-file", ignore_file])
|
|
214
202
|
cmd.append(directory)
|
|
215
|
-
|
|
203
|
+
|
|
216
204
|
# Run ripgrep to get file listing
|
|
217
205
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
218
|
-
|
|
206
|
+
|
|
219
207
|
# Process the output lines
|
|
220
|
-
files = result.stdout.strip().split(
|
|
221
|
-
|
|
208
|
+
files = result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
209
|
+
|
|
222
210
|
# Create ListedFile objects with metadata
|
|
223
211
|
for file_path in files:
|
|
224
212
|
if not file_path: # Skip empty lines
|
|
225
213
|
continue
|
|
226
|
-
|
|
214
|
+
|
|
227
215
|
full_path = os.path.join(directory, file_path)
|
|
228
|
-
|
|
216
|
+
|
|
229
217
|
# Skip if file doesn't exist (though it should)
|
|
230
218
|
if not os.path.exists(full_path):
|
|
231
219
|
continue
|
|
232
|
-
|
|
220
|
+
|
|
233
221
|
# For non-recursive mode, skip files in subdirectories
|
|
234
222
|
if not recursive and os.sep in file_path:
|
|
235
223
|
continue
|
|
236
|
-
|
|
224
|
+
|
|
237
225
|
# Check if path is a file or directory
|
|
238
226
|
if os.path.isfile(full_path):
|
|
239
227
|
entry_type = "file"
|
|
@@ -244,19 +232,19 @@ def _list_files(
|
|
|
244
232
|
else:
|
|
245
233
|
# Skip if it's neither a file nor directory
|
|
246
234
|
continue
|
|
247
|
-
|
|
235
|
+
|
|
248
236
|
try:
|
|
249
237
|
# Get stats for the entry
|
|
250
238
|
stat_info = os.stat(full_path)
|
|
251
239
|
actual_size = stat_info.st_size
|
|
252
|
-
|
|
240
|
+
|
|
253
241
|
# For files, we use the actual size; for directories, we keep size=0
|
|
254
242
|
if entry_type == "file":
|
|
255
243
|
size = actual_size
|
|
256
|
-
|
|
244
|
+
|
|
257
245
|
# Calculate depth
|
|
258
246
|
depth = file_path.count(os.sep)
|
|
259
|
-
|
|
247
|
+
|
|
260
248
|
# Add directory entries if needed for files
|
|
261
249
|
if entry_type == "file":
|
|
262
250
|
dir_path = os.path.dirname(file_path)
|
|
@@ -264,9 +252,12 @@ def _list_files(
|
|
|
264
252
|
# Add directory path components if they don't exist
|
|
265
253
|
path_parts = dir_path.split(os.sep)
|
|
266
254
|
for i in range(len(path_parts)):
|
|
267
|
-
partial_path = os.sep.join(path_parts[:i+1])
|
|
255
|
+
partial_path = os.sep.join(path_parts[: i + 1])
|
|
268
256
|
# Check if we already added this directory
|
|
269
|
-
if not any(
|
|
257
|
+
if not any(
|
|
258
|
+
f.path == partial_path and f.type == "directory"
|
|
259
|
+
for f in results
|
|
260
|
+
):
|
|
270
261
|
results.append(
|
|
271
262
|
ListedFile(
|
|
272
263
|
path=partial_path,
|
|
@@ -276,7 +267,7 @@ def _list_files(
|
|
|
276
267
|
depth=partial_path.count(os.sep),
|
|
277
268
|
)
|
|
278
269
|
)
|
|
279
|
-
|
|
270
|
+
|
|
280
271
|
# Add the entry (file or directory)
|
|
281
272
|
results.append(
|
|
282
273
|
ListedFile(
|
|
@@ -291,11 +282,15 @@ def _list_files(
|
|
|
291
282
|
# Skip files we can't access
|
|
292
283
|
continue
|
|
293
284
|
except subprocess.TimeoutExpired:
|
|
294
|
-
error_msg =
|
|
285
|
+
error_msg = (
|
|
286
|
+
"[red bold]Error:[/red bold] List files command timed out after 30 seconds"
|
|
287
|
+
)
|
|
295
288
|
output_lines.append(error_msg)
|
|
296
289
|
return ListFileOutput(content="\n".join(output_lines))
|
|
297
290
|
except Exception as e:
|
|
298
|
-
error_msg =
|
|
291
|
+
error_msg = (
|
|
292
|
+
f"[red bold]Error:[/red bold] Error during list files operation: {e}"
|
|
293
|
+
)
|
|
299
294
|
output_lines.append(error_msg)
|
|
300
295
|
return ListFileOutput(content="\n".join(output_lines))
|
|
301
296
|
finally:
|
|
@@ -345,27 +340,27 @@ def _list_files(
|
|
|
345
340
|
dir_count = sum(1 for item in results if item.type == "directory")
|
|
346
341
|
file_count = sum(1 for item in results if item.type == "file")
|
|
347
342
|
total_size = sum(item.size for item in results if item.type == "file")
|
|
348
|
-
|
|
343
|
+
|
|
349
344
|
# Build the directory header section
|
|
350
345
|
dir_name = os.path.basename(directory) or directory
|
|
351
346
|
dir_header = f"\U0001f4c1 [bold blue]{dir_name}[/bold blue]"
|
|
352
347
|
output_lines.append(dir_header)
|
|
353
|
-
|
|
348
|
+
|
|
354
349
|
# Sort all items by path for consistent display
|
|
355
350
|
all_items = sorted(results, key=lambda x: x.path)
|
|
356
|
-
|
|
351
|
+
|
|
357
352
|
# Build file and directory tree representation
|
|
358
353
|
parent_dirs_with_content = set()
|
|
359
354
|
for item in all_items:
|
|
360
355
|
# Skip root directory entries with no path
|
|
361
356
|
if item.type == "directory" and not item.path:
|
|
362
357
|
continue
|
|
363
|
-
|
|
358
|
+
|
|
364
359
|
# Track parent directories that contain files/dirs
|
|
365
360
|
if os.sep in item.path:
|
|
366
361
|
parent_path = os.path.dirname(item.path)
|
|
367
362
|
parent_dirs_with_content.add(parent_path)
|
|
368
|
-
|
|
363
|
+
|
|
369
364
|
# Calculate indentation depth based on path separators
|
|
370
365
|
depth = item.path.count(os.sep) + 1 if item.path else 0
|
|
371
366
|
prefix = ""
|
|
@@ -374,10 +369,10 @@ def _list_files(
|
|
|
374
369
|
prefix += "\u2514\u2500\u2500 "
|
|
375
370
|
else:
|
|
376
371
|
prefix += " "
|
|
377
|
-
|
|
372
|
+
|
|
378
373
|
# Get the display name (basename) of the item
|
|
379
374
|
name = os.path.basename(item.path) or item.path
|
|
380
|
-
|
|
375
|
+
|
|
381
376
|
# Add directory or file line with appropriate formatting
|
|
382
377
|
if item.type == "directory":
|
|
383
378
|
dir_line = f"{prefix}\U0001f4c1 [bold blue]{name}/[/bold blue]"
|
|
@@ -387,17 +382,17 @@ def _list_files(
|
|
|
387
382
|
size_str = format_size(item.size)
|
|
388
383
|
file_line = f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]"
|
|
389
384
|
output_lines.append(file_line)
|
|
390
|
-
|
|
385
|
+
|
|
391
386
|
# Add summary information
|
|
392
387
|
summary_header = "\n[bold cyan]Summary:[/bold cyan]"
|
|
393
388
|
output_lines.append(summary_header)
|
|
394
|
-
|
|
389
|
+
|
|
395
390
|
summary_line = f"\U0001f4c1 [blue]{dir_count} directories[/blue], \U0001f4c4 [green]{file_count} files[/green] [dim]({format_size(total_size)} total)[/dim]"
|
|
396
391
|
output_lines.append(summary_line)
|
|
397
|
-
|
|
392
|
+
|
|
398
393
|
final_divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
|
|
399
394
|
output_lines.append(final_divider)
|
|
400
|
-
|
|
395
|
+
|
|
401
396
|
# Return both the content string and the list of ListedFile objects
|
|
402
397
|
return ListFileOutput(content="\n".join(output_lines), files=results)
|
|
403
398
|
|
|
@@ -463,11 +458,10 @@ def _read_file(
|
|
|
463
458
|
def _grep(context: RunContext, search_string: str, directory: str = ".") -> GrepOutput:
|
|
464
459
|
import subprocess
|
|
465
460
|
import json
|
|
466
|
-
import tempfile
|
|
467
461
|
import os
|
|
468
462
|
import shutil
|
|
469
463
|
import sys
|
|
470
|
-
|
|
464
|
+
|
|
471
465
|
directory = os.path.abspath(directory)
|
|
472
466
|
matches: List[MatchInfo] = []
|
|
473
467
|
|
|
@@ -490,7 +484,7 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
490
484
|
# --max-filesize 5M to avoid huge files (increased from 1M)
|
|
491
485
|
# --type=all to search across all recognized text file types
|
|
492
486
|
# --ignore-file to obey our ignore list
|
|
493
|
-
|
|
487
|
+
|
|
494
488
|
# Find ripgrep executable - first check system PATH, then virtual environment
|
|
495
489
|
rg_path = shutil.which("rg")
|
|
496
490
|
if not rg_path:
|
|
@@ -508,43 +502,62 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
508
502
|
if os.path.exists(venv_rg_exe_path):
|
|
509
503
|
rg_path = venv_rg_exe_path
|
|
510
504
|
break
|
|
511
|
-
|
|
505
|
+
|
|
512
506
|
if not rg_path:
|
|
513
|
-
emit_error(
|
|
507
|
+
emit_error(
|
|
508
|
+
"ripgrep (rg) not found. Please install ripgrep to use this tool.",
|
|
509
|
+
message_group=group_id,
|
|
510
|
+
)
|
|
514
511
|
return GrepOutput(matches=[])
|
|
515
|
-
|
|
516
|
-
cmd = [
|
|
517
|
-
|
|
512
|
+
|
|
513
|
+
cmd = [
|
|
514
|
+
rg_path,
|
|
515
|
+
"--json",
|
|
516
|
+
"--max-count",
|
|
517
|
+
"50",
|
|
518
|
+
"--max-filesize",
|
|
519
|
+
"5M",
|
|
520
|
+
"--type=all",
|
|
521
|
+
]
|
|
522
|
+
|
|
518
523
|
# Add ignore patterns to the command via a temporary file
|
|
519
524
|
from code_puppy.tools.common import IGNORE_PATTERNS
|
|
520
|
-
|
|
525
|
+
|
|
526
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".ignore") as f:
|
|
521
527
|
ignore_file = f.name
|
|
522
528
|
for pattern in IGNORE_PATTERNS:
|
|
523
529
|
f.write(f"{pattern}\n")
|
|
524
|
-
|
|
530
|
+
|
|
525
531
|
cmd.extend(["--ignore-file", ignore_file])
|
|
526
532
|
cmd.extend([search_string, directory])
|
|
527
533
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
528
|
-
|
|
534
|
+
|
|
529
535
|
# Parse the JSON output from ripgrep
|
|
530
|
-
for line in result.stdout.strip().split(
|
|
536
|
+
for line in result.stdout.strip().split("\n"):
|
|
531
537
|
if not line:
|
|
532
538
|
continue
|
|
533
539
|
try:
|
|
534
540
|
match_data = json.loads(line)
|
|
535
541
|
# Only process match events, not context or summary
|
|
536
|
-
if match_data.get(
|
|
537
|
-
data = match_data.get(
|
|
538
|
-
path_data = data.get(
|
|
539
|
-
file_path =
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
542
|
+
if match_data.get("type") == "match":
|
|
543
|
+
data = match_data.get("data", {})
|
|
544
|
+
path_data = data.get("path", {})
|
|
545
|
+
file_path = (
|
|
546
|
+
path_data.get("text", "") if path_data.get("text") else ""
|
|
547
|
+
)
|
|
548
|
+
line_number = data.get("line_number", None)
|
|
549
|
+
line_content = (
|
|
550
|
+
data.get("lines", {}).get("text", "")
|
|
551
|
+
if data.get("lines", {}).get("text")
|
|
552
|
+
else ""
|
|
553
|
+
)
|
|
554
|
+
if len(line_content.strip()) > 512:
|
|
555
|
+
line_content = line_content.strip()[0:512]
|
|
543
556
|
if file_path and line_number:
|
|
544
557
|
match_info = MatchInfo(
|
|
545
558
|
file_path=file_path,
|
|
546
559
|
line_number=line_number,
|
|
547
|
-
line_content=line_content.strip()
|
|
560
|
+
line_content=line_content.strip(),
|
|
548
561
|
)
|
|
549
562
|
matches.append(match_info)
|
|
550
563
|
# Limit to 50 matches total, same as original implementation
|
|
@@ -557,7 +570,7 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
557
570
|
except json.JSONDecodeError:
|
|
558
571
|
# Skip lines that aren't valid JSON
|
|
559
572
|
continue
|
|
560
|
-
|
|
573
|
+
|
|
561
574
|
if not matches:
|
|
562
575
|
emit_warning(
|
|
563
576
|
f"No matches found for '{search_string}' in {directory}",
|
|
@@ -568,18 +581,21 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
568
581
|
f"Found {len(matches)} match(es) for '{search_string}' in {directory}",
|
|
569
582
|
message_group=group_id,
|
|
570
583
|
)
|
|
571
|
-
|
|
584
|
+
|
|
572
585
|
except subprocess.TimeoutExpired:
|
|
573
|
-
emit_error(
|
|
586
|
+
emit_error("Grep command timed out after 30 seconds", message_group=group_id)
|
|
574
587
|
except FileNotFoundError:
|
|
575
|
-
emit_error(
|
|
588
|
+
emit_error(
|
|
589
|
+
"ripgrep (rg) not found. Please install ripgrep to use this tool.",
|
|
590
|
+
message_group=group_id,
|
|
591
|
+
)
|
|
576
592
|
except Exception as e:
|
|
577
593
|
emit_error(f"Error during grep operation: {e}", message_group=group_id)
|
|
578
594
|
finally:
|
|
579
595
|
# Clean up the temporary ignore file
|
|
580
596
|
if ignore_file and os.path.exists(ignore_file):
|
|
581
597
|
os.unlink(ignore_file)
|
|
582
|
-
|
|
598
|
+
|
|
583
599
|
return GrepOutput(matches=matches)
|
|
584
600
|
|
|
585
601
|
|
|
@@ -592,8 +608,8 @@ def register_list_files(agent):
|
|
|
592
608
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
593
609
|
) -> ListFileOutput:
|
|
594
610
|
"""List files and directories with intelligent filtering and safety features.
|
|
595
|
-
|
|
596
|
-
This function will only allow recursive listing when the allow_recursion
|
|
611
|
+
|
|
612
|
+
This function will only allow recursive listing when the allow_recursion
|
|
597
613
|
configuration is set to true via the /set allow_recursion=true command.
|
|
598
614
|
|
|
599
615
|
This tool provides comprehensive directory listing with smart home directory
|
|
@@ -635,17 +651,21 @@ def register_list_files(agent):
|
|
|
635
651
|
- Check for errors in the response
|
|
636
652
|
- Combine with grep to find specific file patterns
|
|
637
653
|
"""
|
|
638
|
-
warning=None
|
|
654
|
+
warning = None
|
|
639
655
|
if recursive and not get_allow_recursion():
|
|
640
656
|
warning = "Recursion disabled globally for list_files - returning non-recursive results"
|
|
641
657
|
recursive = False
|
|
642
658
|
result = _list_files(context, directory, recursive)
|
|
643
|
-
|
|
659
|
+
|
|
644
660
|
# Emit the content directly to ensure it's displayed to the user
|
|
645
|
-
emit_info(
|
|
646
|
-
|
|
661
|
+
emit_info(
|
|
662
|
+
result.content, message_group=generate_group_id("list_files", directory)
|
|
663
|
+
)
|
|
647
664
|
if warning:
|
|
648
665
|
result.error = warning
|
|
666
|
+
if (len(result.content)) > 200000:
|
|
667
|
+
result.content = result.content[0:200000]
|
|
668
|
+
result.error = "Results truncated. This is a massive directory tree, recommend non-recursive calls to list_files"
|
|
649
669
|
return result
|
|
650
670
|
|
|
651
671
|
|
|
@@ -716,9 +736,9 @@ def register_grep(agent):
|
|
|
716
736
|
) -> GrepOutput:
|
|
717
737
|
"""Recursively search for text patterns across files using ripgrep (rg).
|
|
718
738
|
|
|
719
|
-
This tool leverages the high-performance ripgrep utility for fast text
|
|
739
|
+
This tool leverages the high-performance ripgrep utility for fast text
|
|
720
740
|
searching across directory trees. It searches across all recognized text file
|
|
721
|
-
types (Python, JavaScript, HTML, CSS, Markdown, etc.) while automatically
|
|
741
|
+
types (Python, JavaScript, HTML, CSS, Markdown, etc.) while automatically
|
|
722
742
|
filtering binary files and limiting results for performance.
|
|
723
743
|
|
|
724
744
|
The search_string parameter supports ripgrep's full flag syntax, allowing
|
code_puppy/tui/app.py
CHANGED
|
@@ -431,7 +431,7 @@ class CodePuppyTUI(App):
|
|
|
431
431
|
# Only cancel the agent task if NO processes were killed
|
|
432
432
|
self._current_worker.cancel()
|
|
433
433
|
state_management._message_history = prune_interrupted_tool_calls(
|
|
434
|
-
state_management.
|
|
434
|
+
state_management.get_message_history()
|
|
435
435
|
)
|
|
436
436
|
self.add_system_message("⚠️ Processing cancelled by user")
|
|
437
437
|
# Stop spinner and clear state only when agent is actually cancelled
|
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
+
"openrouter-sonoma-dusk-alpha": {
|
|
3
|
+
"type": "custom_openai",
|
|
4
|
+
"name": "openrouter/sonoma-dusk-alpha",
|
|
5
|
+
"custom_endpoint": {
|
|
6
|
+
"url": "https://openrouter.ai/api/v1",
|
|
7
|
+
"api_key": "$OPENROUTER_API_KEY",
|
|
8
|
+
"headers": {
|
|
9
|
+
"HTTP-Referer": "https://github.com/mpfaffenberger/code_puppy",
|
|
10
|
+
"X-Title": "Code Puppy"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"context_length": 2000000
|
|
14
|
+
},
|
|
15
|
+
"openrouter-sonoma-sky-alpha": {
|
|
16
|
+
"type": "custom_openai",
|
|
17
|
+
"name": "openrouter/sonoma-sky-alpha",
|
|
18
|
+
"custom_endpoint": {
|
|
19
|
+
"url": "https://openrouter.ai/api/v1",
|
|
20
|
+
"api_key": "$OPENROUTER_API_KEY",
|
|
21
|
+
"headers": {
|
|
22
|
+
"HTTP-Referer": "https://github.com/mpfaffenberger/code_puppy",
|
|
23
|
+
"X-Title": "Code Puppy"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"context_length": 2000000
|
|
27
|
+
},
|
|
2
28
|
"gpt-5": {
|
|
3
29
|
"type": "openai",
|
|
4
30
|
"name": "gpt-5",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.156
|
|
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
|
|
@@ -10,10 +10,11 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
16
|
Classifier: Topic :: Software Development :: Code Generators
|
|
16
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.11
|
|
17
18
|
Requires-Dist: bs4>=0.0.2
|
|
18
19
|
Requires-Dist: fastapi>=0.110.0
|
|
19
20
|
Requires-Dist: httpx-limiter>=0.3.0
|