code-puppy 0.0.323__tar.gz → 0.0.338__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.323 → code_puppy-0.0.338}/PKG-INFO +25 -49
- {code_puppy-0.0.323 → code_puppy-0.0.338}/README.md +23 -48
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/base_agent.py +121 -115
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/claude_cache_client.py +46 -2
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/cli_runner.py +108 -29
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/add_model_menu.py +15 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/autosave_menu.py +5 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/colors_menu.py +5 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/config_commands.py +24 -1
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/core_commands.py +51 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/diff_menu.py +5 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_form.py +4 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_menu.py +5 -1
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/model_settings_menu.py +5 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/motd.py +13 -7
- code_puppy-0.0.338/code_puppy/command_line/onboarding_slides.py +180 -0
- code_puppy-0.0.338/code_puppy/command_line/onboarding_wizard.py +340 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/config.py +3 -2
- code_puppy-0.0.338/code_puppy/http_utils.py +338 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/keymap.py +10 -8
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/messages.py +3 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/rich_renderer.py +114 -22
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/model_factory.py +102 -15
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models.json +4 -4
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/__init__.py +12 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/antigravity_model.py +668 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/transport.py +664 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/reopenable_async_client.py +8 -8
- code_puppy-0.0.338/code_puppy/terminal_utils.py +418 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/command_runner.py +43 -54
- code_puppy-0.0.338/code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/pyproject.toml +2 -1
- code_puppy-0.0.323/code_puppy/http_utils.py +0 -416
- code_puppy-0.0.323/code_puppy/terminal_utils.py +0 -126
- {code_puppy-0.0.323 → code_puppy-0.0.338}/.gitignore +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/LICENSE +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/main.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.323 → code_puppy-0.0.338}/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.338
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: rich>=13.4.2
|
|
|
34
34
|
Requires-Dist: ripgrep==14.1.0
|
|
35
35
|
Requires-Dist: ruff>=0.11.11
|
|
36
36
|
Requires-Dist: tenacity>=8.2.0
|
|
37
|
+
Requires-Dist: termflow-md>=0.1.6
|
|
37
38
|
Requires-Dist: uvicorn>=0.30.0
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
@@ -100,66 +101,32 @@ uvx code-puppy -i
|
|
|
100
101
|
|
|
101
102
|
### UV (Recommended)
|
|
102
103
|
|
|
104
|
+
#### macOS / Linux
|
|
105
|
+
|
|
103
106
|
```bash
|
|
104
107
|
# Install UV if you don't have it
|
|
105
108
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
109
|
-
source ~/.zshrc # or ~/.bashrc
|
|
110
|
-
|
|
111
|
-
# Install and run code-puppy
|
|
112
|
-
uvx code-puppy -i
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
|
|
116
|
-
|
|
117
|
-
### pip (Alternative)
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
pip install code-puppy
|
|
110
|
+
uvx code-puppy
|
|
121
111
|
```
|
|
122
112
|
|
|
123
|
-
|
|
113
|
+
#### Windows
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
On Windows, we recommend installing code-puppy as a global tool for the best experience with keyboard shortcuts (Ctrl+C/Ctrl+X cancellation):
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# Set environment variable permanently
|
|
131
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
132
|
-
source ~/.zshrc # or ~/.bashrc
|
|
117
|
+
```powershell
|
|
118
|
+
# Install UV if you don't have it (run in PowerShell as Admin)
|
|
119
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
uvx code-puppy # No need for --managed-python flag anymore
|
|
121
|
+
uvx code-puppy
|
|
136
122
|
```
|
|
137
123
|
|
|
138
|
-
|
|
124
|
+
## Changelog (By Kittylog!)
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
# Check which Python UV will use
|
|
142
|
-
uv python find
|
|
143
|
-
|
|
144
|
-
# Or check the current project's Python
|
|
145
|
-
uv run python --version
|
|
146
|
-
```
|
|
126
|
+
[📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
|
|
147
127
|
|
|
148
128
|
## Usage
|
|
149
129
|
|
|
150
|
-
### Custom Commands
|
|
151
|
-
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
# Create a custom command
|
|
155
|
-
echo "# Code Review
|
|
156
|
-
|
|
157
|
-
Please review this code for security issues." > .claude/commands/review.md
|
|
158
|
-
|
|
159
|
-
# Use it in Code Puppy
|
|
160
|
-
/review with focus on authentication
|
|
161
|
-
```
|
|
162
|
-
|
|
163
130
|
### Adding Models from models.dev 🆕
|
|
164
131
|
|
|
165
132
|
While there are several models configured right out of the box from providers like Synthetic, Cerebras, OpenAI, Google, and Anthropic, Code Puppy integrates with [models.dev](https://models.dev) to let you browse and add models from **65+ providers** with a single command:
|
|
@@ -227,6 +194,18 @@ The following environment variables control DBOS behavior:
|
|
|
227
194
|
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
|
|
228
195
|
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
|
|
229
196
|
|
|
197
|
+
### Custom Commands
|
|
198
|
+
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Create a custom command
|
|
202
|
+
echo "# Code Review
|
|
203
|
+
|
|
204
|
+
Please review this code for security issues." > .claude/commands/review.md
|
|
205
|
+
|
|
206
|
+
# Use it in Code Puppy
|
|
207
|
+
/review with focus on authentication
|
|
208
|
+
```
|
|
230
209
|
|
|
231
210
|
## Requirements
|
|
232
211
|
|
|
@@ -246,9 +225,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
|
|
|
246
225
|
|
|
247
226
|
Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
|
|
248
227
|
|
|
249
|
-
Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
|
|
250
|
-
|
|
251
|
-
|
|
252
228
|
## Round Robin Model Distribution
|
|
253
229
|
|
|
254
230
|
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
@@ -61,66 +61,32 @@ uvx code-puppy -i
|
|
|
61
61
|
|
|
62
62
|
### UV (Recommended)
|
|
63
63
|
|
|
64
|
+
#### macOS / Linux
|
|
65
|
+
|
|
64
66
|
```bash
|
|
65
67
|
# Install UV if you don't have it
|
|
66
68
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
70
|
-
source ~/.zshrc # or ~/.bashrc
|
|
71
|
-
|
|
72
|
-
# Install and run code-puppy
|
|
73
|
-
uvx code-puppy -i
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
|
|
77
|
-
|
|
78
|
-
### pip (Alternative)
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
pip install code-puppy
|
|
70
|
+
uvx code-puppy
|
|
82
71
|
```
|
|
83
72
|
|
|
84
|
-
|
|
73
|
+
#### Windows
|
|
85
74
|
|
|
86
|
-
|
|
75
|
+
On Windows, we recommend installing code-puppy as a global tool for the best experience with keyboard shortcuts (Ctrl+C/Ctrl+X cancellation):
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Set environment variable permanently
|
|
92
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
93
|
-
source ~/.zshrc # or ~/.bashrc
|
|
77
|
+
```powershell
|
|
78
|
+
# Install UV if you don't have it (run in PowerShell as Admin)
|
|
79
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
uvx code-puppy # No need for --managed-python flag anymore
|
|
81
|
+
uvx code-puppy
|
|
97
82
|
```
|
|
98
83
|
|
|
99
|
-
|
|
84
|
+
## Changelog (By Kittylog!)
|
|
100
85
|
|
|
101
|
-
|
|
102
|
-
# Check which Python UV will use
|
|
103
|
-
uv python find
|
|
104
|
-
|
|
105
|
-
# Or check the current project's Python
|
|
106
|
-
uv run python --version
|
|
107
|
-
```
|
|
86
|
+
[📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
|
|
108
87
|
|
|
109
88
|
## Usage
|
|
110
89
|
|
|
111
|
-
### Custom Commands
|
|
112
|
-
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# Create a custom command
|
|
116
|
-
echo "# Code Review
|
|
117
|
-
|
|
118
|
-
Please review this code for security issues." > .claude/commands/review.md
|
|
119
|
-
|
|
120
|
-
# Use it in Code Puppy
|
|
121
|
-
/review with focus on authentication
|
|
122
|
-
```
|
|
123
|
-
|
|
124
90
|
### Adding Models from models.dev 🆕
|
|
125
91
|
|
|
126
92
|
While there are several models configured right out of the box from providers like Synthetic, Cerebras, OpenAI, Google, and Anthropic, Code Puppy integrates with [models.dev](https://models.dev) to let you browse and add models from **65+ providers** with a single command:
|
|
@@ -188,6 +154,18 @@ The following environment variables control DBOS behavior:
|
|
|
188
154
|
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
|
|
189
155
|
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
|
|
190
156
|
|
|
157
|
+
### Custom Commands
|
|
158
|
+
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Create a custom command
|
|
162
|
+
echo "# Code Review
|
|
163
|
+
|
|
164
|
+
Please review this code for security issues." > .claude/commands/review.md
|
|
165
|
+
|
|
166
|
+
# Use it in Code Puppy
|
|
167
|
+
/review with focus on authentication
|
|
168
|
+
```
|
|
191
169
|
|
|
192
170
|
## Requirements
|
|
193
171
|
|
|
@@ -207,9 +185,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
|
|
|
207
185
|
|
|
208
186
|
Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
|
|
209
187
|
|
|
210
|
-
Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
|
|
211
|
-
|
|
212
|
-
|
|
213
188
|
## Round Robin Model Distribution
|
|
214
189
|
|
|
215
190
|
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
import math
|
|
6
6
|
import signal
|
|
7
|
-
import sys
|
|
8
7
|
import threading
|
|
9
8
|
import uuid
|
|
10
9
|
from abc import ABC, abstractmethod
|
|
@@ -1340,17 +1339,20 @@ class BaseAgent(ABC):
|
|
|
1340
1339
|
) -> None:
|
|
1341
1340
|
"""Handle streaming events from the agent run.
|
|
1342
1341
|
|
|
1343
|
-
This method processes streaming events and emits TextPart
|
|
1344
|
-
content with styled banners as they stream in.
|
|
1342
|
+
This method processes streaming events and emits TextPart, ThinkingPart,
|
|
1343
|
+
and ToolCallPart content with styled banners/tokens as they stream in.
|
|
1345
1344
|
|
|
1346
1345
|
Args:
|
|
1347
1346
|
ctx: The run context.
|
|
1348
1347
|
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1349
1348
|
"""
|
|
1350
1349
|
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1351
|
-
from pydantic_ai.messages import
|
|
1350
|
+
from pydantic_ai.messages import (
|
|
1351
|
+
TextPartDelta,
|
|
1352
|
+
ThinkingPartDelta,
|
|
1353
|
+
ToolCallPartDelta,
|
|
1354
|
+
)
|
|
1352
1355
|
from rich.console import Console
|
|
1353
|
-
from rich.markdown import Markdown
|
|
1354
1356
|
from rich.markup import escape
|
|
1355
1357
|
|
|
1356
1358
|
from code_puppy.messaging.spinner import pause_all_spinners
|
|
@@ -1364,29 +1366,36 @@ class BaseAgent(ABC):
|
|
|
1364
1366
|
# Fallback if console not set (shouldn't happen in normal use)
|
|
1365
1367
|
console = Console()
|
|
1366
1368
|
|
|
1367
|
-
# Track which part indices we're currently streaming (for Text/Thinking parts)
|
|
1369
|
+
# Track which part indices we're currently streaming (for Text/Thinking/Tool parts)
|
|
1368
1370
|
streaming_parts: set[int] = set()
|
|
1369
1371
|
thinking_parts: set[int] = (
|
|
1370
1372
|
set()
|
|
1371
1373
|
) # Track which parts are thinking (for dim style)
|
|
1372
1374
|
text_parts: set[int] = set() # Track which parts are text
|
|
1375
|
+
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
1373
1376
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1374
|
-
|
|
1375
|
-
token_count: dict[int, int] = {} # Track token count per text part
|
|
1377
|
+
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
1376
1378
|
did_stream_anything = False # Track if we streamed any content
|
|
1377
1379
|
|
|
1380
|
+
# Termflow streaming state for text parts
|
|
1381
|
+
from termflow import Parser as TermflowParser
|
|
1382
|
+
from termflow import Renderer as TermflowRenderer
|
|
1383
|
+
|
|
1384
|
+
termflow_parsers: dict[int, TermflowParser] = {}
|
|
1385
|
+
termflow_renderers: dict[int, TermflowRenderer] = {}
|
|
1386
|
+
termflow_line_buffers: dict[int, str] = {} # Buffer incomplete lines
|
|
1387
|
+
|
|
1378
1388
|
def _print_thinking_banner() -> None:
|
|
1379
1389
|
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1380
1390
|
nonlocal did_stream_anything
|
|
1381
|
-
import sys
|
|
1382
1391
|
import time
|
|
1383
1392
|
|
|
1384
1393
|
from code_puppy.config import get_banner_color
|
|
1385
1394
|
|
|
1386
1395
|
pause_all_spinners()
|
|
1387
1396
|
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1388
|
-
|
|
1389
|
-
|
|
1397
|
+
# Clear line and print newline before banner
|
|
1398
|
+
console.print(" " * 50, end="\r")
|
|
1390
1399
|
console.print() # Newline before banner
|
|
1391
1400
|
# Bold banner with configurable color and lightning bolt
|
|
1392
1401
|
thinking_color = get_banner_color("thinking")
|
|
@@ -1396,21 +1405,19 @@ class BaseAgent(ABC):
|
|
|
1396
1405
|
),
|
|
1397
1406
|
end="",
|
|
1398
1407
|
)
|
|
1399
|
-
sys.stdout.flush()
|
|
1400
1408
|
did_stream_anything = True
|
|
1401
1409
|
|
|
1402
1410
|
def _print_response_banner() -> None:
|
|
1403
1411
|
"""Print the AGENT RESPONSE banner with spinner pause and line clear."""
|
|
1404
1412
|
nonlocal did_stream_anything
|
|
1405
|
-
import sys
|
|
1406
1413
|
import time
|
|
1407
1414
|
|
|
1408
1415
|
from code_puppy.config import get_banner_color
|
|
1409
1416
|
|
|
1410
1417
|
pause_all_spinners()
|
|
1411
1418
|
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1412
|
-
|
|
1413
|
-
|
|
1419
|
+
# Clear line and print newline before banner
|
|
1420
|
+
console.print(" " * 50, end="\r")
|
|
1414
1421
|
console.print() # Newline before banner
|
|
1415
1422
|
response_color = get_banner_color("agent_response")
|
|
1416
1423
|
console.print(
|
|
@@ -1418,7 +1425,6 @@ class BaseAgent(ABC):
|
|
|
1418
1425
|
f"[bold white on {response_color}] AGENT RESPONSE [/bold white on {response_color}]"
|
|
1419
1426
|
)
|
|
1420
1427
|
)
|
|
1421
|
-
sys.stdout.flush()
|
|
1422
1428
|
did_stream_anything = True
|
|
1423
1429
|
|
|
1424
1430
|
async for event in events:
|
|
@@ -1437,12 +1443,25 @@ class BaseAgent(ABC):
|
|
|
1437
1443
|
elif isinstance(part, TextPart):
|
|
1438
1444
|
streaming_parts.add(event.index)
|
|
1439
1445
|
text_parts.add(event.index)
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1446
|
+
# Initialize termflow streaming for this text part
|
|
1447
|
+
termflow_parsers[event.index] = TermflowParser()
|
|
1448
|
+
termflow_renderers[event.index] = TermflowRenderer(
|
|
1449
|
+
output=console.file, width=console.width
|
|
1450
|
+
)
|
|
1451
|
+
termflow_line_buffers[event.index] = ""
|
|
1452
|
+
# Handle initial content if present
|
|
1443
1453
|
if part.content and part.content.strip():
|
|
1444
|
-
|
|
1445
|
-
|
|
1454
|
+
_print_response_banner()
|
|
1455
|
+
banner_printed.add(event.index)
|
|
1456
|
+
termflow_line_buffers[event.index] = part.content
|
|
1457
|
+
elif isinstance(part, ToolCallPart):
|
|
1458
|
+
streaming_parts.add(event.index)
|
|
1459
|
+
tool_parts.add(event.index)
|
|
1460
|
+
token_count[event.index] = 0 # Initialize token counter
|
|
1461
|
+
# Track tool name for display
|
|
1462
|
+
banner_printed.add(
|
|
1463
|
+
event.index
|
|
1464
|
+
) # Use banner_printed to track if we've shown tool info
|
|
1446
1465
|
|
|
1447
1466
|
# PartDeltaEvent - stream the content as it arrives
|
|
1448
1467
|
elif isinstance(event, PartDeltaEvent):
|
|
@@ -1450,23 +1469,29 @@ class BaseAgent(ABC):
|
|
|
1450
1469
|
delta = event.delta
|
|
1451
1470
|
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1452
1471
|
if delta.content_delta:
|
|
1453
|
-
# For text parts,
|
|
1472
|
+
# For text parts, stream markdown with termflow
|
|
1454
1473
|
if event.index in text_parts:
|
|
1455
|
-
import sys
|
|
1456
|
-
|
|
1457
1474
|
# Print banner on first content
|
|
1458
1475
|
if event.index not in banner_printed:
|
|
1459
1476
|
_print_response_banner()
|
|
1460
1477
|
banner_printed.add(event.index)
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
count = token_count[event.index]
|
|
1466
|
-
sys.stdout.write(
|
|
1467
|
-
f"\r\x1b[K ⏳ Receiving... {count} tokens"
|
|
1478
|
+
|
|
1479
|
+
# Add content to line buffer
|
|
1480
|
+
termflow_line_buffers[event.index] += (
|
|
1481
|
+
delta.content_delta
|
|
1468
1482
|
)
|
|
1469
|
-
|
|
1483
|
+
|
|
1484
|
+
# Process complete lines
|
|
1485
|
+
parser = termflow_parsers[event.index]
|
|
1486
|
+
renderer = termflow_renderers[event.index]
|
|
1487
|
+
buffer = termflow_line_buffers[event.index]
|
|
1488
|
+
|
|
1489
|
+
while "\n" in buffer:
|
|
1490
|
+
line, buffer = buffer.split("\n", 1)
|
|
1491
|
+
events_to_render = parser.parse_line(line)
|
|
1492
|
+
renderer.render_all(events_to_render)
|
|
1493
|
+
|
|
1494
|
+
termflow_line_buffers[event.index] = buffer
|
|
1470
1495
|
else:
|
|
1471
1496
|
# For thinking parts, stream immediately (dim)
|
|
1472
1497
|
if event.index not in banner_printed:
|
|
@@ -1474,44 +1499,72 @@ class BaseAgent(ABC):
|
|
|
1474
1499
|
banner_printed.add(event.index)
|
|
1475
1500
|
escaped = escape(delta.content_delta)
|
|
1476
1501
|
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1502
|
+
elif isinstance(delta, ToolCallPartDelta):
|
|
1503
|
+
# For tool calls, count chunks received
|
|
1504
|
+
token_count[event.index] += 1
|
|
1505
|
+
# Get tool name if available
|
|
1506
|
+
tool_name = getattr(delta, "tool_name_delta", "")
|
|
1507
|
+
count = token_count[event.index]
|
|
1508
|
+
# Display with tool wrench icon and tool name
|
|
1509
|
+
if tool_name:
|
|
1510
|
+
console.print(
|
|
1511
|
+
f" 🔧 Calling {tool_name}... {count} chunks ",
|
|
1512
|
+
end="\r",
|
|
1513
|
+
)
|
|
1514
|
+
else:
|
|
1515
|
+
console.print(
|
|
1516
|
+
f" 🔧 Calling tool... {count} chunks ",
|
|
1517
|
+
end="\r",
|
|
1518
|
+
)
|
|
1477
1519
|
|
|
1478
1520
|
# PartEndEvent - finish the streaming with a newline
|
|
1479
1521
|
elif isinstance(event, PartEndEvent):
|
|
1480
1522
|
if event.index in streaming_parts:
|
|
1481
|
-
# For text parts,
|
|
1523
|
+
# For text parts, finalize termflow rendering
|
|
1482
1524
|
if event.index in text_parts:
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1525
|
+
# Render any remaining buffered content
|
|
1526
|
+
if event.index in termflow_parsers:
|
|
1527
|
+
parser = termflow_parsers[event.index]
|
|
1528
|
+
renderer = termflow_renderers[event.index]
|
|
1529
|
+
remaining = termflow_line_buffers.get(event.index, "")
|
|
1530
|
+
|
|
1531
|
+
# Parse and render any remaining partial line
|
|
1532
|
+
if remaining.strip():
|
|
1533
|
+
events_to_render = parser.parse_line(remaining)
|
|
1534
|
+
renderer.render_all(events_to_render)
|
|
1535
|
+
|
|
1536
|
+
# Finalize the parser to close any open blocks
|
|
1537
|
+
final_events = parser.finalize()
|
|
1538
|
+
renderer.render_all(final_events)
|
|
1539
|
+
|
|
1540
|
+
# Clean up termflow state
|
|
1541
|
+
del termflow_parsers[event.index]
|
|
1542
|
+
del termflow_renderers[event.index]
|
|
1543
|
+
del termflow_line_buffers[event.index]
|
|
1544
|
+
# For tool parts, clear the chunk counter line
|
|
1545
|
+
elif event.index in tool_parts:
|
|
1546
|
+
# Clear the chunk counter line by printing spaces and returning
|
|
1547
|
+
console.print(" " * 50, end="\r")
|
|
1499
1548
|
# For thinking parts, just print newline
|
|
1500
1549
|
elif event.index in banner_printed:
|
|
1501
1550
|
console.print() # Final newline after streaming
|
|
1551
|
+
|
|
1552
|
+
# Clean up token count
|
|
1553
|
+
token_count.pop(event.index, None)
|
|
1502
1554
|
# Clean up all tracking sets
|
|
1503
1555
|
streaming_parts.discard(event.index)
|
|
1504
1556
|
thinking_parts.discard(event.index)
|
|
1505
1557
|
text_parts.discard(event.index)
|
|
1558
|
+
tool_parts.discard(event.index)
|
|
1506
1559
|
banner_printed.discard(event.index)
|
|
1507
1560
|
|
|
1508
|
-
# Resume spinner if next part is NOT text/thinking (avoid race condition)
|
|
1509
|
-
# If next part is
|
|
1561
|
+
# Resume spinner if next part is NOT text/thinking/tool (avoid race condition)
|
|
1562
|
+
# If next part is None or handled differently, it's safe to resume
|
|
1510
1563
|
# Note: spinner itself handles blank line before appearing
|
|
1511
1564
|
from code_puppy.messaging.spinner import resume_all_spinners
|
|
1512
1565
|
|
|
1513
1566
|
next_kind = getattr(event, "next_part_kind", None)
|
|
1514
|
-
if next_kind not in ("text", "thinking"):
|
|
1567
|
+
if next_kind not in ("text", "thinking", "tool-call"):
|
|
1515
1568
|
resume_all_spinners()
|
|
1516
1569
|
|
|
1517
1570
|
# Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
|
|
@@ -1910,73 +1963,35 @@ class BaseAgent(ABC):
|
|
|
1910
1963
|
def graceful_sigint_handler(_sig, _frame):
|
|
1911
1964
|
# When using keyboard-based cancel, SIGINT should be a no-op
|
|
1912
1965
|
# (just show a hint to user about the configured cancel key)
|
|
1966
|
+
# Also reset terminal to prevent bricking on Windows+uvx
|
|
1913
1967
|
from code_puppy.keymap import get_cancel_agent_display_name
|
|
1914
|
-
import
|
|
1968
|
+
from code_puppy.terminal_utils import reset_windows_terminal_full
|
|
1969
|
+
|
|
1970
|
+
# Reset terminal state first to prevent bricking
|
|
1971
|
+
reset_windows_terminal_full()
|
|
1915
1972
|
|
|
1916
1973
|
cancel_key = get_cancel_agent_display_name()
|
|
1917
|
-
|
|
1918
|
-
# On Windows, we use keyboard listener, so SIGINT might still fire
|
|
1919
|
-
# but we handle cancellation via the key listener
|
|
1920
|
-
pass # Silent on Windows - the key listener handles it
|
|
1921
|
-
else:
|
|
1922
|
-
emit_info(f"Use {cancel_key} to cancel the agent task.")
|
|
1974
|
+
emit_info(f"Use {cancel_key} to cancel the agent task.")
|
|
1923
1975
|
|
|
1924
1976
|
original_handler = None
|
|
1925
1977
|
key_listener_stop_event = None
|
|
1926
1978
|
_key_listener_thread = None
|
|
1927
|
-
_windows_ctrl_handler = None # Store reference to prevent garbage collection
|
|
1928
1979
|
|
|
1929
1980
|
try:
|
|
1930
|
-
if
|
|
1931
|
-
#
|
|
1932
|
-
import ctypes
|
|
1933
|
-
|
|
1934
|
-
# Define the handler function type
|
|
1935
|
-
HANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_ulong)
|
|
1936
|
-
|
|
1937
|
-
def windows_ctrl_handler(ctrl_type):
|
|
1938
|
-
"""Handle Windows console control events."""
|
|
1939
|
-
CTRL_C_EVENT = 0
|
|
1940
|
-
CTRL_BREAK_EVENT = 1
|
|
1941
|
-
|
|
1942
|
-
if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
|
|
1943
|
-
# Check if we're awaiting user input
|
|
1944
|
-
if is_awaiting_user_input():
|
|
1945
|
-
return False # Let default handler run
|
|
1946
|
-
|
|
1947
|
-
# Schedule agent cancellation
|
|
1948
|
-
schedule_agent_cancel()
|
|
1949
|
-
return True # We handled it, don't terminate
|
|
1950
|
-
|
|
1951
|
-
return False # Let other handlers process it
|
|
1952
|
-
|
|
1953
|
-
# Create the callback - must keep reference alive!
|
|
1954
|
-
_windows_ctrl_handler = HANDLER_ROUTINE(windows_ctrl_handler)
|
|
1955
|
-
|
|
1956
|
-
# Register the handler
|
|
1957
|
-
kernel32 = ctypes.windll.kernel32
|
|
1958
|
-
if not kernel32.SetConsoleCtrlHandler(_windows_ctrl_handler, True):
|
|
1959
|
-
emit_warning("Failed to set Windows Ctrl+C handler")
|
|
1960
|
-
|
|
1961
|
-
# Also spawn keyboard listener for Ctrl+X (shell cancel) and other keys
|
|
1962
|
-
key_listener_stop_event = threading.Event()
|
|
1963
|
-
_key_listener_thread = self._spawn_ctrl_x_key_listener(
|
|
1964
|
-
key_listener_stop_event,
|
|
1965
|
-
on_escape=lambda: None, # Ctrl+X handled by command_runner
|
|
1966
|
-
on_cancel_agent=None, # Ctrl+C handled by SetConsoleCtrlHandler above
|
|
1967
|
-
)
|
|
1968
|
-
elif cancel_agent_uses_signal():
|
|
1969
|
-
# Unix with Ctrl+C: Use SIGINT-based cancellation
|
|
1981
|
+
if cancel_agent_uses_signal():
|
|
1982
|
+
# Use SIGINT-based cancellation (default Ctrl+C behavior)
|
|
1970
1983
|
original_handler = signal.signal(
|
|
1971
1984
|
signal.SIGINT, keyboard_interrupt_handler
|
|
1972
1985
|
)
|
|
1973
1986
|
else:
|
|
1974
|
-
#
|
|
1987
|
+
# Use keyboard listener for agent cancellation
|
|
1988
|
+
# Set a graceful SIGINT handler that shows a hint
|
|
1975
1989
|
original_handler = signal.signal(signal.SIGINT, graceful_sigint_handler)
|
|
1990
|
+
# Spawn keyboard listener with the cancel agent callback
|
|
1976
1991
|
key_listener_stop_event = threading.Event()
|
|
1977
1992
|
_key_listener_thread = self._spawn_ctrl_x_key_listener(
|
|
1978
1993
|
key_listener_stop_event,
|
|
1979
|
-
on_escape=lambda: None,
|
|
1994
|
+
on_escape=lambda: None, # Ctrl+X handled by command_runner
|
|
1980
1995
|
on_cancel_agent=schedule_agent_cancel,
|
|
1981
1996
|
)
|
|
1982
1997
|
|
|
@@ -2001,17 +2016,8 @@ class BaseAgent(ABC):
|
|
|
2001
2016
|
# Stop keyboard listener if it was started
|
|
2002
2017
|
if key_listener_stop_event is not None:
|
|
2003
2018
|
key_listener_stop_event.set()
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
import ctypes
|
|
2009
|
-
|
|
2010
|
-
kernel32 = ctypes.windll.kernel32
|
|
2011
|
-
kernel32.SetConsoleCtrlHandler(_windows_ctrl_handler, False)
|
|
2012
|
-
except Exception:
|
|
2013
|
-
pass # Best effort cleanup
|
|
2014
|
-
|
|
2015
|
-
# Restore original signal handler (Unix)
|
|
2016
|
-
if original_handler is not None:
|
|
2019
|
+
# Restore original signal handler
|
|
2020
|
+
if (
|
|
2021
|
+
original_handler is not None
|
|
2022
|
+
): # Explicit None check - SIG_DFL can be 0/falsy!
|
|
2017
2023
|
signal.signal(signal.SIGINT, original_handler)
|