code-puppy 0.0.196__tar.gz → 0.0.197__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.196 → code_puppy-0.0.197}/PKG-INFO +13 -2
- {code_puppy-0.0.196 → code_puppy-0.0.197}/README.md +11 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/base_agent.py +3 -5
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/command_handler.py +63 -95
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/prompt_toolkit_completion.py +34 -10
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/main.py +2 -5
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/app.py +153 -7
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/input_area.py +1 -1
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/status_bar.py +4 -1
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/screens/__init__.py +2 -0
- code_puppy-0.0.197/code_puppy/tui/screens/autosave_picker.py +166 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/screens/settings.py +4 -2
- {code_puppy-0.0.196 → code_puppy-0.0.197}/pyproject.toml +2 -2
- {code_puppy-0.0.196 → code_puppy-0.0.197}/.gitignore +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/LICENSE +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/config.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/models.json +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/tui_state.py +0 -0
- {code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: code-puppy
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.197
|
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
|
@@ -25,7 +25,7 @@ Requires-Dist: logfire>=0.7.1
|
|
25
25
|
Requires-Dist: openai>=1.99.1
|
26
26
|
Requires-Dist: pathspec>=0.11.0
|
27
27
|
Requires-Dist: playwright>=1.40.0
|
28
|
-
Requires-Dist: prompt-toolkit>=3.0.
|
28
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
29
29
|
Requires-Dist: pydantic-ai==1.0.6
|
30
30
|
Requires-Dist: pydantic>=2.4.0
|
31
31
|
Requires-Dist: pyjwt>=2.8.0
|
@@ -69,6 +69,17 @@ Code Puppy is an AI-powered code generation agent, designed to understand progra
|
|
69
69
|
|
70
70
|
## Features
|
71
71
|
|
72
|
+
### Session Autosave & Contexts
|
73
|
+
- Autosaves live in `~/.code_puppy/autosaves` and include a `.pkl` and `_meta.json` per session.
|
74
|
+
- On startup, you’ll be prompted to optionally load a recent autosave (with message counts and timestamps).
|
75
|
+
- Autosaves use a stable session ID per interactive run so subsequent prompts overwrite the same session (not N new files). Rotate via `/session new` when you want a fresh session.
|
76
|
+
- Loading an autosave makes it the active autosave target (future autosaves overwrite that loaded session).
|
77
|
+
- Loading a manual context with `/load_context <name>` automatically rotates the autosave ID to avoid overwriting anything.
|
78
|
+
- Helpers:
|
79
|
+
- `/session id` shows the current autosave ID and file prefix
|
80
|
+
- `/session new` rotates the autosave ID
|
81
|
+
|
82
|
+
|
72
83
|
- **Multi-language support**: Capable of generating code in various programming languages.
|
73
84
|
- **Interactive CLI**: A command-line interface for interactive use.
|
74
85
|
- **Detailed explanations**: Provides insights into generated code to understand its logic and structure.
|
@@ -25,6 +25,17 @@ Code Puppy is an AI-powered code generation agent, designed to understand progra
|
|
25
25
|
|
26
26
|
## Features
|
27
27
|
|
28
|
+
### Session Autosave & Contexts
|
29
|
+
- Autosaves live in `~/.code_puppy/autosaves` and include a `.pkl` and `_meta.json` per session.
|
30
|
+
- On startup, you’ll be prompted to optionally load a recent autosave (with message counts and timestamps).
|
31
|
+
- Autosaves use a stable session ID per interactive run so subsequent prompts overwrite the same session (not N new files). Rotate via `/session new` when you want a fresh session.
|
32
|
+
- Loading an autosave makes it the active autosave target (future autosaves overwrite that loaded session).
|
33
|
+
- Loading a manual context with `/load_context <name>` automatically rotates the autosave ID to avoid overwriting anything.
|
34
|
+
- Helpers:
|
35
|
+
- `/session id` shows the current autosave ID and file prefix
|
36
|
+
- `/session new` rotates the autosave ID
|
37
|
+
|
38
|
+
|
28
39
|
- **Multi-language support**: Capable of generating code in various programming languages.
|
29
40
|
- **Interactive CLI**: A command-line interface for interactive use.
|
30
41
|
- **Detailed explanations**: Provides insights into generated code to understand its logic and structure.
|
@@ -720,10 +720,7 @@ class BaseAgent(ABC):
|
|
720
720
|
emit_system_message(
|
721
721
|
f"[green]Successfully loaded {len(servers)} MCP server(s)[/green]"
|
722
722
|
)
|
723
|
-
|
724
|
-
emit_system_message(
|
725
|
-
"[yellow]No MCP servers available (check if servers are enabled)[/yellow]"
|
726
|
-
)
|
723
|
+
# Stay silent when there are no servers configured/available
|
727
724
|
return servers
|
728
725
|
|
729
726
|
def reload_mcp_servers(self):
|
@@ -891,7 +888,8 @@ class BaseAgent(ABC):
|
|
891
888
|
asyncio.CancelledError: When execution is cancelled by user
|
892
889
|
"""
|
893
890
|
group_id = str(uuid.uuid4())
|
894
|
-
|
891
|
+
# Avoid double-loading: reuse existing agent if already built
|
892
|
+
pydantic_agent = self._code_generation_agent or self.reload_code_generation_agent()
|
895
893
|
|
896
894
|
async def run_agent_task():
|
897
895
|
try:
|
@@ -11,125 +11,93 @@ from code_puppy.tools.tools_content import tools_content
|
|
11
11
|
|
12
12
|
|
13
13
|
def get_commands_help():
|
14
|
-
"""Generate commands help using Rich Text
|
14
|
+
"""Generate aligned commands help using Rich Text for safe markup."""
|
15
15
|
from rich.text import Text
|
16
16
|
|
17
17
|
# Ensure plugins are loaded so custom help can register
|
18
18
|
_ensure_plugins_loaded()
|
19
19
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
help_lines.append(
|
69
|
-
Text("/compact", style="cyan")
|
70
|
-
+ Text(
|
71
|
-
" Summarize and compact current chat history (uses compaction_strategy config)"
|
72
|
-
)
|
73
|
-
)
|
74
|
-
help_lines.append(
|
75
|
-
Text("/dump_context", style="cyan")
|
76
|
-
+ Text(" <name> Save current message history to file")
|
77
|
-
)
|
78
|
-
help_lines.append(
|
79
|
-
Text("/load_context", style="cyan")
|
80
|
-
+ Text(" <name> Load message history from file")
|
81
|
-
)
|
82
|
-
help_lines.append(
|
83
|
-
Text("/set", style="cyan")
|
84
|
-
+ Text(
|
85
|
-
" Set puppy config key-values (e.g., /set yolo_mode true, /set auto_save_session true)"
|
86
|
-
)
|
87
|
-
)
|
88
|
-
help_lines.append(
|
89
|
-
Text("/tools", style="cyan")
|
90
|
-
+ Text(" Show available tools and capabilities")
|
91
|
-
)
|
92
|
-
help_lines.append(
|
93
|
-
Text("/truncate", style="cyan")
|
94
|
-
+ Text(
|
95
|
-
" <N> Truncate message history to N most recent messages (keeping system message)"
|
96
|
-
)
|
97
|
-
)
|
98
|
-
help_lines.append(
|
99
|
-
Text("/<unknown>", style="cyan")
|
100
|
-
+ Text(" Show unknown command warning")
|
101
|
-
)
|
20
|
+
# Collect core commands with their syntax parts and descriptions
|
21
|
+
# (cmd_syntax, description)
|
22
|
+
core_cmds = [
|
23
|
+
("/help, /h", "Show this help message"),
|
24
|
+
("/cd <dir>", "Change directory or show directories"),
|
25
|
+
(
|
26
|
+
"/agent <name>",
|
27
|
+
"Switch to a different agent or show available agents",
|
28
|
+
),
|
29
|
+
("/exit, /quit", "Exit interactive mode"),
|
30
|
+
("/generate-pr-description [@dir]", "Generate comprehensive PR description"),
|
31
|
+
("/model, /m <model>", "Set active model"),
|
32
|
+
("/reasoning <low|medium|high>", "Set OpenAI reasoning effort for GPT-5 models"),
|
33
|
+
("/pin_model <agent> <model>", "Pin a specific model to an agent"),
|
34
|
+
("/mcp", "Manage MCP servers (list, start, stop, status, etc.)"),
|
35
|
+
("/motd", "Show the latest message of the day (MOTD)"),
|
36
|
+
("/show", "Show puppy config key-values"),
|
37
|
+
(
|
38
|
+
"/compact",
|
39
|
+
"Summarize and compact current chat history (uses compaction_strategy config)",
|
40
|
+
),
|
41
|
+
("/dump_context <name>", "Save current message history to file"),
|
42
|
+
("/load_context <name>", "Load message history from file"),
|
43
|
+
(
|
44
|
+
"/set",
|
45
|
+
"Set puppy config (e.g., /set yolo_mode true, /set auto_save_session true)",
|
46
|
+
),
|
47
|
+
("/tools", "Show available tools and capabilities"),
|
48
|
+
(
|
49
|
+
"/truncate <N>",
|
50
|
+
"Truncate history to N most recent messages (keeping system message)",
|
51
|
+
),
|
52
|
+
("/<unknown>", "Show unknown command warning"),
|
53
|
+
]
|
54
|
+
|
55
|
+
# Determine padding width for the left column
|
56
|
+
left_width = max(len(cmd) for cmd, _ in core_cmds) + 2 # add spacing
|
57
|
+
|
58
|
+
lines: list[Text] = []
|
59
|
+
lines.append(Text("Commands Help", style="bold magenta"))
|
60
|
+
|
61
|
+
for cmd, desc in core_cmds:
|
62
|
+
left = Text(cmd.ljust(left_width), style="cyan")
|
63
|
+
right = Text(desc)
|
64
|
+
line = Text()
|
65
|
+
line.append_text(left)
|
66
|
+
line.append_text(right)
|
67
|
+
lines.append(line)
|
102
68
|
|
103
69
|
# Add custom commands from plugins (if any)
|
104
70
|
try:
|
105
71
|
from code_puppy import callbacks
|
106
72
|
|
107
73
|
custom_help_results = callbacks.on_custom_command_help()
|
108
|
-
|
109
|
-
custom_entries = []
|
74
|
+
custom_entries: list[tuple[str, str]] = []
|
110
75
|
for res in custom_help_results:
|
111
76
|
if not res:
|
112
77
|
continue
|
113
78
|
if isinstance(res, tuple) and len(res) == 2:
|
114
|
-
custom_entries.append(res)
|
79
|
+
custom_entries.append((str(res[0]), str(res[1])))
|
115
80
|
elif isinstance(res, list):
|
116
81
|
for item in res:
|
117
82
|
if isinstance(item, tuple) and len(item) == 2:
|
118
|
-
custom_entries.append(item)
|
83
|
+
custom_entries.append((str(item[0]), str(item[1])))
|
119
84
|
if custom_entries:
|
120
|
-
|
121
|
-
|
85
|
+
lines.append(Text("", style="dim"))
|
86
|
+
lines.append(Text("Custom Commands", style="bold magenta"))
|
87
|
+
# Compute padding for custom commands as well
|
88
|
+
custom_left_width = max(len(name) for name, _ in custom_entries) + 3
|
122
89
|
for name, desc in custom_entries:
|
123
|
-
|
124
|
-
|
125
|
-
)
|
90
|
+
left = Text(f"/{name}".ljust(custom_left_width), style="cyan")
|
91
|
+
right = Text(desc)
|
92
|
+
line = Text()
|
93
|
+
line.append_text(left)
|
94
|
+
line.append_text(right)
|
95
|
+
lines.append(line)
|
126
96
|
except Exception:
|
127
|
-
# If callbacks fail, skip custom help silently
|
128
97
|
pass
|
129
98
|
|
130
|
-
# Combine all lines
|
131
99
|
final_text = Text()
|
132
|
-
for i, line in enumerate(
|
100
|
+
for i, line in enumerate(lines):
|
133
101
|
if i > 0:
|
134
102
|
final_text.append("\n")
|
135
103
|
final_text.append_text(line)
|
{code_puppy-0.0.196 → code_puppy-0.0.197}/code_puppy/command_line/prompt_toolkit_completion.py
RENAMED
@@ -194,24 +194,48 @@ async def get_input_with_combined_completion(
|
|
194
194
|
LoadContextCompleter(trigger="/load_context"),
|
195
195
|
]
|
196
196
|
)
|
197
|
-
# Add custom key bindings
|
197
|
+
# Add custom key bindings and multiline toggle
|
198
198
|
bindings = KeyBindings()
|
199
199
|
|
200
|
-
|
200
|
+
# Multiline mode state
|
201
|
+
multiline = {"enabled": False}
|
202
|
+
|
203
|
+
# Toggle multiline with Alt+M
|
204
|
+
@bindings.add(Keys.Escape, "m")
|
201
205
|
def _(event):
|
202
|
-
|
206
|
+
multiline["enabled"] = not multiline["enabled"]
|
207
|
+
status = "ON" if multiline["enabled"] else "OFF"
|
208
|
+
# Print status for user feedback (version-agnostic)
|
209
|
+
print(f"[multiline] {status}", flush=True)
|
210
|
+
|
211
|
+
# Also toggle multiline with F2 (more reliable across platforms)
|
212
|
+
@bindings.add("f2")
|
213
|
+
def _(event):
|
214
|
+
multiline["enabled"] = not multiline["enabled"]
|
215
|
+
status = "ON" if multiline["enabled"] else "OFF"
|
216
|
+
print(f"[multiline] {status}", flush=True)
|
203
217
|
|
204
|
-
#
|
205
|
-
|
218
|
+
# Newline insert bindings — robust and explicit
|
219
|
+
# Ctrl+J (line feed) works in virtually all terminals; mark eager so it wins
|
220
|
+
@bindings.add("c-j", eager=True)
|
206
221
|
def _(event):
|
207
|
-
"""Pressing alt+enter (meta+enter) inserts a newline."""
|
208
222
|
event.app.current_buffer.insert_text("\n")
|
209
223
|
|
210
|
-
#
|
211
|
-
|
224
|
+
# Also allow Ctrl+Enter for newline (terminal-dependent)
|
225
|
+
try:
|
226
|
+
@bindings.add("c-enter", eager=True)
|
227
|
+
def _(event):
|
228
|
+
event.app.current_buffer.insert_text("\n")
|
229
|
+
except Exception:
|
230
|
+
pass
|
231
|
+
|
232
|
+
# Enter behavior depends on multiline mode
|
233
|
+
@bindings.add("enter", filter=~is_searching, eager=True)
|
212
234
|
def _(event):
|
213
|
-
|
214
|
-
|
235
|
+
if multiline["enabled"]:
|
236
|
+
event.app.current_buffer.insert_text("\n")
|
237
|
+
else:
|
238
|
+
event.current_buffer.validate_and_handle()
|
215
239
|
|
216
240
|
@bindings.add(Keys.Escape)
|
217
241
|
def _(event):
|
@@ -272,16 +272,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
272
272
|
emit_info("[bold green]Code Puppy[/bold green] - Interactive Mode")
|
273
273
|
emit_system_message("Type '/exit' or '/quit' to exit the interactive mode.")
|
274
274
|
emit_system_message("Type 'clear' to reset the conversation history.")
|
275
|
+
emit_system_message("[dim]Type /help to view all commands[/dim]")
|
275
276
|
emit_system_message(
|
276
|
-
"Type [bold blue]@[/bold blue] for path completion, or [bold blue]/m[/bold blue] to pick a model.
|
277
|
+
"Type [bold blue]@[/bold blue] for path completion, or [bold blue]/m[/bold blue] to pick a model. Toggle multiline with [bold blue]Alt+M[/bold blue] or [bold blue]F2[/bold blue]; newline: [bold blue]Ctrl+J[/bold blue]."
|
277
278
|
)
|
278
279
|
emit_system_message(
|
279
280
|
"Press [bold red]Ctrl+C[/bold red] during processing to cancel the current task or inference."
|
280
281
|
)
|
281
|
-
from code_puppy.command_line.command_handler import get_commands_help
|
282
|
-
|
283
|
-
help_text = get_commands_help()
|
284
|
-
emit_system_message(help_text)
|
285
282
|
try:
|
286
283
|
from code_puppy.command_line.motd import print_motd
|
287
284
|
|
@@ -176,6 +176,13 @@ class CodePuppyTUI(App):
|
|
176
176
|
# Start the message renderer EARLY to catch startup messages
|
177
177
|
# Using call_after_refresh to start it as soon as possible after mount
|
178
178
|
self.call_after_refresh(self.start_message_renderer_sync)
|
179
|
+
|
180
|
+
# Kick off a non-blocking preload of the agent/model so the
|
181
|
+
# status bar shows loading before first prompt
|
182
|
+
self.call_after_refresh(self.preload_agent_on_startup)
|
183
|
+
|
184
|
+
# After preload, offer to restore an autosave session (like interactive mode)
|
185
|
+
self.call_after_refresh(self.maybe_prompt_restore_autosave)
|
179
186
|
|
180
187
|
# Apply responsive design adjustments
|
181
188
|
self.apply_responsive_layout()
|
@@ -187,16 +194,40 @@ class CodePuppyTUI(App):
|
|
187
194
|
if self.initial_command:
|
188
195
|
self.call_after_refresh(self.process_initial_command)
|
189
196
|
|
197
|
+
def _tighten_text(self, text: str) -> str:
|
198
|
+
"""Aggressively tighten whitespace: trim lines, collapse multiples, drop extra blanks."""
|
199
|
+
try:
|
200
|
+
import re
|
201
|
+
|
202
|
+
# Split into lines, strip each, drop empty runs
|
203
|
+
lines = [re.sub(r"\s+", " ", ln.strip()) for ln in text.splitlines()]
|
204
|
+
# Remove consecutive blank lines
|
205
|
+
tight_lines = []
|
206
|
+
last_blank = False
|
207
|
+
for ln in lines:
|
208
|
+
is_blank = (ln == "")
|
209
|
+
if is_blank and last_blank:
|
210
|
+
continue
|
211
|
+
tight_lines.append(ln)
|
212
|
+
last_blank = is_blank
|
213
|
+
return "\n".join(tight_lines).strip()
|
214
|
+
except Exception:
|
215
|
+
return text.strip()
|
216
|
+
|
190
217
|
def add_system_message(
|
191
218
|
self, content: str, message_group: str = None, group_id: str = None
|
192
219
|
) -> None:
|
193
220
|
"""Add a system message to the chat."""
|
194
221
|
# Support both parameter names for backward compatibility
|
195
222
|
final_group_id = message_group or group_id
|
223
|
+
# Tighten only plain strings
|
224
|
+
content_to_use = (
|
225
|
+
self._tighten_text(content) if isinstance(content, str) else content
|
226
|
+
)
|
196
227
|
message = ChatMessage(
|
197
228
|
id=f"sys_{datetime.now(timezone.utc).timestamp()}",
|
198
229
|
type=MessageType.SYSTEM,
|
199
|
-
content=
|
230
|
+
content=content_to_use,
|
200
231
|
timestamp=datetime.now(timezone.utc),
|
201
232
|
group_id=final_group_id,
|
202
233
|
)
|
@@ -245,10 +276,13 @@ class CodePuppyTUI(App):
|
|
245
276
|
|
246
277
|
def add_error_message(self, content: str, message_group: str = None) -> None:
|
247
278
|
"""Add an error message to the chat."""
|
279
|
+
content_to_use = (
|
280
|
+
self._tighten_text(content) if isinstance(content, str) else content
|
281
|
+
)
|
248
282
|
message = ChatMessage(
|
249
283
|
id=f"error_{datetime.now(timezone.utc).timestamp()}",
|
250
284
|
type=MessageType.ERROR,
|
251
|
-
content=
|
285
|
+
content=content_to_use,
|
252
286
|
timestamp=datetime.now(timezone.utc),
|
253
287
|
group_id=message_group,
|
254
288
|
)
|
@@ -303,9 +337,9 @@ class CodePuppyTUI(App):
|
|
303
337
|
|
304
338
|
# Only handle keys when input field is focused
|
305
339
|
if input_field.has_focus:
|
306
|
-
# Handle Ctrl+Enter
|
307
|
-
if event.key
|
308
|
-
input_field.insert("
|
340
|
+
# Handle Ctrl+Enter or Shift+Enter for a new line
|
341
|
+
if event.key in ("ctrl+enter", "shift+enter"):
|
342
|
+
input_field.insert("\n")
|
309
343
|
event.prevent_default()
|
310
344
|
return
|
311
345
|
|
@@ -484,6 +518,14 @@ class CodePuppyTUI(App):
|
|
484
518
|
self.update_agent_progress("Processing", 75)
|
485
519
|
agent_response = result.output
|
486
520
|
self.add_agent_message(agent_response)
|
521
|
+
|
522
|
+
# Auto-save session if enabled (mirror --interactive)
|
523
|
+
try:
|
524
|
+
from code_puppy.config import auto_save_session_if_enabled
|
525
|
+
auto_save_session_if_enabled()
|
526
|
+
except Exception:
|
527
|
+
pass
|
528
|
+
|
487
529
|
# Refresh history display to show new interaction
|
488
530
|
self.refresh_history_display()
|
489
531
|
|
@@ -842,6 +884,36 @@ class CodePuppyTUI(App):
|
|
842
884
|
"""Synchronous wrapper to start message renderer via run_worker."""
|
843
885
|
self.run_worker(self.start_message_renderer(), exclusive=False)
|
844
886
|
|
887
|
+
async def preload_agent_on_startup(self) -> None:
|
888
|
+
"""Preload the agent/model at startup so loading status is visible."""
|
889
|
+
try:
|
890
|
+
# Show loading in status bar and spinner
|
891
|
+
self.start_agent_progress("Loading")
|
892
|
+
|
893
|
+
# Warm up agent/model without blocking UI
|
894
|
+
import asyncio
|
895
|
+
|
896
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
897
|
+
|
898
|
+
agent = get_current_agent()
|
899
|
+
|
900
|
+
# Run the synchronous reload in a worker thread
|
901
|
+
await asyncio.to_thread(agent.reload_code_generation_agent)
|
902
|
+
|
903
|
+
# After load, refresh current model (in case of fallback or changes)
|
904
|
+
from code_puppy.config import get_global_model_name
|
905
|
+
|
906
|
+
self.current_model = get_global_model_name()
|
907
|
+
|
908
|
+
# Let the user know model/agent are ready
|
909
|
+
self.add_system_message("Model and agent preloaded. Ready to roll 🛼")
|
910
|
+
except Exception as e:
|
911
|
+
# Surface any preload issues but keep app usable
|
912
|
+
self.add_error_message(f"Startup preload failed: {e}")
|
913
|
+
finally:
|
914
|
+
# Always stop spinner and set ready state
|
915
|
+
self.stop_agent_progress()
|
916
|
+
|
845
917
|
async def start_message_renderer(self):
|
846
918
|
"""Start the message renderer to consume messages from the queue."""
|
847
919
|
if not self._renderer_started:
|
@@ -884,9 +956,9 @@ class CodePuppyTUI(App):
|
|
884
956
|
f"Error processing startup message: {e}"
|
885
957
|
)
|
886
958
|
|
887
|
-
# Create a single grouped startup message
|
959
|
+
# Create a single grouped startup message (tightened)
|
888
960
|
grouped_content = "\n".join(startup_content_lines)
|
889
|
-
self.add_system_message(grouped_content)
|
961
|
+
self.add_system_message(self._tighten_text(grouped_content))
|
890
962
|
|
891
963
|
# Clear the startup buffer after processing
|
892
964
|
self.message_queue.clear_startup_buffer()
|
@@ -894,6 +966,80 @@ class CodePuppyTUI(App):
|
|
894
966
|
# Now start the regular message renderer
|
895
967
|
await self.message_renderer.start()
|
896
968
|
|
969
|
+
async def maybe_prompt_restore_autosave(self) -> None:
|
970
|
+
"""Offer to restore an autosave session at startup (TUI version)."""
|
971
|
+
try:
|
972
|
+
import asyncio
|
973
|
+
from pathlib import Path
|
974
|
+
|
975
|
+
from code_puppy.config import AUTOSAVE_DIR, set_current_autosave_from_session_name
|
976
|
+
from code_puppy.session_storage import list_sessions, load_session
|
977
|
+
|
978
|
+
base_dir = Path(AUTOSAVE_DIR)
|
979
|
+
sessions = list_sessions(base_dir)
|
980
|
+
if not sessions:
|
981
|
+
return
|
982
|
+
|
983
|
+
# Show modal picker for selection
|
984
|
+
from .screens.autosave_picker import AutosavePicker
|
985
|
+
|
986
|
+
async def handle_result(result_name: str | None):
|
987
|
+
if not result_name:
|
988
|
+
return
|
989
|
+
try:
|
990
|
+
# Load history and set into agent
|
991
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
992
|
+
|
993
|
+
history = load_session(result_name, base_dir)
|
994
|
+
agent = get_current_agent()
|
995
|
+
agent.set_message_history(history)
|
996
|
+
|
997
|
+
# Set current autosave session id so subsequent autosaves overwrite this session
|
998
|
+
try:
|
999
|
+
set_current_autosave_from_session_name(result_name)
|
1000
|
+
except Exception:
|
1001
|
+
pass
|
1002
|
+
|
1003
|
+
# Update token info/status bar
|
1004
|
+
total_tokens = sum(
|
1005
|
+
agent.estimate_tokens_for_message(msg) for msg in history
|
1006
|
+
)
|
1007
|
+
try:
|
1008
|
+
status_bar = self.query_one(StatusBar)
|
1009
|
+
status_bar.update_token_info(
|
1010
|
+
total_tokens,
|
1011
|
+
agent.get_model_context_length(),
|
1012
|
+
total_tokens / max(1, agent.get_model_context_length()),
|
1013
|
+
)
|
1014
|
+
except Exception:
|
1015
|
+
pass
|
1016
|
+
|
1017
|
+
# Notify
|
1018
|
+
session_path = base_dir / f"{result_name}.pkl"
|
1019
|
+
self.add_system_message(
|
1020
|
+
f"✅ Autosave loaded: {len(history)} messages ({total_tokens} tokens)\n"
|
1021
|
+
f"📁 From: {session_path}"
|
1022
|
+
)
|
1023
|
+
|
1024
|
+
# Refresh history sidebar
|
1025
|
+
self.refresh_history_display()
|
1026
|
+
except Exception as e:
|
1027
|
+
self.add_error_message(f"Failed to load autosave: {e}")
|
1028
|
+
|
1029
|
+
# Push modal and await result
|
1030
|
+
picker = AutosavePicker(base_dir)
|
1031
|
+
|
1032
|
+
# Use Textual's push_screen with a result callback
|
1033
|
+
def on_picker_result(result_name=None):
|
1034
|
+
# Schedule async handler to avoid blocking UI
|
1035
|
+
import asyncio
|
1036
|
+
self.run_worker(handle_result(result_name), exclusive=False)
|
1037
|
+
|
1038
|
+
self.push_screen(picker, on_picker_result)
|
1039
|
+
except Exception as e:
|
1040
|
+
# Fail silently but show debug in chat
|
1041
|
+
self.add_system_message(f"[dim]Autosave prompt error: {e}[/dim]")
|
1042
|
+
|
897
1043
|
async def stop_message_renderer(self):
|
898
1044
|
"""Stop the message renderer."""
|
899
1045
|
if self._renderer_started:
|
@@ -133,7 +133,7 @@ class InputArea(Container):
|
|
133
133
|
yield CustomTextArea(id="input-field", show_line_numbers=False)
|
134
134
|
yield SubmitCancelButton()
|
135
135
|
yield Static(
|
136
|
-
"Enter to send •
|
136
|
+
"Enter to send • Shift+Enter for new line • Ctrl+1 for help",
|
137
137
|
id="input-help",
|
138
138
|
)
|
139
139
|
|
@@ -83,7 +83,10 @@ class StatusBar(Static):
|
|
83
83
|
elif self.agent_status == "Busy":
|
84
84
|
status_indicator = "🔄"
|
85
85
|
status_color = "orange"
|
86
|
-
|
86
|
+
elif self.agent_status == "Loading":
|
87
|
+
status_indicator = "⏳"
|
88
|
+
status_color = "cyan"
|
89
|
+
else: # Ready or anything else
|
87
90
|
status_indicator = "✅"
|
88
91
|
status_color = "green"
|
89
92
|
|
@@ -6,10 +6,12 @@ from .help import HelpScreen
|
|
6
6
|
from .mcp_install_wizard import MCPInstallWizardScreen
|
7
7
|
from .settings import SettingsScreen
|
8
8
|
from .tools import ToolsScreen
|
9
|
+
from .autosave_picker import AutosavePicker
|
9
10
|
|
10
11
|
__all__ = [
|
11
12
|
"HelpScreen",
|
12
13
|
"SettingsScreen",
|
13
14
|
"ToolsScreen",
|
14
15
|
"MCPInstallWizardScreen",
|
16
|
+
"AutosavePicker",
|
15
17
|
]
|