klaude-code 1.3.0__tar.gz → 1.4.0__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 (211) hide show
  1. {klaude_code-1.3.0 → klaude_code-1.4.0}/PKG-INFO +1 -2
  2. {klaude_code-1.3.0 → klaude_code-1.4.0}/pyproject.toml +1 -2
  3. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/runtime.py +2 -2
  4. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/session_cmd.py +24 -9
  5. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/model_cmd.py +18 -13
  6. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/thinking_cmd.py +24 -19
  7. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/select_model.py +40 -30
  8. klaude_code-1.4.0/src/klaude_code/session/selector.py +98 -0
  9. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/event_handler.py +7 -1
  10. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  11. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/key_bindings.py +42 -1
  12. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/developer.py +15 -6
  13. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/errors.py +1 -1
  14. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/tools.py +1 -1
  15. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/searchable_text.py +4 -7
  16. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/status.py +55 -2
  17. klaude_code-1.4.0/src/klaude_code/ui/terminal/selector.py +273 -0
  18. klaude_code-1.3.0/src/klaude_code/session/selector.py +0 -81
  19. {klaude_code-1.3.0 → klaude_code-1.4.0}/README.md +0 -0
  20. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/__init__.py +0 -0
  21. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/__init__.py +0 -0
  22. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/codex/__init__.py +0 -0
  23. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
  24. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  25. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/codex/oauth.py +0 -0
  26. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
  27. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/__init__.py +0 -0
  28. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/auth_cmd.py +0 -0
  29. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/config_cmd.py +0 -0
  30. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/debug.py +0 -0
  31. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/list_model.py +0 -0
  32. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/main.py +0 -0
  33. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/cli/self_update.py +0 -0
  34. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/__init__.py +0 -0
  35. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/clear_cmd.py +0 -0
  36. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/command_abc.py +0 -0
  37. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/debug_cmd.py +0 -0
  38. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/export_cmd.py +0 -0
  39. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/export_online_cmd.py +0 -0
  40. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/fork_session_cmd.py +0 -0
  41. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/help_cmd.py +0 -0
  42. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/prompt-init.md +0 -0
  43. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/prompt-jj-describe.md +0 -0
  44. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/prompt_command.py +0 -0
  45. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/refresh_cmd.py +0 -0
  46. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/registry.py +0 -0
  47. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
  48. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/resume_cmd.py +0 -0
  49. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/status_cmd.py +0 -0
  50. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  51. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/__init__.py +0 -0
  52. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/assets/__init__.py +0 -0
  53. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
  54. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/builtin_config.py +0 -0
  55. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/config/config.py +0 -0
  56. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/const.py +0 -0
  57. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/__init__.py +0 -0
  58. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/agent.py +0 -0
  59. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/executor.py +0 -0
  60. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/manager/__init__.py +0 -0
  61. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
  62. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  63. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  64. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompt.py +0 -0
  65. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  66. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
  67. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  68. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  69. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  70. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  71. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  72. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
  73. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  74. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  75. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/reminders.py +0 -0
  76. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/task.py +0 -0
  77. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/__init__.py +2 -2
  78. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
  79. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
  80. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  81. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  82. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  83. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  84. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  85. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  86. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
  87. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
  88. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  89. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  90. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  91. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  92. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  93. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  94. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  95. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  96. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  97. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
  98. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  99. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  100. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  101. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  102. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  103. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  104. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  105. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  106. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  107. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
  108. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/tool_context.py +0 -0
  109. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
  110. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
  111. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/truncation.py +0 -0
  112. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
  113. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  114. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  115. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  116. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  117. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  118. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  119. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/core/turn.py +0 -0
  120. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/__init__.py +0 -0
  121. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  122. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/anthropic/client.py +0 -0
  123. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/anthropic/input.py +0 -0
  124. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/client.py +0 -0
  125. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/codex/__init__.py +0 -0
  126. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/codex/client.py +0 -0
  127. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/input_common.py +0 -0
  128. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  129. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  130. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  131. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  132. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  133. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  134. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openrouter/client.py +0 -0
  135. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openrouter/input.py +0 -0
  136. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  137. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/registry.py +0 -0
  138. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/responses/__init__.py +0 -0
  139. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/responses/client.py +0 -0
  140. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/responses/input.py +0 -0
  141. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/llm/usage.py +0 -0
  142. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/__init__.py +0 -0
  143. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/commands.py +0 -0
  144. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/events.py +0 -0
  145. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/llm_param.py +0 -0
  146. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/model.py +0 -0
  147. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/op.py +0 -0
  148. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/op_handler.py +0 -0
  149. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  150. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  151. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
  152. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  153. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  154. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/protocol/tools.py +0 -0
  155. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/__init__.py +0 -0
  156. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/codec.py +0 -0
  157. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/export.py +0 -0
  158. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/session.py +0 -0
  159. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/store.py +0 -0
  160. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/templates/export_session.html +0 -0
  161. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  162. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/__init__.py +0 -0
  163. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  164. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  165. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  166. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  167. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  168. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/loader.py +0 -0
  169. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/manager.py +0 -0
  170. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/skill/system_skills.py +0 -0
  171. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/trace/__init__.py +0 -0
  172. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/trace/log.py +0 -0
  173. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/__init__.py +0 -0
  174. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/core/__init__.py +0 -0
  175. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/core/display.py +0 -0
  176. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/core/input.py +0 -0
  177. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
  178. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/__init__.py +0 -0
  179. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  180. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
  181. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  182. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
  183. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  184. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  185. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  186. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
  187. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
  188. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
  189. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
  190. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
  191. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/common.py +0 -0
  192. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
  193. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
  194. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/metadata.py +0 -0
  195. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
  196. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
  197. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
  198. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/__init__.py +0 -0
  199. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  200. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
  201. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/live.py +0 -0
  202. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/markdown.py +0 -0
  203. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/quote.py +0 -0
  204. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/rich/theme.py +0 -0
  205. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
  206. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/terminal/color.py +0 -0
  207. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/terminal/control.py +0 -0
  208. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
  209. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  210. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/utils/__init__.py +0 -0
  211. {klaude_code-1.3.0 → klaude_code-1.4.0}/src/klaude_code/ui/utils/common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
@@ -12,7 +12,6 @@ Requires-Dist: pillow>=12.0.0
12
12
  Requires-Dist: prompt-toolkit>=3.0.52
13
13
  Requires-Dist: pydantic>=2.11.7
14
14
  Requires-Dist: pyyaml>=6.0.2
15
- Requires-Dist: questionary>=2.1.1
16
15
  Requires-Dist: rich>=14.1.0
17
16
  Requires-Dist: trafilatura>=2.0.0
18
17
  Requires-Dist: typer>=0.17.3
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.3.0"
7
+ version = "1.4.0"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -19,7 +19,6 @@ dependencies = [
19
19
  "prompt-toolkit>=3.0.52",
20
20
  "pydantic>=2.11.7",
21
21
  "pyyaml>=6.0.2",
22
- "questionary>=2.1.1",
23
22
  "rich>=14.1.0",
24
23
  "trafilatura>=2.0.0",
25
24
  "typer>=0.17.3",
@@ -273,7 +273,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
273
273
  display.wrapped_display.renderer.stop_bottom_live()
274
274
 
275
275
  # Pass the pre-detected theme to avoid redundant TTY queries.
276
- # Querying the terminal background again after questionary's interactive selection
276
+ # Querying the terminal background again after an interactive selection
277
277
  # can interfere with prompt_toolkit's terminal state and break history navigation.
278
278
  is_light_background: bool | None = None
279
279
  if components.theme == "light":
@@ -352,7 +352,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
352
352
  op.UserInputOperation(input=user_input, session_id=active_session_id)
353
353
  )
354
354
  # If it's an interactive command (e.g., /model), avoid starting the ESC monitor
355
- # to prevent TTY conflicts with interactive prompts (questionary/prompt_toolkit).
355
+ # to prevent TTY conflicts with interactive prompt_toolkit UIs.
356
356
  if has_interactive_command(user_input.text):
357
357
  await components.executor.wait_for(submission_id)
358
358
  else:
@@ -7,8 +7,11 @@ from klaude_code.trace import log
7
7
 
8
8
 
9
9
  def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) -> bool:
10
- """Show session list and confirm deletion using questionary."""
11
- import questionary
10
+ """Show session list and confirm deletion using prompt_toolkit."""
11
+
12
+ from prompt_toolkit.styles import Style
13
+
14
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
12
15
 
13
16
  def _fmt(ts: float) -> str:
14
17
  try:
@@ -24,14 +27,26 @@ def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) ->
24
27
  first_msg += "..."
25
28
  log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
26
29
 
27
- return (
28
- questionary.confirm(
29
- message,
30
- default=False,
31
- style=questionary.Style([("question", "bold")]),
32
- ).ask()
33
- or False
30
+ items: list[SelectItem[bool]] = [
31
+ SelectItem(title=[("class:text", "No\n")], value=False, search_text="No"),
32
+ SelectItem(title=[("class:text", "Yes\n")], value=True, search_text="Yes"),
33
+ ]
34
+
35
+ result = select_one(
36
+ message=message,
37
+ items=items,
38
+ pointer="→",
39
+ style=Style(
40
+ [
41
+ ("question", "bold"),
42
+ ("pointer", "ansigreen"),
43
+ ("highlighted", "ansigreen"),
44
+ ("text", ""),
45
+ ]
46
+ ),
47
+ use_search_filter=False,
34
48
  )
49
+ return bool(result)
35
50
 
36
51
 
37
52
  def session_clean(
@@ -1,38 +1,43 @@
1
1
  import asyncio
2
2
 
3
- import questionary
3
+ from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.config.select_model import select_model_from_config
7
7
  from klaude_code.protocol import commands, events, model, op
8
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
8
9
 
9
- SELECT_STYLE = questionary.Style(
10
+ SELECT_STYLE = Style(
10
11
  [
11
12
  ("instruction", "ansibrightblack"),
12
- ("pointer", "ansicyan"),
13
- ("highlighted", "ansicyan"),
13
+ ("pointer", "ansigreen"),
14
+ ("highlighted", "ansigreen"),
14
15
  ("text", "ansibrightblack"),
16
+ ("question", ""),
15
17
  ]
16
18
  )
17
19
 
18
20
 
19
21
  def _confirm_change_default_model_sync(selected_model: str) -> bool:
20
- choices: list[questionary.Choice] = [
21
- questionary.Choice(title="No (session only)", value=False),
22
- questionary.Choice(title="Yes (save as default main_model in ~/.klaude/klaude-config.yaml)", value=True),
22
+ items: list[SelectItem[bool]] = [
23
+ SelectItem(title=[("class:text", "No (session only)\n")], value=False, search_text="No"),
24
+ SelectItem(
25
+ title=[("class:text", "Yes (save as default main_model in ~/.klaude/klaude-config.yaml)\n")],
26
+ value=True,
27
+ search_text="Yes",
28
+ ),
23
29
  ]
24
30
 
25
31
  try:
26
32
  # Add a blank line between the model selector and this confirmation prompt.
27
- questionary.print("")
28
- result = questionary.select(
33
+ print("")
34
+ result = select_one(
29
35
  message=f"Save '{selected_model}' as default model?",
30
- choices=choices,
36
+ items=items,
31
37
  pointer="→",
32
- instruction="Use arrow keys to move, Enter to select",
33
- use_jk_keys=False,
34
38
  style=SELECT_STYLE,
35
- ).ask()
39
+ use_search_filter=False,
40
+ )
36
41
  except KeyboardInterrupt:
37
42
  return False
38
43
 
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
2
 
3
- import questionary
3
+ from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.protocol import commands, events, llm_param, model
7
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
7
8
 
8
9
  # Thinking level options for different protocols
9
10
  RESPONSES_LEVELS = ["low", "medium", "high"]
@@ -118,12 +119,13 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
118
119
  return "unknown protocol"
119
120
 
120
121
 
121
- SELECT_STYLE = questionary.Style(
122
+ SELECT_STYLE = Style(
122
123
  [
123
124
  ("instruction", "ansibrightblack"),
124
- ("pointer", "ansicyan"),
125
- ("highlighted", "ansicyan"),
125
+ ("pointer", "ansigreen"),
126
+ ("highlighted", "ansigreen"),
126
127
  ("text", "ansibrightblack"),
128
+ ("question", ""),
127
129
  ]
128
130
  )
129
131
 
@@ -131,17 +133,18 @@ SELECT_STYLE = questionary.Style(
131
133
  def _select_responses_thinking_sync(model_name: str | None) -> llm_param.Thinking | None:
132
134
  """Select thinking level for responses/codex protocol (sync version)."""
133
135
  levels = _get_levels_for_responses(model_name)
134
- choices: list[questionary.Choice] = [questionary.Choice(title=level, value=level) for level in levels]
136
+ items: list[SelectItem[str]] = [
137
+ SelectItem(title=[("class:text", level + "\n")], value=level, search_text=level) for level in levels
138
+ ]
135
139
 
136
140
  try:
137
- result = questionary.select(
141
+ result = select_one(
138
142
  message="Select reasoning effort:",
139
- choices=choices,
143
+ items=items,
140
144
  pointer="→",
141
- instruction="Use arrow keys to move, Enter to select",
142
- use_jk_keys=False,
143
145
  style=SELECT_STYLE,
144
- ).ask()
146
+ use_search_filter=False,
147
+ )
145
148
 
146
149
  if result is None:
147
150
  return None
@@ -152,22 +155,24 @@ def _select_responses_thinking_sync(model_name: str | None) -> llm_param.Thinkin
152
155
 
153
156
  def _select_anthropic_thinking_sync() -> llm_param.Thinking | None:
154
157
  """Select thinking level for anthropic/openai_compatible protocol (sync version)."""
155
- choices: list[questionary.Choice] = [
156
- questionary.Choice(title=label, value=tokens) for label, tokens in ANTHROPIC_LEVELS
158
+ items: list[SelectItem[int]] = [
159
+ SelectItem(title=[("class:text", label + "\n")], value=tokens or 0, search_text=label)
160
+ for label, tokens in ANTHROPIC_LEVELS
157
161
  ]
158
162
 
159
163
  try:
160
- result = questionary.select(
164
+ result = select_one(
161
165
  message="Select thinking level:",
162
- choices=choices,
166
+ items=items,
163
167
  pointer="→",
164
- instruction="Use arrow keys to move, Enter to select",
165
- use_jk_keys=False,
166
168
  style=SELECT_STYLE,
167
- ).ask()
168
- if not result:
169
+ use_search_filter=False,
170
+ )
171
+ if result is None:
172
+ return None
173
+ if result == 0:
169
174
  return llm_param.Thinking(type="disabled", budget_tokens=0)
170
- return llm_param.Thinking(type="enabled", budget_tokens=result or 0)
175
+ return llm_param.Thinking(type="enabled", budget_tokens=result)
171
176
  except KeyboardInterrupt:
172
177
  return None
173
178
 
@@ -108,60 +108,70 @@ def select_model_from_config(preferred: str | None = None) -> str | None:
108
108
  return None
109
109
 
110
110
  try:
111
- import questionary
111
+ from prompt_toolkit.styles import Style
112
112
 
113
- choices: list[questionary.Choice] = []
113
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
114
114
 
115
115
  max_model_name_length = max(len(m.model_name) for m in filtered_models)
116
116
 
117
- # Build model_id with thinking suffix as a single unit
118
- def build_model_id_with_thinking(m: ModelEntry) -> str:
119
- model_id = m.model_params.model or "N/A"
117
+ def _thinking_info(m: ModelEntry) -> str:
120
118
  thinking = m.model_params.thinking
121
- if thinking:
122
- if thinking.reasoning_effort:
123
- return f"{model_id} ({thinking.reasoning_effort})"
124
- elif thinking.budget_tokens:
125
- return f"{model_id} (think {thinking.budget_tokens})"
126
- return model_id
127
-
128
- model_id_with_thinking = {m.model_name: build_model_id_with_thinking(m) for m in filtered_models}
129
- max_model_id_length = max(len(v) for v in model_id_with_thinking.values())
130
-
119
+ if not thinking:
120
+ return ""
121
+ if thinking.reasoning_effort:
122
+ return f"reasoning effort {thinking.reasoning_effort}"
123
+ if thinking.budget_tokens:
124
+ return f"thinking budget {thinking.budget_tokens}"
125
+ return "thinking (configured)"
126
+
127
+ items: list[SelectItem[str]] = []
131
128
  for m in filtered_models:
132
- star = "★ " if m.model_name == config.main_model else " "
133
- model_id_str = model_id_with_thinking[m.model_name]
134
- title = f"{star}{m.model_name:<{max_model_name_length}} → {model_id_str:<{max_model_id_length}} @ {m.provider}"
135
- choices.append(questionary.Choice(title=title, value=m.model_name))
129
+ model_id = m.model_params.model or "N/A"
130
+ first_line_prefix = f"{m.model_name:<{max_model_name_length}} → "
131
+ thinking_info = _thinking_info(m)
132
+ meta_parts = [m.provider]
133
+ if thinking_info:
134
+ meta_parts.append(thinking_info)
135
+ if m.model_params.verbosity:
136
+ meta_parts.append(f"verbosity {m.model_params.verbosity}")
137
+ meta_line = " " + " · ".join(meta_parts)
138
+ title = [
139
+ ("class:msg", first_line_prefix),
140
+ ("class:msg bold", f"{model_id}\n"),
141
+ ("class:meta", f"{meta_line}\n\n"),
142
+ ]
143
+ search_text = f"{m.model_name} {model_id} {m.provider}"
144
+ items.append(SelectItem(title=title, value=m.model_name, search_text=search_text))
136
145
 
137
146
  try:
138
147
  message = f"Select a model (filtered by '{preferred}'):" if preferred else "Select a model:"
139
- result = questionary.select(
148
+ result = select_one(
140
149
  message=message,
141
- choices=choices,
150
+ items=items,
142
151
  pointer="→",
143
- instruction="↑↓ to move • Enter to select",
144
- use_jk_keys=False,
145
152
  use_search_filter=True,
146
- style=questionary.Style(
153
+ initial_value=config.main_model,
154
+ style=Style(
147
155
  [
148
- ("instruction", "ansibrightblack"),
149
- ("pointer", "ansicyan"),
150
- ("highlighted", "ansicyan"),
156
+ ("pointer", "ansigreen"),
157
+ ("highlighted", "ansigreen"),
158
+ ("msg", ""),
159
+ ("meta", "fg:ansibrightblack"),
151
160
  ("text", "ansibrightblack"),
161
+ ("question", ""),
162
+ ("search_prefix", "ansibrightblack"),
152
163
  # search filter colors at the bottom
153
164
  ("search_success", "noinherit fg:ansigreen"),
154
165
  ("search_none", "noinherit fg:ansired"),
155
- ("question-mark", "fg:ansigreen"),
156
166
  ]
157
167
  ),
158
- ).ask()
168
+ )
159
169
  if isinstance(result, str) and result in names:
160
170
  return result
161
171
  except KeyboardInterrupt:
162
172
  return None
163
173
  except Exception as e:
164
- log((f"Failed to use questionary for model selection: {e}", "yellow"))
174
+ log((f"Failed to use prompt_toolkit for model selection: {e}", "yellow"))
165
175
  # Never return an unvalidated model name here.
166
176
  # If we can't interactively select, fall back to a known configured model.
167
177
  if isinstance(preferred, str) and preferred in names:
@@ -0,0 +1,98 @@
1
+ import time
2
+
3
+ from klaude_code.trace import log, log_debug
4
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
5
+
6
+ from .session import Session
7
+
8
+
9
+ def _relative_time(ts: float) -> str:
10
+ """Format timestamp as relative time like '5 days ago'."""
11
+ now = time.time()
12
+ diff = now - ts
13
+
14
+ if diff < 60:
15
+ return "just now"
16
+ elif diff < 3600:
17
+ mins = int(diff / 60)
18
+ return f"{mins} minute{'s' if mins != 1 else ''} ago"
19
+ elif diff < 86400:
20
+ hours = int(diff / 3600)
21
+ return f"{hours} hour{'s' if hours != 1 else ''} ago"
22
+ elif diff < 604800:
23
+ days = int(diff / 86400)
24
+ return f"{days} day{'s' if days != 1 else ''} ago"
25
+ elif diff < 2592000:
26
+ weeks = int(diff / 604800)
27
+ return f"{weeks} week{'s' if weeks != 1 else ''} ago"
28
+ else:
29
+ months = int(diff / 2592000)
30
+ return f"{months} month{'s' if months != 1 else ''} ago"
31
+
32
+
33
+ def resume_select_session() -> str | None:
34
+ sessions = Session.list_sessions()
35
+ if not sessions:
36
+ log("No sessions found for this project.")
37
+ return None
38
+
39
+ try:
40
+ from prompt_toolkit.styles import Style
41
+
42
+ items: list[SelectItem[str]] = []
43
+ for s in sessions:
44
+ first_msg = s.first_user_message or "N/A"
45
+ first_msg = first_msg.strip().replace("\n", " ")
46
+
47
+ msg_count = "N/A" if s.messages_count == -1 else f"{s.messages_count} messages"
48
+ model = s.model_name or "N/A"
49
+
50
+ title = [
51
+ ("class:msg", f"{first_msg}\n"),
52
+ ("class:meta", f" {msg_count} · {_relative_time(s.updated_at)} · {model} · {s.id}\n\n"),
53
+ ]
54
+ items.append(
55
+ SelectItem(
56
+ title=title,
57
+ value=str(s.id),
58
+ search_text=f"{first_msg} {model} {s.id}",
59
+ )
60
+ )
61
+
62
+ return select_one(
63
+ message="Select a session to resume:",
64
+ items=items,
65
+ pointer="→",
66
+ style=Style(
67
+ [
68
+ ("msg", ""),
69
+ ("meta", "fg:ansibrightblack"),
70
+ ("pointer", "bold fg:ansigreen"),
71
+ ("highlighted", "fg:ansigreen"),
72
+ ("search_prefix", "fg:ansibrightblack"),
73
+ ("search_success", "noinherit fg:ansigreen"),
74
+ ("search_none", "noinherit fg:ansired"),
75
+ ("question", "bold"),
76
+ ("text", ""),
77
+ ]
78
+ ),
79
+ )
80
+ except Exception as e:
81
+ log_debug(f"Failed to use prompt_toolkit for session select, {e}")
82
+
83
+ for i, s in enumerate(sessions, 1):
84
+ first_msg = (s.first_user_message or "N/A").strip().replace("\n", " ")
85
+ if len(first_msg) > 60:
86
+ first_msg = first_msg[:59] + "…"
87
+ msg_count = "N/A" if s.messages_count == -1 else f"{s.messages_count} msgs"
88
+ model = s.model_name or "N/A"
89
+ print(f"{i}. {first_msg}")
90
+ print(f" {_relative_time(s.updated_at)} · {msg_count} · {model}")
91
+ try:
92
+ raw = input("Select a session number: ").strip()
93
+ idx = int(raw)
94
+ if 1 <= idx <= len(sessions):
95
+ return str(sessions[idx - 1].id)
96
+ except (ValueError, EOFError):
97
+ return None
98
+ return None
@@ -12,6 +12,7 @@ from klaude_code.ui.core.stage_manager import Stage, StageManager
12
12
  from klaude_code.ui.modes.repl.renderer import REPLRenderer
13
13
  from klaude_code.ui.renderers.assistant import ASSISTANT_MESSAGE_MARK
14
14
  from klaude_code.ui.renderers.thinking import THINKING_MESSAGE_MARK, normalize_thinking_content
15
+ from klaude_code.ui.rich import status as r_status
15
16
  from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
16
17
  from klaude_code.ui.rich.theme import ThemeKey
17
18
  from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier
@@ -352,6 +353,8 @@ class DisplayEventHandler:
352
353
  self.renderer.display_user_message(event)
353
354
 
354
355
  def _on_task_start(self, event: events.TaskStartEvent) -> None:
356
+ if event.sub_agent_state is None:
357
+ r_status.set_task_start()
355
358
  self.renderer.spinner_start()
356
359
  self.renderer.display_task_start(event)
357
360
  emit_osc94(OSC94States.INDETERMINATE)
@@ -501,6 +504,7 @@ class DisplayEventHandler:
501
504
  async def _on_task_finish(self, event: events.TaskFinishEvent) -> None:
502
505
  self.renderer.display_task_finish(event)
503
506
  if not self.renderer.is_sub_agent_session(event.session_id):
507
+ r_status.clear_task_start()
504
508
  emit_osc94(OSC94States.HIDDEN)
505
509
  self.spinner_status.reset()
506
510
  self.renderer.spinner_stop()
@@ -511,6 +515,7 @@ class DisplayEventHandler:
511
515
  async def _on_interrupt(self, event: events.InterruptEvent) -> None:
512
516
  self.renderer.spinner_stop()
513
517
  self.spinner_status.reset()
518
+ r_status.clear_task_start()
514
519
  await self.stage_manager.transition_to(Stage.WAITING)
515
520
  emit_osc94(OSC94States.HIDDEN)
516
521
  self.renderer.display_interrupt()
@@ -528,6 +533,7 @@ class DisplayEventHandler:
528
533
  await self.stage_manager.transition_to(Stage.WAITING)
529
534
  self.renderer.spinner_stop()
530
535
  self.spinner_status.reset()
536
+ r_status.clear_task_start()
531
537
 
532
538
  # ─────────────────────────────────────────────────────────────────────────────
533
539
  # Private helper methods
@@ -619,7 +625,7 @@ class DisplayEventHandler:
619
625
  # 1 cell for glyph + 1 cell of padding between columns (collapsed).
620
626
  spinner_prefix_cells = 2
621
627
 
622
- hint_cells = cell_len(const.STATUS_HINT_TEXT)
628
+ hint_cells = cell_len(r_status.current_hint_text())
623
629
  right_cells = cell_len(right_text.plain) if right_text is not None else 0
624
630
 
625
631
  max_main_cells = terminal_width - spinner_prefix_cells - hint_cells - right_cells - 1
@@ -55,7 +55,7 @@ class PromptToolkitInput(InputProviderABC):
55
55
  self._pre_prompt = pre_prompt
56
56
  self._post_prompt = post_prompt
57
57
  # Use provided value if available to avoid redundant TTY queries that may interfere
58
- # with prompt_toolkit's terminal state after questionary has been used.
58
+ # with prompt_toolkit's terminal state after interactive UIs have been used.
59
59
  self._is_light_terminal_background = (
60
60
  is_light_background if is_light_background is not None else is_light_terminal_background(timeout=0.2)
61
61
  )
@@ -34,6 +34,47 @@ def create_key_bindings(
34
34
  """
35
35
  kb = KeyBindings()
36
36
 
37
+ def _should_submit_instead_of_accepting_completion(buf: Buffer) -> bool:
38
+ """Return True when Enter should submit even if completions are visible.
39
+
40
+ We show completions proactively for contexts like `/`.
41
+ If the user already typed an exact candidate (e.g. `/clear`), accepting
42
+ a completion often only adds a trailing space and makes Enter require
43
+ two presses. In that case, prefer submitting.
44
+ """
45
+ state = buf.complete_state
46
+ if state is None or not state.completions:
47
+ return False
48
+
49
+ try:
50
+ doc = buf.document # type: ignore[reportUnknownMemberType]
51
+ text = cast(str, doc.text) # type: ignore[reportUnknownMemberType]
52
+ cursor_pos = cast(int, doc.cursor_position) # type: ignore[reportUnknownMemberType]
53
+ except Exception:
54
+ return False
55
+
56
+ # Only apply this heuristic when the caret is at the end of the buffer.
57
+ if cursor_pos != len(text):
58
+ return False
59
+
60
+ for completion in state.completions:
61
+ try:
62
+ start = cursor_pos + completion.start_position
63
+ if start < 0 or start > cursor_pos:
64
+ continue
65
+
66
+ replaced = text[start:cursor_pos]
67
+ inserted = completion.text
68
+
69
+ # If the user already typed an exact candidate, don't force
70
+ # accepting a completion (which often just adds a space).
71
+ if replaced == inserted or replaced == inserted.rstrip():
72
+ return True
73
+ except Exception:
74
+ continue
75
+
76
+ return False
77
+
37
78
  def _select_first_completion_if_needed(buf: Buffer) -> None:
38
79
  """Ensure the completion menu has an active selection.
39
80
 
@@ -98,7 +139,7 @@ def create_key_bindings(
98
139
  # When completions are visible, Enter accepts the current selection.
99
140
  # This aligns with common TUI completion UX: navigation doesn't modify
100
141
  # the buffer, and Enter/Tab inserts the selected option.
101
- if _accept_current_completion(buf):
142
+ if not _should_submit_instead_of_accepting_completion(buf) and _accept_current_completion(buf):
102
143
  return
103
144
 
104
145
  # If the entire buffer is whitespace-only, insert a newline rather than submitting.
@@ -67,23 +67,32 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
67
67
 
68
68
  if e.item.at_files:
69
69
  grid = create_grid()
70
+ # Group at_files by (operation, mentioned_in)
71
+ grouped: dict[tuple[str, str | None], list[str]] = {}
70
72
  for at_file in e.item.at_files:
71
- if at_file.mentioned_in:
73
+ key = (at_file.operation, at_file.mentioned_in)
74
+ if key not in grouped:
75
+ grouped[key] = []
76
+ grouped[key].append(at_file.path)
77
+
78
+ for (operation, mentioned_in), paths in grouped.items():
79
+ path_texts = Text(", ", ThemeKey.REMINDER).join(render_path(p, ThemeKey.REMINDER_BOLD) for p in paths)
80
+ if mentioned_in:
72
81
  grid.add_row(
73
82
  Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
74
83
  Text.assemble(
75
- (f"{at_file.operation} ", ThemeKey.REMINDER),
76
- render_path(at_file.path, ThemeKey.REMINDER_BOLD),
84
+ (f"{operation} ", ThemeKey.REMINDER),
85
+ path_texts,
77
86
  (" mentioned in ", ThemeKey.REMINDER),
78
- render_path(at_file.mentioned_in, ThemeKey.REMINDER_BOLD),
87
+ render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
79
88
  ),
80
89
  )
81
90
  else:
82
91
  grid.add_row(
83
92
  Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
84
93
  Text.assemble(
85
- (f"{at_file.operation} ", ThemeKey.REMINDER),
86
- render_path(at_file.path, ThemeKey.REMINDER_BOLD),
94
+ (f"{operation} ", ThemeKey.REMINDER),
95
+ path_texts,
87
96
  ),
88
97
  )
89
98
  parts.append(grid)
@@ -17,5 +17,5 @@ def render_tool_error(error_msg: Text) -> RenderableType:
17
17
  """Render error with indent for tool results."""
18
18
  grid = create_grid()
19
19
  error_msg.style = ThemeKey.ERROR
20
- grid.add_row(Text(" "), error_msg)
20
+ grid.add_row(Text(" "), error_msg)
21
21
  return grid
@@ -27,7 +27,7 @@ MARK_PLAN = "◈"
27
27
  MARK_READ = "→"
28
28
  MARK_EDIT = "±"
29
29
  MARK_WRITE = "+"
30
- MARK_MOVE = ""
30
+ MARK_MOVE = "±"
31
31
  MARK_MERMAID = "⧉"
32
32
  MARK_WEB_FETCH = "→"
33
33
  MARK_WEB_SEARCH = "✱"
@@ -6,9 +6,7 @@ from collections.abc import Iterable, Sequence
6
6
  class SearchableFormattedText:
7
7
  """
8
8
  Wrapper for prompt_toolkit formatted text that also supports string-like
9
- methods used by questionary's search filter (e.g., ``.lower()``).
10
-
11
- This allows using ``use_search_filter=True`` with a formatted ``Choice.title``.
9
+ methods commonly expected by search filters (e.g., ``.lower()``).
12
10
 
13
11
  - ``fragments``: A sequence of (style, text) tuples accepted by
14
12
  prompt_toolkit's ``to_formatted_text``.
@@ -32,7 +30,7 @@ class SearchableFormattedText:
32
30
  def __str__(self) -> str: # pragma: no cover - utility
33
31
  return self._plain
34
32
 
35
- # Minimal string API to satisfy questionary's search filter logic.
33
+ # Minimal string API for search filtering.
36
34
  def lower(self) -> str:
37
35
  return self._plain.lower()
38
36
 
@@ -47,10 +45,9 @@ class SearchableFormattedText:
47
45
 
48
46
  class SearchableFormattedList(list[tuple[str, str]]):
49
47
  """
50
- List variant compatible with questionary's expected ``Choice.title`` type.
48
+ List variant compatible with prompt_toolkit formatted-text usage.
51
49
 
52
- - Behaves like ``List[Tuple[str, str]]`` for rendering (so ``isinstance(..., list)`` works),
53
- preserving existing styling behavior in questionary.
50
+ - Behaves like ``List[Tuple[str, str]]`` for rendering (so ``isinstance(..., list)`` works).
54
51
  - Provides ``.lower()``/``.upper()`` returning the plain text for search filtering.
55
52
  """
56
53