code-puppy 0.0.195__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.195 → code_puppy-0.0.197}/PKG-INFO +13 -2
- {code_puppy-0.0.195 → code_puppy-0.0.197}/README.md +11 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/base_agent.py +3 -5
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/command_handler.py +96 -97
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/prompt_toolkit_completion.py +34 -10
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/config.py +37 -16
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/main.py +2 -5
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/session_storage.py +15 -6
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/app.py +153 -7
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/input_area.py +1 -1
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/status_bar.py +4 -1
- {code_puppy-0.0.195 → 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.195 → code_puppy-0.0.197}/code_puppy/tui/screens/settings.py +4 -2
- {code_puppy-0.0.195 → code_puppy-0.0.197}/pyproject.toml +2 -2
- {code_puppy-0.0.195 → code_puppy-0.0.197}/.gitignore +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/LICENSE +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/models.json +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.195 → code_puppy-0.0.197}/code_puppy/tui_state.py +0 -0
- {code_puppy-0.0.195 → 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, /set max_saved_sessions 20)"
|
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)
|
@@ -336,6 +304,30 @@ def handle_command(command: str):
|
|
336
304
|
)
|
337
305
|
return True
|
338
306
|
|
307
|
+
if command.startswith("/session"):
|
308
|
+
# /session id -> show current autosave id
|
309
|
+
# /session new -> rotate autosave id
|
310
|
+
tokens = command.split()
|
311
|
+
from code_puppy.config import (
|
312
|
+
AUTOSAVE_DIR,
|
313
|
+
get_current_autosave_id,
|
314
|
+
get_current_autosave_session_name,
|
315
|
+
rotate_autosave_id,
|
316
|
+
)
|
317
|
+
if len(tokens) == 1 or tokens[1] == "id":
|
318
|
+
sid = get_current_autosave_id()
|
319
|
+
emit_info(
|
320
|
+
f"[bold magenta]Autosave Session[/bold magenta]: {sid}\n"
|
321
|
+
f"Files prefix: {Path(AUTOSAVE_DIR) / get_current_autosave_session_name()}"
|
322
|
+
)
|
323
|
+
return True
|
324
|
+
if tokens[1] == "new":
|
325
|
+
new_sid = rotate_autosave_id()
|
326
|
+
emit_success(f"New autosave session id: {new_sid}")
|
327
|
+
return True
|
328
|
+
emit_warning("Usage: /session [id|new]")
|
329
|
+
return True
|
330
|
+
|
339
331
|
if command.startswith("/set"):
|
340
332
|
# Syntax: /set KEY=VALUE or /set KEY VALUE
|
341
333
|
from code_puppy.config import set_config_value
|
@@ -361,7 +353,6 @@ def handle_command(command: str):
|
|
361
353
|
session_help = (
|
362
354
|
"\n[yellow]Session Management[/yellow]"
|
363
355
|
"\n [cyan]auto_save_session[/cyan] Auto-save chat after every response (true/false)"
|
364
|
-
"\n [cyan]max_saved_sessions[/cyan] Cap how many auto-saves to keep (0 = unlimited)"
|
365
356
|
)
|
366
357
|
emit_warning(
|
367
358
|
f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(config_keys)}\n[dim]Note: compaction_strategy can be 'summarization' or 'truncation'[/dim]{session_help}"
|
@@ -712,9 +703,17 @@ def handle_command(command: str):
|
|
712
703
|
agent.set_message_history(history)
|
713
704
|
total_tokens = sum(agent.estimate_tokens_for_message(m) for m in history)
|
714
705
|
|
706
|
+
# Rotate autosave id to avoid overwriting any existing autosave
|
707
|
+
try:
|
708
|
+
from code_puppy.config import rotate_autosave_id
|
709
|
+
new_id = rotate_autosave_id()
|
710
|
+
autosave_info = f"\n[dim]Autosave session rotated to: {new_id}[/dim]"
|
711
|
+
except Exception:
|
712
|
+
autosave_info = ""
|
713
|
+
|
715
714
|
emit_success(
|
716
715
|
f"✅ Context loaded: {len(history)} messages ({total_tokens} tokens)\n"
|
717
|
-
f"📁 From: {session_path}"
|
716
|
+
f"📁 From: {session_path}{autosave_info}"
|
718
717
|
)
|
719
718
|
return True
|
720
719
|
|
{code_puppy-0.0.195 → 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):
|
@@ -3,8 +3,9 @@ import datetime
|
|
3
3
|
import json
|
4
4
|
import os
|
5
5
|
import pathlib
|
6
|
+
from typing import Optional
|
6
7
|
|
7
|
-
from code_puppy.session_storage import
|
8
|
+
from code_puppy.session_storage import save_session
|
8
9
|
|
9
10
|
CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".code_puppy")
|
10
11
|
CONFIG_FILE = os.path.join(CONFIG_DIR, "puppy.cfg")
|
@@ -19,6 +20,9 @@ AUTOSAVE_DIR = os.path.join(CONFIG_DIR, "autosaves")
|
|
19
20
|
DEFAULT_SECTION = "puppy"
|
20
21
|
REQUIRED_KEYS = ["puppy_name", "owner_name"]
|
21
22
|
|
23
|
+
# Runtime-only autosave session ID (per-process)
|
24
|
+
_CURRENT_AUTOSAVE_ID: Optional[str] = None
|
25
|
+
|
22
26
|
# Cache containers for model validation and defaults
|
23
27
|
_model_validation_cache = {}
|
24
28
|
_default_model_cache = None
|
@@ -702,22 +706,40 @@ def set_max_saved_sessions(max_sessions: int):
|
|
702
706
|
set_config_value("max_saved_sessions", str(max_sessions))
|
703
707
|
|
704
708
|
|
705
|
-
def
|
706
|
-
"""
|
707
|
-
|
708
|
-
if
|
709
|
-
|
709
|
+
def get_current_autosave_id() -> str:
|
710
|
+
"""Get or create the current autosave session ID for this process."""
|
711
|
+
global _CURRENT_AUTOSAVE_ID
|
712
|
+
if not _CURRENT_AUTOSAVE_ID:
|
713
|
+
# Use a full timestamp so tests and UX can predict the name if needed
|
714
|
+
_CURRENT_AUTOSAVE_ID = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
715
|
+
return _CURRENT_AUTOSAVE_ID
|
710
716
|
|
711
|
-
autosave_dir = pathlib.Path(AUTOSAVE_DIR)
|
712
|
-
removed_sessions = cleanup_sessions(autosave_dir, max_sessions)
|
713
|
-
if not removed_sessions:
|
714
|
-
return
|
715
717
|
|
716
|
-
|
718
|
+
def rotate_autosave_id() -> str:
|
719
|
+
"""Force a new autosave session ID and return it."""
|
720
|
+
global _CURRENT_AUTOSAVE_ID
|
721
|
+
_CURRENT_AUTOSAVE_ID = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
722
|
+
return _CURRENT_AUTOSAVE_ID
|
723
|
+
|
717
724
|
|
718
|
-
|
719
|
-
for
|
720
|
-
|
725
|
+
def get_current_autosave_session_name() -> str:
|
726
|
+
"""Return the full session name used for autosaves (no file extension)."""
|
727
|
+
return f"auto_session_{get_current_autosave_id()}"
|
728
|
+
|
729
|
+
|
730
|
+
def set_current_autosave_from_session_name(session_name: str) -> str:
|
731
|
+
"""Set the current autosave ID based on a full session name.
|
732
|
+
|
733
|
+
Accepts names like 'auto_session_YYYYMMDD_HHMMSS' and extracts the ID part.
|
734
|
+
Returns the ID that was set.
|
735
|
+
"""
|
736
|
+
global _CURRENT_AUTOSAVE_ID
|
737
|
+
prefix = "auto_session_"
|
738
|
+
if session_name.startswith(prefix):
|
739
|
+
_CURRENT_AUTOSAVE_ID = session_name[len(prefix):]
|
740
|
+
else:
|
741
|
+
_CURRENT_AUTOSAVE_ID = session_name
|
742
|
+
return _CURRENT_AUTOSAVE_ID
|
721
743
|
|
722
744
|
|
723
745
|
def auto_save_session_if_enabled() -> bool:
|
@@ -739,7 +761,7 @@ def auto_save_session_if_enabled() -> bool:
|
|
739
761
|
return False
|
740
762
|
|
741
763
|
now = datetime.datetime.now()
|
742
|
-
session_name =
|
764
|
+
session_name = get_current_autosave_session_name()
|
743
765
|
autosave_dir = pathlib.Path(AUTOSAVE_DIR)
|
744
766
|
|
745
767
|
metadata = save_session(
|
@@ -755,7 +777,6 @@ def auto_save_session_if_enabled() -> bool:
|
|
755
777
|
f"🐾 [dim]Auto-saved session: {metadata.message_count} messages ({metadata.total_tokens} tokens)[/dim]"
|
756
778
|
)
|
757
779
|
|
758
|
-
_cleanup_old_sessions()
|
759
780
|
return True
|
760
781
|
|
761
782
|
except Exception as exc: # pragma: no cover - defensive logging
|
@@ -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
|
|
@@ -96,6 +96,12 @@ def load_session(session_name: str, base_dir: Path) -> SessionHistory:
|
|
96
96
|
return pickle.load(pickle_file)
|
97
97
|
|
98
98
|
|
99
|
+
def list_sessions(base_dir: Path) -> List[str]:
|
100
|
+
if not base_dir.exists():
|
101
|
+
return []
|
102
|
+
return sorted(path.stem for path in base_dir.glob("*.pkl"))
|
103
|
+
|
104
|
+
|
99
105
|
def cleanup_sessions(base_dir: Path, max_sessions: int) -> List[str]:
|
100
106
|
if max_sessions <= 0:
|
101
107
|
return []
|
@@ -126,12 +132,6 @@ def cleanup_sessions(base_dir: Path, max_sessions: int) -> List[str]:
|
|
126
132
|
return removed_sessions
|
127
133
|
|
128
134
|
|
129
|
-
def list_sessions(base_dir: Path) -> List[str]:
|
130
|
-
if not base_dir.exists():
|
131
|
-
return []
|
132
|
-
return sorted(path.stem for path in base_dir.glob("*.pkl"))
|
133
|
-
|
134
|
-
|
135
135
|
async def restore_autosave_interactively(base_dir: Path) -> None:
|
136
136
|
"""Prompt the user to load an autosave session from base_dir, if any exist.
|
137
137
|
|
@@ -232,6 +232,15 @@ async def restore_autosave_interactively(base_dir: Path) -> None:
|
|
232
232
|
|
233
233
|
agent = get_current_agent()
|
234
234
|
agent.set_message_history(history)
|
235
|
+
|
236
|
+
# Set current autosave session id so subsequent autosaves overwrite this session
|
237
|
+
try:
|
238
|
+
from code_puppy.config import set_current_autosave_from_session_name
|
239
|
+
|
240
|
+
set_current_autosave_from_session_name(chosen_name)
|
241
|
+
except Exception:
|
242
|
+
pass
|
243
|
+
|
235
244
|
total_tokens = sum(agent.estimate_tokens_for_message(msg) for msg in history)
|
236
245
|
|
237
246
|
session_path = base_dir / f"{chosen_name}.pkl"
|