code-puppy 0.0.749__tar.gz → 0.0.751__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 (264) hide show
  1. {code_puppy-0.0.749 → code_puppy-0.0.751}/PKG-INFO +2 -2
  2. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_builder.py +5 -1
  3. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_runtime.py +14 -0
  4. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/base_agent.py +16 -0
  5. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/json_agent.py +7 -0
  6. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/callbacks.py +46 -0
  7. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/cli_runner.py +6 -1
  8. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_switching.py +55 -1
  9. code_puppy-0.0.751/code_puppy/platform_utils.py +42 -0
  10. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/splash.py +73 -37
  11. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_invocation.py +5 -0
  12. {code_puppy-0.0.749 → code_puppy-0.0.751}/pyproject.toml +2 -2
  13. code_puppy-0.0.749/code_puppy/platform_utils.py +0 -27
  14. {code_puppy-0.0.749 → code_puppy-0.0.751}/.gitignore +0 -0
  15. {code_puppy-0.0.749 → code_puppy-0.0.751}/LICENSE +0 -0
  16. {code_puppy-0.0.749 → code_puppy-0.0.751}/README.md +0 -0
  17. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/__init__.py +0 -0
  18. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/__main__.py +0 -0
  19. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agent_execution_context.py +0 -0
  20. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/__init__.py +0 -0
  21. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_compaction.py +0 -0
  22. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_diagnostics.py +0 -0
  23. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_history.py +0 -0
  24. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_key_listeners.py +0 -0
  25. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_non_streaming_render.py +0 -0
  26. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_output_limits.py +0 -0
  27. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_run_signals.py +0 -0
  28. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_steer_processor.py +0 -0
  29. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_code_puppy.py +0 -0
  30. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_creator_agent.py +0 -0
  31. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_helios.py +0 -0
  32. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_manager.py +0 -0
  33. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_model_judge.py +0 -0
  34. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_planning.py +0 -0
  35. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_qa_kitten.py +0 -0
  36. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_web_retriever.py +0 -0
  37. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/event_stream_handler.py +0 -0
  38. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/retry_profiles.py +0 -0
  39. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/run_stats.py +0 -0
  40. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/smooth_stream.py +0 -0
  41. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/subagent_stream_handler.py +0 -0
  42. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/atomic_io.py +0 -0
  43. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/atomic_json.py +0 -0
  44. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/chatgpt_codex_client.py +0 -0
  45. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/claude_cache_client.py +0 -0
  46. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/__init__.py +0 -0
  47. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/add_model_menu.py +0 -0
  48. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/agent_menu.py +0 -0
  49. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/attachments.py +0 -0
  50. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/autosave_menu.py +0 -0
  51. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/autosave_search.py +0 -0
  52. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/clipboard.py +0 -0
  53. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/colors_menu.py +0 -0
  54. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/command_handler.py +0 -0
  55. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/command_registry.py +0 -0
  56. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/config_apply.py +0 -0
  57. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/config_commands.py +0 -0
  58. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/core_commands.py +0 -0
  59. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/diff_menu.py +0 -0
  60. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/file_index.py +0 -0
  61. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/file_path_completion.py +0 -0
  62. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/image_utils.py +0 -0
  63. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/load_context_completion.py +0 -0
  64. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/__init__.py +0 -0
  65. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/base.py +0 -0
  66. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  67. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  68. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  69. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/edit_command.py +0 -0
  70. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/handler.py +0 -0
  71. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/help_command.py +0 -0
  72. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/install_command.py +0 -0
  73. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/install_menu.py +0 -0
  74. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/list_command.py +0 -0
  75. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/logs_command.py +0 -0
  76. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
  77. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/remove_command.py +0 -0
  78. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/restart_command.py +0 -0
  79. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/search_command.py +0 -0
  80. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
  81. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  82. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/start_command.py +0 -0
  83. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/status_command.py +0 -0
  84. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  85. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/stop_command.py +0 -0
  86. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/trust_command.py +0 -0
  87. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/utils.py +0 -0
  88. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  89. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp_binding_menu.py +0 -0
  90. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp_completion.py +0 -0
  91. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/model_picker_completion.py +0 -0
  92. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/model_settings_menu.py +0 -0
  93. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/onboarding_slides.py +0 -0
  94. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/onboarding_wizard.py +0 -0
  95. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/pagination.py +0 -0
  96. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/pin_command_completion.py +0 -0
  97. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  98. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/session_commands.py +0 -0
  99. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu.py +0 -0
  100. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_catalog.py +0 -0
  101. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_render.py +0 -0
  102. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_schema.py +0 -0
  103. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_settings.py +0 -0
  104. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_shims.py +0 -0
  105. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_values.py +0 -0
  106. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/shell_passthrough.py +0 -0
  107. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/skills_completion.py +0 -0
  108. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/uc_menu.py +0 -0
  109. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/utils.py +0 -0
  110. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/config.py +0 -0
  111. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/config_file.py +0 -0
  112. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/error_logging.py +0 -0
  113. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/gemini_code_assist.py +0 -0
  114. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/gemini_model.py +0 -0
  115. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/README.md +0 -0
  116. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/__init__.py +0 -0
  117. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/aliases.py +0 -0
  118. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/engine.py +0 -0
  119. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/executor.py +0 -0
  120. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/matcher.py +0 -0
  121. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/models.py +0 -0
  122. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/registry.py +0 -0
  123. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/validator.py +0 -0
  124. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/http_utils.py +0 -0
  125. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/__init__.py +0 -0
  126. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/audit.py +0 -0
  127. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/catalog.py +0 -0
  128. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/formats.py +0 -0
  129. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locale.py +0 -0
  130. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/en-US.json +0 -0
  131. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/es.json +0 -0
  132. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/fr-CA.json +0 -0
  133. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/plurals.py +0 -0
  134. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/pseudo.py +0 -0
  135. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/translate.py +0 -0
  136. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/kennel_provider.py +0 -0
  137. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/keymap.py +0 -0
  138. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/list_filtering.py +0 -0
  139. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/main.py +0 -0
  140. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/__init__.py +0 -0
  141. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/agent_bindings.py +0 -0
  142. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/async_lifecycle.py +0 -0
  143. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/blocking_startup.py +0 -0
  144. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/circuit_breaker.py +0 -0
  145. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/config_wizard.py +0 -0
  146. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/dashboard.py +0 -0
  147. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/error_isolation.py +0 -0
  148. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/examples/retry_example.py +0 -0
  149. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/health_monitor.py +0 -0
  150. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/managed_server.py +0 -0
  151. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/manager.py +0 -0
  152. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/mcp_logs.py +0 -0
  153. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/project_config.py +0 -0
  154. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/registry.py +0 -0
  155. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/retry_manager.py +0 -0
  156. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  157. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/status_tracker.py +0 -0
  158. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/system_tools.py +0 -0
  159. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
  160. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/toolset_utils.py +0 -0
  161. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_prompts/__init__.py +0 -0
  162. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_prompts/hook_creator.py +0 -0
  163. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/__init__.py +0 -0
  164. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bar_painters.py +0 -0
  165. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bar_rendering.py +0 -0
  166. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bottom_bar.py +0 -0
  167. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bus.py +0 -0
  168. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/chords.py +0 -0
  169. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/commands.py +0 -0
  170. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_actions.py +0 -0
  171. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_completion.py +0 -0
  172. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_display.py +0 -0
  173. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_history.py +0 -0
  174. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_keys.py +0 -0
  175. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_paste.py +0 -0
  176. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/external_editor.py +0 -0
  177. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/inline_bar.py +0 -0
  178. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/line_editor.py +0 -0
  179. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/markdown_patches.py +0 -0
  180. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/message_queue.py +0 -0
  181. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/messages.py +0 -0
  182. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/pause_controller.py +0 -0
  183. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/prompt_prefix_style.py +0 -0
  184. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/queue_console.py +0 -0
  185. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/renderers.py +0 -0
  186. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/rich_renderer.py +0 -0
  187. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/run_ui.py +0 -0
  188. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/run_ui_wiring.py +0 -0
  189. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/spinner/__init__.py +0 -0
  190. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/subagent_console.py +0 -0
  191. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/transcript_guard.py +0 -0
  192. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_descriptions.py +0 -0
  193. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_factory.py +0 -0
  194. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_utils.py +0 -0
  195. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models.json +0 -0
  196. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models_dev_api.json +0 -0
  197. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models_dev_parser.py +0 -0
  198. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/observability.py +0 -0
  199. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/__init__.py +0 -0
  200. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/config.py +0 -0
  201. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/oauth_pasteback.py +0 -0
  202. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  203. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/trust.py +0 -0
  204. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/trust_notice.py +0 -0
  205. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/provider_credentials.py +0 -0
  206. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/provider_identity.py +0 -0
  207. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/pydantic_patches.py +0 -0
  208. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/reopenable_async_client.py +0 -0
  209. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/round_robin_model.py +0 -0
  210. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/secret_store.py +0 -0
  211. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/secret_store_backends.py +0 -0
  212. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_format_migration.py +0 -0
  213. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_lifecycle.py +0 -0
  214. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_migration.py +0 -0
  215. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_storage.py +0 -0
  216. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_surrogate_unpickler.py +0 -0
  217. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/skill_provider.py +0 -0
  218. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/status_display.py +0 -0
  219. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/terminal_utils.py +0 -0
  220. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/token_usage.py +0 -0
  221. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/__init__.py +0 -0
  222. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/agent_tools.py +0 -0
  223. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/__init__.py +0 -0
  224. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/constants.py +0 -0
  225. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
  226. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/handler.py +0 -0
  227. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/models.py +0 -0
  228. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/registration.py +0 -0
  229. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/renderers.py +0 -0
  230. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
  231. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/theme.py +0 -0
  232. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
  233. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/__init__.py +0 -0
  234. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_control.py +0 -0
  235. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_interactions.py +0 -0
  236. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
  237. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_locators.py +0 -0
  238. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_manager.py +0 -0
  239. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_navigation.py +0 -0
  240. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
  241. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  242. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_scripts.py +0 -0
  243. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
  244. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_workflows.py +0 -0
  245. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/tool_registry.py +0 -0
  246. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/command_runner.py +0 -0
  247. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/common.py +0 -0
  248. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/display.py +0 -0
  249. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_modifications.py +0 -0
  250. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_operations.py +0 -0
  251. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_permission_state.py +0 -0
  252. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/fs_access.py +0 -0
  253. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/image_tools.py +0 -0
  254. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/io_backends.py +0 -0
  255. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/model_tools.py +0 -0
  256. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/shell_backgrounding.py +0 -0
  257. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/skills_tools.py +0 -0
  258. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_context.py +0 -0
  259. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_usage_metrics.py +0 -0
  260. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/tools_content.py +0 -0
  261. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/universal_constructor.py +0 -0
  262. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/undo_manager.py +0 -0
  263. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/universal_constructor_provider.py +0 -0
  264. {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code-puppy
3
- Version: 0.0.749
3
+ Version: 0.0.751
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
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Software Development :: Code Generators
17
17
  Requires-Python: <3.15,>=3.11
18
18
  Requires-Dist: agent-client-protocol<0.12,>=0.11.0
19
- Requires-Dist: anthropic>=0.108.0
19
+ Requires-Dist: anthropic<1,>=0.108.0
20
20
  Requires-Dist: anyio>=4.7.0
21
21
  Requires-Dist: azure-identity>=1.15.0
22
22
  Requires-Dist: boto3>=1.43.9
@@ -637,10 +637,15 @@ def build_pydantic_agent(
637
637
  model_settings = make_model_settings(resolved_model_name)
638
638
  history_processor = make_history_processor(agent)
639
639
  steer_processor = make_steer_history_processor(agent)
640
+ logical_agent_name = getattr(agent, "name", None) or agent.__class__.__name__
640
641
 
641
642
  def _new_pydantic_agent(toolsets: List[Any]) -> PydanticAgent:
642
643
  return PydanticAgent(
643
644
  model=model,
645
+ # Explicit name: without it pydantic-ai infers one from the
646
+ # caller's frame variables, so observability spans read
647
+ # "invoke_agent pydantic_agent" instead of the logical agent name.
648
+ name=logical_agent_name,
644
649
  instructions=instructions,
645
650
  output_type=output_type,
646
651
  retries=3,
@@ -667,7 +672,6 @@ def build_pydantic_agent(
667
672
  # tool registry actually produced, and filter MCP to avoid name clashes.
668
673
  probe_agent = _new_pydantic_agent(toolsets=[])
669
674
  agent_tools = agent.get_available_tools()
670
- logical_agent_name = getattr(agent, "name", None) or agent.__class__.__name__
671
675
  register_tools_for_agent(
672
676
  probe_agent,
673
677
  agent_tools,
@@ -693,6 +693,20 @@ async def _run_with_mcp_impl(
693
693
  # Hook failures must never block the run.
694
694
  pass
695
695
 
696
+ # Let a ``model_select`` hook route THIS turn to a different model (e.g.
697
+ # small-vs-large by complexity) before the pydantic agent is built. This
698
+ # resets any prior turn's auto choice, respects an explicit runtime
699
+ # override, and invalidates the cached agent when the model changes so the
700
+ # build below picks it up. No-op (and near-zero cost) if no plugin
701
+ # registered the hook.
702
+ try:
703
+ from code_puppy.model_switching import resolve_run_model_selection
704
+
705
+ resolve_run_model_selection(agent, prompt, agent._message_history, group_id)
706
+ except Exception:
707
+ # Selection must never block a run.
708
+ pass
709
+
696
710
  if agent._code_generation_agent is None:
697
711
  build_pydantic_agent(agent)
698
712
  pydantic_agent = agent._code_generation_agent
@@ -72,6 +72,11 @@ class BaseAgent(ABC):
72
72
  self._last_model_name: Optional[str] = None
73
73
  self._runtime_model_name_override: Optional[str] = None
74
74
  self._runtime_system_prompt_additions: List[str] = []
75
+ # Model chosen by a ``model_select`` hook for the current run. Slots
76
+ # below an explicit runtime override but above pinned/JSON/global, and
77
+ # is reset at the start of every run (see resolve_run_model_selection),
78
+ # so it never leaks across turns.
79
+ self._auto_model_override: Optional[str] = None
75
80
  self._puppy_rules: Optional[str] = None
76
81
  self._mcp_servers: List[Any] = []
77
82
  self.cur_model: Optional[pydantic_ai.models.Model] = None
@@ -125,6 +130,14 @@ class BaseAgent(ABC):
125
130
  """
126
131
  self._runtime_model_name_override = model_name
127
132
 
133
+ def get_auto_model_override(self) -> Optional[str]:
134
+ """Return the model chosen by a ``model_select`` hook for this run."""
135
+ return self._auto_model_override
136
+
137
+ def set_auto_model_override(self, model_name: Optional[str]) -> None:
138
+ """Set the ``model_select``-chosen model for this run (not persisted)."""
139
+ self._auto_model_override = model_name
140
+
128
141
  @contextmanager
129
142
  def temporary_model_name_override(
130
143
  self, model_name: Optional[str]
@@ -154,6 +167,9 @@ class BaseAgent(ABC):
154
167
  override = self.get_runtime_model_name_override()
155
168
  if override:
156
169
  return override
170
+ auto = self.get_auto_model_override()
171
+ if auto:
172
+ return auto
157
173
  pinned = get_agent_pinned_model(self.name)
158
174
  return pinned if pinned else get_global_model_name()
159
175
 
@@ -220,6 +220,13 @@ class JSONAgent(BaseAgent):
220
220
  if override:
221
221
  return override
222
222
 
223
+ # A ``model_select`` hook choice outranks the JSON ``model`` field so
224
+ # per-turn routing works for JSON agents too (see get_model_name in
225
+ # BaseAgent for the full precedence ladder).
226
+ auto = self.get_auto_model_override()
227
+ if auto:
228
+ return auto
229
+
223
230
  result = self._config.get("model")
224
231
  if result is None or (isinstance(result, str) and not result.strip()):
225
232
  result = super().get_model_name()
@@ -46,6 +46,7 @@ PhaseType = Literal[
46
46
  "agent_run_start",
47
47
  "agent_run_end",
48
48
  "agent_run_result",
49
+ "model_select",
49
50
  "register_mcp_catalog_servers",
50
51
  "register_browser_types",
51
52
  "register_model_providers",
@@ -132,6 +133,7 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
132
133
  "agent_run_start": [],
133
134
  "agent_run_end": [],
134
135
  "agent_run_result": [],
136
+ "model_select": [],
135
137
  "register_mcp_catalog_servers": [],
136
138
  "register_browser_types": [],
137
139
  "register_model_providers": [],
@@ -1154,6 +1156,50 @@ async def on_agent_run_start(
1154
1156
  )
1155
1157
 
1156
1158
 
1159
+ def on_model_select(
1160
+ *,
1161
+ agent_name: str,
1162
+ current_model: str | None,
1163
+ prompt: str,
1164
+ messages: List[Any],
1165
+ session_id: str | None = None,
1166
+ ) -> str | None:
1167
+ """Ask plugins to choose the model for the current run.
1168
+
1169
+ Fires once per run, before the pydantic agent is (re)built. Lets a plugin
1170
+ route each turn to a different model based on the agent, the effective
1171
+ ("would-be") model, and the message history -- e.g. a small model for
1172
+ trivial turns and a frontier model when it matters, or a cost/latency/
1173
+ failover policy.
1174
+
1175
+ Precedence: an explicit runtime override still wins over this hook; this
1176
+ hook wins over the pinned / JSON / global model. The first callback to
1177
+ return a non-empty string wins; return ``None`` to defer.
1178
+
1179
+ Args:
1180
+ agent_name: Name of the agent about to run.
1181
+ current_model: The model that would be used absent any hook.
1182
+ prompt: The current user prompt after submission hooks have rewritten it.
1183
+ messages: The agent's prior message history for this run.
1184
+ session_id: Optional per-run identifier.
1185
+
1186
+ Returns:
1187
+ A model name to use for this run, or ``None`` to keep ``current_model``.
1188
+ """
1189
+ results = _trigger_callbacks_sync(
1190
+ "model_select",
1191
+ agent_name=agent_name,
1192
+ current_model=current_model,
1193
+ prompt=prompt,
1194
+ messages=messages,
1195
+ session_id=session_id,
1196
+ )
1197
+ for result in results:
1198
+ if isinstance(result, str) and result.strip():
1199
+ return result
1200
+ return None
1201
+
1202
+
1157
1203
  async def on_agent_run_end(
1158
1204
  agent_name: str,
1159
1205
  model_name: str,
@@ -274,14 +274,19 @@ async def main():
274
274
  try:
275
275
  import pyfiglet
276
276
 
277
+ # Width-aware banner: full CODE PUPPY when it fits, PUP when
278
+ # the terminal is too narrow (phones, tight splits).
279
+ banner_columns = display_console.width
277
280
  intro_lines = pyfiglet.figlet_format(
278
- startup_banner_text(), font="ansi_shadow"
281
+ startup_banner_text(banner_columns), font="ansi_shadow"
279
282
  ).split("\n")
280
283
 
281
284
  # Simple blue to green gradient (top to bottom)
282
285
  gradient_colors = ["bright_blue", "bright_cyan", "bright_green"]
283
286
  display_console.print("\n")
284
287
 
288
+ # Left-justified on purpose -- the full-screen splash handles
289
+ # the centered spectacle; this banner tops the scrollback.
285
290
  lines = []
286
291
  for line_num, line in enumerate(intro_lines):
287
292
  if line.strip():
@@ -2,11 +2,65 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Optional
5
+ from typing import Any, List, Optional
6
6
 
7
7
  from code_puppy.config import set_model_name
8
8
 
9
9
 
10
+ def resolve_run_model_selection(
11
+ agent: Any,
12
+ prompt: str,
13
+ messages: List[Any],
14
+ session_id: Optional[str] = None,
15
+ ) -> Optional[str]:
16
+ """Apply the ``model_select`` hook for one run and return the chosen model.
17
+
18
+ Called once at the start of every run, BEFORE the pydantic agent is built.
19
+
20
+ Behaviour / precedence:
21
+ * The per-run auto override is always cleared first, so a choice never
22
+ leaks into a later turn.
23
+ * An explicit runtime override (e.g. ``invoke_agent_with_model``) wins and
24
+ short-circuits the hook entirely.
25
+ * Otherwise the hook is asked to pick a model. If it returns a name that
26
+ differs from the effective model, we install it as the run's auto
27
+ override and invalidate the cached pydantic agent so the build picks up
28
+ the new model.
29
+
30
+ Returns the chosen model name if the hook changed it, else ``None``.
31
+ Never raises -- a misbehaving selector must not break a run.
32
+ """
33
+ try:
34
+ # Reset any previous turn's auto choice so it can't leak forward.
35
+ if agent.get_auto_model_override():
36
+ agent.set_auto_model_override(None)
37
+ agent._code_generation_agent = None
38
+
39
+ # Explicit runtime override always wins -- don't even ask the hook.
40
+ if agent.get_runtime_model_name_override():
41
+ return None
42
+
43
+ from code_puppy.callbacks import on_model_select
44
+
45
+ current = agent.get_model_name()
46
+ selected = on_model_select(
47
+ agent_name=getattr(agent, "name", None),
48
+ current_model=current,
49
+ prompt=prompt,
50
+ messages=messages or [],
51
+ session_id=session_id,
52
+ )
53
+ if selected and selected != current:
54
+ agent.set_auto_model_override(selected)
55
+ # Force a rebuild so the new model is actually used this turn.
56
+ agent._code_generation_agent = None
57
+ return selected
58
+ except Exception:
59
+ # A broken selector must never block the agent run.
60
+ pass
61
+ return None
62
+
63
+
10
64
  def _get_effective_agent_model(agent) -> Optional[str]:
11
65
  """Safely fetch the effective model name for an agent."""
12
66
  try:
@@ -0,0 +1,42 @@
1
+ """Small platform-detection helpers shared by UI entry points."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+
9
+ # ``CODE PUPPY`` rendered in pyfiglet's ansi_shadow spans 79 columns
10
+ # (pinned by tests/test_platform_utils.py). Baked so banner selection
11
+ # stays import-light -- no pyfiglet needed just to pick a label.
12
+ _FULL_BANNER = "CODE PUPPY"
13
+ _FULL_BANNER_WIDTH = 79
14
+ _COMPACT_BANNER = "PUP"
15
+
16
+
17
+ def is_android() -> bool:
18
+ """Return whether Code Puppy is running on Android or in Termux.
19
+
20
+ Current Android Python builds expose an ``android`` platform name, while
21
+ older Termux builds may report ``linux``. Environment markers cover that
22
+ compatibility gap without making each UI entry point invent its own check.
23
+ """
24
+ if sys.platform.startswith("android"):
25
+ return True
26
+
27
+ return bool(
28
+ os.environ.get("TERMUX_VERSION")
29
+ or (os.environ.get("ANDROID_ROOT") and os.environ.get("ANDROID_DATA"))
30
+ )
31
+
32
+
33
+ def startup_banner_text(columns: int | None = None) -> str:
34
+ """Return the widest startup banner label the terminal can fit.
35
+
36
+ Width-based rather than platform-based: a phone terminal in landscape
37
+ earns the full banner, and a squeezed desktop split gets the compact
38
+ one. ``columns`` defaults to the detected terminal width.
39
+ """
40
+ if columns is None:
41
+ columns = shutil.get_terminal_size(fallback=(80, 24)).columns
42
+ return _FULL_BANNER if columns >= _FULL_BANNER_WIDTH else _COMPACT_BANNER
@@ -7,10 +7,11 @@ and stop it the moment they finish. Do not import anything from
7
7
  ``code_puppy`` here -- that would defeat the entire point.
8
8
 
9
9
  The animation is pure art (no text), so it stays outside the i18n seam.
10
- Every frame repaints its full region with line-clears, which also absorbs
11
- any stray import-time output (e.g. plugin warnings emitted while
12
- ``cli_runner`` loads). Everything fails soft: a broken terminal gets a
13
- no-op splash, never a crashed CLI.
10
+ It runs full-screen on the alternate screen buffer, centered both axes;
11
+ leaving the alt screen on stop() restores the user's terminal exactly as
12
+ it was. Stray import-time output (e.g. plugin warnings emitted while
13
+ ``cli_runner`` loads) is buffered and replayed afterwards. Everything
14
+ fails soft: a broken terminal gets a no-op splash, never a crashed CLI.
14
15
  """
15
16
 
16
17
  from __future__ import annotations
@@ -49,33 +50,40 @@ _PYRAMID = (
49
50
  _CHARS = (" ", "\u2591", "\u2592", "\u2588")
50
51
  _PYRAMID_WIDTH = 44
51
52
 
52
- # "Powered by" / "Pydantic" pre-rendered in pyfiglet's ansi_shadow (the same
53
- # font as the main CODE PUPPY banner). Baked as constants: pyfiglet is not
54
- # stdlib and this module must stay import-light. Solid blocks render as the
55
- # neon core tier; the box-drawing shadow trim renders as the mid glow tier.
56
- _TAGLINE_TOP = (
57
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
58
- "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255a\u2588\u2588\u2557 \u2588\u2588\u2554\u255d",
59
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u255a\u2588\u2588\u2588\u2588\u2554\u255d",
60
- "\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u255a\u2588\u2588\u2554\u255d",
61
- "\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u255a\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u2588\u2588\u2551",
62
- "\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u255d\u255a\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d",
53
+ # "CODE PUPPY" / "PUP" pre-rendered in pyfiglet's ansi_shadow (the same
54
+ # font as the scrollback banner). Baked as constants: pyfiglet is not
55
+ # stdlib and this module must stay import-light. Solid blocks render as
56
+ # the neon core tier; the box-drawing shadow trim renders as the mid
57
+ # glow tier.
58
+ _BANNER_FULL = (
59
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
60
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255a\u2588\u2588\u2557 \u2588\u2588\u2554\u255d",
61
+ "\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u255a\u2588\u2588\u2588\u2588\u2554\u255d",
62
+ "\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u255d \u255a\u2588\u2588\u2554\u255d",
63
+ "\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551",
64
+ " \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d",
63
65
  )
64
- _TAGLINE_TOP_WIDTH = 80
65
- _TAGLINE_BOTTOM = (
66
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
67
- "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255a\u2588\u2588\u2557 \u2588\u2588\u2554\u255d\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255a\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255d\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d",
68
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u255a\u2588\u2588\u2588\u2588\u2554\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551",
69
- "\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u255a\u2588\u2588\u2554\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551",
70
- "\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
71
- "\u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d",
66
+ _BANNER_FULL_WIDTH = 79
67
+ _BANNER_COMPACT = (
68
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
69
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
70
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d",
71
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d",
72
+ "\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551",
73
+ "\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d",
72
74
  )
73
- _TAGLINE_BOTTOM_WIDTH = 63
75
+ _BANNER_COMPACT_WIDTH = 25
74
76
  _SHADOW_TRIM = frozenset("\u2550\u2551\u2554\u2557\u255a\u255d")
75
77
 
76
78
  _RESET = "\x1b[0m"
77
79
  _HIDE_CURSOR = "\x1b[?25l"
78
80
  _SHOW_CURSOR = "\x1b[?25h"
81
+ # Alternate screen buffer (like vim/less): the splash owns a blank
82
+ # full-screen canvas, and leaving it restores the user's scrollback
83
+ # untouched -- no erase bookkeeping needed on the way out.
84
+ _ALT_SCREEN_ON = "\x1b[?1049h"
85
+ _ALT_SCREEN_OFF = "\x1b[?1049l"
86
+ _CLEAR_SCREEN = "\x1b[2J"
79
87
  # Synchronized output (DEC 2026): terminals that support it (iTerm2, kitty,
80
88
  # WezTerm, Ghostty, Alacritty, ...) render the whole frame atomically --
81
89
  # zero flicker. Terminals that don't simply ignore the markers.
@@ -119,13 +127,13 @@ def _compose_rows(columns: int, lines: int):
119
127
 
120
128
  Returns a list of (kind, content) rows: ``art`` rows are pyramid tier
121
129
  digits, ``text`` rows are literal figlet glyphs. Degrades gracefully:
122
- full "Powered by / Pydantic" -> "Pydantic" only -> pyramid only.
130
+ pyramid + "CODE PUPPY" -> pyramid + "PUP" -> pyramid only.
123
131
  """
124
- top = [(line, _TAGLINE_TOP_WIDTH) for line in _TAGLINE_TOP]
125
- bottom = [(line, _TAGLINE_BOTTOM_WIDTH) for line in _TAGLINE_BOTTOM]
132
+ full = [(line, _BANNER_FULL_WIDTH) for line in _BANNER_FULL]
133
+ compact = [(line, _BANNER_COMPACT_WIDTH) for line in _BANNER_COMPACT]
126
134
  variants = (
127
- (top + [("", 0)] + bottom, _TAGLINE_TOP_WIDTH),
128
- (bottom, _TAGLINE_BOTTOM_WIDTH),
135
+ (full, _BANNER_FULL_WIDTH),
136
+ (compact, _BANNER_COMPACT_WIDTH),
129
137
  ([], _PYRAMID_WIDTH),
130
138
  )
131
139
  for text_rows, text_width in variants:
@@ -146,6 +154,22 @@ def _compose_rows(columns: int, lines: int):
146
154
  return rows
147
155
 
148
156
 
157
+ def _center_rows(rows, columns: int):
158
+ """Pad every row so the artwork block sits horizontally centered.
159
+
160
+ Art rows pad with '0' (the empty glow tier) and text rows with spaces,
161
+ so ``_build_frame`` needs no knowledge of centering at all.
162
+ """
163
+ block_width = max((len(content) for _, content in rows), default=0)
164
+ pad = max(0, (columns - block_width) // 2)
165
+ if not pad:
166
+ return list(rows)
167
+ return [
168
+ (kind, ("0" * pad if kind == "art" else " " * pad) + content)
169
+ for kind, content in rows
170
+ ]
171
+
172
+
149
173
  def _build_frame(phase: int, truecolor: bool, rows) -> str:
150
174
  """Render one frame as newline-joined lines with NO trailing newline.
151
175
 
@@ -249,20 +273,25 @@ class _Splash:
249
273
  target=self._run, name="code-puppy-splash", daemon=True
250
274
  )
251
275
  size = shutil.get_terminal_size(fallback=(80, 24))
252
- self._rows = _compose_rows(size.columns, size.lines)
276
+ self._rows = _center_rows(_compose_rows(size.columns, size.lines), size.columns)
253
277
  self._height = len(self._rows)
278
+ # 1-based row where the frame starts, vertically centering the art.
279
+ self._top_row = max(1, (size.lines - self._height) // 2 + 1)
254
280
  self._stopped = False
255
281
  self._thread.start()
256
282
 
257
283
  def _run(self) -> None:
258
284
  phase = 0
259
- # Reposition to frame top: carriage return + cursor-up (height-1).
260
- # The frame has no trailing newline, so nothing ever scrolls and the
261
- # cursor parks at the end of the last row between frames.
262
- reposition = f"\r\x1b[{self._height - 1}A"
285
+ # The alt screen is ours alone, so every frame repaints from the
286
+ # same absolute, vertically-centered origin. The frame has no
287
+ # trailing newline, so nothing ever scrolls.
288
+ reposition = f"\x1b[{self._top_row};1H"
263
289
  try:
264
290
  first = _build_frame(phase, self._truecolor, self._rows)
265
- self._stream.write(f"{_HIDE_CURSOR}{_SYNC_START}{first}{_SYNC_END}")
291
+ self._stream.write(
292
+ f"{_ALT_SCREEN_ON}{_HIDE_CURSOR}{_CLEAR_SCREEN}"
293
+ f"{_SYNC_START}{reposition}{first}{_SYNC_END}"
294
+ )
266
295
  self._stream.flush()
267
296
  while not self._stop_event.wait(_FRAME_SECONDS):
268
297
  phase = (phase + 2) % _SHEEN_PERIOD
@@ -289,8 +318,9 @@ class _Splash:
289
318
  self._stop_event.set()
290
319
  self._thread.join(timeout=1.0)
291
320
  try:
292
- # Back to frame top, erase to end of screen, restore cursor.
293
- self._stream.write(f"\r\x1b[{self._height - 1}A\x1b[0J{_SHOW_CURSOR}")
321
+ # Leaving the alt screen restores the pre-splash terminal
322
+ # content and cursor position in one move; nothing to erase.
323
+ self._stream.write(f"{_ALT_SCREEN_OFF}{_SHOW_CURSOR}")
294
324
  self._stream.flush()
295
325
  except Exception:
296
326
  pass
@@ -358,6 +388,12 @@ def start_splash(
358
388
  return _NullSplash()
359
389
  if not _wants_splash(sys.argv):
360
390
  return _NullSplash()
391
+ # _compose_rows degrades CODE PUPPY -> PUP -> bare pyramid,
392
+ # but below even the pyramid's footprint there is nothing
393
+ # honest left to draw: stay silent, never clip art.
394
+ size = shutil.get_terminal_size(fallback=(80, 24))
395
+ if size.columns < _PYRAMID_WIDTH + 2 or size.lines < len(_PYRAMID) + 2:
396
+ return _NullSplash()
361
397
  if not _enable_windows_vt(stream):
362
398
  return _NullSplash()
363
399
  return _Splash(stream, min_seconds=min_seconds)
@@ -406,6 +406,11 @@ async def _invoke_agent_impl(
406
406
  # (e.g. DBOS) may swap them via the agent_run_context hook.
407
407
  temp_agent = Agent(
408
408
  model=model,
409
+ # Explicit name: without it pydantic-ai infers one from the
410
+ # caller's frame variables, so every sub-agent's observability
411
+ # span reads "invoke_agent temp_agent" instead of the logical
412
+ # agent name (e.g. "invoke_agent web-retriever").
413
+ name=agent_name,
409
414
  instructions=instructions,
410
415
  output_type=str,
411
416
  retries=3,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.749"
7
+ version = "0.0.751"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.15"
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "termflow-md>=0.1.11",
33
33
  "Pillow>=10.0.0",
34
34
  "azure-identity>=1.15.0",
35
- "anthropic>=0.108.0",
35
+ "anthropic>=0.108.0,<1",
36
36
  "boto3>=1.43.9",
37
37
  "keyring>=24.0.0",
38
38
  "agent-client-protocol>=0.11.0,<0.12",
@@ -1,27 +0,0 @@
1
- """Small platform-detection helpers shared by UI entry points."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import sys
7
-
8
-
9
- def is_android() -> bool:
10
- """Return whether Code Puppy is running on Android or in Termux.
11
-
12
- Current Android Python builds expose an ``android`` platform name, while
13
- older Termux builds may report ``linux``. Environment markers cover that
14
- compatibility gap without making each UI entry point invent its own check.
15
- """
16
- if sys.platform.startswith("android"):
17
- return True
18
-
19
- return bool(
20
- os.environ.get("TERMUX_VERSION")
21
- or (os.environ.get("ANDROID_ROOT") and os.environ.get("ANDROID_DATA"))
22
- )
23
-
24
-
25
- def startup_banner_text() -> str:
26
- """Return the platform-appropriate startup banner label."""
27
- return "PUP" if is_android() else "CODE PUPPY"
File without changes
File without changes
File without changes