code-puppy 0.0.380__tar.gz → 0.0.382__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 (233) hide show
  1. {code_puppy-0.0.380 → code_puppy-0.0.382}/PKG-INFO +1 -1
  2. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/callbacks.py +70 -0
  3. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/cli_runner.py +93 -1
  4. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/core_commands.py +61 -0
  5. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/motd.py +26 -3
  6. code_puppy-0.0.382/code_puppy/command_line/wiggum_state.py +78 -0
  7. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/config.py +3 -2
  8. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/model_factory.py +43 -0
  9. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_manager.py +66 -5
  10. {code_puppy-0.0.380 → code_puppy-0.0.382}/pyproject.toml +1 -1
  11. code_puppy-0.0.380/code_puppy/plugins/ralph/__init__.py +0 -13
  12. code_puppy-0.0.380/code_puppy/plugins/ralph/agents.py +0 -433
  13. code_puppy-0.0.380/code_puppy/plugins/ralph/commands.py +0 -208
  14. code_puppy-0.0.380/code_puppy/plugins/ralph/loop_controller.py +0 -289
  15. code_puppy-0.0.380/code_puppy/plugins/ralph/models.py +0 -125
  16. code_puppy-0.0.380/code_puppy/plugins/ralph/register_callbacks.py +0 -140
  17. code_puppy-0.0.380/code_puppy/plugins/ralph/state_manager.py +0 -322
  18. code_puppy-0.0.380/code_puppy/plugins/ralph/tools.py +0 -451
  19. {code_puppy-0.0.380 → code_puppy-0.0.382}/.gitignore +0 -0
  20. {code_puppy-0.0.380 → code_puppy-0.0.382}/LICENSE +0 -0
  21. {code_puppy-0.0.380 → code_puppy-0.0.382}/README.md +0 -0
  22. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/__init__.py +0 -0
  23. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/__main__.py +0 -0
  24. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/__init__.py +0 -0
  25. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_c_reviewer.py +0 -0
  26. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_code_puppy.py +0 -0
  27. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_code_reviewer.py +0 -0
  28. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  29. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_creator_agent.py +0 -0
  30. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  31. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_helios.py +0 -0
  32. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  33. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_manager.py +0 -0
  34. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_pack_leader.py +0 -0
  35. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_planning.py +0 -0
  36. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_python_programmer.py +0 -0
  37. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_python_reviewer.py +0 -0
  38. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_qa_expert.py +0 -0
  39. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_qa_kitten.py +0 -0
  40. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_security_auditor.py +0 -0
  41. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_terminal_qa.py +0 -0
  42. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  43. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/base_agent.py +0 -0
  44. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/event_stream_handler.py +0 -0
  45. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/json_agent.py +0 -0
  46. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/__init__.py +0 -0
  47. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/bloodhound.py +0 -0
  48. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/husky.py +0 -0
  49. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/retriever.py +0 -0
  50. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/shepherd.py +0 -0
  51. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/terrier.py +0 -0
  52. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/pack/watchdog.py +0 -0
  53. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/prompt_reviewer.py +0 -0
  54. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/agents/subagent_stream_handler.py +0 -0
  55. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/__init__.py +0 -0
  56. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/app.py +0 -0
  57. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/main.py +0 -0
  58. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/pty_manager.py +0 -0
  59. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/routers/__init__.py +0 -0
  60. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/routers/agents.py +0 -0
  61. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/routers/commands.py +0 -0
  62. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/routers/config.py +0 -0
  63. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/routers/sessions.py +0 -0
  64. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/templates/terminal.html +0 -0
  65. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/api/websocket.py +0 -0
  66. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/chatgpt_codex_client.py +0 -0
  67. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/claude_cache_client.py +0 -0
  68. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/__init__.py +0 -0
  69. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/add_model_menu.py +0 -0
  70. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/agent_menu.py +0 -0
  71. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/attachments.py +0 -0
  72. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/autosave_menu.py +0 -0
  73. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/clipboard.py +0 -0
  74. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/colors_menu.py +0 -0
  75. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/command_handler.py +0 -0
  76. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/command_registry.py +0 -0
  77. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/config_commands.py +0 -0
  78. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/diff_menu.py +0 -0
  79. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/file_path_completion.py +0 -0
  80. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/load_context_completion.py +0 -0
  81. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/__init__.py +0 -0
  82. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/base.py +0 -0
  83. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  84. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  85. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  86. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/edit_command.py +0 -0
  87. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/handler.py +0 -0
  88. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/help_command.py +0 -0
  89. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/install_command.py +0 -0
  90. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/install_menu.py +0 -0
  91. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/list_command.py +0 -0
  92. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/logs_command.py +0 -0
  93. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/remove_command.py +0 -0
  94. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/restart_command.py +0 -0
  95. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/search_command.py +0 -0
  96. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  97. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/start_command.py +0 -0
  98. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/status_command.py +0 -0
  99. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  100. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/stop_command.py +0 -0
  101. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/test_command.py +0 -0
  102. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/utils.py +0 -0
  103. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  104. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/mcp_completion.py +0 -0
  105. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/model_picker_completion.py +0 -0
  106. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/model_settings_menu.py +0 -0
  107. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/onboarding_slides.py +0 -0
  108. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/onboarding_wizard.py +0 -0
  109. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/pin_command_completion.py +0 -0
  110. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  111. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/session_commands.py +0 -0
  112. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/uc_menu.py +0 -0
  113. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/command_line/utils.py +0 -0
  114. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/error_logging.py +0 -0
  115. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/gemini_code_assist.py +0 -0
  116. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/gemini_model.py +0 -0
  117. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/http_utils.py +0 -0
  118. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/keymap.py +0 -0
  119. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/main.py +0 -0
  120. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/__init__.py +0 -0
  121. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/async_lifecycle.py +0 -0
  122. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/blocking_startup.py +0 -0
  123. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  124. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/circuit_breaker.py +0 -0
  125. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/config_wizard.py +0 -0
  126. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/dashboard.py +0 -0
  127. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/error_isolation.py +0 -0
  128. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/examples/retry_example.py +0 -0
  129. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/health_monitor.py +0 -0
  130. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/managed_server.py +0 -0
  131. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/manager.py +0 -0
  132. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/mcp_logs.py +0 -0
  133. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/registry.py +0 -0
  134. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/retry_manager.py +0 -0
  135. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  136. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/status_tracker.py +0 -0
  137. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/mcp_/system_tools.py +0 -0
  138. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/__init__.py +0 -0
  139. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/bus.py +0 -0
  140. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/commands.py +0 -0
  141. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/markdown_patches.py +0 -0
  142. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/message_queue.py +0 -0
  143. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/messages.py +0 -0
  144. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/queue_console.py +0 -0
  145. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/renderers.py +0 -0
  146. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/rich_renderer.py +0 -0
  147. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/spinner/__init__.py +0 -0
  148. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  149. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  150. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/messaging/subagent_console.py +0 -0
  151. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/model_switching.py +0 -0
  152. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/model_utils.py +0 -0
  153. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/models.json +0 -0
  154. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/models_dev_api.json +0 -0
  155. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/models_dev_parser.py +0 -0
  156. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/__init__.py +0 -0
  157. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  158. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  159. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
  160. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  161. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  162. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  163. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  164. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  165. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  166. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  167. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
  168. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  169. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  170. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  171. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  172. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  173. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  174. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  175. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  176. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  177. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  178. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  179. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  180. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  181. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +0 -0
  182. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  183. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  184. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  185. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/example_custom_command/README.md +0 -0
  186. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  187. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  188. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  189. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
  190. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
  191. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
  192. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  193. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  194. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  195. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  196. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  197. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/universal_constructor/__init__.py +0 -0
  198. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/universal_constructor/models.py +0 -0
  199. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/universal_constructor/register_callbacks.py +0 -0
  200. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/universal_constructor/registry.py +0 -0
  201. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/plugins/universal_constructor/sandbox.py +0 -0
  202. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
  203. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/pydantic_patches.py +0 -0
  204. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/reopenable_async_client.py +0 -0
  205. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/round_robin_model.py +0 -0
  206. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/session_storage.py +0 -0
  207. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/status_display.py +0 -0
  208. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/summarization_agent.py +0 -0
  209. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/terminal_utils.py +0 -0
  210. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/__init__.py +0 -0
  211. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/agent_tools.py +0 -0
  212. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/__init__.py +0 -0
  213. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_control.py +0 -0
  214. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_interactions.py +0 -0
  215. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_locators.py +0 -0
  216. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_navigation.py +0 -0
  217. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  218. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_scripts.py +0 -0
  219. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/browser_workflows.py +0 -0
  220. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/chromium_terminal_manager.py +0 -0
  221. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/terminal_command_tools.py +0 -0
  222. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/terminal_screenshot_tools.py +0 -0
  223. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/browser/terminal_tools.py +0 -0
  224. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/command_runner.py +0 -0
  225. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/common.py +0 -0
  226. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/display.py +0 -0
  227. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/file_modifications.py +0 -0
  228. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/file_operations.py +0 -0
  229. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/subagent_context.py +0 -0
  230. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/tools_content.py +0 -0
  231. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/tools/universal_constructor.py +0 -0
  232. {code_puppy-0.0.380 → code_puppy-0.0.382}/code_puppy/uvx_detection.py +0 -0
  233. {code_puppy-0.0.380 → code_puppy-0.0.382}/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.380
3
+ Version: 0.0.382
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
@@ -13,6 +13,7 @@ PhaseType = Literal[
13
13
  "delete_file",
14
14
  "run_shell_command",
15
15
  "load_model_config",
16
+ "load_models_config",
16
17
  "load_prompt",
17
18
  "agent_reload",
18
19
  "custom_command",
@@ -28,6 +29,9 @@ PhaseType = Literal[
28
29
  "agent_run_start",
29
30
  "agent_run_end",
30
31
  "register_mcp_catalog_servers",
32
+ "register_browser_types",
33
+ "get_motd",
34
+ "register_model_providers",
31
35
  ]
32
36
  CallbackFunc = Callable[..., Any]
33
37
 
@@ -41,6 +45,7 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
41
45
  "delete_file": [],
42
46
  "run_shell_command": [],
43
47
  "load_model_config": [],
48
+ "load_models_config": [],
44
49
  "load_prompt": [],
45
50
  "agent_reload": [],
46
51
  "custom_command": [],
@@ -56,6 +61,9 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
56
61
  "agent_run_start": [],
57
62
  "agent_run_end": [],
58
63
  "register_mcp_catalog_servers": [],
64
+ "register_browser_types": [],
65
+ "get_motd": [],
66
+ "register_model_providers": [],
59
67
  }
60
68
 
61
69
  logger = logging.getLogger(__name__)
@@ -206,6 +214,19 @@ def on_load_model_config(*args, **kwargs) -> List[Any]:
206
214
  return _trigger_callbacks_sync("load_model_config", *args, **kwargs)
207
215
 
208
216
 
217
+ def on_load_models_config() -> List[Any]:
218
+ """Trigger callbacks to load additional model configurations.
219
+
220
+ Plugins can register callbacks that return a dict of model configurations
221
+ to be merged with the built-in models.json. Plugin models override built-in
222
+ models with the same name.
223
+
224
+ Returns:
225
+ List of model config dicts from all registered callbacks.
226
+ """
227
+ return _trigger_callbacks_sync("load_models_config")
228
+
229
+
209
230
  def on_edit_file(*args, **kwargs) -> Any:
210
231
  return _trigger_callbacks_sync("edit_file", *args, **kwargs)
211
232
 
@@ -531,3 +552,52 @@ def on_register_mcp_catalog_servers() -> List[Any]:
531
552
  List of results from all registered callbacks (each should be a list of MCPServerTemplate).
532
553
  """
533
554
  return _trigger_callbacks_sync("register_mcp_catalog_servers")
555
+
556
+
557
+ def on_register_browser_types() -> List[Any]:
558
+ """Trigger callbacks to register custom browser types/providers.
559
+
560
+ Plugins can register callbacks that return a dict mapping browser type names
561
+ to initialization functions. This allows plugins to provide custom browser
562
+ implementations (like Camoufox for stealth browsing).
563
+
564
+ Each callback should return a dict with:
565
+ - key: str - the browser type name (e.g., "camoufox", "firefox-stealth")
566
+ - value: callable - async initialization function that takes (manager, **kwargs)
567
+ and sets up the browser on the manager instance
568
+
569
+ Example callback:
570
+ def register_my_browser_types():
571
+ return {
572
+ "camoufox": initialize_camoufox,
573
+ "my-stealth-browser": initialize_my_stealth,
574
+ }
575
+
576
+ Returns:
577
+ List of dicts from all registered callbacks.
578
+ """
579
+ return _trigger_callbacks_sync("register_browser_types")
580
+
581
+
582
+ def on_get_motd() -> List[Any]:
583
+ """Trigger callbacks to get custom MOTD content.
584
+
585
+ Plugins can register callbacks that return a tuple of (message, version).
586
+ The last non-None result will be used as the MOTD.
587
+
588
+ Returns:
589
+ List of (message, version) tuples from registered callbacks.
590
+ """
591
+ return _trigger_callbacks_sync("get_motd")
592
+
593
+
594
+ def on_register_model_providers() -> List[Any]:
595
+ """Trigger callbacks to register custom model provider classes.
596
+
597
+ Plugins can register callbacks that return a dict mapping provider names
598
+ to model classes. Example: {"walmart_gemini": WalmartGeminiModel}
599
+
600
+ Returns:
601
+ List of dicts from all registered callbacks.
602
+ """
603
+ return _trigger_callbacks_sync("register_model_providers")
@@ -551,7 +551,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
551
551
  reset_windows_terminal_full()
552
552
  from code_puppy.messaging import emit_warning
553
553
 
554
- emit_warning("\nInput cancelled")
554
+ # Stop wiggum mode on Ctrl+C
555
+ from code_puppy.command_line.wiggum_state import is_wiggum_active, stop_wiggum
556
+ if is_wiggum_active():
557
+ stop_wiggum()
558
+ emit_warning("\n🍩 Wiggum loop stopped!")
559
+ else:
560
+ emit_warning("\nInput cancelled")
555
561
  continue
556
562
 
557
563
  # Check for exit commands (plain text or command form)
@@ -718,6 +724,12 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
718
724
  ensure_ctrl_c_disabled()
719
725
  except ImportError:
720
726
  pass
727
+ # Stop wiggum mode on cancellation
728
+ from code_puppy.command_line.wiggum_state import is_wiggum_active, stop_wiggum
729
+ if is_wiggum_active():
730
+ stop_wiggum()
731
+ from code_puppy.messaging import emit_warning
732
+ emit_warning("🍩 Wiggum loop stopped due to cancellation")
721
733
  continue
722
734
  # Get the structured response
723
735
  agent_response = result.output
@@ -758,6 +770,86 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
758
770
 
759
771
  auto_save_session_if_enabled()
760
772
 
773
+ # ================================================================
774
+ # WIGGUM LOOP: Re-run prompt if wiggum mode is active
775
+ # ================================================================
776
+ from code_puppy.command_line.wiggum_state import (
777
+ get_wiggum_prompt,
778
+ get_wiggum_count,
779
+ increment_wiggum_count,
780
+ is_wiggum_active,
781
+ stop_wiggum,
782
+ )
783
+
784
+ while is_wiggum_active():
785
+ wiggum_prompt = get_wiggum_prompt()
786
+ if not wiggum_prompt:
787
+ stop_wiggum()
788
+ break
789
+
790
+ # Increment and show debug message
791
+ loop_num = increment_wiggum_count()
792
+ from code_puppy.messaging import emit_warning, emit_system_message
793
+
794
+ emit_warning(f"\n🍩 WIGGUM RELOOPING! (Loop #{loop_num})")
795
+ emit_system_message(f"Re-running prompt: {wiggum_prompt}")
796
+
797
+ # Reset context/history for fresh start
798
+ new_session_id = finalize_autosave_session()
799
+ current_agent.clear_message_history()
800
+ emit_system_message(f"Context cleared. Session rotated to: {new_session_id}")
801
+
802
+ # Small delay to let user see the debug message
803
+ import time
804
+ time.sleep(0.5)
805
+
806
+ try:
807
+ # Re-run the wiggum prompt
808
+ result, current_agent_task = await run_prompt_with_attachments(
809
+ current_agent,
810
+ wiggum_prompt,
811
+ spinner_console=message_renderer.console,
812
+ )
813
+
814
+ if result is None:
815
+ # Cancelled - stop wiggum mode
816
+ emit_warning("Wiggum loop cancelled by user")
817
+ stop_wiggum()
818
+ break
819
+
820
+ # Get the structured response
821
+ agent_response = result.output
822
+
823
+ # Emit structured message for proper markdown rendering
824
+ response_msg = AgentResponseMessage(
825
+ content=agent_response,
826
+ is_markdown=True,
827
+ )
828
+ get_message_bus().emit(response_msg)
829
+
830
+ # Update message history
831
+ if hasattr(result, "all_messages"):
832
+ current_agent.set_message_history(list(result.all_messages()))
833
+
834
+ # Flush console
835
+ display_console.file.flush() if hasattr(
836
+ display_console.file, "flush"
837
+ ) else None
838
+ time.sleep(0.1)
839
+
840
+ # Auto-save
841
+ auto_save_session_if_enabled()
842
+
843
+ except KeyboardInterrupt:
844
+ emit_warning("\n🍩 Wiggum loop interrupted by Ctrl+C")
845
+ stop_wiggum()
846
+ break
847
+ except Exception as e:
848
+ from code_puppy.messaging import emit_error
849
+ emit_error(f"Wiggum loop error: {e}")
850
+ stop_wiggum()
851
+ break
852
+
761
853
  # Re-disable Ctrl+C if needed (uvx mode) - must be done after
762
854
  # each iteration as various operations may restore console mode
763
855
  try:
@@ -790,3 +790,64 @@ def handle_generate_pr_description_command(command: str) -> str:
790
790
 
791
791
  # Return the prompt to be processed by the main chat system
792
792
  return pr_prompt
793
+
794
+
795
+ @register_command(
796
+ name="wiggum",
797
+ description="Loop mode: re-run the same prompt when agent finishes (like Wiggum chasing donuts 🍩)",
798
+ usage="/wiggum <prompt>",
799
+ category="core",
800
+ )
801
+ def handle_wiggum_command(command: str) -> str | bool:
802
+ """Start wiggum loop mode.
803
+
804
+ When active, the agent will automatically re-run the same prompt
805
+ after completing, resetting context each time. Use Ctrl+C to stop.
806
+
807
+ Example:
808
+ /wiggum say hello world
809
+ """
810
+ from code_puppy.command_line.wiggum_state import start_wiggum, stop_wiggum
811
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
812
+
813
+ # Extract the prompt after /wiggum
814
+ parts = command.split(maxsplit=1)
815
+ if len(parts) < 2 or not parts[1].strip():
816
+ emit_warning("Usage: /wiggum <prompt>")
817
+ emit_info("Example: /wiggum say hello world")
818
+ emit_info("This will repeatedly run 'say hello world' after each completion.")
819
+ emit_info("Press Ctrl+C to stop the loop.")
820
+ return True
821
+
822
+ prompt = parts[1].strip()
823
+
824
+ # Start wiggum mode
825
+ start_wiggum(prompt)
826
+ emit_success(f"🍩 WIGGUM MODE ACTIVATED!")
827
+ emit_info(f"Prompt: {prompt}")
828
+ emit_info("The agent will re-loop this prompt after each completion.")
829
+ emit_info("Press Ctrl+C to stop the wiggum loop.")
830
+
831
+ # Return the prompt to execute immediately
832
+ return prompt
833
+
834
+
835
+ @register_command(
836
+ name="wiggum_stop",
837
+ description="Stop wiggum loop mode",
838
+ usage="/wiggum_stop",
839
+ aliases=["stopwiggum", "ws"],
840
+ category="core",
841
+ )
842
+ def handle_wiggum_stop_command(command: str) -> bool:
843
+ """Stop wiggum loop mode."""
844
+ from code_puppy.command_line.wiggum_state import is_wiggum_active, stop_wiggum
845
+ from code_puppy.messaging import emit_info, emit_success
846
+
847
+ if is_wiggum_active():
848
+ stop_wiggum()
849
+ emit_success("🍩 Wiggum mode stopped!")
850
+ else:
851
+ emit_info("Wiggum mode is not active.")
852
+
853
+ return True
@@ -25,6 +25,28 @@ Reminder that Code Puppy supports three different OAuth subscriptions:
25
25
  MOTD_TRACK_FILE = os.path.join(CONFIG_DIR, "motd.txt")
26
26
 
27
27
 
28
+ def get_motd_content() -> tuple[str, str]:
29
+ """Get MOTD content, checking plugins first.
30
+
31
+ Returns:
32
+ Tuple of (message, version) - either from plugin or built-in.
33
+ """
34
+ # Check if plugins want to override MOTD
35
+ try:
36
+ from code_puppy.callbacks import on_get_motd
37
+
38
+ results = on_get_motd()
39
+ # Use the last non-None result
40
+ for result in reversed(results):
41
+ if result is not None and isinstance(result, tuple) and len(result) == 2:
42
+ return result
43
+ except Exception:
44
+ pass
45
+
46
+ # Fall back to built-in MOTD
47
+ return (MOTD_MESSAGE, MOTD_VERSION)
48
+
49
+
28
50
  def has_seen_motd(version: str) -> bool: # 🐕 Check if puppy has seen this MOTD!
29
51
  if not os.path.exists(MOTD_TRACK_FILE):
30
52
  return False
@@ -62,12 +84,13 @@ def print_motd(
62
84
  Returns:
63
85
  True if the MOTD was printed, False otherwise 🐾
64
86
  """
65
- if force or not has_seen_motd(MOTD_VERSION):
87
+ message, version = get_motd_content()
88
+ if force or not has_seen_motd(version):
66
89
  # Create a Rich Markdown object for proper rendering 🎨🐶
67
90
  from rich.markdown import Markdown
68
91
 
69
- markdown_content = Markdown(MOTD_MESSAGE)
92
+ markdown_content = Markdown(message)
70
93
  emit_info(markdown_content)
71
- mark_motd_seen(MOTD_VERSION)
94
+ mark_motd_seen(version)
72
95
  return True
73
96
  return False
@@ -0,0 +1,78 @@
1
+ """Wiggum loop state management.
2
+
3
+ This module tracks the state for the /wiggum command, which causes
4
+ the agent to automatically re-run the same prompt after completing,
5
+ like Chief Wiggum chasing donuts in circles. 🍩
6
+
7
+ Usage:
8
+ /wiggum <prompt> - Start looping with the given prompt
9
+ Ctrl+C - Stop the wiggum loop
10
+ """
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional
14
+
15
+
16
+ @dataclass
17
+ class WiggumState:
18
+ """State container for wiggum loop mode."""
19
+
20
+ active: bool = False
21
+ prompt: Optional[str] = None
22
+ loop_count: int = 0
23
+
24
+ def start(self, prompt: str) -> None:
25
+ """Start wiggum mode with the given prompt."""
26
+ self.active = True
27
+ self.prompt = prompt
28
+ self.loop_count = 0
29
+
30
+ def stop(self) -> None:
31
+ """Stop wiggum mode."""
32
+ self.active = False
33
+ self.prompt = None
34
+ self.loop_count = 0
35
+
36
+ def increment(self) -> int:
37
+ """Increment and return the loop count."""
38
+ self.loop_count += 1
39
+ return self.loop_count
40
+
41
+
42
+ # Global singleton for wiggum state
43
+ _wiggum_state = WiggumState()
44
+
45
+
46
+ def get_wiggum_state() -> WiggumState:
47
+ """Get the global wiggum state."""
48
+ return _wiggum_state
49
+
50
+
51
+ def is_wiggum_active() -> bool:
52
+ """Check if wiggum mode is currently active."""
53
+ return _wiggum_state.active
54
+
55
+
56
+ def get_wiggum_prompt() -> Optional[str]:
57
+ """Get the current wiggum prompt, if active."""
58
+ return _wiggum_state.prompt if _wiggum_state.active else None
59
+
60
+
61
+ def start_wiggum(prompt: str) -> None:
62
+ """Start wiggum mode with the given prompt."""
63
+ _wiggum_state.start(prompt)
64
+
65
+
66
+ def stop_wiggum() -> None:
67
+ """Stop wiggum mode."""
68
+ _wiggum_state.stop()
69
+
70
+
71
+ def increment_wiggum_count() -> int:
72
+ """Increment wiggum loop count and return the new value."""
73
+ return _wiggum_state.increment()
74
+
75
+
76
+ def get_wiggum_count() -> int:
77
+ """Get the current wiggum loop count."""
78
+ return _wiggum_state.loop_count
@@ -715,8 +715,9 @@ def set_model_setting(model_name: str, setting: str, value: Optional[float]) ->
715
715
  if value is None:
716
716
  set_config_value(key, "")
717
717
  elif isinstance(value, float):
718
- # Round floats to nearest tenth to avoid floating point weirdness
719
- set_config_value(key, str(round(value, 1)))
718
+ # Round floats to nearest hundredth to avoid floating point weirdness
719
+ # (allows 0.05 step increments for temperature/top_p)
720
+ set_config_value(key, str(round(value, 2)))
720
721
  else:
721
722
  set_config_value(key, str(value))
722
723
 
@@ -30,6 +30,26 @@ from .round_robin_model import RoundRobinModel
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
33
+ # Registry for custom model provider classes from plugins
34
+ _CUSTOM_MODEL_PROVIDERS: Dict[str, type] = {}
35
+
36
+
37
+ def _load_plugin_model_providers():
38
+ """Load custom model providers from plugins."""
39
+ global _CUSTOM_MODEL_PROVIDERS
40
+ try:
41
+ from code_puppy.callbacks import on_register_model_providers
42
+ results = on_register_model_providers()
43
+ for result in results:
44
+ if isinstance(result, dict):
45
+ _CUSTOM_MODEL_PROVIDERS.update(result)
46
+ except Exception:
47
+ pass # Don't break if plugins fail
48
+
49
+
50
+ # Load plugin model providers at module initialization
51
+ _load_plugin_model_providers()
52
+
33
53
 
34
54
  def get_api_key(env_var_name: str) -> str | None:
35
55
  """Get an API key from config first, then fall back to environment variable.
@@ -279,6 +299,20 @@ class ModelFactory:
279
299
  logging.getLogger(__name__).warning(
280
300
  f"Failed to load {label} config from {source_path}: {exc}"
281
301
  )
302
+
303
+ # Let plugins add/override models via load_models_config hook
304
+ try:
305
+ from code_puppy.callbacks import on_load_models_config
306
+
307
+ results = on_load_models_config()
308
+ for result in results:
309
+ if isinstance(result, dict):
310
+ config.update(result) # Plugin models override built-in
311
+ except Exception as exc:
312
+ logging.getLogger(__name__).debug(
313
+ f"Failed to load plugin models config: {exc}"
314
+ )
315
+
282
316
  return config
283
317
 
284
318
  @staticmethod
@@ -294,6 +328,15 @@ class ModelFactory:
294
328
 
295
329
  model_type = model_config.get("type")
296
330
 
331
+ # Check for plugin-registered model provider classes first
332
+ if model_type in _CUSTOM_MODEL_PROVIDERS:
333
+ provider_class = _CUSTOM_MODEL_PROVIDERS[model_type]
334
+ try:
335
+ return provider_class(model_name=model_name, model_config=model_config, config=config)
336
+ except Exception as e:
337
+ logger.error(f"Custom model provider '{model_type}' failed: {e}")
338
+ return None
339
+
297
340
  if model_type == "gemini":
298
341
  api_key = get_api_key("GEMINI_API_KEY")
299
342
  if not api_key:
@@ -8,13 +8,41 @@ import atexit
8
8
  import contextvars
9
9
  import os
10
10
  from pathlib import Path
11
- from typing import Optional
11
+ from typing import Callable, Dict, Optional
12
12
 
13
13
  from playwright.async_api import Browser, BrowserContext, Page
14
14
 
15
15
  from code_puppy import config
16
16
  from code_puppy.messaging import emit_info, emit_success, emit_warning
17
17
 
18
+ # Registry for custom browser types from plugins (e.g., Camoufox for stealth browsing)
19
+ _CUSTOM_BROWSER_TYPES: Dict[str, Callable] = {}
20
+ _BROWSER_TYPES_LOADED: bool = False
21
+
22
+
23
+ def _load_plugin_browser_types() -> None:
24
+ """Load custom browser types from plugins.
25
+
26
+ This is called lazily on first browser initialization to allow plugins
27
+ to register custom browser providers (like Camoufox for stealth browsing).
28
+ """
29
+ global _CUSTOM_BROWSER_TYPES, _BROWSER_TYPES_LOADED
30
+
31
+ if _BROWSER_TYPES_LOADED:
32
+ return
33
+
34
+ _BROWSER_TYPES_LOADED = True
35
+
36
+ try:
37
+ from code_puppy.callbacks import on_register_browser_types
38
+
39
+ results = on_register_browser_types()
40
+ for result in results:
41
+ if isinstance(result, dict):
42
+ _CUSTOM_BROWSER_TYPES.update(result)
43
+ except Exception:
44
+ pass # Don't break if plugins fail to load
45
+
18
46
  # Store active manager instances by session ID
19
47
  _active_managers: dict[str, "BrowserManager"] = {}
20
48
 
@@ -78,14 +106,19 @@ class BrowserManager:
78
106
  _context: Optional[BrowserContext] = None
79
107
  _initialized: bool = False
80
108
 
81
- def __init__(self, session_id: Optional[str] = None):
109
+ def __init__(
110
+ self, session_id: Optional[str] = None, browser_type: Optional[str] = None
111
+ ):
82
112
  """Initialize manager settings.
83
113
 
84
114
  Args:
85
115
  session_id: Optional session ID for this instance.
86
116
  If None, uses 'default' as the session ID.
117
+ browser_type: Optional browser type to use. If None, uses Chromium.
118
+ Custom types can be registered via the register_browser_types hook.
87
119
  """
88
120
  self.session_id = session_id or "default"
121
+ self.browser_type = browser_type # None means default Chromium
89
122
 
90
123
  # Default to headless=True (no browser spam during tests)
91
124
  # Override with BROWSER_HEADLESS=false to see the browser
@@ -124,7 +157,27 @@ class BrowserManager:
124
157
  raise
125
158
 
126
159
  async def _initialize_browser(self) -> None:
127
- """Initialize Playwright Chromium browser with persistent context."""
160
+ """Initialize browser with persistent context.
161
+
162
+ Checks for custom browser types registered via plugins first,
163
+ then falls back to default Playwright Chromium.
164
+ """
165
+ # Load plugin browser types on first initialization
166
+ _load_plugin_browser_types()
167
+
168
+ # Check if a custom browser type was requested and is available
169
+ if self.browser_type and self.browser_type in _CUSTOM_BROWSER_TYPES:
170
+ emit_info(
171
+ f"Using custom browser type '{self.browser_type}' "
172
+ f"(session: {self.session_id})"
173
+ )
174
+ init_func = _CUSTOM_BROWSER_TYPES[self.browser_type]
175
+ # Custom init functions should set self._context and self._browser
176
+ await init_func(self)
177
+ self._initialized = True
178
+ return
179
+
180
+ # Default: use Playwright Chromium
128
181
  from playwright.async_api import async_playwright
129
182
 
130
183
  emit_info(f"Using persistent profile: {self.profile_dir}")
@@ -220,13 +273,18 @@ class BrowserManager:
220
273
  emit_info(f"Browser closed (session: {self.session_id})")
221
274
 
222
275
 
223
- def get_browser_manager(session_id: Optional[str] = None) -> BrowserManager:
276
+ def get_browser_manager(
277
+ session_id: Optional[str] = None, browser_type: Optional[str] = None
278
+ ) -> BrowserManager:
224
279
  """Get or create a BrowserManager instance.
225
280
 
226
281
  Args:
227
282
  session_id: Optional session ID. If provided and a manager with this
228
283
  session exists, returns that manager. Otherwise creates a new one.
229
284
  If None, uses 'default' as the session ID.
285
+ browser_type: Optional browser type to use for new managers.
286
+ Ignored if a manager for this session already exists.
287
+ Custom types can be registered via the register_browser_types hook.
230
288
 
231
289
  Returns:
232
290
  A BrowserManager instance.
@@ -237,11 +295,14 @@ def get_browser_manager(session_id: Optional[str] = None) -> BrowserManager:
237
295
 
238
296
  # Named session (for multi-agent use)
239
297
  manager = get_browser_manager("qa-agent-1")
298
+
299
+ # Custom browser type (e.g., stealth browser from plugin)
300
+ manager = get_browser_manager("stealth-session", browser_type="camoufox")
240
301
  """
241
302
  session_id = session_id or "default"
242
303
 
243
304
  if session_id not in _active_managers:
244
- _active_managers[session_id] = BrowserManager(session_id)
305
+ _active_managers[session_id] = BrowserManager(session_id, browser_type)
245
306
 
246
307
  return _active_managers[session_id]
247
308
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.380"
7
+ version = "0.0.382"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -1,13 +0,0 @@
1
- """Ralph Plugin - Autonomous AI agent loop for completing PRDs.
2
-
3
- Based on Geoffrey Huntley's Ralph pattern: https://ghuntley.com/ralph/
4
-
5
- This plugin provides:
6
- - PRD Generator agent for creating detailed requirements
7
- - Ralph Converter agent for converting PRDs to JSON format
8
- - Ralph Orchestrator agent for autonomous execution
9
- - Tools for managing prd.json and progress.txt
10
- - /ralph commands for controlling the workflow
11
- """
12
-
13
- __version__ = "0.1.0"