code-puppy 0.0.146__tar.gz → 0.0.148__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.146 → code_puppy-0.0.148}/PKG-INFO +1 -1
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agent.py +13 -5
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/meta_command_handler.py +1 -1
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/config.py +15 -4
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/file_operations.py +47 -205
- {code_puppy-0.0.146 → code_puppy-0.0.148}/pyproject.toml +1 -1
- {code_puppy-0.0.146 → code_puppy-0.0.148}/.gitignore +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/LICENSE +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/README.md +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/agent_orchestrator.json +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/agents/runtime_manager.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/main.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/async_lifecycle.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/blocking_startup.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/captured_stdio_server.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/circuit_breaker.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/config_wizard.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/dashboard.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/error_isolation.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/examples/retry_example.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/health_monitor.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/managed_server.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/manager.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/registry.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/retry_manager.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/server_registry_catalog.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/status_tracker.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/mcp/system_tools.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/message_history_processor.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/models.json +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/state_management.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/token_utils.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/token_check.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/app.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/input_area.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/components/status_bar.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/screens/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/screens/settings.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/__init__.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_agent_command.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_chat_message.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_chat_view.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_command_history.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_copy_button.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_custom_widgets.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_disclaimer.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_enums.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_file_browser.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_help.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_history_file_reader.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_input_area.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_settings.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_sidebar.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_sidebar_history.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_status_bar.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_timestamped_history.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/tui/tests/test_tools.py +0 -0
- {code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/version_checker.py +0 -0
|
@@ -24,11 +24,19 @@ from code_puppy.tools.common import console
|
|
|
24
24
|
|
|
25
25
|
def load_puppy_rules():
|
|
26
26
|
global PUPPY_RULES
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
|
|
28
|
+
# Check for all 4 combinations of the rules file
|
|
29
|
+
possible_paths = ["AGENTS.md", "AGENT.md", "agents.md", "agent.md"]
|
|
30
|
+
|
|
31
|
+
for path_str in possible_paths:
|
|
32
|
+
puppy_rules_path = Path(path_str)
|
|
33
|
+
if puppy_rules_path.exists():
|
|
34
|
+
with open(puppy_rules_path, "r") as f:
|
|
35
|
+
puppy_rules = f.read()
|
|
36
|
+
return puppy_rules
|
|
37
|
+
|
|
38
|
+
# If none of the files exist, return None
|
|
39
|
+
return None
|
|
32
40
|
|
|
33
41
|
|
|
34
42
|
# Load at import
|
|
@@ -17,7 +17,7 @@ META_COMMANDS_HELP = """
|
|
|
17
17
|
~m <model> Set active model
|
|
18
18
|
~motd Show the latest message of the day (MOTD)
|
|
19
19
|
~show Show puppy config key-values
|
|
20
|
-
~set Set puppy config key-values (message_limit, protected_token_count, compaction_threshold, etc.)
|
|
20
|
+
~set Set puppy config key-values (message_limit, protected_token_count, compaction_threshold, allow_recursion, etc.)
|
|
21
21
|
~<unknown> Show unknown meta command warning
|
|
22
22
|
"""
|
|
23
23
|
|
|
@@ -69,6 +69,17 @@ def get_owner_name():
|
|
|
69
69
|
# using get_protected_token_count() and get_summarization_threshold()
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
def get_allow_recursion() -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Get the allow_recursion configuration value.
|
|
75
|
+
Returns True if recursion is allowed, False otherwise.
|
|
76
|
+
"""
|
|
77
|
+
val = get_value("allow_recursion")
|
|
78
|
+
if val is None:
|
|
79
|
+
return False # Default to False for safety
|
|
80
|
+
return str(val).lower() in ("1", "true", "yes", "on")
|
|
81
|
+
|
|
82
|
+
|
|
72
83
|
def get_model_context_length() -> int:
|
|
73
84
|
"""
|
|
74
85
|
Get the context length for the currently configured model from models.json
|
|
@@ -93,9 +104,9 @@ def get_model_context_length() -> int:
|
|
|
93
104
|
def get_config_keys():
|
|
94
105
|
"""
|
|
95
106
|
Returns the list of all config keys currently in puppy.cfg,
|
|
96
|
-
plus certain preset expected keys (e.g. "yolo_mode", "model", "compaction_strategy", "message_limit").
|
|
107
|
+
plus certain preset expected keys (e.g. "yolo_mode", "model", "compaction_strategy", "message_limit", "allow_recursion").
|
|
97
108
|
"""
|
|
98
|
-
default_keys = ["yolo_mode", "model", "compaction_strategy", "message_limit"]
|
|
109
|
+
default_keys = ["yolo_mode", "model", "compaction_strategy", "message_limit", "allow_recursion"]
|
|
99
110
|
config = configparser.ConfigParser()
|
|
100
111
|
config.read(CONFIG_FILE)
|
|
101
112
|
keys = set(config[DEFAULT_SECTION].keys()) if DEFAULT_SECTION in config else set()
|
|
@@ -351,7 +362,7 @@ def initialize_command_history_file():
|
|
|
351
362
|
def get_yolo_mode():
|
|
352
363
|
"""
|
|
353
364
|
Checks puppy.cfg for 'yolo_mode' (case-insensitive in value only).
|
|
354
|
-
Defaults to
|
|
365
|
+
Defaults to True if not set.
|
|
355
366
|
Allowed values for ON: 1, '1', 'true', 'yes', 'on' (all case-insensitive for value).
|
|
356
367
|
"""
|
|
357
368
|
true_vals = {"1", "true", "yes", "on"}
|
|
@@ -360,7 +371,7 @@ def get_yolo_mode():
|
|
|
360
371
|
if str(cfg_val).strip().lower() in true_vals:
|
|
361
372
|
return True
|
|
362
373
|
return False
|
|
363
|
-
return
|
|
374
|
+
return True
|
|
364
375
|
|
|
365
376
|
|
|
366
377
|
def get_mcp_disabled():
|
|
@@ -41,6 +41,7 @@ class ListedFile(BaseModel):
|
|
|
41
41
|
path: str | None
|
|
42
42
|
type: str | None
|
|
43
43
|
size: int = 0
|
|
44
|
+
full_path: str | None
|
|
44
45
|
depth: int | None
|
|
45
46
|
|
|
46
47
|
|
|
@@ -124,6 +125,7 @@ def is_project_directory(directory):
|
|
|
124
125
|
def _list_files(
|
|
125
126
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
126
127
|
) -> ListFileOutput:
|
|
128
|
+
|
|
127
129
|
results = []
|
|
128
130
|
directory = os.path.abspath(directory)
|
|
129
131
|
|
|
@@ -153,7 +155,8 @@ def _list_files(
|
|
|
153
155
|
)
|
|
154
156
|
|
|
155
157
|
# Smart home directory detection - auto-limit recursion for performance
|
|
156
|
-
|
|
158
|
+
# But allow recursion in tests (when context=None) or when explicitly requested
|
|
159
|
+
if context is not None and is_likely_home_directory(directory) and recursive:
|
|
157
160
|
if not is_project_directory(directory):
|
|
158
161
|
emit_warning(
|
|
159
162
|
"🏠 Detected home directory - limiting to non-recursive listing for performance",
|
|
@@ -167,22 +170,24 @@ def _list_files(
|
|
|
167
170
|
folder_structure = {}
|
|
168
171
|
file_list = []
|
|
169
172
|
for root, dirs, files in os.walk(directory):
|
|
173
|
+
# Filter out ignored directories
|
|
170
174
|
dirs[:] = [d for d in dirs if not should_ignore_path(os.path.join(root, d))]
|
|
175
|
+
|
|
171
176
|
rel_path = os.path.relpath(root, directory)
|
|
172
177
|
depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
|
|
173
178
|
if rel_path == ".":
|
|
174
179
|
rel_path = ""
|
|
180
|
+
|
|
181
|
+
# Add directory entry for subdirectories (except root)
|
|
175
182
|
if rel_path:
|
|
176
183
|
dir_path = os.path.join(directory, rel_path)
|
|
177
184
|
results.append(
|
|
178
185
|
ListedFile(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"depth": depth,
|
|
185
|
-
}
|
|
186
|
+
path=rel_path,
|
|
187
|
+
type="directory",
|
|
188
|
+
size=0,
|
|
189
|
+
full_path=dir_path,
|
|
190
|
+
depth=depth,
|
|
186
191
|
)
|
|
187
192
|
)
|
|
188
193
|
folder_structure[rel_path] = {
|
|
@@ -190,6 +195,26 @@ def _list_files(
|
|
|
190
195
|
"depth": depth,
|
|
191
196
|
"full_path": dir_path,
|
|
192
197
|
}
|
|
198
|
+
else: # Root directory - add both directories and files
|
|
199
|
+
# Add directories
|
|
200
|
+
for d in dirs:
|
|
201
|
+
dir_path = os.path.join(root, d)
|
|
202
|
+
results.append(
|
|
203
|
+
ListedFile(
|
|
204
|
+
path=d,
|
|
205
|
+
type="directory",
|
|
206
|
+
size=0,
|
|
207
|
+
full_path=dir_path,
|
|
208
|
+
depth=depth,
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
folder_structure[d] = {
|
|
212
|
+
"path": d,
|
|
213
|
+
"depth": depth,
|
|
214
|
+
"full_path": dir_path,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Add files to results
|
|
193
218
|
for file in files:
|
|
194
219
|
file_path = os.path.join(root, file)
|
|
195
220
|
if should_ignore_path(file_path):
|
|
@@ -284,8 +309,6 @@ def _list_files(
|
|
|
284
309
|
f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]",
|
|
285
310
|
message_group=group_id,
|
|
286
311
|
)
|
|
287
|
-
else:
|
|
288
|
-
emit_warning("Directory is empty", message_group=group_id)
|
|
289
312
|
dir_count = sum(1 for item in results if item.type == "directory")
|
|
290
313
|
file_count = sum(1 for item in results if item.type == "file")
|
|
291
314
|
total_size = sum(item.size for item in results if item.type == "file")
|
|
@@ -433,207 +456,18 @@ def _grep(context: RunContext, search_string: str, directory: str = ".") -> Grep
|
|
|
433
456
|
return GrepOutput(matches=matches)
|
|
434
457
|
|
|
435
458
|
|
|
436
|
-
# Exported top-level functions for direct import by tests and other code
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def list_files(context, directory=".", recursive=True):
|
|
440
|
-
return _list_files(context, directory, recursive)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def read_file(context, file_path, start_line=None, num_lines=None):
|
|
444
|
-
return _read_file(context, file_path, start_line, num_lines)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def grep(context, search_string, directory="."):
|
|
448
|
-
return _grep(context, search_string, directory)
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def register_file_operations_tools(agent):
|
|
452
|
-
@agent.tool
|
|
453
|
-
def list_files(
|
|
454
|
-
context: RunContext, directory: str = ".", recursive: bool = True
|
|
455
|
-
) -> ListFileOutput:
|
|
456
|
-
"""List files and directories with intelligent filtering and safety features.
|
|
457
|
-
|
|
458
|
-
This tool provides comprehensive directory listing with smart home directory
|
|
459
|
-
detection, project-aware recursion, and token-safe output. It automatically
|
|
460
|
-
ignores common build artifacts, cache directories, and other noise while
|
|
461
|
-
providing rich file metadata and visual formatting.
|
|
462
|
-
|
|
463
|
-
Args:
|
|
464
|
-
context (RunContext): The PydanticAI runtime context for the agent.
|
|
465
|
-
directory (str, optional): Path to the directory to list. Can be relative
|
|
466
|
-
or absolute. Defaults to "." (current directory).
|
|
467
|
-
recursive (bool, optional): Whether to recursively list subdirectories.
|
|
468
|
-
Automatically disabled for home directories unless they contain
|
|
469
|
-
project indicators. Defaults to True.
|
|
470
|
-
|
|
471
|
-
Returns:
|
|
472
|
-
ListFileOutput: A structured response containing:
|
|
473
|
-
- files (List[ListedFile]): List of files and directories found, where
|
|
474
|
-
each ListedFile contains:
|
|
475
|
-
- path (str | None): Relative path from the listing directory
|
|
476
|
-
- type (str | None): "file" or "directory"
|
|
477
|
-
- size (int): File size in bytes (0 for directories)
|
|
478
|
-
- full_path (str | None): Absolute path to the item
|
|
479
|
-
- depth (int | None): Nesting depth from the root directory
|
|
480
|
-
- error (str | None): Error message if listing failed
|
|
481
|
-
|
|
482
|
-
Note:
|
|
483
|
-
- Automatically ignores common patterns (.git, node_modules, __pycache__, etc.)
|
|
484
|
-
- Limits output to 10,000 tokens for safety (suggests non-recursive if exceeded)
|
|
485
|
-
- Smart home directory detection prevents performance issues
|
|
486
|
-
- Files are displayed with appropriate icons and size formatting
|
|
487
|
-
- Project directories are detected via common configuration files
|
|
488
|
-
|
|
489
|
-
Examples:
|
|
490
|
-
>>> result = list_files(ctx, "./src", recursive=True)
|
|
491
|
-
>>> if not result.error:
|
|
492
|
-
... for file in result.files:
|
|
493
|
-
... if file.type == "file" and file.path.endswith(".py"):
|
|
494
|
-
... print(f"Python file: {file.path} ({file.size} bytes)")
|
|
495
|
-
|
|
496
|
-
Best Practice:
|
|
497
|
-
- Use recursive=False for initial exploration of unknown directories
|
|
498
|
-
- When encountering "too many files" errors, try non-recursive listing
|
|
499
|
-
- Check the error field before processing the files list
|
|
500
|
-
"""
|
|
501
|
-
list_files_result = _list_files(context, directory, recursive)
|
|
502
|
-
num_tokens = (
|
|
503
|
-
len(list_files_result.model_dump_json()) / 4
|
|
504
|
-
) # Rough estimate of tokens
|
|
505
|
-
if num_tokens > 10000:
|
|
506
|
-
return ListFileOutput(
|
|
507
|
-
files=[],
|
|
508
|
-
error="Too many files - tokens exceeded. Try listing non-recursively",
|
|
509
|
-
)
|
|
510
|
-
return list_files_result
|
|
511
|
-
|
|
512
|
-
@agent.tool
|
|
513
|
-
def read_file(
|
|
514
|
-
context: RunContext,
|
|
515
|
-
file_path: str = "",
|
|
516
|
-
start_line: int | None = None,
|
|
517
|
-
num_lines: int | None = None,
|
|
518
|
-
) -> ReadFileOutput:
|
|
519
|
-
"""Read file contents with optional line-range selection and token safety.
|
|
520
|
-
|
|
521
|
-
This tool provides safe file reading with automatic token counting and
|
|
522
|
-
optional line-range selection for handling large files efficiently.
|
|
523
|
-
It protects against reading excessively large files that could overwhelm
|
|
524
|
-
the agent's context window.
|
|
525
|
-
|
|
526
|
-
Args:
|
|
527
|
-
context (RunContext): The PydanticAI runtime context for the agent.
|
|
528
|
-
file_path (str): Path to the file to read. Can be relative or absolute.
|
|
529
|
-
Cannot be empty.
|
|
530
|
-
start_line (int | None, optional): Starting line number for partial reads
|
|
531
|
-
(1-based indexing). If specified, num_lines must also be provided.
|
|
532
|
-
Defaults to None (read entire file).
|
|
533
|
-
num_lines (int | None, optional): Number of lines to read starting from
|
|
534
|
-
start_line. Must be specified if start_line is provided.
|
|
535
|
-
Defaults to None (read to end of file).
|
|
536
|
-
|
|
537
|
-
Returns:
|
|
538
|
-
ReadFileOutput: A structured response containing:
|
|
539
|
-
- content (str | None): The file contents or error message
|
|
540
|
-
- num_tokens (int): Estimated token count (constrained to < 10,000)
|
|
541
|
-
- error (str | None): Error message if reading failed
|
|
542
|
-
|
|
543
|
-
Note:
|
|
544
|
-
- Files larger than 10,000 estimated tokens cannot be read entirely
|
|
545
|
-
- Token estimation uses ~4 characters per token approximation
|
|
546
|
-
- Line numbers are 1-based (first line is line 1)
|
|
547
|
-
- Supports UTF-8 encoding with fallback error handling
|
|
548
|
-
- Non-existent files return "FILE NOT FOUND" for backward compatibility
|
|
549
|
-
|
|
550
|
-
Examples:
|
|
551
|
-
>>> # Read entire file
|
|
552
|
-
>>> result = read_file(ctx, "config.py")
|
|
553
|
-
>>> if not result.error:
|
|
554
|
-
... print(f"File has {result.num_tokens} tokens")
|
|
555
|
-
... print(result.content)
|
|
556
|
-
|
|
557
|
-
>>> # Read specific line range
|
|
558
|
-
>>> result = read_file(ctx, "large_file.py", start_line=100, num_lines=50)
|
|
559
|
-
>>> # Reads lines 100-149
|
|
560
|
-
|
|
561
|
-
Raises:
|
|
562
|
-
ValueError: If file exceeds 10,000 token safety limit (caught and returned as error)
|
|
563
|
-
|
|
564
|
-
Best Practice:
|
|
565
|
-
- For large files, use line-range reading to avoid token limits
|
|
566
|
-
- Always check the error field before processing content
|
|
567
|
-
- Use grep tool first to locate relevant sections in large files
|
|
568
|
-
- Prefer reading configuration files entirely, code files in chunks
|
|
569
|
-
"""
|
|
570
|
-
return _read_file(context, file_path, start_line, num_lines)
|
|
571
|
-
|
|
572
|
-
@agent.tool
|
|
573
|
-
def grep(
|
|
574
|
-
context: RunContext, search_string: str = "", directory: str = "."
|
|
575
|
-
) -> GrepOutput:
|
|
576
|
-
"""Recursively search for text patterns across files with intelligent filtering.
|
|
577
|
-
|
|
578
|
-
This tool provides powerful text searching across directory trees with
|
|
579
|
-
automatic filtering of irrelevant files, binary detection, and match limiting
|
|
580
|
-
for performance. It's essential for code exploration and finding specific
|
|
581
|
-
patterns or references.
|
|
582
|
-
|
|
583
|
-
Args:
|
|
584
|
-
context (RunContext): The PydanticAI runtime context for the agent.
|
|
585
|
-
search_string (str): The text pattern to search for. Performs exact
|
|
586
|
-
string matching (not regex). Cannot be empty.
|
|
587
|
-
directory (str, optional): Root directory to start the recursive search.
|
|
588
|
-
Can be relative or absolute. Defaults to "." (current directory).
|
|
589
|
-
|
|
590
|
-
Returns:
|
|
591
|
-
GrepOutput: A structured response containing:
|
|
592
|
-
- matches (List[MatchInfo]): List of matches found, where each
|
|
593
|
-
MatchInfo contains:
|
|
594
|
-
- file_path (str | None): Absolute path to the file containing the match
|
|
595
|
-
- line_number (int | None): Line number where match was found (1-based)
|
|
596
|
-
- line_content (str | None): Full line content containing the match
|
|
597
|
-
|
|
598
|
-
Note:
|
|
599
|
-
- Automatically ignores common patterns (.git, node_modules, __pycache__, etc.)
|
|
600
|
-
- Skips binary files and handles Unicode decode errors gracefully
|
|
601
|
-
- Limited to 200 matches maximum for performance and relevance
|
|
602
|
-
- UTF-8 encoding with error tolerance for text files
|
|
603
|
-
- Results are not sorted - appear in filesystem traversal order
|
|
604
|
-
|
|
605
|
-
Examples:
|
|
606
|
-
>>> # Search for function definitions
|
|
607
|
-
>>> result = grep(ctx, "def calculate_", "./src")
|
|
608
|
-
>>> for match in result.matches:
|
|
609
|
-
... print(f"{match.file_path}:{match.line_number}: {match.line_content.strip()}")
|
|
610
|
-
|
|
611
|
-
>>> # Find configuration references
|
|
612
|
-
>>> result = grep(ctx, "DATABASE_URL", ".")
|
|
613
|
-
>>> print(f"Found {len(result.matches)} references to DATABASE_URL")
|
|
614
|
-
|
|
615
|
-
Warning:
|
|
616
|
-
- Large codebases may hit the 200 match limit
|
|
617
|
-
- Search is case-sensitive and literal (no regex patterns)
|
|
618
|
-
- Binary files are automatically skipped with warnings
|
|
619
|
-
|
|
620
|
-
Best Practice:
|
|
621
|
-
- Use specific search terms to avoid too many matches
|
|
622
|
-
- Start with narrow directory scope for faster results
|
|
623
|
-
- Combine with read_file to examine matches in detail
|
|
624
|
-
- For case-insensitive search, try multiple variants manually
|
|
625
|
-
"""
|
|
626
|
-
return _grep(context, search_string, directory)
|
|
627
|
-
|
|
628
|
-
|
|
629
459
|
def register_list_files(agent):
|
|
630
460
|
"""Register only the list_files tool."""
|
|
461
|
+
from code_puppy.config import get_allow_recursion
|
|
631
462
|
|
|
632
463
|
@agent.tool(strict=False)
|
|
633
464
|
def list_files(
|
|
634
465
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
635
466
|
) -> ListFileOutput:
|
|
636
467
|
"""List files and directories with intelligent filtering and safety features.
|
|
468
|
+
|
|
469
|
+
This function will only allow recursive listing when the allow_recursion
|
|
470
|
+
configuration is set to true via the /set allow_recursion=true command.
|
|
637
471
|
|
|
638
472
|
This tool provides comprehensive directory listing with smart home directory
|
|
639
473
|
detection, project-aware recursion, and token-safe output. It automatically
|
|
@@ -646,7 +480,8 @@ def register_list_files(agent):
|
|
|
646
480
|
or absolute. Defaults to "." (current directory).
|
|
647
481
|
recursive (bool, optional): Whether to recursively list subdirectories.
|
|
648
482
|
Automatically disabled for home directories unless they contain
|
|
649
|
-
project indicators.
|
|
483
|
+
project indicators. Also requires allow_recursion=true in config.
|
|
484
|
+
Defaults to True.
|
|
650
485
|
|
|
651
486
|
Returns:
|
|
652
487
|
ListFileOutput: A structured response containing:
|
|
@@ -680,7 +515,14 @@ def register_list_files(agent):
|
|
|
680
515
|
- Check for errors in the response
|
|
681
516
|
- Combine with grep to find specific file patterns
|
|
682
517
|
"""
|
|
683
|
-
|
|
518
|
+
warning=None
|
|
519
|
+
if recursive and not get_allow_recursion():
|
|
520
|
+
warning = "Recursion disabled globally for list_files - returning non-recursive results"
|
|
521
|
+
recursive = False
|
|
522
|
+
result = _list_files(context, directory, recursive)
|
|
523
|
+
if warning:
|
|
524
|
+
result.error = warning
|
|
525
|
+
return result
|
|
684
526
|
|
|
685
527
|
|
|
686
528
|
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
|
{code_puppy-0.0.146 → code_puppy-0.0.148}/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
|
{code_puppy-0.0.146 → code_puppy-0.0.148}/code_puppy/command_line/model_picker_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.146 → code_puppy-0.0.148}/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
|
|
File without changes
|
{code_puppy-0.0.146 → code_puppy-0.0.148}/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.146 → code_puppy-0.0.148}/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
|