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.
Files changed (183) hide show
  1. {code_puppy-0.0.323 → code_puppy-0.0.338}/PKG-INFO +25 -49
  2. {code_puppy-0.0.323 → code_puppy-0.0.338}/README.md +23 -48
  3. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/base_agent.py +121 -115
  4. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/claude_cache_client.py +46 -2
  5. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/cli_runner.py +108 -29
  6. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/add_model_menu.py +15 -0
  7. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/autosave_menu.py +5 -0
  8. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/colors_menu.py +5 -0
  9. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/config_commands.py +24 -1
  10. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/core_commands.py +51 -0
  11. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/diff_menu.py +5 -0
  12. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_form.py +4 -0
  13. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_menu.py +5 -1
  14. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/model_settings_menu.py +5 -0
  15. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/motd.py +13 -7
  16. code_puppy-0.0.338/code_puppy/command_line/onboarding_slides.py +180 -0
  17. code_puppy-0.0.338/code_puppy/command_line/onboarding_wizard.py +340 -0
  18. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/config.py +3 -2
  19. code_puppy-0.0.338/code_puppy/http_utils.py +338 -0
  20. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/keymap.py +10 -8
  21. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/messages.py +3 -0
  22. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/rich_renderer.py +114 -22
  23. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/model_factory.py +102 -15
  24. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models.json +4 -4
  25. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/__init__.py +12 -0
  26. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  27. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  28. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/antigravity_model.py +668 -0
  29. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/config.py +42 -0
  30. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  31. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  32. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  33. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  34. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  35. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/token.py +167 -0
  36. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/transport.py +664 -0
  37. code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  38. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
  39. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
  40. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/utils.py +126 -7
  41. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/reopenable_async_client.py +8 -8
  42. code_puppy-0.0.338/code_puppy/terminal_utils.py +418 -0
  43. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/command_runner.py +43 -54
  44. code_puppy-0.0.338/code_puppy/uvx_detection.py +242 -0
  45. {code_puppy-0.0.323 → code_puppy-0.0.338}/pyproject.toml +2 -1
  46. code_puppy-0.0.323/code_puppy/http_utils.py +0 -416
  47. code_puppy-0.0.323/code_puppy/terminal_utils.py +0 -126
  48. {code_puppy-0.0.323 → code_puppy-0.0.338}/.gitignore +0 -0
  49. {code_puppy-0.0.323 → code_puppy-0.0.338}/LICENSE +0 -0
  50. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/__init__.py +0 -0
  51. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/__main__.py +0 -0
  52. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/__init__.py +0 -0
  53. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_c_reviewer.py +0 -0
  54. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_code_puppy.py +0 -0
  55. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_code_reviewer.py +0 -0
  56. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  57. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_creator_agent.py +0 -0
  58. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  59. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  60. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_manager.py +0 -0
  61. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_planning.py +0 -0
  62. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_python_programmer.py +0 -0
  63. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_python_reviewer.py +0 -0
  64. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_expert.py +0 -0
  65. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_kitten.py +0 -0
  66. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_security_auditor.py +0 -0
  67. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  68. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/json_agent.py +0 -0
  69. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/agents/prompt_reviewer.py +0 -0
  70. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/callbacks.py +0 -0
  71. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/chatgpt_codex_client.py +0 -0
  72. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/__init__.py +0 -0
  73. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/attachments.py +0 -0
  74. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/command_handler.py +0 -0
  75. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/command_registry.py +0 -0
  76. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/file_path_completion.py +0 -0
  77. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/load_context_completion.py +0 -0
  78. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/__init__.py +0 -0
  79. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/add_command.py +0 -0
  80. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/base.py +0 -0
  81. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  82. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  83. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/edit_command.py +0 -0
  84. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/handler.py +0 -0
  85. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/help_command.py +0 -0
  86. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_command.py +0 -0
  87. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/list_command.py +0 -0
  88. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/logs_command.py +0 -0
  89. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/remove_command.py +0 -0
  90. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/restart_command.py +0 -0
  91. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/search_command.py +0 -0
  92. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  93. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_command.py +0 -0
  94. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/status_command.py +0 -0
  95. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  96. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_command.py +0 -0
  97. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/test_command.py +0 -0
  98. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/utils.py +0 -0
  99. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  100. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/mcp_completion.py +0 -0
  101. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/model_picker_completion.py +0 -0
  102. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/pin_command_completion.py +0 -0
  103. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  104. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/session_commands.py +0 -0
  105. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/command_line/utils.py +0 -0
  106. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/error_logging.py +0 -0
  107. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/gemini_code_assist.py +0 -0
  108. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/main.py +0 -0
  109. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/__init__.py +0 -0
  110. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/async_lifecycle.py +0 -0
  111. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/blocking_startup.py +0 -0
  112. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  113. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/circuit_breaker.py +0 -0
  114. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/config_wizard.py +0 -0
  115. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/dashboard.py +0 -0
  116. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/error_isolation.py +0 -0
  117. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/examples/retry_example.py +0 -0
  118. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/health_monitor.py +0 -0
  119. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/managed_server.py +0 -0
  120. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/manager.py +0 -0
  121. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/mcp_logs.py +0 -0
  122. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/registry.py +0 -0
  123. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/retry_manager.py +0 -0
  124. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  125. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/status_tracker.py +0 -0
  126. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/mcp_/system_tools.py +0 -0
  127. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/__init__.py +0 -0
  128. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/bus.py +0 -0
  129. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/commands.py +0 -0
  130. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/markdown_patches.py +0 -0
  131. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/message_queue.py +0 -0
  132. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/queue_console.py +0 -0
  133. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/renderers.py +0 -0
  134. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/__init__.py +0 -0
  135. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  136. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  137. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/model_utils.py +0 -0
  138. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models_dev_api.json +0 -0
  139. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/models_dev_parser.py +0 -0
  140. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  141. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  142. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  143. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  144. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  145. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  146. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  147. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  148. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  149. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  150. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  152. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/README.md +0 -0
  153. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  154. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  155. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  156. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  157. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  158. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  159. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  160. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  161. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/prompts/codex_system_prompt.md +0 -0
  162. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/pydantic_patches.py +0 -0
  163. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/round_robin_model.py +0 -0
  164. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/session_storage.py +0 -0
  165. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/status_display.py +0 -0
  166. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/summarization_agent.py +0 -0
  167. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/__init__.py +0 -0
  168. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/agent_tools.py +0 -0
  169. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/__init__.py +0 -0
  170. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_control.py +0 -0
  171. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_interactions.py +0 -0
  172. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_locators.py +0 -0
  173. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_navigation.py +0 -0
  174. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  175. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_scripts.py +0 -0
  176. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_workflows.py +0 -0
  177. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  178. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/browser/vqa_agent.py +0 -0
  179. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/common.py +0 -0
  180. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/file_modifications.py +0 -0
  181. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/file_operations.py +0 -0
  182. {code_puppy-0.0.323 → code_puppy-0.0.338}/code_puppy/tools/tools_content.py +0 -0
  183. {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.323
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
- # Set UV to always use managed Python (one-time setup)
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
- *Note: pip installation requires your system Python to be 3.11 or newer.*
113
+ #### Windows
124
114
 
125
- ### Permanent Python Management
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
- To make UV always use managed Python versions (recommended):
128
-
129
- ```bash
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
- # Now all UV commands will prefer managed Python installations
135
- uvx code-puppy # No need for --managed-python flag anymore
121
+ uvx code-puppy
136
122
  ```
137
123
 
138
- ### Verifying Python Version
124
+ ## Changelog (By Kittylog!)
139
125
 
140
- ```bash
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
- # Set UV to always use managed Python (one-time setup)
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
- *Note: pip installation requires your system Python to be 3.11 or newer.*
73
+ #### Windows
85
74
 
86
- ### Permanent Python Management
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
- To make UV always use managed Python versions (recommended):
89
-
90
- ```bash
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
- # Now all UV commands will prefer managed Python installations
96
- uvx code-puppy # No need for --managed-python flag anymore
81
+ uvx code-puppy
97
82
  ```
98
83
 
99
- ### Verifying Python Version
84
+ ## Changelog (By Kittylog!)
100
85
 
101
- ```bash
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 and ThinkingPart
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 TextPartDelta, ThinkingPartDelta
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
- text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
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
- sys.stdout.write("\r\x1b[K") # Clear line
1389
- sys.stdout.flush()
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
- sys.stdout.write("\r\x1b[K") # Clear line
1413
- sys.stdout.flush()
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
- text_buffer[event.index] = [] # Initialize buffer
1441
- token_count[event.index] = 0 # Initialize token counter
1442
- # Buffer initial content if present
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
- text_buffer[event.index].append(part.content)
1445
- token_count[event.index] += 1
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, show token counter then render at end
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
- # Accumulate text for final markdown render
1462
- text_buffer[event.index].append(delta.content_delta)
1463
- token_count[event.index] += 1
1464
- # Update token counter in place (single line)
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
- sys.stdout.flush()
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, clear counter line and render markdown
1523
+ # For text parts, finalize termflow rendering
1482
1524
  if event.index in text_parts:
1483
- import sys
1484
-
1485
- # Clear the token counter line
1486
- sys.stdout.write("\r\x1b[K")
1487
- sys.stdout.flush()
1488
- # Render the final markdown nicely
1489
- if event.index in text_buffer:
1490
- try:
1491
- final_content = "".join(text_buffer[event.index])
1492
- if final_content.strip():
1493
- console.print(Markdown(final_content))
1494
- except Exception:
1495
- pass
1496
- del text_buffer[event.index]
1497
- # Clean up token count
1498
- token_count.pop(event.index, None)
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 a tool call or None, it's safe to resume
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 sys
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
- if sys.platform == "win32":
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 sys.platform == "win32":
1931
- # Windows: Use SetConsoleCtrlHandler for reliable Ctrl+C handling
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
- # Unix with different cancel key: Use keyboard listener
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
- # Unregister Windows Ctrl handler
2006
- if sys.platform == "win32" and _windows_ctrl_handler is not None:
2007
- try:
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)