code-puppy 0.0.575__tar.gz → 0.0.577__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 (386) hide show
  1. {code_puppy-0.0.575 → code_puppy-0.0.577}/PKG-INFO +1 -1
  2. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/cli_runner.py +58 -0
  3. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/autosave_menu.py +222 -15
  4. code_puppy-0.0.577/code_puppy/command_line/autosave_search.py +184 -0
  5. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/command_registry.py +12 -2
  6. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/core_commands.py +55 -0
  7. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/session_commands.py +103 -0
  8. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/config.py +289 -0
  9. code_puppy-0.0.577/code_puppy/plugins/quick_resume/__init__.py +0 -0
  10. code_puppy-0.0.577/code_puppy/plugins/quick_resume/register_callbacks.py +79 -0
  11. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/payload.py +7 -2
  12. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/file_modifications.py +6 -0
  13. code_puppy-0.0.577/code_puppy/undo_manager.py +62 -0
  14. {code_puppy-0.0.575 → code_puppy-0.0.577}/pyproject.toml +1 -1
  15. {code_puppy-0.0.575 → code_puppy-0.0.577}/.gitignore +0 -0
  16. {code_puppy-0.0.575 → code_puppy-0.0.577}/LICENSE +0 -0
  17. {code_puppy-0.0.575 → code_puppy-0.0.577}/README.md +0 -0
  18. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/__init__.py +0 -0
  19. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/__main__.py +0 -0
  20. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/__init__.py +0 -0
  21. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_builder.py +0 -0
  22. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_compaction.py +0 -0
  23. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_diagnostics.py +0 -0
  24. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_history.py +0 -0
  25. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_key_listeners.py +0 -0
  26. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_non_streaming_render.py +0 -0
  27. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_run_signals.py +0 -0
  28. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_runtime.py +0 -0
  29. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/_steer_processor.py +0 -0
  30. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_code_puppy.py +0 -0
  31. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_creator_agent.py +0 -0
  32. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_helios.py +0 -0
  33. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_manager.py +0 -0
  34. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_planning.py +0 -0
  35. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/agent_qa_kitten.py +0 -0
  36. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/base_agent.py +0 -0
  37. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/event_stream_handler.py +0 -0
  38. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/json_agent.py +0 -0
  39. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/run_stats.py +0 -0
  40. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/smooth_stream.py +0 -0
  41. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/agents/subagent_stream_handler.py +0 -0
  42. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/callbacks.py +0 -0
  43. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/chatgpt_codex_client.py +0 -0
  44. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/claude_cache_client.py +0 -0
  45. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/__init__.py +0 -0
  46. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/add_model_menu.py +0 -0
  47. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/agent_menu.py +0 -0
  48. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/attachments.py +0 -0
  49. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/clipboard.py +0 -0
  50. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/colors_menu.py +0 -0
  51. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/command_handler.py +0 -0
  52. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/config_apply.py +0 -0
  53. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/config_commands.py +0 -0
  54. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/diff_menu.py +0 -0
  55. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/file_index.py +0 -0
  56. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/file_path_completion.py +0 -0
  57. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/judges_menu.py +0 -0
  58. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/load_context_completion.py +0 -0
  59. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/__init__.py +0 -0
  60. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/base.py +0 -0
  61. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  62. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  63. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  64. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/edit_command.py +0 -0
  65. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/handler.py +0 -0
  66. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/help_command.py +0 -0
  67. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/install_command.py +0 -0
  68. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/install_menu.py +0 -0
  69. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/list_command.py +0 -0
  70. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/logs_command.py +0 -0
  71. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/remove_command.py +0 -0
  72. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/restart_command.py +0 -0
  73. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/search_command.py +0 -0
  74. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
  75. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  76. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/start_command.py +0 -0
  77. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/status_command.py +0 -0
  78. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  79. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/stop_command.py +0 -0
  80. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/utils.py +0 -0
  81. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  82. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp_binding_menu.py +0 -0
  83. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/mcp_completion.py +0 -0
  84. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/model_picker_completion.py +0 -0
  85. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/model_settings_menu.py +0 -0
  86. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/onboarding_slides.py +0 -0
  87. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/onboarding_wizard.py +0 -0
  88. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/pagination.py +0 -0
  89. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/pin_command_completion.py +0 -0
  90. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  91. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu.py +0 -0
  92. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_catalog.py +0 -0
  93. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_render.py +0 -0
  94. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_schema.py +0 -0
  95. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_settings.py +0 -0
  96. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_shims.py +0 -0
  97. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/set_menu_values.py +0 -0
  98. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/shell_passthrough.py +0 -0
  99. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/skills_completion.py +0 -0
  100. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/uc_menu.py +0 -0
  101. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/utils.py +0 -0
  102. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/command_line/wiggum_state.py +0 -0
  103. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/error_logging.py +0 -0
  104. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/gemini_code_assist.py +0 -0
  105. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/gemini_model.py +0 -0
  106. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/README.md +0 -0
  107. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/__init__.py +0 -0
  108. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/aliases.py +0 -0
  109. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/engine.py +0 -0
  110. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/executor.py +0 -0
  111. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/matcher.py +0 -0
  112. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/models.py +0 -0
  113. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/registry.py +0 -0
  114. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/hook_engine/validator.py +0 -0
  115. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/http_utils.py +0 -0
  116. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/keymap.py +0 -0
  117. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/list_filtering.py +0 -0
  118. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/main.py +0 -0
  119. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/__init__.py +0 -0
  120. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/agent_bindings.py +0 -0
  121. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/async_lifecycle.py +0 -0
  122. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/blocking_startup.py +0 -0
  123. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  124. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/circuit_breaker.py +0 -0
  125. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/config_wizard.py +0 -0
  126. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/dashboard.py +0 -0
  127. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/error_isolation.py +0 -0
  128. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/examples/retry_example.py +0 -0
  129. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/health_monitor.py +0 -0
  130. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/managed_server.py +0 -0
  131. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/manager.py +0 -0
  132. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/mcp_logs.py +0 -0
  133. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/registry.py +0 -0
  134. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/retry_manager.py +0 -0
  135. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  136. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/status_tracker.py +0 -0
  137. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_/system_tools.py +0 -0
  138. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_prompts/__init__.py +0 -0
  139. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/mcp_prompts/hook_creator.py +0 -0
  140. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/__init__.py +0 -0
  141. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/bus.py +0 -0
  142. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/commands.py +0 -0
  143. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/markdown_patches.py +0 -0
  144. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/message_queue.py +0 -0
  145. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/messages.py +0 -0
  146. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/pause_controller.py +0 -0
  147. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/queue_console.py +0 -0
  148. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/renderers.py +0 -0
  149. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/rich_renderer.py +0 -0
  150. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/spinner/__init__.py +0 -0
  151. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  152. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  153. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/messaging/subagent_console.py +0 -0
  154. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/model_descriptions.py +0 -0
  155. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/model_factory.py +0 -0
  156. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/model_switching.py +0 -0
  157. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/model_utils.py +0 -0
  158. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/models.json +0 -0
  159. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/models_dev_api.json +0 -0
  160. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/models_dev_parser.py +0 -0
  161. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/__init__.py +0 -0
  162. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/__init__.py +0 -0
  163. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/config.py +0 -0
  164. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/discovery.py +0 -0
  165. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/downloader.py +0 -0
  166. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/enabled_skills.py +0 -0
  167. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/installer.py +0 -0
  168. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/metadata.py +0 -0
  169. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/prompt_builder.py +0 -0
  170. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/register_callbacks.py +0 -0
  171. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/remote_catalog.py +0 -0
  172. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/skill_catalog.py +0 -0
  173. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/skill_commands.py +0 -0
  174. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/skills_install_menu.py +0 -0
  175. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_skills/skills_menu.py +0 -0
  176. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_steering/README.md +0 -0
  177. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_steering/__init__.py +0 -0
  178. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_steering/line_editor.py +0 -0
  179. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_steering/register_callbacks.py +0 -0
  180. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/agent_steering/steering_prompt.py +0 -0
  181. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/aws_bedrock/__init__.py +0 -0
  182. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/aws_bedrock/config.py +0 -0
  183. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/aws_bedrock/register_callbacks.py +0 -0
  184. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/aws_bedrock/utils.py +0 -0
  185. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/README.md +0 -0
  186. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/__init__.py +0 -0
  187. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/config.py +0 -0
  188. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/discovery.py +0 -0
  189. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/register_callbacks.py +0 -0
  190. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/token.py +0 -0
  191. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/azure_foundry/utils.py +0 -0
  192. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  193. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  194. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  195. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  196. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  197. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  198. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_hooks/__init__.py +0 -0
  199. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_hooks/config.py +0 -0
  200. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_hooks/register_callbacks.py +0 -0
  201. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  202. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  203. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  204. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  205. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/fast_mode.py +0 -0
  206. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/prompt_handler.py +0 -0
  207. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  208. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/test_fast_mode.py +0 -0
  209. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  210. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +0 -0
  211. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  212. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/code_puppy_agent/SKILL.md +0 -0
  213. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/code_puppy_agent/__init__.py +0 -0
  214. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/code_puppy_agent/register_callbacks.py +0 -0
  215. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/config.py +0 -0
  216. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/context_indicator/__init__.py +0 -0
  217. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/context_indicator/register_callbacks.py +0 -0
  218. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/context_indicator/usage.py +0 -0
  219. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/copilot_auth/__init__.py +0 -0
  220. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/copilot_auth/config.py +0 -0
  221. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/copilot_auth/reasoning_client.py +0 -0
  222. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/copilot_auth/register_callbacks.py +0 -0
  223. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/copilot_auth/utils.py +0 -0
  224. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  225. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  226. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/__init__.py +0 -0
  227. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/cancel.py +0 -0
  228. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/commands.py +0 -0
  229. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/config.py +0 -0
  230. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/lifecycle.py +0 -0
  231. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/register_callbacks.py +0 -0
  232. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/runtime.py +0 -0
  233. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/startup_lock.py +0 -0
  234. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/workflow_ids.py +0 -0
  235. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/dbos_durable_exec/wrapper.py +0 -0
  236. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/destructive_command_guard/__init__.py +0 -0
  237. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/destructive_command_guard/detector.py +0 -0
  238. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/destructive_command_guard/register_callbacks.py +0 -0
  239. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/emoji_filter/__init__.py +0 -0
  240. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/emoji_filter/config.py +0 -0
  241. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/emoji_filter/register_callbacks.py +0 -0
  242. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/emoji_filter/stripper.py +0 -0
  243. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/example_custom_command/README.md +0 -0
  244. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  245. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  246. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  247. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/force_push_guard/__init__.py +0 -0
  248. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/force_push_guard/detector.py +0 -0
  249. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/force_push_guard/register_callbacks.py +0 -0
  250. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/force_push_guard/test_detector.py +0 -0
  251. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
  252. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
  253. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
  254. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/frontend_emitter/session_context.py +0 -0
  255. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_creator/__init__.py +0 -0
  256. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_creator/register_callbacks.py +0 -0
  257. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_manager/__init__.py +0 -0
  258. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_manager/config.py +0 -0
  259. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_manager/hooks_menu.py +0 -0
  260. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/hook_manager/register_callbacks.py +0 -0
  261. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  262. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/obsidian_agent/README.md +0 -0
  263. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/obsidian_agent/__init__.py +0 -0
  264. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/obsidian_agent/agent_obsidian.py +0 -0
  265. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/obsidian_agent/register_callbacks.py +0 -0
  266. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/ollama/__init__.py +0 -0
  267. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/ollama/register_callbacks.py +0 -0
  268. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/ollama_setup/__init__.py +0 -0
  269. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/ollama_setup/completer.py +0 -0
  270. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/ollama_setup/register_callbacks.py +0 -0
  271. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/plugin_list/__init__.py +0 -0
  272. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/plugin_list/plugins_menu.py +0 -0
  273. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/plugin_list/register_callbacks.py +0 -0
  274. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/pop_command/__init__.py +0 -0
  275. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/pop_command/register_callbacks.py +0 -0
  276. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prompt_newline/__init__.py +0 -0
  277. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prompt_newline/config.py +0 -0
  278. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prompt_newline/register_callbacks.py +0 -0
  279. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prune/__init__.py +0 -0
  280. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prune/prune_menu.py +0 -0
  281. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prune/prune_model.py +0 -0
  282. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prune/prune_render.py +0 -0
  283. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/prune/register_callbacks.py +0 -0
  284. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/README.md +0 -0
  285. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/__init__.py +0 -0
  286. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/commands.py +0 -0
  287. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/config.py +0 -0
  288. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/kennel.py +0 -0
  289. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/packer.py +0 -0
  290. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/recorder.py +0 -0
  291. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/register_callbacks.py +0 -0
  292. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/retriever.py +0 -0
  293. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/schema.py +0 -0
  294. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/state.py +0 -0
  295. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/tools.py +0 -0
  296. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/puppy_kennel/wings.py +0 -0
  297. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/review_pr/__init__.py +0 -0
  298. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/review_pr/register_callbacks.py +0 -0
  299. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  300. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  301. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  302. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  303. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/__init__.py +0 -0
  304. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/config.py +0 -0
  305. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/prompt_patch.py +0 -0
  306. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/register_callbacks.py +0 -0
  307. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/runner.py +0 -0
  308. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/statusline/statusline_command.py +0 -0
  309. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/README.md +0 -0
  310. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/__init__.py +0 -0
  311. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/coalesce_patch.py +0 -0
  312. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/register_callbacks.py +0 -0
  313. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/resume_repaint.py +0 -0
  314. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/subagent_panel/state.py +0 -0
  315. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/switch_agent_resume/__init__.py +0 -0
  316. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/switch_agent_resume/register_callbacks.py +0 -0
  317. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/synthetic_status/__init__.py +0 -0
  318. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/synthetic_status/register_callbacks.py +0 -0
  319. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/synthetic_status/status_api.py +0 -0
  320. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/README.md +0 -0
  321. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/__init__.py +0 -0
  322. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/bundled_palettes.py +0 -0
  323. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/content_styles.py +0 -0
  324. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/osc_palette.py +0 -0
  325. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/picker.py +0 -0
  326. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/register_callbacks.py +0 -0
  327. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/rich_themes.py +0 -0
  328. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/theme/themes.py +0 -0
  329. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/token_ratio_learner/__init__.py +0 -0
  330. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/token_ratio_learner/ratios.py +0 -0
  331. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/token_ratio_learner/register_callbacks.py +0 -0
  332. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/universal_constructor/__init__.py +0 -0
  333. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/universal_constructor/models.py +0 -0
  334. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/universal_constructor/register_callbacks.py +0 -0
  335. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/universal_constructor/registry.py +0 -0
  336. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/universal_constructor/sandbox.py +0 -0
  337. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wide_completion_menu/__init__.py +0 -0
  338. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wide_completion_menu/register_callbacks.py +0 -0
  339. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wiggum/__init__.py +0 -0
  340. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wiggum/judge.py +0 -0
  341. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wiggum/judge_config.py +0 -0
  342. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wiggum/register_callbacks.py +0 -0
  343. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/plugins/wiggum/state.py +0 -0
  344. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/provider_credentials.py +0 -0
  345. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/provider_identity.py +0 -0
  346. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/pydantic_patches.py +0 -0
  347. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/reopenable_async_client.py +0 -0
  348. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/round_robin_model.py +0 -0
  349. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/session_storage.py +0 -0
  350. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/status_display.py +0 -0
  351. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/summarization_agent.py +0 -0
  352. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/terminal_utils.py +0 -0
  353. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/__init__.py +0 -0
  354. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/agent_tools.py +0 -0
  355. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/__init__.py +0 -0
  356. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/constants.py +0 -0
  357. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
  358. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/handler.py +0 -0
  359. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/models.py +0 -0
  360. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/registration.py +0 -0
  361. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/renderers.py +0 -0
  362. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
  363. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/theme.py +0 -0
  364. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
  365. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/__init__.py +0 -0
  366. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_control.py +0 -0
  367. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_interactions.py +0 -0
  368. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_locators.py +0 -0
  369. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_manager.py +0 -0
  370. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_navigation.py +0 -0
  371. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  372. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_scripts.py +0 -0
  373. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/browser/browser_workflows.py +0 -0
  374. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/command_runner.py +0 -0
  375. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/common.py +0 -0
  376. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/display.py +0 -0
  377. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/file_operations.py +0 -0
  378. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/image_tools.py +0 -0
  379. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/model_tools.py +0 -0
  380. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/skills_tools.py +0 -0
  381. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/subagent_context.py +0 -0
  382. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/subagent_invocation.py +0 -0
  383. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/tools_content.py +0 -0
  384. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/tools/universal_constructor.py +0 -0
  385. {code_puppy-0.0.575 → code_puppy-0.0.577}/code_puppy/uvx_detection.py +0 -0
  386. {code_puppy-0.0.575 → code_puppy-0.0.577}/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.575
3
+ Version: 0.0.577
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
@@ -133,6 +133,48 @@ def _resume_session_from_path(raw_path: str) -> None:
133
133
  )
134
134
 
135
135
 
136
+ def apply_quick_resume(args) -> bool:
137
+ """Resolve ``--quick-resume [PATH]`` into ``args.resume`` so the existing
138
+ resume machinery loads it.
139
+
140
+ Looks up the most recent autosave for PATH (defaulting to cwd), scoped to
141
+ the nearest git worktree root + branch when available, with a no-git
142
+ fallback. No-op when ``--quick-resume`` was not requested or ``--resume`` is
143
+ already set (explicit ``--resume`` always wins). Returns True when a target
144
+ was resolved.
145
+ """
146
+ existing_resume = getattr(args, "resume", None)
147
+ quick_resume_target = getattr(args, "quick_resume", None)
148
+ if quick_resume_target is None or (
149
+ existing_resume and str(existing_resume).strip()
150
+ ):
151
+ return False
152
+
153
+ from code_puppy.config import (
154
+ format_quick_resume_scope,
155
+ get_quick_resume_location,
156
+ resolve_quick_resume_pickle,
157
+ )
158
+ from code_puppy.messaging import emit_info
159
+
160
+ target_path = str(quick_resume_target).strip() or "."
161
+
162
+ # Diagnostic identifies the lookup scope without leaking full local paths.
163
+ cwd, branch = get_quick_resume_location(target_path)
164
+ emit_info(
165
+ "\U0001f50d Quick Resume selected - finding latest session for "
166
+ f"{format_quick_resume_scope(cwd, branch)}"
167
+ )
168
+
169
+ quick_resume_pickle = resolve_quick_resume_pickle(target_path)
170
+ if quick_resume_pickle:
171
+ args.resume = quick_resume_pickle
172
+ return True
173
+
174
+ emit_info("No previous session found for this scope; starting fresh.")
175
+ return False
176
+
177
+
136
178
  async def main():
137
179
  """Main async entry point for Code Puppy CLI."""
138
180
  parser = argparse.ArgumentParser(description="Code Puppy - A code generation agent")
@@ -174,6 +216,18 @@ async def main():
174
216
  metavar="PATH",
175
217
  help="Resume a saved session from a .pkl file (e.g. ~/.code_puppy/contexts/foo.pkl)",
176
218
  )
219
+ parser.add_argument(
220
+ "--quick-resume",
221
+ "-qr",
222
+ nargs="?",
223
+ const=".",
224
+ default=None,
225
+ metavar="PATH",
226
+ help=(
227
+ "Resume the most recent session for PATH (defaults to the current "
228
+ "directory; scopes to git root + branch when available)"
229
+ ),
230
+ )
177
231
  parser.add_argument(
178
232
  "command", nargs="*", help="Run a single command (deprecated, use -p instead)"
179
233
  )
@@ -395,6 +449,10 @@ async def main():
395
449
 
396
450
  await callbacks.on_startup()
397
451
 
452
+ # Resolve --quick-resume [PATH] into --resume so the resume machinery below
453
+ # loads the most recent session for that canonical (git-root + branch) scope.
454
+ apply_quick_resume(args)
455
+
398
456
  if args.resume:
399
457
  _resume_session_from_path(args.resume)
400
458
 
@@ -20,9 +20,15 @@ from prompt_toolkit.widgets import Frame
20
20
  from rich.console import Console
21
21
  from rich.markdown import Markdown
22
22
 
23
+ from code_puppy.command_line.autosave_search import (
24
+ SessionContentIndex,
25
+ entry_matches,
26
+ iter_alphabet_bindings,
27
+ )
23
28
  from code_puppy.command_line.pagination import (
24
29
  ensure_visible_page,
25
30
  get_page_bounds,
31
+ get_page_for_index,
26
32
  get_total_pages,
27
33
  )
28
34
  from code_puppy.config import AUTOSAVE_DIR
@@ -163,17 +169,40 @@ def _render_menu_panel(
163
169
  page: int,
164
170
  selected_idx: int,
165
171
  browse_mode: bool = False,
172
+ search_text: str = "",
173
+ in_search_mode: bool = False,
174
+ search_buffer: str = "",
175
+ status_line: Optional[Tuple[str, str]] = None,
166
176
  ) -> List:
167
- """Render the left menu panel with pagination."""
177
+ """Render the left menu panel with pagination.
178
+
179
+ ``status_line`` is an optional ``(style, text)`` pair that, when
180
+ provided, replaces the normal search/filter indicator. The picker
181
+ uses it to surface transient states like ``Filtering...`` (right
182
+ after the user commits a search) and ``Indexing N/M...`` (while the
183
+ background pre-warm task is still chewing through sessions).
184
+ """
168
185
  lines = []
169
186
  total_pages = get_total_pages(len(entries), PAGE_SIZE)
170
187
  start_idx, end_idx = get_page_bounds(page, len(entries), PAGE_SIZE)
171
188
 
172
189
  lines.append(("", f" Session Page(s): ({page + 1}/{total_pages})"))
190
+ if status_line is not None:
191
+ lines.append(("", "\n"))
192
+ lines.append(status_line)
193
+ elif in_search_mode:
194
+ lines.append(("", "\n"))
195
+ lines.append(("fg:ansiyellow", f" Searching: '{search_buffer}'"))
196
+ elif search_text:
197
+ lines.append(("", "\n"))
198
+ lines.append(("fg:ansiyellow", f" Filter: '{search_text}'"))
173
199
  lines.append(("", "\n\n"))
174
200
 
175
201
  if not entries:
176
- lines.append(("fg:yellow", " No autosave sessions found."))
202
+ if search_text or in_search_mode:
203
+ lines.append(("fg:yellow", " No sessions match your search."))
204
+ else:
205
+ lines.append(("fg:yellow", " No autosave sessions found."))
177
206
  lines.append(("", "\n\n"))
178
207
  # Navigation hints (always show)
179
208
  lines.append(("", "\n"))
@@ -225,6 +254,8 @@ def _render_menu_panel(
225
254
  lines.append(("", "Page\n"))
226
255
  lines.append(("fg:ansicyan", " e "))
227
256
  lines.append(("", "Browse msgs\n"))
257
+ lines.append(("fg:ansibrightblack", " / "))
258
+ lines.append(("", "Search content\n"))
228
259
  lines.append(("fg:green", " Enter "))
229
260
  lines.append(("", "Load\n"))
230
261
  lines.append(("fg:ansibrightred", " Ctrl+C "))
@@ -515,7 +546,7 @@ async def interactive_autosave_picker() -> Optional[str]:
515
546
  return None
516
547
 
517
548
  # State
518
- selected_idx = [0] # Current selection (global index)
549
+ selected_idx = [0] # Current selection (index into visible_entries)
519
550
  current_page = [0] # Current page
520
551
  result = [None] # Selected session name
521
552
 
@@ -524,21 +555,90 @@ async def interactive_autosave_picker() -> Optional[str]:
524
555
  message_idx = [0] # Current message index (0 = most recent)
525
556
  cached_history = [None] # Cached history for current session in browse mode
526
557
 
527
- total_pages = get_total_pages(len(entries), PAGE_SIZE)
558
+ # Search/filter state (mirrors set_menu.py's `/`-search UX)
559
+ search_text = [""] # Committed filter (drives visible_entries)
560
+ in_search_mode = [False] # Currently typing into the search buffer?
561
+ search_buffer = [""] # Live keystrokes before Enter commits them
562
+ visible_entries: List[List[Tuple[str, dict]]] = [list(entries)]
563
+ content_index = SessionContentIndex() # Lazy content cache for THIS picker
564
+ is_filtering = [False] # True while the Enter-handler is doing the work
565
+ total_to_index = len(entries) # Denominator for the prewarm progress hint
528
566
 
529
567
  def get_current_entry() -> Optional[Tuple[str, dict]]:
530
- if 0 <= selected_idx[0] < len(entries):
531
- return entries[selected_idx[0]]
568
+ visible = visible_entries[0]
569
+ if 0 <= selected_idx[0] < len(visible):
570
+ return visible[selected_idx[0]]
532
571
  return None
533
572
 
573
+ def _filter_entries(needle: str) -> List[Tuple[str, dict]]:
574
+ """Pure filter: needle in, filtered list out. Safe to run off-thread.
575
+
576
+ Reads ``entries``, ``content_index``, and ``base_dir`` from the
577
+ enclosing closure -- ``entries`` is built once and never mutated,
578
+ and ``content_index`` is protected by its own internal lock, so
579
+ this is safe to invoke from an ``asyncio.to_thread`` worker.
580
+ """
581
+ if not needle:
582
+ return list(entries)
583
+ return [e for e in entries if entry_matches(e, needle, content_index, base_dir)]
584
+
585
+ def _apply_filter_result(filtered: List[Tuple[str, dict]]) -> None:
586
+ """Apply a filter result to picker state. Must run on the main thread."""
587
+ visible_entries[0] = filtered
588
+ if not filtered:
589
+ selected_idx[0] = 0
590
+ current_page[0] = 0
591
+ return
592
+ selected_idx[0] = min(selected_idx[0], len(filtered) - 1)
593
+ current_page[0] = get_page_for_index(selected_idx[0], PAGE_SIZE)
594
+
595
+ def update_visible_entries() -> None:
596
+ """Synchronous re-filter -- only safe when the cache is warm or empty.
597
+
598
+ Used for the picker's initial setup (no filter active -> trivial)
599
+ and as a fallback. The post-Enter path goes through
600
+ :func:`asyncio.to_thread` instead so a cold-cache filter does not
601
+ freeze the event loop.
602
+ """
603
+ _apply_filter_result(_filter_entries(search_text[0]))
604
+
534
605
  # Build UI
535
606
  menu_control = FormattedTextControl(text="")
536
607
  preview_control = FormattedTextControl(text="")
537
608
 
609
+ def _compute_status_line() -> Optional[Tuple[str, str]]:
610
+ """Resolve which transient indicator (if any) takes the header slot.
611
+
612
+ Priority order:
613
+ 1. ``Filtering...`` -- user just hit Enter, filter is running.
614
+ 2. ``Searching: '...'`` / ``Filter: '...'`` -- normal search UX,
615
+ handled by the renderer's own branches when this returns None.
616
+ 3. ``Indexing N/M...`` -- background pre-warm in progress and no
617
+ other search activity to crowd out.
618
+ """
619
+ if is_filtering[0]:
620
+ return ("fg:ansicyan bold", " Filtering...")
621
+ if in_search_mode[0] or search_text[0]:
622
+ return None # Let the renderer show the search/filter line.
623
+ cached = content_index.count()
624
+ if 0 < cached < total_to_index:
625
+ return (
626
+ "fg:ansibrightblack",
627
+ f" Indexing {cached}/{total_to_index}...",
628
+ )
629
+ return None
630
+
538
631
  def update_display():
539
632
  """Update both panels."""
540
633
  menu_control.text = _render_menu_panel(
541
- entries, current_page[0], selected_idx[0], browse_mode[0]
634
+ visible_entries[0],
635
+ current_page[0],
636
+ selected_idx[0],
637
+ browse_mode[0],
638
+ search_text=search_text[0],
639
+ in_search_mode=in_search_mode[0],
640
+ search_buffer=search_buffer[0],
641
+ status_line=_compute_status_line(),
542
642
  )
543
643
  # Show message browser if in browse mode, otherwise show preview
544
644
  if browse_mode[0] and cached_history[0] is not None:
@@ -574,6 +674,8 @@ async def interactive_autosave_picker() -> Optional[str]:
574
674
  @kb.add("up")
575
675
  @kb.add("c-p") # Ctrl+P = previous (Emacs-style)
576
676
  def _(event):
677
+ if in_search_mode[0]:
678
+ return # While typing the search buffer, arrows do nothing.
577
679
  if browse_mode[0]:
578
680
  # In browse mode: go to older message
579
681
  if cached_history[0] and message_idx[0] < len(cached_history[0]) - 1:
@@ -586,7 +688,7 @@ async def interactive_autosave_picker() -> Optional[str]:
586
688
  current_page[0] = ensure_visible_page(
587
689
  selected_idx[0],
588
690
  current_page[0],
589
- len(entries),
691
+ len(visible_entries[0]),
590
692
  PAGE_SIZE,
591
693
  )
592
694
  update_display()
@@ -594,6 +696,8 @@ async def interactive_autosave_picker() -> Optional[str]:
594
696
  @kb.add("down")
595
697
  @kb.add("c-n") # Ctrl+N = next (Emacs-style)
596
698
  def _(event):
699
+ if in_search_mode[0]:
700
+ return
597
701
  if browse_mode[0]:
598
702
  # In browse mode: go to newer message
599
703
  if message_idx[0] > 0:
@@ -601,18 +705,20 @@ async def interactive_autosave_picker() -> Optional[str]:
601
705
  update_display()
602
706
  else:
603
707
  # Normal mode: navigate sessions
604
- if selected_idx[0] < len(entries) - 1:
708
+ if selected_idx[0] < len(visible_entries[0]) - 1:
605
709
  selected_idx[0] += 1
606
710
  current_page[0] = ensure_visible_page(
607
711
  selected_idx[0],
608
712
  current_page[0],
609
- len(entries),
713
+ len(visible_entries[0]),
610
714
  PAGE_SIZE,
611
715
  )
612
716
  update_display()
613
717
 
614
718
  @kb.add("left")
615
719
  def _(event):
720
+ if in_search_mode[0]:
721
+ return
616
722
  if current_page[0] > 0:
617
723
  current_page[0] -= 1
618
724
  selected_idx[0] = current_page[0] * PAGE_SIZE
@@ -620,14 +726,25 @@ async def interactive_autosave_picker() -> Optional[str]:
620
726
 
621
727
  @kb.add("right")
622
728
  def _(event):
729
+ if in_search_mode[0]:
730
+ return
731
+ # Recompute total_pages from visible_entries every call -- filtering
732
+ # changes the list length and a stale captured value would let users
733
+ # page past the end of a filtered result.
734
+ total_pages = get_total_pages(len(visible_entries[0]), PAGE_SIZE)
623
735
  if current_page[0] < total_pages - 1:
624
736
  current_page[0] += 1
625
737
  selected_idx[0] = current_page[0] * PAGE_SIZE
626
738
  update_display()
627
739
 
628
740
  @kb.add("e")
741
+ @kb.add("E")
629
742
  def _(event):
630
- """Enter message browse mode."""
743
+ """Enter message browse mode (or feed the search buffer)."""
744
+ if in_search_mode[0]:
745
+ search_buffer[0] += "e"
746
+ update_display()
747
+ return
631
748
  if browse_mode[0]:
632
749
  return # Already in browse mode
633
750
  entry = get_current_entry()
@@ -643,20 +760,30 @@ async def interactive_autosave_picker() -> Optional[str]:
643
760
 
644
761
  @kb.add("escape")
645
762
  def _(event):
646
- """Exit browse mode or cancel."""
763
+ """Exit search mode, browse mode, or cancel -- in that priority."""
764
+ if in_search_mode[0]:
765
+ in_search_mode[0] = False
766
+ search_buffer[0] = ""
767
+ update_display()
768
+ return
647
769
  if browse_mode[0]:
648
770
  browse_mode[0] = False
649
771
  cached_history[0] = None
650
772
  message_idx[0] = 0
651
773
  update_display()
652
774
  else:
653
- # Not in browse mode - treat as cancel
775
+ # Not in any sub-mode - treat as cancel
654
776
  result[0] = None
655
777
  event.app.exit()
656
778
 
657
779
  @kb.add("q")
780
+ @kb.add("Q")
658
781
  def _(event):
659
- """Exit browse mode (only when in browse mode)."""
782
+ """Feed the search buffer, or exit browse mode if not searching."""
783
+ if in_search_mode[0]:
784
+ search_buffer[0] += "q"
785
+ update_display()
786
+ return
660
787
  if browse_mode[0]:
661
788
  browse_mode[0] = False
662
789
  cached_history[0] = None
@@ -664,12 +791,57 @@ async def interactive_autosave_picker() -> Optional[str]:
664
791
  update_display()
665
792
 
666
793
  @kb.add("enter")
667
- def _(event):
794
+ async def _(event):
795
+ if in_search_mode[0]:
796
+ # Commit the buffer as the active filter. Repaint "Filtering..."
797
+ # BEFORE doing the work so users get feedback even when the
798
+ # content index is cold and the lookup has to read pickles.
799
+ search_text[0] = search_buffer[0]
800
+ in_search_mode[0] = False
801
+ search_buffer[0] = ""
802
+ is_filtering[0] = True
803
+ update_display()
804
+ event.app.invalidate()
805
+ # Run the (potentially blocking) filter on a worker thread
806
+ # so the event loop stays responsive. ``await`` yields here,
807
+ # which also gives prompt_toolkit the tick it needs to paint
808
+ # the "Filtering..." indicator before the worker starts.
809
+ try:
810
+ filtered = await asyncio.to_thread(_filter_entries, search_text[0])
811
+ _apply_filter_result(filtered)
812
+ finally:
813
+ is_filtering[0] = False
814
+ update_display()
815
+ event.app.invalidate()
816
+ return
668
817
  entry = get_current_entry()
669
818
  if entry:
670
819
  result[0] = entry[0] # Store session name
671
820
  event.app.exit()
672
821
 
822
+ @kb.add("/")
823
+ def _(event):
824
+ """Enter search mode. Disabled inside browse mode (focus is on msgs)."""
825
+ if browse_mode[0]:
826
+ return
827
+ in_search_mode[0] = True
828
+ search_buffer[0] = ""
829
+ update_display()
830
+
831
+ @kb.add("backspace")
832
+ def _(event):
833
+ if in_search_mode[0]:
834
+ search_buffer[0] = search_buffer[0][:-1]
835
+ update_display()
836
+
837
+ for _key, _append in iter_alphabet_bindings():
838
+
839
+ @kb.add(_key)
840
+ def _alpha(event, _c=_append):
841
+ if in_search_mode[0]:
842
+ search_buffer[0] += _c
843
+ update_display()
844
+
673
845
  @kb.add("c-c")
674
846
  def _(event):
675
847
  result[0] = None
@@ -683,6 +855,32 @@ async def interactive_autosave_picker() -> Optional[str]:
683
855
  mouse_support=False,
684
856
  )
685
857
 
858
+ async def _prewarm_index() -> None:
859
+ """Eagerly populate the content index in the background.
860
+
861
+ Without this, the first content-search blocks the UI on N pickle
862
+ reads. Running the loads on a worker thread keeps the event loop
863
+ responsive; ``app.invalidate()`` after each one drives the
864
+ ``Indexing N/M...`` progress hint in the menu header.
865
+ """
866
+ for name, _meta in entries:
867
+ if name in content_index:
868
+ continue
869
+ try:
870
+ await asyncio.to_thread(content_index.lookup, name, base_dir)
871
+ except asyncio.CancelledError:
872
+ raise
873
+ except Exception:
874
+ # SessionContentIndex.lookup already swallows + caches
875
+ # load errors; we should never get here, but be paranoid.
876
+ pass
877
+ try:
878
+ app.invalidate()
879
+ except Exception:
880
+ # If the app is tearing down, invalidate may explode --
881
+ # not our problem, just stop pre-warming gracefully.
882
+ return
883
+
686
884
  set_awaiting_user_input(True)
687
885
 
688
886
  # Enter alternate screen buffer once for entire session
@@ -691,6 +889,8 @@ async def interactive_autosave_picker() -> Optional[str]:
691
889
  sys.stdout.flush()
692
890
  await asyncio.sleep(0.05)
693
891
 
892
+ prewarm_task = asyncio.create_task(_prewarm_index())
893
+
694
894
  try:
695
895
  # Initial display
696
896
  update_display()
@@ -703,6 +903,13 @@ async def interactive_autosave_picker() -> Optional[str]:
703
903
  await app.run_async()
704
904
 
705
905
  finally:
906
+ # Cancel the background pre-warm if it hasn't finished; suppress
907
+ # the resulting CancelledError so the picker exits cleanly.
908
+ prewarm_task.cancel()
909
+ try:
910
+ await prewarm_task
911
+ except (asyncio.CancelledError, Exception):
912
+ pass
706
913
  # Exit alternate screen buffer once at end
707
914
  sys.stdout.write("\033[?1049l") # Exit alternate buffer
708
915
  sys.stdout.flush()
@@ -0,0 +1,184 @@
1
+ """Search/filter helpers for the ``/resume`` (autosave_load) session picker.
2
+
3
+ Lives separately from :mod:`code_puppy.command_line.autosave_menu` so that
4
+ file (already overweight at ~700+ lines) does not grow further. Everything
5
+ in here is pure / IO-tolerant so it is unit-testable without spinning up
6
+ a prompt_toolkit ``Application``.
7
+
8
+ The picker UX mirrors ``/set``: pressing ``/`` enters search mode, alphabet
9
+ chars append to a buffer, ``Enter`` commits the buffer, ``Esc`` cancels the
10
+ search. The novel bit is that this picker filters on *session content* --
11
+ the concatenated text of every message in each session -- rather than just
12
+ the timestamp/metadata visible in the left menu. Content is loaded lazily
13
+ and cached for the picker's lifetime so we do not re-unpickle session
14
+ files on every keystroke.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from datetime import datetime
20
+ from pathlib import Path
21
+ from threading import Lock
22
+ from typing import Callable, Dict, Iterator, Optional, Tuple
23
+
24
+ from code_puppy.session_storage import load_session
25
+
26
+ # Alphabet bound to the search buffer while ``in_search_mode`` is True.
27
+ # Lowercase ASCII letters (except ``e`` and ``q``), digits, underscore,
28
+ # hyphen, and space. ``e`` and ``q`` are deliberately excluded because
29
+ # autosave_menu.py already binds them to nav actions (browse-msgs and
30
+ # exit-browse respectively); those existing handlers are dual-mode --
31
+ # they append to the search buffer when ``in_search_mode`` is True and
32
+ # fire their nav action otherwise. Same trick set_menu uses for ``r``.
33
+ SEARCH_ALPHABET = "abcdfghijklmnoprstuvwxyz0123456789_- "
34
+
35
+
36
+ def iter_alphabet_bindings() -> Iterator[Tuple[str, str]]:
37
+ """Yield ``(key_to_bind, char_to_append)`` pairs for the alphabet.
38
+
39
+ For each lowercase character in :data:`SEARCH_ALPHABET` we yield both
40
+ the lowercase and (when distinct) the uppercase form. Both forms
41
+ append the *lowercase* character so the buffer stays case-folded --
42
+ search match is already case-insensitive, and folding on input means
43
+ the rendered ``Searching: '...'`` line is stable regardless of
44
+ Shift/Caps Lock state.
45
+
46
+ Yields lowercase before uppercase so prompt_toolkit's keybinding
47
+ registration order is deterministic and easy to reason about.
48
+ """
49
+ for ch in SEARCH_ALPHABET:
50
+ yield ch, ch
51
+ upper = ch.upper()
52
+ if upper != ch:
53
+ yield upper, ch
54
+
55
+
56
+ def _session_text(history: list) -> str:
57
+ """Concatenate the raw text content of every message part.
58
+
59
+ Deliberately uses raw ``part.content`` (only when it is a non-empty
60
+ ``str``) instead of going through
61
+ :func:`code_puppy.command_line.autosave_menu._extract_message_content`.
62
+ That helper decorates tool messages with ``"Tool Call: <name>"`` and
63
+ ``"Tool Result: <name>"`` prefixes -- if we indexed the decorated
64
+ form, typing ``tool call`` would match every session that ever
65
+ called a tool. Noise, not signal.
66
+ """
67
+ chunks: list = []
68
+ for msg in history:
69
+ for part in getattr(msg, "parts", ()) or ():
70
+ content = getattr(part, "content", None)
71
+ if isinstance(content, str) and content:
72
+ chunks.append(content)
73
+ return "\n".join(chunks)
74
+
75
+
76
+ class SessionContentIndex:
77
+ """Lazy, per-picker cache of ``{session_name -> lowercased text}``.
78
+
79
+ Loading a session unpickles the whole file, so we only do it on
80
+ demand and cache the result for the lifetime of one picker
81
+ invocation. Errors are tolerated and cached as empty strings so we
82
+ do not re-hit broken files on every keystroke.
83
+
84
+ The ``loader`` injection point exists for testing -- production code
85
+ always uses :func:`code_puppy.session_storage.load_session`.
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ loader: Optional[Callable[[str, Path], list]] = None,
91
+ ) -> None:
92
+ self._loader = loader or load_session
93
+ self._cache: Dict[str, str] = {}
94
+ # The pre-warm task runs on an ``asyncio.to_thread`` worker while
95
+ # the picker's render path reads cache state on the event loop;
96
+ # the post-Enter filter (PR review accept #2) also runs on a
97
+ # worker. CPython's GIL makes individual dict ops atomic, but
98
+ # the ``check-then-load-then-store`` sequence in ``lookup`` is
99
+ # not -- the lock keeps the invariant tidy and survives a future
100
+ # free-threaded build where GIL atomicity disappears.
101
+ self._lock = Lock()
102
+
103
+ def lookup(self, session_name: str, base_dir: Path) -> str:
104
+ with self._lock:
105
+ cached = self._cache.get(session_name)
106
+ if cached is not None or session_name in self._cache:
107
+ # ``cached is not None`` short-circuits the common case;
108
+ # the explicit ``in`` check picks up cached-empty-string
109
+ # entries (failed loads) without re-running the loader.
110
+ return cached or ""
111
+ # Loader runs OUTSIDE the lock so a slow pickle read does not
112
+ # block other threads from checking the cache for unrelated keys.
113
+ try:
114
+ history = self._loader(session_name, base_dir)
115
+ text = _session_text(history).lower()
116
+ except Exception:
117
+ # Cache the failure so a broken pickle does not slow every keystroke.
118
+ text = ""
119
+ with self._lock:
120
+ # Last-writer-wins. If a concurrent ``lookup`` for the same
121
+ # key beat us here that is fine -- the value is identical.
122
+ self._cache[session_name] = text
123
+ return text
124
+
125
+ def count(self) -> int:
126
+ """Number of sessions cached so far (success or failure).
127
+
128
+ The picker uses this to render an ``Indexing N/M…`` progress hint
129
+ while the background pre-warm task is still running.
130
+ """
131
+ with self._lock:
132
+ return len(self._cache)
133
+
134
+ def __contains__(self, session_name: object) -> bool:
135
+ """Membership check so callers don't have to peek at ``_cache``.
136
+
137
+ Used by the picker's pre-warm loop to skip sessions that were
138
+ already loaded on demand by an earlier ``lookup``.
139
+ """
140
+ with self._lock:
141
+ return session_name in self._cache
142
+
143
+
144
+ def _formatted_timestamp(metadata: dict) -> str:
145
+ """Mirror the ``YYYY-MM-DD HH:MM`` format the left menu shows.
146
+
147
+ Search must hit what the user *sees*, so the formatting has to
148
+ match :func:`autosave_menu._render_menu_panel`.
149
+ """
150
+ timestamp = metadata.get("timestamp", "")
151
+ try:
152
+ dt = datetime.fromisoformat(timestamp)
153
+ return dt.strftime("%Y-%m-%d %H:%M")
154
+ except (ValueError, TypeError):
155
+ return str(timestamp)
156
+
157
+
158
+ def entry_matches(
159
+ entry: Tuple[str, dict],
160
+ needle: str,
161
+ index: SessionContentIndex,
162
+ base_dir: Path,
163
+ ) -> bool:
164
+ """Return True if ``needle`` is a substring of the session.
165
+
166
+ Empty needle matches everything (the picker shows the full list).
167
+ Cheap metadata checks (session name, formatted timestamp, message
168
+ count) run first; the full session content is only loaded via
169
+ ``index`` if the cheap checks miss. Typing ``2026-06`` therefore
170
+ never triggers a single pickle read.
171
+ """
172
+ if not needle:
173
+ return True
174
+ needle_lower = needle.lower()
175
+ session_name, metadata = entry
176
+
177
+ if needle_lower in session_name.lower():
178
+ return True
179
+ if needle_lower in _formatted_timestamp(metadata).lower():
180
+ return True
181
+ if needle_lower in str(metadata.get("message_count", "")).lower():
182
+ return True
183
+
184
+ return needle_lower in index.lookup(session_name, base_dir)