code-puppy 0.0.152__tar.gz → 0.0.153__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.
- {code_puppy-0.0.152 → code_puppy-0.0.153}/PKG-INFO +1 -1
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/config.py +1 -1
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/message_history_processor.py +2 -2
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/file_operations.py +207 -129
- {code_puppy-0.0.152 → code_puppy-0.0.153}/pyproject.toml +1 -1
- {code_puppy-0.0.152 → code_puppy-0.0.153}/.gitignore +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/LICENSE +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/README.md +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agent.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/agent_orchestrator.json +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/agents/runtime_manager.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/meta_command_handler.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/main.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/async_lifecycle.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/blocking_startup.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/captured_stdio_server.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/circuit_breaker.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/config_wizard.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/dashboard.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/error_isolation.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/examples/retry_example.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/health_monitor.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/managed_server.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/manager.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/registry.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/retry_manager.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/server_registry_catalog.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/status_tracker.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/mcp/system_tools.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/models.json +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/state_management.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/token_utils.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/token_check.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/app.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/input_area.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/status_bar.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/screens/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/screens/settings.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/__init__.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_agent_command.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_chat_message.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_chat_view.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_command_history.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_copy_button.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_custom_widgets.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_disclaimer.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_enums.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_file_browser.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_help.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_history_file_reader.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_input_area.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_settings.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_sidebar.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_sidebar_history.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_status_bar.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_timestamped_history.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_tools.py +0 -0
- {code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/version_checker.py +0 -0
|
@@ -31,7 +31,7 @@ def estimate_token_count(text: str) -> int:
|
|
|
31
31
|
Simple token estimation using len(message) - 4.
|
|
32
32
|
This replaces tiktoken with a much simpler approach.
|
|
33
33
|
"""
|
|
34
|
-
return max(1, len(text)
|
|
34
|
+
return int(max(1, len(text)) / 4)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def stringify_message_part(part) -> str:
|
|
@@ -86,7 +86,7 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
|
86
86
|
if part_str:
|
|
87
87
|
total_tokens += estimate_token_count(part_str)
|
|
88
88
|
|
|
89
|
-
return max(1, total_tokens)
|
|
89
|
+
return int(max(1, total_tokens) / 4)
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# file_operations.py
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import tempfile
|
|
4
5
|
from typing import List
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel, conint
|
|
@@ -46,7 +47,7 @@ class ListedFile(BaseModel):
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class ListFileOutput(BaseModel):
|
|
49
|
-
|
|
50
|
+
content: str
|
|
50
51
|
error: str | None = None
|
|
51
52
|
|
|
52
53
|
|
|
@@ -125,116 +126,182 @@ def is_project_directory(directory):
|
|
|
125
126
|
def _list_files(
|
|
126
127
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
127
128
|
) -> ListFileOutput:
|
|
128
|
-
|
|
129
|
+
import subprocess
|
|
130
|
+
import tempfile
|
|
131
|
+
import shutil
|
|
132
|
+
import sys
|
|
133
|
+
|
|
129
134
|
results = []
|
|
130
135
|
directory = os.path.abspath(directory)
|
|
131
136
|
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
|
|
137
|
+
# Build string representation
|
|
138
|
+
output_lines = []
|
|
139
|
+
|
|
140
|
+
directory_listing_header = "\n[bold white on blue] DIRECTORY LISTING [/bold white on blue]"
|
|
141
|
+
output_lines.append(directory_listing_header)
|
|
142
|
+
|
|
143
|
+
directory_info = f"\U0001f4c2 [bold cyan]{directory}[/bold cyan] [dim](recursive={recursive})[/dim]\n"
|
|
144
|
+
output_lines.append(directory_info)
|
|
145
|
+
|
|
146
|
+
divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
|
|
147
|
+
output_lines.append(divider)
|
|
148
|
+
|
|
144
149
|
if not os.path.exists(directory):
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
150
|
+
error_msg = f"[red bold]Error:[/red bold] Directory '{directory}' does not exist"
|
|
151
|
+
output_lines.append(error_msg)
|
|
152
|
+
|
|
153
|
+
output_lines.append(divider)
|
|
154
|
+
return ListFileOutput(content="\n".join(output_lines))
|
|
150
155
|
if not os.path.isdir(directory):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
+
error_msg = f"[red bold]Error:[/red bold] '{directory}' is not a directory"
|
|
157
|
+
output_lines.append(error_msg)
|
|
158
|
+
|
|
159
|
+
output_lines.append(divider)
|
|
160
|
+
return ListFileOutput(content="\n".join(output_lines))
|
|
156
161
|
|
|
157
162
|
# Smart home directory detection - auto-limit recursion for performance
|
|
158
163
|
# But allow recursion in tests (when context=None) or when explicitly requested
|
|
159
164
|
if context is not None and is_likely_home_directory(directory) and recursive:
|
|
160
165
|
if not is_project_directory(directory):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
f"💡 To force recursive listing in home directory, use list_files('{directory}', recursive=True) explicitly",
|
|
167
|
-
message_group=group_id,
|
|
168
|
-
)
|
|
166
|
+
warning_msg = "[yellow bold]Warning:[/yellow bold] 🏠 Detected home directory - limiting to non-recursive listing for performance"
|
|
167
|
+
output_lines.append(warning_msg)
|
|
168
|
+
|
|
169
|
+
info_msg = f"[dim]💡 To force recursive listing in home directory, use list_files('{directory}', recursive=True) explicitly[/dim]"
|
|
170
|
+
output_lines.append(info_msg)
|
|
169
171
|
recursive = False
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
|
|
173
|
+
# Create a temporary ignore file with our ignore patterns
|
|
174
|
+
ignore_file = None
|
|
175
|
+
try:
|
|
176
|
+
# Find ripgrep executable - first check system PATH, then virtual environment
|
|
177
|
+
rg_path = shutil.which("rg")
|
|
178
|
+
if not rg_path:
|
|
179
|
+
# Try to find it in the virtual environment
|
|
180
|
+
# Use sys.executable to determine the Python environment path
|
|
181
|
+
python_dir = os.path.dirname(sys.executable)
|
|
182
|
+
# Check both 'bin' (Unix) and 'Scripts' (Windows) directories
|
|
183
|
+
for rg_dir in ["bin", "Scripts"]:
|
|
184
|
+
venv_rg_path = os.path.join(python_dir, "rg")
|
|
185
|
+
if os.path.exists(venv_rg_path):
|
|
186
|
+
rg_path = venv_rg_path
|
|
187
|
+
break
|
|
188
|
+
# Also check with .exe extension for Windows
|
|
189
|
+
venv_rg_exe_path = os.path.join(python_dir, "rg.exe")
|
|
190
|
+
if os.path.exists(venv_rg_exe_path):
|
|
191
|
+
rg_path = venv_rg_exe_path
|
|
192
|
+
break
|
|
175
193
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
194
|
+
if not rg_path:
|
|
195
|
+
error_msg = f"[red bold]Error:[/red bold] ripgrep (rg) not found. Please install ripgrep to use this tool."
|
|
196
|
+
output_lines.append(error_msg)
|
|
197
|
+
return ListFileOutput(content="\n".join(output_lines))
|
|
198
|
+
|
|
199
|
+
# Build command for ripgrep --files
|
|
200
|
+
cmd = [rg_path, "--files"]
|
|
180
201
|
|
|
181
|
-
#
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
# For non-recursive mode, we'll limit depth after getting results
|
|
203
|
+
if not recursive:
|
|
204
|
+
cmd.extend(["--max-depth", "1"])
|
|
205
|
+
|
|
206
|
+
# Add ignore patterns to the command via a temporary file
|
|
207
|
+
from code_puppy.tools.common import IGNORE_PATTERNS
|
|
208
|
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ignore') as f:
|
|
209
|
+
ignore_file = f.name
|
|
210
|
+
for pattern in IGNORE_PATTERNS:
|
|
211
|
+
f.write(f"{pattern}\n")
|
|
212
|
+
|
|
213
|
+
cmd.extend(["--ignore-file", ignore_file])
|
|
214
|
+
cmd.append(directory)
|
|
215
|
+
|
|
216
|
+
# Run ripgrep to get file listing
|
|
217
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
218
|
+
|
|
219
|
+
# Process the output lines
|
|
220
|
+
files = result.stdout.strip().split('\n') if result.stdout.strip() else []
|
|
221
|
+
|
|
222
|
+
# Create ListedFile objects with metadata
|
|
223
|
+
for file_path in files:
|
|
224
|
+
if not file_path: # Skip empty lines
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
full_path = os.path.join(directory, file_path)
|
|
228
|
+
|
|
229
|
+
# Skip if file doesn't exist (though it should)
|
|
230
|
+
if not os.path.exists(full_path):
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
# For non-recursive mode, skip files in subdirectories
|
|
234
|
+
if not recursive and os.sep in file_path:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
# Check if path is a file or directory
|
|
238
|
+
if os.path.isfile(full_path):
|
|
239
|
+
entry_type = "file"
|
|
240
|
+
size = os.path.getsize(full_path)
|
|
241
|
+
elif os.path.isdir(full_path):
|
|
242
|
+
entry_type = "directory"
|
|
243
|
+
size = 0
|
|
244
|
+
else:
|
|
245
|
+
# Skip if it's neither a file nor directory
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
# Get stats for the entry
|
|
250
|
+
stat_info = os.stat(full_path)
|
|
251
|
+
actual_size = stat_info.st_size
|
|
252
|
+
|
|
253
|
+
# For files, we use the actual size; for directories, we keep size=0
|
|
254
|
+
if entry_type == "file":
|
|
255
|
+
size = actual_size
|
|
256
|
+
|
|
257
|
+
# Calculate depth
|
|
258
|
+
depth = file_path.count(os.sep)
|
|
259
|
+
|
|
260
|
+
# Add directory entries if needed for files
|
|
261
|
+
if entry_type == "file":
|
|
262
|
+
dir_path = os.path.dirname(file_path)
|
|
263
|
+
if dir_path:
|
|
264
|
+
# Add directory path components if they don't exist
|
|
265
|
+
path_parts = dir_path.split(os.sep)
|
|
266
|
+
for i in range(len(path_parts)):
|
|
267
|
+
partial_path = os.sep.join(path_parts[:i+1])
|
|
268
|
+
# Check if we already added this directory
|
|
269
|
+
if not any(f.path == partial_path and f.type == "directory" for f in results):
|
|
270
|
+
results.append(
|
|
271
|
+
ListedFile(
|
|
272
|
+
path=partial_path,
|
|
273
|
+
type="directory",
|
|
274
|
+
size=0,
|
|
275
|
+
full_path=os.path.join(directory, partial_path),
|
|
276
|
+
depth=partial_path.count(os.sep),
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Add the entry (file or directory)
|
|
202
281
|
results.append(
|
|
203
282
|
ListedFile(
|
|
204
|
-
path=
|
|
205
|
-
type=
|
|
206
|
-
size=
|
|
207
|
-
full_path=
|
|
283
|
+
path=file_path,
|
|
284
|
+
type=entry_type,
|
|
285
|
+
size=size,
|
|
286
|
+
full_path=full_path,
|
|
208
287
|
depth=depth,
|
|
209
288
|
)
|
|
210
289
|
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"depth": depth,
|
|
214
|
-
"full_path": dir_path,
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
# Add files to results
|
|
218
|
-
for file in files:
|
|
219
|
-
file_path = os.path.join(root, file)
|
|
220
|
-
if should_ignore_path(file_path):
|
|
290
|
+
except (FileNotFoundError, PermissionError, OSError):
|
|
291
|
+
# Skip files we can't access
|
|
221
292
|
continue
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
except (FileNotFoundError, PermissionError):
|
|
235
|
-
continue
|
|
236
|
-
if not recursive:
|
|
237
|
-
break
|
|
293
|
+
except subprocess.TimeoutExpired:
|
|
294
|
+
error_msg = f"[red bold]Error:[/red bold] List files command timed out after 30 seconds"
|
|
295
|
+
output_lines.append(error_msg)
|
|
296
|
+
return ListFileOutput(content="\n".join(output_lines))
|
|
297
|
+
except Exception as e:
|
|
298
|
+
error_msg = f"[red bold]Error:[/red bold] Error during list files operation: {e}"
|
|
299
|
+
output_lines.append(error_msg)
|
|
300
|
+
return ListFileOutput(content="\n".join(output_lines))
|
|
301
|
+
finally:
|
|
302
|
+
# Clean up the temporary ignore file
|
|
303
|
+
if ignore_file and os.path.exists(ignore_file):
|
|
304
|
+
os.unlink(ignore_file)
|
|
238
305
|
|
|
239
306
|
def format_size(size_bytes):
|
|
240
307
|
if size_bytes < 1024:
|
|
@@ -275,20 +342,31 @@ def _list_files(
|
|
|
275
342
|
else:
|
|
276
343
|
return "\U0001f4c4"
|
|
277
344
|
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
345
|
+
dir_count = sum(1 for item in results if item.type == "directory")
|
|
346
|
+
file_count = sum(1 for item in results if item.type == "file")
|
|
347
|
+
total_size = sum(item.size for item in results if item.type == "file")
|
|
348
|
+
|
|
349
|
+
# Build the directory header section
|
|
350
|
+
dir_name = os.path.basename(directory) or directory
|
|
351
|
+
dir_header = f"\U0001f4c1 [bold blue]{dir_name}[/bold blue]"
|
|
352
|
+
output_lines.append(dir_header)
|
|
353
|
+
|
|
354
|
+
# Sort all items by path for consistent display
|
|
284
355
|
all_items = sorted(results, key=lambda x: x.path)
|
|
356
|
+
|
|
357
|
+
# Build file and directory tree representation
|
|
285
358
|
parent_dirs_with_content = set()
|
|
286
|
-
for
|
|
359
|
+
for item in all_items:
|
|
360
|
+
# Skip root directory entries with no path
|
|
287
361
|
if item.type == "directory" and not item.path:
|
|
288
362
|
continue
|
|
363
|
+
|
|
364
|
+
# Track parent directories that contain files/dirs
|
|
289
365
|
if os.sep in item.path:
|
|
290
366
|
parent_path = os.path.dirname(item.path)
|
|
291
367
|
parent_dirs_with_content.add(parent_path)
|
|
368
|
+
|
|
369
|
+
# Calculate indentation depth based on path separators
|
|
292
370
|
depth = item.path.count(os.sep) + 1 if item.path else 0
|
|
293
371
|
prefix = ""
|
|
294
372
|
for d in range(depth):
|
|
@@ -296,29 +374,32 @@ def _list_files(
|
|
|
296
374
|
prefix += "\u2514\u2500\u2500 "
|
|
297
375
|
else:
|
|
298
376
|
prefix += " "
|
|
377
|
+
|
|
378
|
+
# Get the display name (basename) of the item
|
|
299
379
|
name = os.path.basename(item.path) or item.path
|
|
380
|
+
|
|
381
|
+
# Add directory or file line with appropriate formatting
|
|
300
382
|
if item.type == "directory":
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
message_group=group_id,
|
|
304
|
-
)
|
|
383
|
+
dir_line = f"{prefix}\U0001f4c1 [bold blue]{name}/[/bold blue]"
|
|
384
|
+
output_lines.append(dir_line)
|
|
305
385
|
else:
|
|
306
386
|
icon = get_file_icon(item.path)
|
|
307
387
|
size_str = format_size(item.size)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
|
|
388
|
+
file_line = f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]"
|
|
389
|
+
output_lines.append(file_line)
|
|
390
|
+
|
|
391
|
+
# Add summary information
|
|
392
|
+
summary_header = "\n[bold cyan]Summary:[/bold cyan]"
|
|
393
|
+
output_lines.append(summary_header)
|
|
394
|
+
|
|
395
|
+
summary_line = f"\U0001f4c1 [blue]{dir_count} directories[/blue], \U0001f4c4 [green]{file_count} files[/green] [dim]({format_size(total_size)} total)[/dim]"
|
|
396
|
+
output_lines.append(summary_line)
|
|
397
|
+
|
|
398
|
+
final_divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
|
|
399
|
+
output_lines.append(final_divider)
|
|
400
|
+
|
|
401
|
+
# Return both the content string and the list of ListedFile objects
|
|
402
|
+
return ListFileOutput(content="\n".join(output_lines), files=results)
|
|
322
403
|
|
|
323
404
|
|
|
324
405
|
def _read_file(
|
|
@@ -530,25 +611,18 @@ def register_list_files(agent):
|
|
|
530
611
|
Defaults to True.
|
|
531
612
|
|
|
532
613
|
Returns:
|
|
533
|
-
ListFileOutput: A
|
|
534
|
-
-
|
|
535
|
-
each ListedFile contains:
|
|
536
|
-
- path (str | None): Relative path from the listing directory
|
|
537
|
-
- type (str | None): "file" or "directory"
|
|
538
|
-
- size (int): File size in bytes (0 for directories)
|
|
539
|
-
- full_path (str | None): Absolute path to the item
|
|
540
|
-
- depth (int | None): Nesting depth from the root directory
|
|
614
|
+
ListFileOutput: A response containing:
|
|
615
|
+
- content (str): String representation of the directory listing
|
|
541
616
|
- error (str | None): Error message if listing failed
|
|
542
617
|
|
|
543
618
|
Examples:
|
|
544
619
|
>>> # List current directory
|
|
545
620
|
>>> result = list_files(ctx)
|
|
546
|
-
>>>
|
|
547
|
-
... print(f"{file.type}: {file.path} ({file.size} bytes)")
|
|
621
|
+
>>> print(result.content)
|
|
548
622
|
|
|
549
623
|
>>> # List specific directory non-recursively
|
|
550
624
|
>>> result = list_files(ctx, "/path/to/project", recursive=False)
|
|
551
|
-
>>> print(
|
|
625
|
+
>>> print(result.content)
|
|
552
626
|
|
|
553
627
|
>>> # Handle potential errors
|
|
554
628
|
>>> result = list_files(ctx, "/nonexistent/path")
|
|
@@ -566,9 +640,13 @@ def register_list_files(agent):
|
|
|
566
640
|
warning = "Recursion disabled globally for list_files - returning non-recursive results"
|
|
567
641
|
recursive = False
|
|
568
642
|
result = _list_files(context, directory, recursive)
|
|
643
|
+
|
|
644
|
+
# Emit the content directly to ensure it's displayed to the user
|
|
645
|
+
emit_info(result.content, message_group=generate_group_id("list_files", directory))
|
|
646
|
+
|
|
569
647
|
if warning:
|
|
570
648
|
result.error = warning
|
|
571
|
-
return
|
|
649
|
+
return result
|
|
572
650
|
|
|
573
651
|
|
|
574
652
|
def register_read_file(agent):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/load_context_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/model_picker_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/command_line/prompt_toolkit_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/components/command_history_modal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.152 → code_puppy-0.0.153}/code_puppy/tui/tests/test_sidebar_history_navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|