klaude-code 1.2.29__tar.gz → 1.2.30__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 (206) hide show
  1. {klaude_code-1.2.29 → klaude_code-1.2.30}/PKG-INFO +1 -1
  2. {klaude_code-1.2.29 → klaude_code-1.2.30}/pyproject.toml +1 -1
  3. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/__init__.py +3 -0
  4. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/clear_cmd.py +4 -1
  5. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/command_abc.py +4 -0
  6. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/fork_session_cmd.py +2 -2
  7. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/model_cmd.py +42 -2
  8. klaude_code-1.2.30/src/klaude_code/command/resume_cmd.py +51 -0
  9. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/status_cmd.py +1 -1
  10. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/assets/builtin_config.yaml +11 -0
  11. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/executor.py +42 -8
  12. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/task.py +7 -6
  13. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.py +5 -2
  14. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/commands.py +1 -0
  15. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/op.py +12 -0
  16. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/op_handler.py +5 -0
  17. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/common.py +2 -0
  18. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/mermaid_viewer.py +1 -2
  19. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/metadata.py +1 -0
  20. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/thinking.py +1 -1
  21. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/tools.py +175 -11
  22. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/theme.py +15 -0
  23. {klaude_code-1.2.29 → klaude_code-1.2.30}/README.md +0 -0
  24. {klaude_code-1.2.29/src/klaude_code/ui/renderers → klaude_code-1.2.30/src/klaude_code}/__init__.py +0 -0
  25. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/__init__.py +0 -0
  26. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/__init__.py +0 -0
  27. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/exceptions.py +0 -0
  28. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  29. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/oauth.py +0 -0
  30. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/token_manager.py +0 -0
  31. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/__init__.py +0 -0
  32. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/auth_cmd.py +0 -0
  33. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/config_cmd.py +0 -0
  34. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/debug.py +0 -0
  35. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/list_model.py +0 -0
  36. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/main.py +0 -0
  37. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/runtime.py +0 -0
  38. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/self_update.py +0 -0
  39. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/session_cmd.py +0 -0
  40. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/debug_cmd.py +0 -0
  41. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/export_cmd.py +0 -0
  42. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/export_online_cmd.py +0 -0
  43. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/help_cmd.py +0 -0
  44. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt-init.md +0 -0
  45. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt-jj-describe.md +0 -0
  46. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt_command.py +0 -0
  47. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/refresh_cmd.py +0 -0
  48. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/registry.py +0 -0
  49. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/release_notes_cmd.py +0 -0
  50. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  51. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/thinking_cmd.py +0 -0
  52. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/__init__.py +0 -0
  53. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/assets/__init__.py +0 -0
  54. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/builtin_config.py +0 -0
  55. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/config.py +0 -0
  56. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/select_model.py +0 -0
  57. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/const.py +0 -0
  58. {klaude_code-1.2.29/src/klaude_code/core/tool/web → klaude_code-1.2.30/src/klaude_code/core}/__init__.py +0 -0
  59. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/agent.py +0 -0
  60. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/__init__.py +0 -0
  61. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients.py +0 -0
  62. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  63. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  64. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompt.py +0 -0
  65. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  66. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
  67. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  68. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  69. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  70. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  71. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  72. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
  73. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  74. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  75. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/reminders.py +0 -0
  76. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/__init__.py +0 -0
  77. {klaude_code-1.2.29/src/klaude_code/core/tool/todo → klaude_code-1.2.30/src/klaude_code/core/tool/file}/__init__.py +0 -0
  78. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/_utils.py +0 -0
  79. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  80. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  81. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  82. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  83. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  84. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  85. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  86. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  87. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  88. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  89. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  90. {klaude_code-1.2.29/src/klaude_code/core/tool/skill → klaude_code-1.2.30/src/klaude_code/core/tool/shell}/__init__.py +0 -0
  91. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  92. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  93. {klaude_code-1.2.29/src/klaude_code/core/tool/shell → klaude_code-1.2.30/src/klaude_code/core/tool/skill}/__init__.py +0 -0
  94. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  95. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  96. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  97. {klaude_code-1.2.29/src/klaude_code/core/tool/file → klaude_code-1.2.30/src/klaude_code/core/tool/todo}/__init__.py +0 -0
  98. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  99. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  100. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  101. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  102. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  103. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_abc.py +0 -0
  104. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_context.py +0 -0
  105. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_registry.py +0 -0
  106. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_runner.py +0 -0
  107. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/truncation.py +0 -0
  108. {klaude_code-1.2.29/src/klaude_code/core → klaude_code-1.2.30/src/klaude_code/core/tool/web}/__init__.py +0 -0
  109. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  110. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  111. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  112. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  113. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  114. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  115. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/turn.py +0 -0
  116. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/__init__.py +0 -0
  117. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  118. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/client.py +0 -0
  119. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/input.py +0 -0
  120. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/client.py +0 -0
  121. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/codex/__init__.py +0 -0
  122. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/codex/client.py +0 -0
  123. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/input_common.py +0 -0
  124. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  125. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  126. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  127. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  128. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  129. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  130. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/client.py +0 -0
  131. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/input.py +0 -0
  132. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  133. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/registry.py +0 -0
  134. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/__init__.py +0 -0
  135. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/client.py +0 -0
  136. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/input.py +0 -0
  137. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/usage.py +0 -0
  138. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/__init__.py +0 -0
  139. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/events.py +0 -0
  140. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/llm_param.py +0 -0
  141. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/model.py +0 -0
  142. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  143. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  144. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
  145. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  146. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  147. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/tools.py +0 -0
  148. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/__init__.py +0 -0
  149. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/codec.py +0 -0
  150. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/export.py +0 -0
  151. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/selector.py +0 -0
  152. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/session.py +0 -0
  153. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/store.py +0 -0
  154. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/templates/export_session.html +0 -0
  155. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  156. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/__init__.py +0 -0
  157. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  158. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  159. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  160. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  161. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  162. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/loader.py +0 -0
  163. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/manager.py +0 -0
  164. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/system_skills.py +0 -0
  165. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/trace/__init__.py +0 -0
  166. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/trace/log.py +0 -0
  167. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/__init__.py +0 -0
  168. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/__init__.py +0 -0
  169. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/display.py +0 -0
  170. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/input.py +0 -0
  171. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/stage_manager.py +0 -0
  172. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/__init__.py +0 -0
  173. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  174. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/display.py +0 -0
  175. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  176. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/display.py +0 -0
  177. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  178. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  179. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  180. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/display.py +0 -0
  181. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
  182. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
  183. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  184. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
  185. {klaude_code-1.2.29/src/klaude_code → klaude_code-1.2.30/src/klaude_code/ui/renderers}/__init__.py +0 -0
  186. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/assistant.py +0 -0
  187. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/developer.py +0 -0
  188. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/diffs.py +0 -0
  189. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/errors.py +0 -0
  190. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
  191. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/user_input.py +0 -0
  192. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/__init__.py +0 -0
  193. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  194. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/code_panel.py +0 -0
  195. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/live.py +0 -0
  196. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/markdown.py +0 -0
  197. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/quote.py +0 -0
  198. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  199. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/status.py +0 -0
  200. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/__init__.py +0 -0
  201. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/color.py +0 -0
  202. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/control.py +0 -0
  203. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/notifier.py +0 -0
  204. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  205. {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/utils/__init__.py +0 -0
  206. {klaude_code-1.2.29 → klaude_code-1.2.30}/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.2.29
3
+ Version: 1.2.30
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.2.29"
7
+ version = "1.2.30"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -36,6 +36,7 @@ def ensure_commands_loaded() -> None:
36
36
  from .model_cmd import ModelCommand
37
37
  from .refresh_cmd import RefreshTerminalCommand
38
38
  from .release_notes_cmd import ReleaseNotesCommand
39
+ from .resume_cmd import ResumeCommand
39
40
  from .status_cmd import StatusCommand
40
41
  from .terminal_setup_cmd import TerminalSetupCommand
41
42
  from .thinking_cmd import ThinkingCommand
@@ -47,6 +48,7 @@ def ensure_commands_loaded() -> None:
47
48
  register(ThinkingCommand())
48
49
  register(ModelCommand())
49
50
  register(ForkSessionCommand())
51
+ register(ResumeCommand())
50
52
  load_prompt_commands()
51
53
  register(StatusCommand())
52
54
  register(HelpCommand())
@@ -70,6 +72,7 @@ def __getattr__(name: str) -> object:
70
72
  "ModelCommand": "model_cmd",
71
73
  "RefreshTerminalCommand": "refresh_cmd",
72
74
  "ReleaseNotesCommand": "release_notes_cmd",
75
+ "ResumeCommand": "resume_cmd",
73
76
  "StatusCommand": "status_cmd",
74
77
  "TerminalSetupCommand": "terminal_setup_cmd",
75
78
  "ThinkingCommand": "thinking_cmd",
@@ -15,4 +15,7 @@ class ClearCommand(CommandABC):
15
15
 
16
16
  async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
17
17
  del user_input # unused
18
- return CommandResult(operations=[op.ClearSessionOperation(session_id=agent.session.id)])
18
+ return CommandResult(
19
+ operations=[op.ClearSessionOperation(session_id=agent.session.id)],
20
+ persist_user_input=False,
21
+ )
@@ -42,6 +42,10 @@ class CommandResult(BaseModel):
42
42
  ) = None # List of UI events to display immediately
43
43
  operations: list[op.Operation] | None = None
44
44
 
45
+ # Persistence controls: some slash commands are UI/control actions and should not be written to session history.
46
+ persist_user_input: bool = True
47
+ persist_events: bool = True
48
+
45
49
 
46
50
  class CommandABC(ABC):
47
51
  """Abstract base class for slash commands."""
@@ -24,7 +24,7 @@ class ForkSessionCommand(CommandABC):
24
24
  command_output=model.CommandOutput(command_name=self.name),
25
25
  ),
26
26
  )
27
- return CommandResult(events=[event])
27
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
28
28
 
29
29
  new_session = agent.session.fork()
30
30
  await new_session.wait_for_flush()
@@ -39,4 +39,4 @@ class ForkSessionCommand(CommandABC):
39
39
  ),
40
40
  ),
41
41
  )
42
- return CommandResult(events=[event])
42
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
@@ -1,9 +1,43 @@
1
1
  import asyncio
2
2
 
3
+ import questionary
4
+
3
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
6
  from klaude_code.config.select_model import select_model_from_config
5
7
  from klaude_code.protocol import commands, events, model, op
6
8
 
9
+ SELECT_STYLE = questionary.Style(
10
+ [
11
+ ("instruction", "ansibrightblack"),
12
+ ("pointer", "ansicyan"),
13
+ ("highlighted", "ansicyan"),
14
+ ("text", "ansibrightblack"),
15
+ ]
16
+ )
17
+
18
+
19
+ 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)", value=True),
23
+ ]
24
+
25
+ try:
26
+ # Add a blank line between the model selector and this confirmation prompt.
27
+ questionary.print("")
28
+ result = questionary.select(
29
+ message=f"Save '{selected_model}' as default model (main_model)?",
30
+ choices=choices,
31
+ pointer="→",
32
+ instruction="Use arrow keys to move, Enter to select",
33
+ use_jk_keys=False,
34
+ style=SELECT_STYLE,
35
+ ).ask()
36
+ except KeyboardInterrupt:
37
+ return False
38
+
39
+ return bool(result)
40
+
7
41
 
8
42
  class ModelCommand(CommandABC):
9
43
  """Display or change the model configuration."""
@@ -44,7 +78,13 @@ class ModelCommand(CommandABC):
44
78
  )
45
79
  ]
46
80
  )
47
-
81
+ save_as_default = await asyncio.to_thread(_confirm_change_default_model_sync, selected_model)
48
82
  return CommandResult(
49
- operations=[op.ChangeModelOperation(session_id=agent.session.id, model_name=selected_model)]
83
+ operations=[
84
+ op.ChangeModelOperation(
85
+ session_id=agent.session.id,
86
+ model_name=selected_model,
87
+ save_as_default=save_as_default,
88
+ )
89
+ ]
50
90
  )
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+
3
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
+ from klaude_code.protocol import commands, events, model, op
5
+ from klaude_code.session.selector import resume_select_session
6
+
7
+
8
+ class ResumeCommand(CommandABC):
9
+ """Resume a previous session."""
10
+
11
+ @property
12
+ def name(self) -> commands.CommandName:
13
+ return commands.CommandName.RESUME
14
+
15
+ @property
16
+ def summary(self) -> str:
17
+ return "Resume a previous session"
18
+
19
+ @property
20
+ def is_interactive(self) -> bool:
21
+ return True
22
+
23
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
24
+ del user_input # unused
25
+
26
+ if agent.session.messages_count > 0:
27
+ event = events.DeveloperMessageEvent(
28
+ session_id=agent.session.id,
29
+ item=model.DeveloperMessageItem(
30
+ content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
31
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
32
+ ),
33
+ )
34
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
35
+
36
+ selected_session_id = await asyncio.to_thread(resume_select_session)
37
+ if selected_session_id is None:
38
+ event = events.DeveloperMessageEvent(
39
+ session_id=agent.session.id,
40
+ item=model.DeveloperMessageItem(
41
+ content="(no session selected)",
42
+ command_output=model.CommandOutput(command_name=self.name),
43
+ ),
44
+ )
45
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
46
+
47
+ return CommandResult(
48
+ operations=[op.ResumeSessionOperation(target_session_id=selected_session_id)],
49
+ persist_user_input=False,
50
+ persist_events=False,
51
+ )
@@ -152,4 +152,4 @@ class StatusCommand(CommandABC):
152
152
  ),
153
153
  )
154
154
 
155
- return CommandResult(events=[event])
155
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
@@ -53,6 +53,17 @@ provider_list:
53
53
  protocol: openrouter
54
54
  api_key: ${OPENROUTER_API_KEY}
55
55
  model_list:
56
+ - model_name: gpt-5-mini
57
+ model_params:
58
+ model: openai/gpt-5-mini
59
+ max_tokens: 128000
60
+ context_limit: 400000
61
+ thinking:
62
+ reasoning_effort: high
63
+ cost:
64
+ input: 0.25
65
+ output: 2.0
66
+ cache_read: 0.03
56
67
  - model_name: gpt-5.1-codex-max
57
68
  model_params:
58
69
  model: openai/gpt-5.1-codex-max
@@ -203,13 +203,15 @@ class ExecutorContext:
203
203
  raise ValueError("Multiple RunAgentOperation results are not supported")
204
204
 
205
205
  persisted_user_input = run_ops[0].input if run_ops else user_input
206
- agent.session.append_history(
207
- [model.UserMessageItem(content=persisted_user_input.text, images=persisted_user_input.images)]
208
- )
206
+
207
+ if result.persist_user_input:
208
+ agent.session.append_history(
209
+ [model.UserMessageItem(content=persisted_user_input.text, images=persisted_user_input.images)]
210
+ )
209
211
 
210
212
  if result.events:
211
213
  for evt in result.events:
212
- if isinstance(evt, events.DeveloperMessageEvent):
214
+ if result.persist_events and isinstance(evt, events.DeveloperMessageEvent):
213
215
  agent.session.append_history([evt.item])
214
216
  await self.emit_event(evt)
215
217
 
@@ -237,12 +239,13 @@ class ExecutorContext:
237
239
  agent.session.model_config_name = operation.model_name
238
240
  agent.session.model_thinking = llm_config.thinking
239
241
 
240
- # Save the selection as default main_model
241
- config.main_model = operation.model_name
242
- await config.save()
242
+ if operation.save_as_default:
243
+ config.main_model = operation.model_name
244
+ await config.save()
243
245
 
246
+ default_note = " (saved as default)" if operation.save_as_default else ""
244
247
  developer_item = model.DeveloperMessageItem(
245
- content=f"Switched to: {llm_config.model}",
248
+ content=f"Switched to: {llm_config.model}{default_note}",
246
249
  command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
247
250
  )
248
251
  agent.session.append_history([developer_item])
@@ -305,6 +308,37 @@ class ExecutorContext:
305
308
  )
306
309
  await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
307
310
 
311
+ async def handle_resume_session(self, operation: op.ResumeSessionOperation) -> None:
312
+ target_session = Session.load(operation.target_session_id)
313
+ if (
314
+ target_session.model_thinking is not None
315
+ and target_session.model_name
316
+ and target_session.model_name == self.llm_clients.main.model_name
317
+ ):
318
+ self.llm_clients.main.get_llm_config().thinking = target_session.model_thinking
319
+
320
+ profile = self.model_profile_provider.build_profile(self.llm_clients.main)
321
+ from klaude_code.core.agent import Agent
322
+
323
+ agent = Agent(session=target_session, profile=profile)
324
+
325
+ async for evt in agent.replay_history():
326
+ await self.emit_event(evt)
327
+
328
+ await self.emit_event(
329
+ events.WelcomeEvent(
330
+ work_dir=str(target_session.work_dir),
331
+ llm_config=self.llm_clients.main.get_llm_config(),
332
+ )
333
+ )
334
+
335
+ self._agent = agent
336
+ log_debug(
337
+ f"Resumed session: {target_session.id}",
338
+ style="cyan",
339
+ debug_type=DebugType.EXECUTION,
340
+ )
341
+
308
342
  async def handle_export_session(self, operation: op.ExportSessionOperation) -> None:
309
343
  agent = await self._ensure_agent(operation.session_id)
310
344
  try:
@@ -238,12 +238,13 @@ class TaskExecutor:
238
238
  return
239
239
 
240
240
  if turn is None or turn.task_finished:
241
- # Sub-agent with empty result should retry instead of finishing
242
- if ctx.sub_agent_state is not None and turn is not None and not turn.task_result.strip():
243
- yield events.ErrorEvent(
244
- error_message="Sub-agent returned empty result, retrying...",
245
- can_retry=True,
246
- )
241
+ # Empty result should retry instead of finishing
242
+ if turn is not None and not turn.task_result.strip():
243
+ if ctx.sub_agent_state is not None:
244
+ error_msg = "Sub-agent returned empty result, retrying..."
245
+ else:
246
+ error_msg = "Agent returned empty result, retrying..."
247
+ yield events.ErrorEvent(error_message=error_msg, can_retry=True)
247
248
  continue
248
249
  break
249
250
 
@@ -334,7 +334,9 @@ class BashTool(ToolABC):
334
334
  _best_effort_update_file_tracker(args.command)
335
335
  return model.ToolResultItem(
336
336
  status="success",
337
- output=output.strip(),
337
+ # Preserve leading whitespace for tools like `nl -ba`.
338
+ # Only trim trailing newlines to avoid adding an extra blank line in the UI.
339
+ output=output.rstrip("\n"),
338
340
  )
339
341
  else:
340
342
  combined = ""
@@ -346,7 +348,8 @@ class BashTool(ToolABC):
346
348
  combined = f"Command exited with code {rc}"
347
349
  return model.ToolResultItem(
348
350
  status="error",
349
- output=combined.strip(),
351
+ # Preserve leading whitespace; only trim trailing newlines.
352
+ output=combined.rstrip("\n"),
350
353
  )
351
354
  except FileNotFoundError:
352
355
  return model.ToolResultItem(
@@ -16,6 +16,7 @@ class CommandName(str, Enum):
16
16
  RELEASE_NOTES = "release-notes"
17
17
  THINKING = "thinking"
18
18
  FORK_SESSION = "fork-session"
19
+ RESUME = "resume"
19
20
  # PLAN and DOC are dynamically registered now, but kept here if needed for reference
20
21
  # or we can remove them if no code explicitly imports them.
21
22
  # PLAN = "plan"
@@ -27,6 +27,7 @@ class OperationType(Enum):
27
27
  CHANGE_MODEL = "change_model"
28
28
  CHANGE_THINKING = "change_thinking"
29
29
  CLEAR_SESSION = "clear_session"
30
+ RESUME_SESSION = "resume_session"
30
31
  EXPORT_SESSION = "export_session"
31
32
  INTERRUPT = "interrupt"
32
33
  INIT_AGENT = "init_agent"
@@ -73,6 +74,7 @@ class ChangeModelOperation(Operation):
73
74
  type: OperationType = OperationType.CHANGE_MODEL
74
75
  session_id: str
75
76
  model_name: str
77
+ save_as_default: bool = False
76
78
 
77
79
  async def execute(self, handler: OperationHandler) -> None:
78
80
  await handler.handle_change_model(self)
@@ -98,6 +100,16 @@ class ClearSessionOperation(Operation):
98
100
  await handler.handle_clear_session(self)
99
101
 
100
102
 
103
+ class ResumeSessionOperation(Operation):
104
+ """Operation for resuming a different session."""
105
+
106
+ type: OperationType = OperationType.RESUME_SESSION
107
+ target_session_id: str
108
+
109
+ async def execute(self, handler: OperationHandler) -> None:
110
+ await handler.handle_resume_session(self)
111
+
112
+
101
113
  class ExportSessionOperation(Operation):
102
114
  """Operation for exporting a session transcript to HTML."""
103
115
 
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  ExportSessionOperation,
17
17
  InitAgentOperation,
18
18
  InterruptOperation,
19
+ ResumeSessionOperation,
19
20
  RunAgentOperation,
20
21
  UserInputOperation,
21
22
  )
@@ -44,6 +45,10 @@ class OperationHandler(Protocol):
44
45
  """Handle a clear session operation."""
45
46
  ...
46
47
 
48
+ async def handle_resume_session(self, operation: ResumeSessionOperation) -> None:
49
+ """Handle a resume session operation."""
50
+ ...
51
+
47
52
  async def handle_export_session(self, operation: ExportSessionOperation) -> None:
48
53
  """Handle an export session operation."""
49
54
  ...
@@ -24,6 +24,8 @@ def truncate_display(
24
24
 
25
25
  Applies `ThemeKey.TOOL_RESULT_TRUNCATED` style to truncation indicators.
26
26
  """
27
+ # Expand tabs to spaces to ensure correct alignment when Rich applies padding.
28
+ text = text.expandtabs(8)
27
29
 
28
30
  if max_lines <= 0:
29
31
  truncated_lines = text.split("\n")
@@ -50,8 +50,7 @@ def ensure_viewer_file(*, code: str, link: str, tool_call_id: str) -> Path | Non
50
50
 
51
51
 
52
52
  def build_viewer(*, code: str, link: str, tool_call_id: str) -> Path | None:
53
- """Create a local Mermaid viewer HTML file.
54
- """
53
+ """Create a local Mermaid viewer HTML file."""
55
54
 
56
55
  if not code:
57
56
  return None
@@ -97,6 +97,7 @@ def _render_task_metadata_block(
97
97
  context_size = format_number(metadata.usage.context_size or 0)
98
98
  parts.append(
99
99
  Text.assemble(
100
+ ("context ", ThemeKey.METADATA_DIM),
100
101
  (context_size, ThemeKey.METADATA),
101
102
  (f" ({metadata.usage.context_usage_percent:.1f}%)", ThemeKey.METADATA_DIM),
102
103
  )
@@ -10,7 +10,7 @@ from klaude_code.ui.rich.markdown import ThinkingMarkdown
10
10
  from klaude_code.ui.rich.theme import ThemeKey
11
11
 
12
12
  # UI markers
13
- THINKING_MESSAGE_MARK = ""
13
+ THINKING_MESSAGE_MARK = ""
14
14
 
15
15
 
16
16
  def normalize_thinking_content(content: str) -> str:
@@ -1,7 +1,10 @@
1
1
  import json
2
+ import re
2
3
  from pathlib import Path
3
4
  from typing import Any, cast
4
5
 
6
+ from pygments.lexers import BashLexer # pyright: ignore[reportUnknownVariableType]
7
+ from pygments.token import Token
5
8
  from rich import box
6
9
  from rich.console import Group, RenderableType
7
10
  from rich.padding import Padding
@@ -15,6 +18,7 @@ from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_to
15
18
  from klaude_code.ui.renderers import diffs as r_diffs
16
19
  from klaude_code.ui.renderers import mermaid_viewer as r_mermaid_viewer
17
20
  from klaude_code.ui.renderers.common import create_grid, truncate_display
21
+ from klaude_code.ui.rich.code_panel import CodePanel
18
22
  from klaude_code.ui.rich.markdown import NoInsetMarkdown
19
23
  from klaude_code.ui.rich.theme import ThemeKey
20
24
 
@@ -36,6 +40,86 @@ MARK_TODO_PENDING = "▢"
36
40
  MARK_TODO_IN_PROGRESS = "◉"
37
41
  MARK_TODO_COMPLETED = "✔"
38
42
 
43
+ # Token types for bash syntax highlighting
44
+ _BASH_STRING_TOKENS = frozenset(
45
+ {
46
+ Token.Literal.String,
47
+ Token.Literal.String.Double,
48
+ Token.Literal.String.Single,
49
+ Token.Literal.String.Backtick,
50
+ Token.Literal.String.Escape,
51
+ Token.Literal.String.Heredoc,
52
+ Token.Comment,
53
+ Token.Comment.Single,
54
+ Token.Comment.Hashbang,
55
+ }
56
+ )
57
+
58
+ _BASH_OPERATOR_TOKENS = frozenset(
59
+ {
60
+ Token.Operator,
61
+ Token.Punctuation,
62
+ }
63
+ )
64
+
65
+ # Operators that start a new command context (next non-whitespace token is a command)
66
+ _BASH_COMMAND_STARTERS = frozenset({"&&", "||", "|", ";", "&"})
67
+
68
+ # Commands that have subcommands (e.g., git commit, docker run)
69
+ _SUBCOMMAND_COMMANDS = frozenset({
70
+ # Version control
71
+ "git", "jj", "hg", "svn",
72
+ # Container & orchestration
73
+ "docker", "docker-compose", "podman", "kubectl", "helm",
74
+ # Package managers
75
+ "npm", "yarn", "pnpm", "cargo", "uv", "pip", "poetry", "brew", "apt", "apt-get", "dnf", "yum", "pacman",
76
+ # Cloud CLIs
77
+ "aws", "gcloud", "az",
78
+ # Language tools
79
+ "go", "rustup", "python", "ruby",
80
+ # Other common tools
81
+ "gh", "systemctl", "launchctl", "supervisorctl",
82
+ })
83
+
84
+ _BASH_LEXER: Any = BashLexer(ensurenl=False) # pyright: ignore[reportUnknownVariableType]
85
+
86
+ # Regex to match heredoc: << [-]? [space]? ['"]? DELIMITER ['"]? [extra] \n body \n DELIMITER
87
+ # Groups: (<<-?) (space) (quote) (delimiter) (quote) (extra on first line) (body) (end delimiter)
88
+ _HEREDOC_PATTERN = re.compile(
89
+ r"^(<<-?)(\s*)(['\"]?)(\w+)\3([^\n]*)(\n.*\n)(\4)$",
90
+ re.DOTALL,
91
+ )
92
+
93
+
94
+ def _append_heredoc(result: Text, token_value: str) -> None:
95
+ """Append heredoc token with delimiter highlighting."""
96
+ match = _HEREDOC_PATTERN.match(token_value)
97
+ if match:
98
+ operator, space, quote, delimiter, extra, body, end_delimiter = match.groups()
99
+ # << or <<-
100
+ result.append(operator, style=ThemeKey.BASH_OPERATOR)
101
+ # Optional space
102
+ if space:
103
+ result.append(space)
104
+ # Opening quote
105
+ if quote:
106
+ result.append(quote, style=ThemeKey.BASH_HEREDOC_DELIMITER)
107
+ # Delimiter name (e.g., EOF)
108
+ result.append(delimiter, style=ThemeKey.BASH_HEREDOC_DELIMITER)
109
+ # Closing quote
110
+ if quote:
111
+ result.append(quote, style=ThemeKey.BASH_HEREDOC_DELIMITER)
112
+ # Extra content on first line (e.g., "> file.py")
113
+ if extra:
114
+ result.append(extra, style=ThemeKey.BASH_ARGUMENT)
115
+ # Body content
116
+ result.append(body, style=ThemeKey.BASH_STRING)
117
+ # End delimiter
118
+ result.append(end_delimiter, style=ThemeKey.BASH_HEREDOC_DELIMITER)
119
+ else:
120
+ # Fallback: couldn't parse heredoc structure
121
+ result.append(token_value, style=ThemeKey.BASH_STRING)
122
+
39
123
 
40
124
  def is_sub_agent_tool(tool_name: str) -> bool:
41
125
  return _is_sub_agent_tool(tool_name)
@@ -81,6 +165,65 @@ def render_generic_tool_call(tool_name: str, arguments: str, markup: str = MARK_
81
165
  return grid
82
166
 
83
167
 
168
+ def _highlight_bash_command(command: str) -> Text:
169
+ """Apply bash syntax highlighting to a command string, returning Rich Text.
170
+
171
+ Styling:
172
+ - Command names (first token after line start or operators): bold green
173
+ - Subcommands (for commands like git, docker): bold green
174
+ - Arguments: green
175
+ - Operators (&&, ||, |, ;): dim green
176
+ - Strings and comments: green
177
+ """
178
+ result = Text()
179
+ token_type: Any
180
+ token_value: str
181
+
182
+ # Track whether next non-whitespace token is a command
183
+ expect_command = True
184
+ # Track whether next non-flag token is a subcommand
185
+ expect_subcommand = False
186
+
187
+ for token_type, token_value in _BASH_LEXER.get_tokens(command):
188
+ # Determine style based on token type and context
189
+ if token_type in _BASH_STRING_TOKENS:
190
+ # Check if this is a heredoc (starts with <<)
191
+ if token_value.startswith("<<"):
192
+ _append_heredoc(result, token_value)
193
+ else:
194
+ result.append(token_value, style=ThemeKey.BASH_STRING)
195
+ expect_subcommand = False
196
+ elif token_type in _BASH_OPERATOR_TOKENS:
197
+ result.append(token_value, style=ThemeKey.BASH_OPERATOR)
198
+ # After command-starting operators, next token is a command
199
+ if token_value in _BASH_COMMAND_STARTERS:
200
+ expect_command = True
201
+ expect_subcommand = False
202
+ elif token_type in (Token.Text.Whitespace,):
203
+ result.append(token_value)
204
+ elif token_type == Token.Name.Builtin:
205
+ # Built-in commands are always commands
206
+ result.append(token_value, style=ThemeKey.BASH_COMMAND)
207
+ expect_command = False
208
+ expect_subcommand = token_value in _SUBCOMMAND_COMMANDS
209
+ elif expect_command and token_value.strip():
210
+ # First non-whitespace token in command context
211
+ result.append(token_value, style=ThemeKey.BASH_COMMAND)
212
+ expect_command = False
213
+ expect_subcommand = token_value in _SUBCOMMAND_COMMANDS
214
+ elif expect_subcommand and token_value.strip() and not token_value.startswith("-"):
215
+ # Subcommand: non-flag token after a command that has subcommands
216
+ result.append(token_value, style=ThemeKey.BASH_COMMAND)
217
+ expect_subcommand = False
218
+ else:
219
+ # Regular arguments (including flags, which reset subcommand expectation)
220
+ result.append(token_value, style=ThemeKey.BASH_ARGUMENT)
221
+ if token_value.strip():
222
+ expect_subcommand = False
223
+
224
+ return result
225
+
226
+
84
227
  def render_bash_tool_call(arguments: str) -> RenderableType:
85
228
  grid = create_grid()
86
229
  tool_name_column = Text.assemble((MARK_BASH, ThemeKey.TOOL_MARK), " ", ("Bash", ThemeKey.TOOL_NAME))
@@ -105,22 +248,43 @@ def render_bash_tool_call(arguments: str) -> RenderableType:
105
248
 
106
249
  payload: dict[str, object] = cast(dict[str, object], payload_raw)
107
250
 
108
- summary = Text("", ThemeKey.TOOL_PARAM)
109
251
  command = payload.get("command")
110
252
  timeout_ms = payload.get("timeout_ms")
111
253
 
254
+ # Build the command display with optional timeout suffix
112
255
  if isinstance(command, str) and command.strip():
113
- summary.append(command.strip(), style=ThemeKey.TOOL_PARAM)
114
-
115
- if isinstance(timeout_ms, int):
116
- if summary:
117
- summary.append(" ")
118
- if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
119
- summary.append(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
120
- else:
121
- summary.append(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
256
+ cmd_str = command.strip()
257
+ line_count = len(cmd_str.splitlines())
258
+
259
+ highlighted = _highlight_bash_command(cmd_str)
260
+
261
+ # For commands > 10 lines, use CodePanel for better display
262
+ if line_count > 10:
263
+ code_panel = CodePanel(highlighted, border_style=ThemeKey.LINES)
264
+ if isinstance(timeout_ms, int):
265
+ if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
266
+ timeout_text = Text(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
267
+ else:
268
+ timeout_text = Text(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
269
+ grid.add_row(tool_name_column, Group(code_panel, timeout_text))
270
+ else:
271
+ grid.add_row(tool_name_column, code_panel)
272
+ return grid
273
+ if isinstance(timeout_ms, int):
274
+ if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
275
+ highlighted.append(f" {timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
276
+ else:
277
+ highlighted.append(f" {timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
278
+ grid.add_row(tool_name_column, highlighted)
279
+ else:
280
+ summary = Text("", ThemeKey.TOOL_PARAM)
281
+ if isinstance(timeout_ms, int):
282
+ if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
283
+ summary.append(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
284
+ else:
285
+ summary.append(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
286
+ grid.add_row(tool_name_column, summary)
122
287
 
123
- grid.add_row(tool_name_column, summary)
124
288
  return grid
125
289
 
126
290