code-puppy 0.0.337__tar.gz → 0.0.339__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 (182) hide show
  1. {code_puppy-0.0.337 → code_puppy-0.0.339}/PKG-INFO +19 -71
  2. {code_puppy-0.0.337 → code_puppy-0.0.339}/README.md +16 -70
  3. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/base_agent.py +55 -30
  4. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/claude_cache_client.py +46 -2
  5. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/cli_runner.py +45 -32
  6. code_puppy-0.0.339/code_puppy/command_line/clipboard.py +527 -0
  7. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/core_commands.py +34 -0
  8. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/prompt_toolkit_completion.py +112 -0
  9. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/model_factory.py +16 -0
  10. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/models.json +2 -2
  11. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/utils.py +126 -7
  12. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/terminal_utils.py +128 -1
  13. {code_puppy-0.0.337 → code_puppy-0.0.339}/pyproject.toml +3 -1
  14. {code_puppy-0.0.337 → code_puppy-0.0.339}/.gitignore +0 -0
  15. {code_puppy-0.0.337 → code_puppy-0.0.339}/LICENSE +0 -0
  16. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/__init__.py +0 -0
  17. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/__main__.py +0 -0
  18. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/__init__.py +0 -0
  19. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_c_reviewer.py +0 -0
  20. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_code_puppy.py +0 -0
  21. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_code_reviewer.py +0 -0
  22. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  23. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_creator_agent.py +0 -0
  24. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  25. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  26. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_manager.py +0 -0
  27. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_planning.py +0 -0
  28. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_python_programmer.py +0 -0
  29. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_python_reviewer.py +0 -0
  30. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_qa_expert.py +0 -0
  31. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_qa_kitten.py +0 -0
  32. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_security_auditor.py +0 -0
  33. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  34. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/json_agent.py +0 -0
  35. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/agents/prompt_reviewer.py +0 -0
  36. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/callbacks.py +0 -0
  37. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/chatgpt_codex_client.py +0 -0
  38. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/__init__.py +0 -0
  39. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/add_model_menu.py +0 -0
  40. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/attachments.py +0 -0
  41. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/autosave_menu.py +0 -0
  42. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/colors_menu.py +0 -0
  43. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/command_handler.py +0 -0
  44. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/command_registry.py +0 -0
  45. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/config_commands.py +0 -0
  46. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/diff_menu.py +0 -0
  47. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/file_path_completion.py +0 -0
  48. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/load_context_completion.py +0 -0
  49. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/__init__.py +0 -0
  50. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/add_command.py +0 -0
  51. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/base.py +0 -0
  52. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  53. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  54. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  55. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/edit_command.py +0 -0
  56. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/handler.py +0 -0
  57. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/help_command.py +0 -0
  58. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/install_command.py +0 -0
  59. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/install_menu.py +0 -0
  60. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/list_command.py +0 -0
  61. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/logs_command.py +0 -0
  62. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/remove_command.py +0 -0
  63. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/restart_command.py +0 -0
  64. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/search_command.py +0 -0
  65. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  66. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/start_command.py +0 -0
  67. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/status_command.py +0 -0
  68. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  69. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/stop_command.py +0 -0
  70. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/test_command.py +0 -0
  71. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/utils.py +0 -0
  72. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  73. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/mcp_completion.py +0 -0
  74. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/model_picker_completion.py +0 -0
  75. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/model_settings_menu.py +0 -0
  76. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/motd.py +0 -0
  77. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/onboarding_slides.py +0 -0
  78. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/onboarding_wizard.py +0 -0
  79. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/pin_command_completion.py +0 -0
  80. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/session_commands.py +0 -0
  81. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/command_line/utils.py +0 -0
  82. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/config.py +0 -0
  83. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/error_logging.py +0 -0
  84. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/gemini_code_assist.py +0 -0
  85. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/http_utils.py +0 -0
  86. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/keymap.py +0 -0
  87. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/main.py +0 -0
  88. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/__init__.py +0 -0
  89. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/async_lifecycle.py +0 -0
  90. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/blocking_startup.py +0 -0
  91. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  92. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/circuit_breaker.py +0 -0
  93. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/config_wizard.py +0 -0
  94. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/dashboard.py +0 -0
  95. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/error_isolation.py +0 -0
  96. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/examples/retry_example.py +0 -0
  97. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/health_monitor.py +0 -0
  98. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/managed_server.py +0 -0
  99. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/manager.py +0 -0
  100. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/mcp_logs.py +0 -0
  101. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/registry.py +0 -0
  102. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/retry_manager.py +0 -0
  103. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  104. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/status_tracker.py +0 -0
  105. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/mcp_/system_tools.py +0 -0
  106. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/__init__.py +0 -0
  107. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/bus.py +0 -0
  108. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/commands.py +0 -0
  109. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/markdown_patches.py +0 -0
  110. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/message_queue.py +0 -0
  111. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/messages.py +0 -0
  112. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/queue_console.py +0 -0
  113. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/renderers.py +0 -0
  114. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/rich_renderer.py +0 -0
  115. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/spinner/__init__.py +0 -0
  116. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  117. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  118. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/model_utils.py +0 -0
  119. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/models_dev_api.json +0 -0
  120. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/models_dev_parser.py +0 -0
  121. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/__init__.py +0 -0
  122. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  123. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  124. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
  125. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  126. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  127. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  128. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  129. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  130. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  131. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  132. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
  133. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  134. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  135. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  136. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  137. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  138. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  139. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  140. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  141. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  142. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  143. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  144. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  145. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  146. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  147. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  148. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/example_custom_command/README.md +0 -0
  149. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  150. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  151. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  152. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  153. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  154. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  155. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  156. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  157. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/prompts/codex_system_prompt.md +0 -0
  158. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/pydantic_patches.py +0 -0
  159. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/reopenable_async_client.py +0 -0
  160. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/round_robin_model.py +0 -0
  161. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/session_storage.py +0 -0
  162. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/status_display.py +0 -0
  163. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/summarization_agent.py +0 -0
  164. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/__init__.py +0 -0
  165. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/agent_tools.py +0 -0
  166. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/__init__.py +0 -0
  167. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_control.py +0 -0
  168. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_interactions.py +0 -0
  169. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_locators.py +0 -0
  170. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_navigation.py +0 -0
  171. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  172. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_scripts.py +0 -0
  173. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/browser_workflows.py +0 -0
  174. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  175. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/browser/vqa_agent.py +0 -0
  176. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/command_runner.py +0 -0
  177. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/common.py +0 -0
  178. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/file_modifications.py +0 -0
  179. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/file_operations.py +0 -0
  180. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/tools/tools_content.py +0 -0
  181. {code_puppy-0.0.337 → code_puppy-0.0.339}/code_puppy/uvx_detection.py +0 -0
  182. {code_puppy-0.0.337 → code_puppy-0.0.339}/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.337
3
+ Version: 0.0.339
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
@@ -22,6 +22,7 @@ Requires-Dist: httpx[http2]>=0.24.1
22
22
  Requires-Dist: json-repair>=0.46.2
23
23
  Requires-Dist: logfire>=0.7.1
24
24
  Requires-Dist: openai>=1.99.1
25
+ Requires-Dist: pillow>=10.0.0
25
26
  Requires-Dist: playwright>=1.40.0
26
27
  Requires-Dist: prompt-toolkit>=3.0.52
27
28
  Requires-Dist: pydantic-ai==1.25.0
@@ -34,6 +35,7 @@ Requires-Dist: rich>=13.4.2
34
35
  Requires-Dist: ripgrep==14.1.0
35
36
  Requires-Dist: ruff>=0.11.11
36
37
  Requires-Dist: tenacity>=8.2.0
38
+ Requires-Dist: termflow-md>=0.1.6
37
39
  Requires-Dist: uvicorn>=0.30.0
38
40
  Description-Content-Type: text/markdown
39
41
 
@@ -106,12 +108,7 @@ uvx code-puppy -i
106
108
  # Install UV if you don't have it
107
109
  curl -LsSf https://astral.sh/uv/install.sh | sh
108
110
 
109
- # Set UV to always use managed Python (one-time setup)
110
- echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
111
- source ~/.zshrc # or ~/.bashrc
112
-
113
- # Install and run code-puppy
114
- uvx code-puppy -i
111
+ uvx code-puppy
115
112
  ```
116
113
 
117
114
  #### Windows
@@ -122,73 +119,15 @@ On Windows, we recommend installing code-puppy as a global tool for the best exp
122
119
  # Install UV if you don't have it (run in PowerShell as Admin)
123
120
  powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
124
121
 
125
- # Install code-puppy as a global tool
126
- uv tool install code-puppy
127
-
128
- # Run code-puppy
129
- code-puppy -i
130
- ```
131
-
132
- **Why `uv tool install` on Windows?** Running with `uvx` creates an extra process layer that can interfere with keyboard signal handling (Ctrl+C, Ctrl+X). Installing as a tool runs code-puppy directly for reliable cancellation.
133
-
134
- #### Upgrading
135
-
136
- ```bash
137
- # Upgrade code-puppy to the latest version
138
- uv tool upgrade code-puppy
139
-
140
- # Or upgrade all installed tools
141
- uv tool upgrade --all
142
- ```
143
-
144
- UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
145
-
146
- ### pip (Alternative)
147
-
148
- ```bash
149
- pip install code-puppy
122
+ uvx code-puppy
150
123
  ```
151
124
 
152
- *Note: pip installation requires your system Python to be 3.11 or newer.*
153
-
154
- ### Permanent Python Management
155
-
156
- To make UV always use managed Python versions (recommended):
157
-
158
- ```bash
159
- # Set environment variable permanently
160
- echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
161
- source ~/.zshrc # or ~/.bashrc
162
-
163
- # Now all UV commands will prefer managed Python installations
164
- uvx code-puppy # No need for --managed-python flag anymore
165
- ```
125
+ ## Changelog (By Kittylog!)
166
126
 
167
- ### Verifying Python Version
168
-
169
- ```bash
170
- # Check which Python UV will use
171
- uv python find
172
-
173
- # Or check the current project's Python
174
- uv run python --version
175
- ```
127
+ [📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
176
128
 
177
129
  ## Usage
178
130
 
179
- ### Custom Commands
180
- 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.
181
-
182
- ```bash
183
- # Create a custom command
184
- echo "# Code Review
185
-
186
- Please review this code for security issues." > .claude/commands/review.md
187
-
188
- # Use it in Code Puppy
189
- /review with focus on authentication
190
- ```
191
-
192
131
  ### Adding Models from models.dev 🆕
193
132
 
194
133
  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:
@@ -256,6 +195,18 @@ The following environment variables control DBOS behavior:
256
195
  - `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.
257
196
  - `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).
258
197
 
198
+ ### Custom Commands
199
+ 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.
200
+
201
+ ```bash
202
+ # Create a custom command
203
+ echo "# Code Review
204
+
205
+ Please review this code for security issues." > .claude/commands/review.md
206
+
207
+ # Use it in Code Puppy
208
+ /review with focus on authentication
209
+ ```
259
210
 
260
211
  ## Requirements
261
212
 
@@ -275,9 +226,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
275
226
 
276
227
  Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
277
228
 
278
- Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
279
-
280
-
281
229
  ## Round Robin Model Distribution
282
230
 
283
231
  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.
@@ -67,12 +67,7 @@ uvx code-puppy -i
67
67
  # Install UV if you don't have it
68
68
  curl -LsSf https://astral.sh/uv/install.sh | sh
69
69
 
70
- # Set UV to always use managed Python (one-time setup)
71
- echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
72
- source ~/.zshrc # or ~/.bashrc
73
-
74
- # Install and run code-puppy
75
- uvx code-puppy -i
70
+ uvx code-puppy
76
71
  ```
77
72
 
78
73
  #### Windows
@@ -83,73 +78,15 @@ On Windows, we recommend installing code-puppy as a global tool for the best exp
83
78
  # Install UV if you don't have it (run in PowerShell as Admin)
84
79
  powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
85
80
 
86
- # Install code-puppy as a global tool
87
- uv tool install code-puppy
88
-
89
- # Run code-puppy
90
- code-puppy -i
91
- ```
92
-
93
- **Why `uv tool install` on Windows?** Running with `uvx` creates an extra process layer that can interfere with keyboard signal handling (Ctrl+C, Ctrl+X). Installing as a tool runs code-puppy directly for reliable cancellation.
94
-
95
- #### Upgrading
96
-
97
- ```bash
98
- # Upgrade code-puppy to the latest version
99
- uv tool upgrade code-puppy
100
-
101
- # Or upgrade all installed tools
102
- uv tool upgrade --all
103
- ```
104
-
105
- UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
106
-
107
- ### pip (Alternative)
108
-
109
- ```bash
110
- pip install code-puppy
81
+ uvx code-puppy
111
82
  ```
112
83
 
113
- *Note: pip installation requires your system Python to be 3.11 or newer.*
114
-
115
- ### Permanent Python Management
116
-
117
- To make UV always use managed Python versions (recommended):
118
-
119
- ```bash
120
- # Set environment variable permanently
121
- echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
122
- source ~/.zshrc # or ~/.bashrc
123
-
124
- # Now all UV commands will prefer managed Python installations
125
- uvx code-puppy # No need for --managed-python flag anymore
126
- ```
84
+ ## Changelog (By Kittylog!)
127
85
 
128
- ### Verifying Python Version
129
-
130
- ```bash
131
- # Check which Python UV will use
132
- uv python find
133
-
134
- # Or check the current project's Python
135
- uv run python --version
136
- ```
86
+ [📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
137
87
 
138
88
  ## Usage
139
89
 
140
- ### Custom Commands
141
- 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.
142
-
143
- ```bash
144
- # Create a custom command
145
- echo "# Code Review
146
-
147
- Please review this code for security issues." > .claude/commands/review.md
148
-
149
- # Use it in Code Puppy
150
- /review with focus on authentication
151
- ```
152
-
153
90
  ### Adding Models from models.dev 🆕
154
91
 
155
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:
@@ -217,6 +154,18 @@ The following environment variables control DBOS behavior:
217
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.
218
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).
219
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
+ ```
220
169
 
221
170
  ## Requirements
222
171
 
@@ -236,9 +185,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
236
185
 
237
186
  Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
238
187
 
239
- Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
240
-
241
-
242
188
  ## Round Robin Model Distribution
243
189
 
244
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.
@@ -1353,7 +1353,6 @@ class BaseAgent(ABC):
1353
1353
  ToolCallPartDelta,
1354
1354
  )
1355
1355
  from rich.console import Console
1356
- from rich.markdown import Markdown
1357
1356
  from rich.markup import escape
1358
1357
 
1359
1358
  from code_puppy.messaging.spinner import pause_all_spinners
@@ -1375,10 +1374,17 @@ class BaseAgent(ABC):
1375
1374
  text_parts: set[int] = set() # Track which parts are text
1376
1375
  tool_parts: set[int] = set() # Track which parts are tool calls
1377
1376
  banner_printed: set[int] = set() # Track if banner was already printed
1378
- text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
1379
1377
  token_count: dict[int, int] = {} # Track token count per text/tool part
1380
1378
  did_stream_anything = False # Track if we streamed any content
1381
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
+
1382
1388
  def _print_thinking_banner() -> None:
1383
1389
  """Print the THINKING banner with spinner pause and line clear."""
1384
1390
  nonlocal did_stream_anything
@@ -1437,13 +1443,17 @@ 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
- # Count chunks (each part counts as 1)
1446
- token_count[event.index] += 1
1454
+ _print_response_banner()
1455
+ banner_printed.add(event.index)
1456
+ termflow_line_buffers[event.index] = part.content
1447
1457
  elif isinstance(part, ToolCallPart):
1448
1458
  streaming_parts.add(event.index)
1449
1459
  tool_parts.add(event.index)
@@ -1459,22 +1469,29 @@ class BaseAgent(ABC):
1459
1469
  delta = event.delta
1460
1470
  if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
1461
1471
  if delta.content_delta:
1462
- # For text parts, show token counter then render at end
1472
+ # For text parts, stream markdown with termflow
1463
1473
  if event.index in text_parts:
1464
1474
  # Print banner on first content
1465
1475
  if event.index not in banner_printed:
1466
1476
  _print_response_banner()
1467
1477
  banner_printed.add(event.index)
1468
- # Accumulate text for final markdown render
1469
- text_buffer[event.index].append(delta.content_delta)
1470
- # Count chunks received
1471
- token_count[event.index] += 1
1472
- # Update chunk counter in place (single line)
1473
- count = token_count[event.index]
1474
- console.print(
1475
- f" ⏳ Receiving... {count} chunks ",
1476
- end="\r",
1478
+
1479
+ # Add content to line buffer
1480
+ termflow_line_buffers[event.index] += (
1481
+ delta.content_delta
1477
1482
  )
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
1478
1495
  else:
1479
1496
  # For thinking parts, stream immediately (dim)
1480
1497
  if event.index not in banner_printed:
@@ -1503,19 +1520,27 @@ class BaseAgent(ABC):
1503
1520
  # PartEndEvent - finish the streaming with a newline
1504
1521
  elif isinstance(event, PartEndEvent):
1505
1522
  if event.index in streaming_parts:
1506
- # For text parts, clear counter line and render markdown
1523
+ # For text parts, finalize termflow rendering
1507
1524
  if event.index in text_parts:
1508
- # Clear the chunk counter line by printing spaces and returning
1509
- console.print(" " * 50, end="\r")
1510
- # Render the final markdown nicely
1511
- if event.index in text_buffer:
1512
- try:
1513
- final_content = "".join(text_buffer[event.index])
1514
- if final_content.strip():
1515
- console.print(Markdown(final_content))
1516
- except Exception:
1517
- pass
1518
- del text_buffer[event.index]
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]
1519
1544
  # For tool parts, clear the chunk counter line
1520
1545
  elif event.index in tool_parts:
1521
1546
  # Clear the chunk counter line by printing spaces and returning
@@ -10,7 +10,7 @@ serialization, avoiding httpx/Pydantic internals.
10
10
  from __future__ import annotations
11
11
 
12
12
  import json
13
- from typing import Any, Callable
13
+ from typing import Any, Callable, MutableMapping
14
14
 
15
15
  import httpx
16
16
 
@@ -56,7 +56,28 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
56
56
  except Exception:
57
57
  # Swallow wrapper errors; do not break real calls.
58
58
  pass
59
- return await super().send(request, *args, **kwargs)
59
+ response = await super().send(request, *args, **kwargs)
60
+ try:
61
+ if response.status_code == 401 and not request.extensions.get(
62
+ "claude_oauth_refresh_attempted"
63
+ ):
64
+ refreshed_token = self._refresh_claude_oauth_token()
65
+ if refreshed_token:
66
+ await response.aclose()
67
+ body_bytes = self._extract_body_bytes(request)
68
+ headers = dict(request.headers)
69
+ self._update_auth_headers(headers, refreshed_token)
70
+ retry_request = self.build_request(
71
+ method=request.method,
72
+ url=request.url,
73
+ headers=headers,
74
+ content=body_bytes,
75
+ )
76
+ retry_request.extensions["claude_oauth_refresh_attempted"] = True
77
+ return await super().send(retry_request, *args, **kwargs)
78
+ except Exception:
79
+ pass
80
+ return response
60
81
 
61
82
  @staticmethod
62
83
  def _extract_body_bytes(request: httpx.Request) -> bytes | None:
@@ -78,6 +99,29 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
78
99
 
79
100
  return None
80
101
 
102
+ @staticmethod
103
+ def _update_auth_headers(
104
+ headers: MutableMapping[str, str], access_token: str
105
+ ) -> None:
106
+ bearer_value = f"Bearer {access_token}"
107
+ if "Authorization" in headers or "authorization" in headers:
108
+ headers["Authorization"] = bearer_value
109
+ elif "x-api-key" in headers or "X-API-Key" in headers:
110
+ headers["x-api-key"] = access_token
111
+ else:
112
+ headers["Authorization"] = bearer_value
113
+
114
+ def _refresh_claude_oauth_token(self) -> str | None:
115
+ try:
116
+ from code_puppy.plugins.claude_code_oauth.utils import refresh_access_token
117
+
118
+ refreshed_token = refresh_access_token(force=True)
119
+ if refreshed_token:
120
+ self._update_auth_headers(self.headers, refreshed_token)
121
+ return refreshed_token
122
+ except Exception:
123
+ return None
124
+
81
125
  @staticmethod
82
126
  def _inject_cache_control(body: bytes) -> bytes | None:
83
127
  try:
@@ -17,14 +17,12 @@ import traceback
17
17
  from pathlib import Path
18
18
 
19
19
  from dbos import DBOS, DBOSConfig
20
- from rich.console import Console, ConsoleOptions, RenderResult
21
- from rich.markdown import CodeBlock, Markdown
22
- from rich.syntax import Syntax
23
- from rich.text import Text
20
+ from rich.console import Console
24
21
 
25
22
  from code_puppy import __version__, callbacks, plugins
26
23
  from code_puppy.agents import get_current_agent
27
24
  from code_puppy.command_line.attachments import parse_prompt_attachments
25
+ from code_puppy.command_line.clipboard import get_clipboard_manager
28
26
  from code_puppy.config import (
29
27
  AUTOSAVE_DIR,
30
28
  COMMAND_HISTORY_FILE,
@@ -43,6 +41,7 @@ from code_puppy.keymap import (
43
41
  )
44
42
  from code_puppy.messaging import emit_info
45
43
  from code_puppy.terminal_utils import (
44
+ print_truecolor_warning,
46
45
  reset_unix_terminal,
47
46
  reset_windows_terminal_ansi,
48
47
  reset_windows_terminal_full,
@@ -91,7 +90,6 @@ async def main():
91
90
  "command", nargs="*", help="Run a single command (deprecated, use -p instead)"
92
91
  )
93
92
  args = parser.parse_args()
94
- from rich.console import Console
95
93
 
96
94
  from code_puppy.messaging import (
97
95
  RichConsoleRenderer,
@@ -146,6 +144,9 @@ async def main():
146
144
  except ImportError:
147
145
  emit_system_message("🐶 Code Puppy is Loading...")
148
146
 
147
+ # Check for truecolor support and warn if not available
148
+ print_truecolor_warning(display_console)
149
+
149
150
  available_port = find_available_port()
150
151
  if available_port is None:
151
152
  emit_error("No available ports in range 8090-9010!")
@@ -354,6 +355,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
354
355
  emit_system_message(
355
356
  "Type @ for path completion, or /model to pick a model. Toggle multiline with Alt+M or F2; newline: Ctrl+J."
356
357
  )
358
+ emit_system_message("Paste images: Ctrl+V (even on Mac!), F3, or /paste command.")
359
+ import platform
360
+
361
+ if platform.system() == "Darwin":
362
+ emit_system_message(
363
+ "💡 macOS tip: Use Ctrl+V (not Cmd+V) to paste images in terminal."
364
+ )
357
365
  cancel_key = get_cancel_agent_display_name()
358
366
  emit_system_message(
359
367
  f"Press {cancel_key} during processing to cancel the current task or inference. Use Ctrl+X to interrupt running shell commands."
@@ -567,6 +575,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
567
575
 
568
576
  # Check for clear command (supports both `clear` and `/clear`)
569
577
  if task.strip().lower() in ("clear", "/clear"):
578
+ from code_puppy.command_line.clipboard import get_clipboard_manager
570
579
  from code_puppy.messaging import (
571
580
  emit_info,
572
581
  emit_system_message,
@@ -579,6 +588,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
579
588
  emit_warning("Conversation history cleared!")
580
589
  emit_system_message("The agent will not remember previous interactions.")
581
590
  emit_info(f"Auto-save session rotated to: {new_session_id}")
591
+
592
+ # Also clear pending clipboard images
593
+ clipboard_manager = get_clipboard_manager()
594
+ clipboard_count = clipboard_manager.get_pending_count()
595
+ clipboard_manager.clear_pending()
596
+ if clipboard_count > 0:
597
+ emit_info(f"Cleared {clipboard_count} pending clipboard image(s)")
582
598
  continue
583
599
 
584
600
  # Parse attachments first so leading paths aren't misread as commands
@@ -679,8 +695,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
679
695
  save_command_to_history(task)
680
696
 
681
697
  try:
682
- prettier_code_blocks()
683
-
684
698
  # No need to get agent directly - use manager's run methods
685
699
 
686
700
  # Use our custom helper to enable attachment handling with spinner support
@@ -750,28 +764,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
750
764
  pass
751
765
 
752
766
 
753
- def prettier_code_blocks():
754
- """Configure Rich to use prettier code block rendering."""
755
-
756
- class SimpleCodeBlock(CodeBlock):
757
- def __rich_console__(
758
- self, console: Console, options: ConsoleOptions
759
- ) -> RenderResult:
760
- code = str(self.text).rstrip()
761
- yield Text(self.lexer_name, style="dim")
762
- syntax = Syntax(
763
- code,
764
- self.lexer_name,
765
- theme=self.theme,
766
- background_color="default",
767
- line_numbers=True,
768
- )
769
- yield syntax
770
- yield Text(f"/{self.lexer_name}", style="dim")
771
-
772
- Markdown.elements["fence"] = SimpleCodeBlock
773
-
774
-
775
767
  async def run_prompt_with_attachments(
776
768
  agent,
777
769
  raw_prompt: str,
@@ -785,6 +777,7 @@ async def run_prompt_with_attachments(
785
777
  tuple: (result, task) where result is the agent response and task is the asyncio task
786
778
  """
787
779
  import asyncio
780
+ import re
788
781
 
789
782
  from code_puppy.messaging import emit_system_message, emit_warning
790
783
 
@@ -793,21 +786,41 @@ async def run_prompt_with_attachments(
793
786
  for warning in processed_prompt.warnings:
794
787
  emit_warning(warning)
795
788
 
789
+ # Get clipboard images and merge with file attachments
790
+ clipboard_manager = get_clipboard_manager()
791
+ clipboard_images = clipboard_manager.get_pending_images()
792
+
793
+ # Clear pending clipboard images after retrieval
794
+ clipboard_manager.clear_pending()
795
+
796
+ # Build summary of all attachments
796
797
  summary_parts = []
797
798
  if processed_prompt.attachments:
798
- summary_parts.append(f"binary files: {len(processed_prompt.attachments)}")
799
+ summary_parts.append(f"files: {len(processed_prompt.attachments)}")
800
+ if clipboard_images:
801
+ summary_parts.append(f"clipboard images: {len(clipboard_images)}")
799
802
  if processed_prompt.link_attachments:
800
803
  summary_parts.append(f"urls: {len(processed_prompt.link_attachments)}")
801
804
  if summary_parts:
802
805
  emit_system_message("Attachments detected -> " + ", ".join(summary_parts))
803
806
 
804
- if not processed_prompt.prompt:
807
+ # Clean up clipboard placeholders from the prompt text
808
+ cleaned_prompt = processed_prompt.prompt
809
+ if clipboard_images and cleaned_prompt:
810
+ cleaned_prompt = re.sub(
811
+ r"\[📋 clipboard image \d+\]\s*", "", cleaned_prompt
812
+ ).strip()
813
+
814
+ if not cleaned_prompt:
805
815
  emit_warning(
806
816
  "Prompt is empty after removing attachments; add instructions and retry."
807
817
  )
808
818
  return None, None
809
819
 
820
+ # Combine file attachments with clipboard images
810
821
  attachments = [attachment.content for attachment in processed_prompt.attachments]
822
+ attachments.extend(clipboard_images) # Add clipboard images
823
+
811
824
  link_attachments = [link.url_part for link in processed_prompt.link_attachments]
812
825
 
813
826
  # IMPORTANT: Set the shared console on the agent so that streaming output
@@ -819,7 +832,7 @@ async def run_prompt_with_attachments(
819
832
  # Create the agent task first so we can track and cancel it
820
833
  agent_task = asyncio.create_task(
821
834
  agent.run_with_mcp(
822
- processed_prompt.prompt,
835
+ cleaned_prompt, # Use cleaned prompt (clipboard placeholders removed)
823
836
  attachments=attachments,
824
837
  link_attachments=link_attachments,
825
838
  )