code-puppy 0.0.781__tar.gz → 0.0.783__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 (273) hide show
  1. {code_puppy-0.0.781 → code_puppy-0.0.783}/PKG-INFO +3 -4
  2. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/cli_runner.py +14 -63
  3. code_puppy-0.0.783/code_puppy/command_line/completers.py +434 -0
  4. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/file_path_completion.py +1 -2
  5. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/load_context_completion.py +1 -1
  6. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/custom_server_form.py +15 -8
  7. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/install_menu.py +7 -7
  8. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp_completion.py +1 -2
  9. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/model_picker_completion.py +1 -25
  10. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/pin_command_completion.py +1 -2
  11. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/skills_completion.py +1 -2
  12. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_completion.py +4 -50
  13. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_history.py +9 -27
  14. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/pydantic_patches.py +1 -62
  15. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/theme.py +5 -5
  16. {code_puppy-0.0.781 → code_puppy-0.0.783}/pyproject.toml +3 -4
  17. code_puppy-0.0.781/code_puppy/command_line/prompt_toolkit_completion.py +0 -1071
  18. {code_puppy-0.0.781 → code_puppy-0.0.783}/.gitignore +0 -0
  19. {code_puppy-0.0.781 → code_puppy-0.0.783}/LICENSE +0 -0
  20. {code_puppy-0.0.781 → code_puppy-0.0.783}/README.md +0 -0
  21. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/__init__.py +0 -0
  22. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/__main__.py +0 -0
  23. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agent_execution_context.py +0 -0
  24. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/__init__.py +0 -0
  25. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_builder.py +0 -0
  26. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_compaction.py +0 -0
  27. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_diagnostics.py +0 -0
  28. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_history.py +0 -0
  29. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_key_listeners.py +0 -0
  30. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_model_message_transform.py +0 -0
  31. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_non_streaming_render.py +0 -0
  32. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_output_limits.py +0 -0
  33. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_run_signals.py +0 -0
  34. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_runtime.py +0 -0
  35. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_steer_processor.py +0 -0
  36. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/_subagent_recursion.py +0 -0
  37. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_code_puppy.py +0 -0
  38. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_creator_agent.py +0 -0
  39. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_helios.py +0 -0
  40. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_manager.py +0 -0
  41. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_model_judge.py +0 -0
  42. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_planning.py +0 -0
  43. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_qa_kitten.py +0 -0
  44. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/agent_web_retriever.py +0 -0
  45. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/base_agent.py +0 -0
  46. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/event_stream_handler.py +0 -0
  47. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/json_agent.py +0 -0
  48. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/retry_profiles.py +0 -0
  49. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/run_stats.py +0 -0
  50. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/smooth_stream.py +0 -0
  51. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/agents/subagent_stream_handler.py +0 -0
  52. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/atomic_io.py +0 -0
  53. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/atomic_json.py +0 -0
  54. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/callbacks.py +0 -0
  55. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/chatgpt_codex_client.py +0 -0
  56. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/claude_cache_client.py +0 -0
  57. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/__init__.py +0 -0
  58. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/add_model_details.py +0 -0
  59. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/add_model_menu.py +0 -0
  60. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/agent_menu.py +0 -0
  61. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/attachments.py +0 -0
  62. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/autosave_menu.py +0 -0
  63. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/autosave_search.py +0 -0
  64. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/clipboard.py +0 -0
  65. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/colors_menu.py +0 -0
  66. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/command_handler.py +0 -0
  67. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/command_registry.py +0 -0
  68. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/completion_cache.py +0 -0
  69. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/config_apply.py +0 -0
  70. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/config_commands.py +0 -0
  71. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/core_commands.py +0 -0
  72. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/file_index.py +0 -0
  73. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/help_catalog.py +0 -0
  74. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/help_overlay.py +0 -0
  75. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/image_utils.py +0 -0
  76. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/__init__.py +0 -0
  77. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/base.py +0 -0
  78. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  79. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  80. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/edit_command.py +0 -0
  81. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/handler.py +0 -0
  82. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/help_command.py +0 -0
  83. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/install_command.py +0 -0
  84. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/list_command.py +0 -0
  85. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/logs_command.py +0 -0
  86. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
  87. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/remove_command.py +0 -0
  88. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/restart_command.py +0 -0
  89. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/search_command.py +0 -0
  90. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
  91. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  92. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/start_command.py +0 -0
  93. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/status_command.py +0 -0
  94. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  95. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/stop_command.py +0 -0
  96. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/trust_command.py +0 -0
  97. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/utils.py +0 -0
  98. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  99. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/mcp_binding_menu.py +0 -0
  100. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/menu_session.py +0 -0
  101. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/model_settings_defs.py +0 -0
  102. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/model_settings_menu.py +0 -0
  103. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/onboarding_slides.py +0 -0
  104. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/onboarding_wizard.py +0 -0
  105. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/pagination.py +0 -0
  106. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/session_browser.py +0 -0
  107. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/session_browser_data.py +0 -0
  108. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/session_commands.py +0 -0
  109. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu.py +0 -0
  110. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu_catalog.py +0 -0
  111. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu_schema.py +0 -0
  112. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu_settings.py +0 -0
  113. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu_shims.py +0 -0
  114. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/set_menu_values.py +0 -0
  115. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/shell_passthrough.py +0 -0
  116. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/tui_style.py +0 -0
  117. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/uc_menu.py +0 -0
  118. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/command_line/utils.py +0 -0
  119. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/config.py +0 -0
  120. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/config_file.py +0 -0
  121. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/error_logging.py +0 -0
  122. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/gemini_code_assist.py +0 -0
  123. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/gemini_model.py +0 -0
  124. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/README.md +0 -0
  125. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/__init__.py +0 -0
  126. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/aliases.py +0 -0
  127. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/engine.py +0 -0
  128. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/executor.py +0 -0
  129. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/matcher.py +0 -0
  130. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/models.py +0 -0
  131. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/registry.py +0 -0
  132. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/hook_engine/validator.py +0 -0
  133. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/http_utils.py +0 -0
  134. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/__init__.py +0 -0
  135. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/audit.py +0 -0
  136. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/catalog.py +0 -0
  137. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/formats.py +0 -0
  138. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/locale.py +0 -0
  139. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/locales/en-US.json +0 -0
  140. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/locales/es.json +0 -0
  141. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/locales/fr-CA.json +0 -0
  142. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/plurals.py +0 -0
  143. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/pseudo.py +0 -0
  144. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/i18n/translate.py +0 -0
  145. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/kennel_provider.py +0 -0
  146. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/keymap.py +0 -0
  147. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/list_filtering.py +0 -0
  148. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/main.py +0 -0
  149. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/__init__.py +0 -0
  150. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/agent_bindings.py +0 -0
  151. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/async_lifecycle.py +0 -0
  152. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/blocking_startup.py +0 -0
  153. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/circuit_breaker.py +0 -0
  154. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/config_wizard.py +0 -0
  155. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/dashboard.py +0 -0
  156. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/error_isolation.py +0 -0
  157. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/examples/retry_example.py +0 -0
  158. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/health_monitor.py +0 -0
  159. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/managed_server.py +0 -0
  160. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/manager.py +0 -0
  161. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/mcp_logs.py +0 -0
  162. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/project_config.py +0 -0
  163. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/registry.py +0 -0
  164. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/retry_manager.py +0 -0
  165. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  166. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/status_tracker.py +0 -0
  167. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/system_tools.py +0 -0
  168. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
  169. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_/toolset_utils.py +0 -0
  170. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_prompts/__init__.py +0 -0
  171. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/mcp_prompts/hook_creator.py +0 -0
  172. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/__init__.py +0 -0
  173. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/bar_painters.py +0 -0
  174. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/bar_rendering.py +0 -0
  175. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/bottom_bar.py +0 -0
  176. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/bus.py +0 -0
  177. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/chords.py +0 -0
  178. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/commands.py +0 -0
  179. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_actions.py +0 -0
  180. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_display.py +0 -0
  181. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_keys.py +0 -0
  182. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/editor_paste.py +0 -0
  183. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/external_editor.py +0 -0
  184. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/inline_bar.py +0 -0
  185. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/line_editor.py +0 -0
  186. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/markdown_patches.py +0 -0
  187. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/message_queue.py +0 -0
  188. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/messages.py +0 -0
  189. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/pause_controller.py +0 -0
  190. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/prompt_prefix_style.py +0 -0
  191. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/queue_console.py +0 -0
  192. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/renderers.py +0 -0
  193. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/rich_renderer.py +0 -0
  194. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/run_ui.py +0 -0
  195. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/run_ui_wiring.py +0 -0
  196. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/spinner/__init__.py +0 -0
  197. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/subagent_console.py +0 -0
  198. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/messaging/transcript_guard.py +0 -0
  199. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/model_descriptions.py +0 -0
  200. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/model_factory.py +0 -0
  201. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/model_switching.py +0 -0
  202. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/model_utils.py +0 -0
  203. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/models.json +0 -0
  204. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/models_dev_api.json +0 -0
  205. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/models_dev_parser.py +0 -0
  206. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/observability.py +0 -0
  207. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/platform_utils.py +0 -0
  208. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/__init__.py +0 -0
  209. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/config.py +0 -0
  210. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/oauth_pasteback.py +0 -0
  211. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  212. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/trust.py +0 -0
  213. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/plugins/trust_notice.py +0 -0
  214. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/provider_credentials.py +0 -0
  215. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/provider_identity.py +0 -0
  216. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/reopenable_async_client.py +0 -0
  217. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/round_robin_model.py +0 -0
  218. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/secret_store.py +0 -0
  219. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/secret_store_backends.py +0 -0
  220. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/session_format_migration.py +0 -0
  221. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/session_lifecycle.py +0 -0
  222. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/session_migration.py +0 -0
  223. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/session_storage.py +0 -0
  224. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/session_surrogate_unpickler.py +0 -0
  225. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/skill_provider.py +0 -0
  226. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/splash.py +0 -0
  227. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/status_display.py +0 -0
  228. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/terminal_utils.py +0 -0
  229. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/token_usage.py +0 -0
  230. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/__init__.py +0 -0
  231. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/agent_tools.py +0 -0
  232. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/__init__.py +0 -0
  233. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/constants.py +0 -0
  234. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
  235. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/handler.py +0 -0
  236. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/models.py +0 -0
  237. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/registration.py +0 -0
  238. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/renderers.py +0 -0
  239. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
  240. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
  241. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/__init__.py +0 -0
  242. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_control.py +0 -0
  243. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_interactions.py +0 -0
  244. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
  245. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_locators.py +0 -0
  246. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_manager.py +0 -0
  247. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_navigation.py +0 -0
  248. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
  249. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  250. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_scripts.py +0 -0
  251. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
  252. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/browser_workflows.py +0 -0
  253. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/browser/tool_registry.py +0 -0
  254. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/command_runner.py +0 -0
  255. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/common.py +0 -0
  256. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/display.py +0 -0
  257. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/file_modifications.py +0 -0
  258. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/file_operations.py +0 -0
  259. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/file_permission_state.py +0 -0
  260. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/fs_access.py +0 -0
  261. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/image_tools.py +0 -0
  262. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/io_backends.py +0 -0
  263. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/model_tools.py +0 -0
  264. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/shell_backgrounding.py +0 -0
  265. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/skills_tools.py +0 -0
  266. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/subagent_context.py +0 -0
  267. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/subagent_invocation.py +0 -0
  268. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/subagent_usage_metrics.py +0 -0
  269. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/tools_content.py +0 -0
  270. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/tools/universal_constructor.py +0 -0
  271. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/undo_manager.py +0 -0
  272. {code_puppy-0.0.781 → code_puppy-0.0.783}/code_puppy/universal_constructor_provider.py +0 -0
  273. {code_puppy-0.0.781 → code_puppy-0.0.783}/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.781
3
+ Version: 0.0.783
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
@@ -20,7 +20,7 @@ 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
23
- Requires-Dist: code-puppy-core-plugins>=0.0.12
23
+ Requires-Dist: code-puppy-core-plugins>=0.0.26
24
24
  Requires-Dist: httpx[http2]>=0.24.1
25
25
  Requires-Dist: json-repair>=0.46.2
26
26
  Requires-Dist: keyring>=24.0.0
@@ -30,7 +30,6 @@ Requires-Dist: openai>=2.45.0
30
30
  Requires-Dist: packaging>=24.0
31
31
  Requires-Dist: pillow>=10.0.0
32
32
  Requires-Dist: playwright>=1.40.0; sys_platform != 'android'
33
- Requires-Dist: prompt-toolkit>=3.0.52
34
33
  Requires-Dist: pydantic-ai-harness==0.23.0
35
34
  Requires-Dist: pydantic-ai-slim[anthropic,mcp,openai]==2.31.0
36
35
  Requires-Dist: pydantic>=2.4.0
@@ -40,7 +39,7 @@ Requires-Dist: rapidfuzz>=3.13.0
40
39
  Requires-Dist: requests>=2.28.0
41
40
  Requires-Dist: rich>=13.4.2
42
41
  Requires-Dist: ripgrep==14.1.0; sys_platform != 'android'
43
- Requires-Dist: termflow-md>=0.8.0
42
+ Requires-Dist: termflow-md>=0.9.0
44
43
  Requires-Dist: typer>=0.12.0
45
44
  Provides-Extra: bedrock
46
45
  Requires-Dist: boto3>=1.35.0; extra == 'bedrock'
@@ -371,7 +371,7 @@ async def main():
371
371
  )
372
372
 
373
373
  if disable_windows_ctrl_c():
374
- # Keep the clamp sticky across terminal resets / prompt_toolkit mode restores.
374
+ # Keep the clamp sticky across terminal resets / console mode restores.
375
375
  set_keep_ctrl_c_disabled(True)
376
376
 
377
377
  # Load API keys from puppy.cfg into environment variables
@@ -618,7 +618,7 @@ async def main():
618
618
  def _use_persistent_prompt() -> bool:
619
619
  """Should the REPL use the persistent bottom-bar prompt (Phase A)?
620
620
 
621
- False (→ classic prompt_toolkit path) when:
621
+ False (-> classic plain-input path) when:
622
622
  * rollback flag: env CODE_PUPPY_CLASSIC_PROMPT=1 or config
623
623
  ``classic_prompt`` truthy — protects the eyeball-testing period;
624
624
  * CODE_PUPPY_NO_TUI=1 (tests / pexpect harnesses);
@@ -666,7 +666,7 @@ def _persistent_prompt_parts() -> tuple:
666
666
  in-band escapes, so colors can't ride inside the string itself).
667
667
  """
668
668
  try:
669
- from code_puppy.command_line.prompt_toolkit_completion import (
669
+ from code_puppy.command_line.completers import (
670
670
  PROMPT_STYLES,
671
671
  get_prompt_with_active_model,
672
672
  )
@@ -820,35 +820,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
820
820
 
821
821
  emit_error(t("cli.initial_command.error", error=str(e)))
822
822
 
823
- # Check if prompt_toolkit is installed
824
- try:
825
- from code_puppy.command_line.prompt_toolkit_completion import (
826
- get_input_with_combined_completion,
827
- get_prompt_with_active_model,
828
- )
829
- except ImportError:
830
- from code_puppy.messaging import emit_warning
831
-
832
- emit_warning(t("cli.prompt_toolkit.installing"))
833
- try:
834
- import subprocess
835
-
836
- subprocess.check_call(
837
- [sys.executable, "-m", "pip", "install", "--quiet", "prompt_toolkit"]
838
- )
839
- from code_puppy.messaging import emit_success
840
-
841
- emit_success(t("cli.prompt_toolkit.installed"))
842
- from code_puppy.command_line.prompt_toolkit_completion import (
843
- get_input_with_combined_completion,
844
- get_prompt_with_active_model,
845
- )
846
- except Exception as e:
847
- from code_puppy.messaging import emit_error, emit_warning
848
-
849
- emit_error(t("cli.prompt_toolkit.install_error", error=e))
850
- emit_warning(t("cli.prompt_toolkit.fallback"))
851
-
852
823
  # Autosave loading is now manual - use /autosave_load command
853
824
 
854
825
  record_terminal_session(get_current_session_name(), overwrite=False)
@@ -926,47 +897,27 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
926
897
  # doubled every line's noise. Text() so bracket-y input renders as-is.
927
898
  emit_info(_prompt_echo_text(task))
928
899
  else:
929
- # Use prompt_toolkit for enhanced input with path completion
930
- try:
931
- # Windows-specific: Reset terminal state before prompting
932
- reset_windows_terminal_ansi()
933
-
934
- # Use the async version of get_input_with_combined_completion
935
- task = await get_input_with_combined_completion(
936
- get_prompt_with_active_model(),
937
- history_file=COMMAND_HISTORY_FILE,
938
- )
939
-
940
- # Windows: re-clamp raw-Ctrl+C mode after prompt_toolkit
941
- # (prompt_toolkit restores console mode on exit)
942
- try:
943
- from code_puppy.terminal_utils import ensure_ctrl_c_disabled
900
+ # Classic (non-TTY / rollback) prompt: plain blocking input.
901
+ # No completion here -- the persistent bottom-bar editor is
902
+ # the featured path; this branch exists for pipes, CI, and
903
+ # the CODE_PUPPY_CLASSIC_PROMPT escape hatch.
904
+ reset_windows_terminal_ansi()
905
+ task = input(">>> ")
906
+ from code_puppy.messaging.editor_history import HistoryStore
944
907
 
945
- ensure_ctrl_c_disabled()
946
- except ImportError:
947
- pass
948
- except ImportError:
949
- # Fall back to basic input if prompt_toolkit is not available
950
- task = input(">>> ")
908
+ HistoryStore(COMMAND_HISTORY_FILE).append(task)
951
909
 
952
910
  except (KeyboardInterrupt, asyncio.CancelledError) as cancel_exc:
953
911
  # Ctrl+C: cancel input and continue. Reset terminal state on Windows
954
912
  # so it doesn't become unresponsive.
955
913
  reset_windows_terminal_full()
956
914
  from code_puppy.callbacks import on_interactive_turn_cancel
957
- from code_puppy.command_line.prompt_toolkit_completion import (
958
- pop_non_ctrl_c_cancel,
959
- )
960
915
  from code_puppy.messaging import emit_success, emit_warning
961
916
  from code_puppy.messaging.run_ui import DOUBLE_CTRL_C_WINDOW_S
962
917
 
963
- # Double Ctrl+C at the idle prompt quits like Ctrl+D. Escape and
964
- # Ctrl+X raise the same KeyboardInterrupt but mark themselves, so
965
- # mashing Escape can never exit; CancelledError never counts.
966
- was_plain_ctrl_c = (
967
- isinstance(cancel_exc, KeyboardInterrupt)
968
- and not pop_non_ctrl_c_cancel()
969
- )
918
+ # Double Ctrl+C at the idle prompt quits like Ctrl+D.
919
+ # CancelledError never counts.
920
+ was_plain_ctrl_c = isinstance(cancel_exc, KeyboardInterrupt)
970
921
  now = time.monotonic()
971
922
  if was_plain_ctrl_c and now - last_idle_ctrl_c <= DOUBLE_CTRL_C_WINDOW_S:
972
923
  emit_success("\n" + t("cli.goodbye_ctrld"))
@@ -0,0 +1,434 @@
1
+ """Slash-command completers and the REPL prompt prefix.
2
+
3
+ Home of the completion stack (on termflow's completer protocol) and the
4
+ prompt-prefix builder, extracted from the retired classic
5
+ prompt_toolkit input path. The raw bottom-bar editor consumes the stack
6
+ through :func:`build_completer_stack`.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import unicodedata
13
+
14
+ from termflow.tui.completion import Completer, Completion, merge_completers
15
+
16
+ from code_puppy.command_line.command_registry import get_unique_commands
17
+ from code_puppy.command_line.completion_cache import TTLCache
18
+ from code_puppy.command_line.utils import list_directory
19
+ from code_puppy.config import (
20
+ get_config_keys,
21
+ get_puppy_name,
22
+ get_value,
23
+ )
24
+
25
+ _config_keys_cache: TTLCache[tuple[object, list[str]]] = TTLCache()
26
+ _slash_catalog_cache: TTLCache[list[dict[str, str]]] = TTLCache()
27
+
28
+
29
+ def _sanitize_for_encoding(text: str) -> str:
30
+ """Remove or replace characters that can't be safely encoded.
31
+
32
+ Handles lone surrogates (U+D800-U+DFFF, invalid in UTF-8) and other
33
+ problematic sequences from Windows copy-paste.
34
+ """
35
+ try:
36
+ text.encode("utf-8")
37
+ return text # String is already valid UTF-8
38
+ except UnicodeEncodeError:
39
+ pass
40
+
41
+ try:
42
+ return text.encode("utf-8", errors="surrogatepass").decode(
43
+ "utf-8", errors="replace"
44
+ )
45
+ except (UnicodeEncodeError, UnicodeDecodeError):
46
+ # Last resort: filter out all non-BMP and surrogate characters
47
+ return "".join(
48
+ char
49
+ for char in text
50
+ if ord(char) < 0xD800 or (ord(char) > 0xDFFF and ord(char) < 0x10000)
51
+ )
52
+
53
+
54
+ def _strip_variation_selectors(text: str) -> str:
55
+ """Remove variation selectors (U+FE00-FE0F) from text.
56
+
57
+ These invisible characters modify emoji rendering but cause width
58
+ calculation mismatches between line editors and terminal emulators.
59
+ """
60
+ return "".join(c for c in text if not (0xFE00 <= ord(c) <= 0xFE0F))
61
+
62
+
63
+ def _normalize_emoji_spacing(text: str) -> str:
64
+ """Normalize emoji spacing for consistent terminal rendering.
65
+
66
+ Some emojis have East Asian Width 'N' (Neutral) which terminals render
67
+ inconsistently. This adds a space after such emojis to prevent
68
+ the following character from overlapping.
69
+ """
70
+ result = []
71
+ text = _strip_variation_selectors(text)
72
+ for char in text:
73
+ result.append(char)
74
+ # Add padding after Neutral-width emoji to prevent overlap
75
+ if (
76
+ 0x1F300 <= ord(char) <= 0x1FAFF
77
+ and unicodedata.east_asian_width(char) == "N"
78
+ ):
79
+ result.append(" ") # Extra space buffer
80
+ return "".join(result)
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Completers
85
+ # ---------------------------------------------------------------------------
86
+
87
+
88
+ class SetCompleter(Completer):
89
+ def __init__(self, trigger: str = "/set"):
90
+ self.trigger = trigger
91
+
92
+ def get_completions(self, document, complete_event):
93
+ cursor_position = document.cursor_position
94
+ text_before_cursor = document.text_before_cursor
95
+ stripped_text_for_trigger_check = text_before_cursor.lstrip()
96
+
97
+ # If user types just /set (no space), suggest adding a space
98
+ if stripped_text_for_trigger_check == self.trigger:
99
+ yield Completion(
100
+ self.trigger + " ",
101
+ start_position=-len(self.trigger),
102
+ display=self.trigger + " ",
103
+ display_meta="set config key",
104
+ )
105
+ return
106
+
107
+ # Require a space after /set before showing completions
108
+ if not stripped_text_for_trigger_check.startswith(self.trigger + " "):
109
+ return
110
+
111
+ # Determine the part of the text that is relevant for this completer
112
+ # (handles cases like " /set foo" where the trigger isn't at index 0)
113
+ actual_trigger_pos = text_before_cursor.find(self.trigger)
114
+
115
+ # Extract the input after /set and space (up to cursor)
116
+ trigger_end = actual_trigger_pos + len(self.trigger) + 1 # +1 for the space
117
+ text_after_trigger = text_before_cursor[trigger_end:cursor_position].lstrip()
118
+ start_position = -len(text_after_trigger)
119
+
120
+ # --- SPECIAL HANDLING FOR 'model' KEY ---
121
+ if text_after_trigger == "model":
122
+ # Don't return any completions -- let ModelNameCompleter handle it
123
+ return
124
+
125
+ # Get config keys and sort them alphabetically for consistent display.
126
+ # Per-model controls belong exclusively to /model_settings.
127
+ from code_puppy.command_line.config_apply import MODEL_SETTINGS_ONLY_KEYS
128
+
129
+ cached_getter, config_keys = _config_keys_cache.get(
130
+ lambda: (get_config_keys, sorted(get_config_keys()))
131
+ )
132
+ if cached_getter is not get_config_keys:
133
+ _config_keys_cache.clear()
134
+ _, config_keys = _config_keys_cache.get(
135
+ lambda: (get_config_keys, sorted(get_config_keys()))
136
+ )
137
+
138
+ for key in config_keys:
139
+ if key in {"model", "puppy_token"} | MODEL_SETTINGS_ONLY_KEYS:
140
+ continue
141
+ if key.startswith(text_after_trigger):
142
+ prev_value = get_value(key)
143
+ value_part = f" = {prev_value}" if prev_value is not None else " = "
144
+ completion_text = f"{key}{value_part}"
145
+
146
+ yield Completion(
147
+ completion_text,
148
+ start_position=start_position,
149
+ display_meta="",
150
+ )
151
+
152
+
153
+ class CDCompleter(Completer):
154
+ def __init__(self, trigger: str = "/cd"):
155
+ self.trigger = trigger
156
+
157
+ def get_completions(self, document, complete_event):
158
+ text_before_cursor = document.text_before_cursor
159
+ stripped_text = text_before_cursor.lstrip()
160
+
161
+ # Require a space after /cd before showing completions
162
+ if not stripped_text.startswith(self.trigger + " "):
163
+ return
164
+
165
+ # Extract the directory path after /cd and space (up to cursor)
166
+ trigger_pos = text_before_cursor.find(self.trigger)
167
+ trigger_end = trigger_pos + len(self.trigger) + 1 # +1 for the space
168
+ dir_path = text_before_cursor[trigger_end:].lstrip()
169
+ start_position = -(len(dir_path))
170
+
171
+ try:
172
+ # Treat a bare `~` as `~/` so we complete inside the home
173
+ # directory, not the parent containing the username folder.
174
+ lookup_path = "~/" if dir_path == "~" else dir_path
175
+ expanded_lookup = os.path.expanduser(lookup_path)
176
+
177
+ # If the typed path ends with a separator, we're completing inside
178
+ # that directory and should match all child names.
179
+ if lookup_path.endswith(os.sep):
180
+ part = expanded_lookup
181
+ name_prefix = ""
182
+ else:
183
+ part = os.path.dirname(expanded_lookup) or "."
184
+ name_prefix = os.path.basename(expanded_lookup)
185
+
186
+ dirs, _ = list_directory(part)
187
+ dirnames = [d for d in dirs if d.startswith(name_prefix)]
188
+
189
+ # Preserve user's typed style (~, relative, absolute) in emitted
190
+ # completion text instead of leaking expanded absolute paths.
191
+ if dir_path == "~":
192
+ typed_base = "~"
193
+ elif dir_path.endswith(os.sep):
194
+ stripped_base = dir_path.rstrip(os.sep)
195
+ if not stripped_base and dir_path.startswith(os.sep):
196
+ typed_base = os.sep
197
+ else:
198
+ typed_base = stripped_base
199
+ else:
200
+ typed_base = os.path.dirname(dir_path.rstrip(os.sep))
201
+
202
+ for d in dirnames:
203
+ suggestion = os.path.join(typed_base, d) if typed_base else d
204
+ suggestion = suggestion.rstrip(os.sep) + os.sep
205
+ yield Completion(
206
+ suggestion,
207
+ start_position=start_position,
208
+ display=d + os.sep,
209
+ display_meta="Directory",
210
+ )
211
+ except Exception:
212
+ # Silently ignore errors (e.g., permission issues, non-existent dir)
213
+ pass
214
+
215
+
216
+ class AgentCompleter(Completer):
217
+ """A completer that triggers on '/agent' to show available agents."""
218
+
219
+ def __init__(self, trigger: str = "/agent", prefix: str = ""):
220
+ self.trigger = trigger
221
+ self.prefix = prefix
222
+
223
+ def get_completions(self, document, complete_event):
224
+ cursor_position = document.cursor_position
225
+ text_before_cursor = document.text_before_cursor
226
+ stripped_text = text_before_cursor.lstrip()
227
+
228
+ # Require a space after /agent before showing completions
229
+ if not stripped_text.startswith(self.trigger + " "):
230
+ return
231
+
232
+ # Extract the input after /agent and space (up to cursor)
233
+ trigger_pos = text_before_cursor.find(self.trigger)
234
+ trigger_end = trigger_pos + len(self.trigger) + 1 # +1 for the space
235
+ text_after_trigger = text_before_cursor[trigger_end:cursor_position].lstrip()
236
+ if self.prefix:
237
+ if not text_after_trigger.startswith(self.prefix):
238
+ return
239
+ text_after_trigger = text_after_trigger[len(self.prefix) :]
240
+ start_position = -len(text_after_trigger)
241
+
242
+ try:
243
+ from code_puppy.command_line.pin_command_completion import load_agent_names
244
+
245
+ agent_names = load_agent_names()
246
+ except Exception:
247
+ return
248
+
249
+ try:
250
+ from code_puppy.command_line.pin_command_completion import (
251
+ _get_agent_display_meta,
252
+ )
253
+ except ImportError:
254
+ _get_agent_display_meta = lambda x: "default" # noqa: E731
255
+
256
+ for agent_name in agent_names:
257
+ if agent_name.lower().startswith(text_after_trigger.lower()):
258
+ yield Completion(
259
+ agent_name,
260
+ start_position=start_position,
261
+ display=agent_name,
262
+ display_meta=_get_agent_display_meta(agent_name),
263
+ )
264
+
265
+
266
+ def _read_slash_catalog() -> list[dict[str, str]]:
267
+ """Load registered and plugin command help without filtering or reordering."""
268
+ catalog = []
269
+ for cmd in get_unique_commands():
270
+ catalog.append(
271
+ {"text": cmd.name, "display": f"/{cmd.name}", "meta": cmd.description}
272
+ )
273
+ catalog.extend(
274
+ {
275
+ "text": alias,
276
+ "display": f"/{alias} (alias for /{cmd.name})",
277
+ "meta": cmd.description,
278
+ }
279
+ for alias in cmd.aliases
280
+ )
281
+ try:
282
+ from code_puppy import callbacks, plugins
283
+
284
+ plugins.load_plugin_callbacks()
285
+ for result in callbacks.on_custom_command_help():
286
+ items = result if isinstance(result, list) else [result]
287
+ for item in items:
288
+ if isinstance(item, tuple) and len(item) == 2:
289
+ name, description = map(str, item)
290
+ catalog.append(
291
+ {"text": name, "display": f"/{name}", "meta": description}
292
+ )
293
+ except Exception:
294
+ pass
295
+ return catalog
296
+
297
+
298
+ class SlashCompleter(Completer):
299
+ """Completes '/' at the beginning of the line with all slash commands."""
300
+
301
+ def get_completions(self, document, complete_event):
302
+ text_before_cursor = document.text_before_cursor
303
+ stripped_text = text_before_cursor.lstrip()
304
+
305
+ # Only trigger if '/' is the first non-whitespace character
306
+ if not stripped_text.startswith("/"):
307
+ return
308
+
309
+ # A bare slash intentionally yields every command so the menu
310
+ # appears immediately while typing.
311
+ partial = stripped_text[1:]
312
+ start_position = -len(partial)
313
+
314
+ try:
315
+ catalog = _slash_catalog_cache.get(_read_slash_catalog)
316
+ except Exception:
317
+ return
318
+ partial_lower = partial.lower()
319
+ all_completions = [
320
+ item for item in catalog if item["text"].lower().startswith(partial_lower)
321
+ ]
322
+ all_completions.sort(key=lambda item: item["text"].lower())
323
+
324
+ # Strip variation selectors (U+FE00-FE0F) from display strings: they
325
+ # cause width mismatches -> phantom spaces (e.g. in the /judges menu).
326
+ for completion in all_completions:
327
+ yield Completion(
328
+ completion["text"],
329
+ start_position=start_position,
330
+ display=_strip_variation_selectors(completion["display"]),
331
+ display_meta=_strip_variation_selectors(completion["meta"]),
332
+ )
333
+
334
+
335
+ def build_completer_stack() -> Completer:
336
+ """The full REPL completer stack (single source of truth)."""
337
+ from code_puppy.callbacks import get_completion_providers
338
+ from code_puppy.command_line.file_path_completion import FilePathCompleter
339
+ from code_puppy.command_line.load_context_completion import LoadContextCompleter
340
+ from code_puppy.command_line.mcp_completion import MCPCompleter
341
+ from code_puppy.command_line.model_picker_completion import ModelNameCompleter
342
+ from code_puppy.command_line.pin_command_completion import (
343
+ PinCompleter,
344
+ UnpinCompleter,
345
+ )
346
+ from code_puppy.command_line.skills_completion import SkillsCompleter
347
+
348
+ return merge_completers(
349
+ [
350
+ FilePathCompleter(symbol="@"),
351
+ ModelNameCompleter(trigger="/model"),
352
+ ModelNameCompleter(trigger="/m"),
353
+ CDCompleter(trigger="/cd"),
354
+ SetCompleter(trigger="/set"),
355
+ LoadContextCompleter(trigger="/load_context"),
356
+ PinCompleter(trigger="/pin_model"),
357
+ UnpinCompleter(trigger="/unpin"),
358
+ AgentCompleter(trigger="/agent"),
359
+ AgentCompleter(trigger="/a"),
360
+ AgentCompleter(trigger="/switch-agent"),
361
+ AgentCompleter(trigger="/sa"),
362
+ AgentCompleter(trigger="/fork", prefix="@"),
363
+ ModelNameCompleter(trigger="/fork", prefix="@"),
364
+ MCPCompleter(trigger="/mcp"),
365
+ SkillsCompleter(trigger="/skills"),
366
+ *get_completion_providers(),
367
+ SlashCompleter(),
368
+ ]
369
+ )
370
+
371
+
372
+ # ---------------------------------------------------------------------------
373
+ # Prompt prefix
374
+ # ---------------------------------------------------------------------------
375
+
376
+ # REPL prompt palette (source of truth -- the bottom-bar prompt converts
377
+ # these to raw SGR via messaging.prompt_prefix_style). IMPORTANT: use
378
+ # `ansi*` names -- bare names resolve to truecolor hex and IGNORE the terminal
379
+ # palette, so /theme's OSC remap couldn't restyle the prompt.
380
+ PROMPT_STYLES = {
381
+ "puppy": "bold ansimagenta",
382
+ "agent": "bold ansiblue",
383
+ "model": "bold ansicyan",
384
+ "cwd": "bold ansigreen",
385
+ "arrow": "bold ansiyellow",
386
+ }
387
+
388
+
389
+ def get_prompt_with_active_model(base: str = ">>> ") -> list:
390
+ """Styled prompt fragments: puppy [agent] [model] (cwd) >>>.
391
+
392
+ Returns a plain list of ``(style, text)`` tuples (the FormattedText
393
+ shape without the class) for ``flatten_prompt_fragments``.
394
+ """
395
+ from code_puppy.agents.agent_manager import get_current_agent
396
+ from code_puppy.command_line.model_picker_completion import get_active_model
397
+
398
+ puppy = get_puppy_name()
399
+ # When nothing is configured this is None - surface that explicitly as
400
+ # [None] so the user immediately sees they need to /add_model.
401
+ global_model = get_active_model()
402
+
403
+ current_agent = get_current_agent()
404
+ agent_display = current_agent.display_name if current_agent else "code-puppy"
405
+
406
+ agent_model = None
407
+ if current_agent and hasattr(current_agent, "get_model_name"):
408
+ agent_model = current_agent.get_model_name()
409
+
410
+ if agent_model and agent_model != global_model:
411
+ model_display = f"[{global_model} \u2192 {agent_model}]"
412
+ elif agent_model:
413
+ model_display = f"[{agent_model}]"
414
+ else:
415
+ # global_model may be None -> renders as [None].
416
+ model_display = f"[{global_model}]"
417
+
418
+ cwd = os.getcwd()
419
+ home = os.path.expanduser("~")
420
+ if cwd.startswith(home):
421
+ cwd_display = "~" + cwd[len(home) :]
422
+ else:
423
+ cwd_display = cwd
424
+ return [
425
+ ("class:puppy class:tui.header", f"{puppy}"),
426
+ ("", " "),
427
+ (
428
+ "class:agent class:tui.label",
429
+ f"[{_normalize_emoji_spacing(agent_display)}] ",
430
+ ),
431
+ ("class:model class:tui.title", model_display + " "),
432
+ ("class:cwd class:tui.muted", "(" + str(cwd_display) + ") "),
433
+ ("class:arrow class:tui.help-key", str(base)),
434
+ ]
@@ -20,8 +20,7 @@ import glob
20
20
  import os
21
21
  from typing import Iterable, List, Tuple
22
22
 
23
- from prompt_toolkit.completion import Completer, Completion
24
- from prompt_toolkit.document import Document
23
+ from termflow.tui.completion import Completer, Completion, Document
25
24
 
26
25
  from code_puppy.command_line import file_index
27
26
 
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from prompt_toolkit.completion import Completer, Completion
3
+ from termflow.tui.completion import Completer, Completion
4
4
 
5
5
  from code_puppy.config import CONFIG_DIR
6
6
 
@@ -294,14 +294,10 @@ def build_form_menu(form: CustomServerForm, initial_index: int = 0, **overrides)
294
294
 
295
295
  json_status = "valid" if form._validate_json() else "INVALID"
296
296
  items = [
297
- MenuItem(
298
- f"Server Name: {form.server_name or '(not set)'}", value=_FIELD_NAME
299
- ),
297
+ MenuItem(f"Server Name: {form.server_name or '(not set)'}", value=_FIELD_NAME),
300
298
  MenuItem(f"Server Type: {form._get_current_type()}", value=_FIELD_TYPE),
301
299
  MenuItem(f"JSON Configuration ({json_status})", value=_FIELD_JSON),
302
- MenuItem(
303
- f"Load example for {form._get_current_type()}", value=_ACTION_EXAMPLE
304
- ),
300
+ MenuItem(f"Load example for {form._get_current_type()}", value=_ACTION_EXAMPLE),
305
301
  MenuItem("Save & Install", value=_ACTION_SAVE),
306
302
  MenuItem("Cancel", value=_ACTION_CANCEL),
307
303
  ]
@@ -414,7 +410,11 @@ def run_json_fallback_editor(form: CustomServerForm, **overrides) -> None:
414
410
  return f"Invalid JSON: {e.msg}"
415
411
  return None
416
412
 
417
- compact = json.dumps(json.loads(form.json_config)) if _parses(form.json_config) else form.json_config
413
+ compact = (
414
+ json.dumps(json.loads(form.json_config))
415
+ if _parses(form.json_config)
416
+ else form.json_config
417
+ )
418
418
  builder = (
419
419
  TextInputBuilder("JSON Configuration (single line)")
420
420
  .prompt("JSON: ")
@@ -456,7 +456,14 @@ def run_form_flow(
456
456
  ) -> bool:
457
457
  """The field-edit loop. True when a server was installed."""
458
458
  cursor = 0
459
- field_order = [_FIELD_NAME, _FIELD_TYPE, _FIELD_JSON, _ACTION_EXAMPLE, _ACTION_SAVE, _ACTION_CANCEL]
459
+ field_order = [
460
+ _FIELD_NAME,
461
+ _FIELD_TYPE,
462
+ _FIELD_JSON,
463
+ _ACTION_EXAMPLE,
464
+ _ACTION_SAVE,
465
+ _ACTION_CANCEL,
466
+ ]
460
467
  while True:
461
468
  result = menu_factory(form, initial_index=cursor).run()
462
469
  if result.cancelled or result.item is None: