klaude-code 1.2.24__tar.gz → 1.2.25__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.
- {klaude_code-1.2.24 → klaude_code-1.2.25}/PKG-INFO +2 -1
- {klaude_code-1.2.24 → klaude_code-1.2.25}/pyproject.toml +2 -1
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/runtime.py +17 -1
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/thinking_cmd.py +37 -28
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/const.py +3 -5
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/executor.py +45 -2
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/op.py +11 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/op_handler.py +5 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/display.py +2 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/event_handler.py +3 -11
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +12 -1
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/renderer.py +99 -12
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/code_panel.py +24 -5
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/live.py +17 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/markdown.py +167 -104
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/status.py +5 -11
- {klaude_code-1.2.24 → klaude_code-1.2.25}/README.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/list_model.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/main.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/config/config.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/export.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/session.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/developer.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/rich/theme.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.25
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
7
7
|
Requires-Dist: ddgs>=9.9.3
|
|
8
8
|
Requires-Dist: diff-match-patch>=20241021
|
|
9
|
+
Requires-Dist: markdown-it-py>=4.0.0
|
|
9
10
|
Requires-Dist: openai>=1.102.0
|
|
10
11
|
Requires-Dist: pillow>=12.0.0
|
|
11
12
|
Requires-Dist: prompt-toolkit>=3.0.52
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "klaude-code"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.25"
|
|
8
8
|
description = "Add your description here"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -13,6 +13,7 @@ dependencies = [
|
|
|
13
13
|
"chardet>=5.2.0",
|
|
14
14
|
"ddgs>=9.9.3",
|
|
15
15
|
"diff-match-patch>=20241021",
|
|
16
|
+
"markdown-it-py>=4.0.0",
|
|
16
17
|
"openai>=1.102.0",
|
|
17
18
|
"pillow>=12.0.0",
|
|
18
19
|
"prompt-toolkit>=3.0.52",
|
|
@@ -265,7 +265,23 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
265
265
|
)
|
|
266
266
|
|
|
267
267
|
# Set up input provider for interactive mode
|
|
268
|
-
|
|
268
|
+
def _stop_rich_bottom_ui() -> None:
|
|
269
|
+
display = components.display
|
|
270
|
+
if isinstance(display, ui.REPLDisplay):
|
|
271
|
+
display.renderer.spinner_stop()
|
|
272
|
+
display.renderer.stop_bottom_live()
|
|
273
|
+
elif (
|
|
274
|
+
isinstance(display, ui.DebugEventDisplay)
|
|
275
|
+
and display.wrapped_display
|
|
276
|
+
and isinstance(display.wrapped_display, ui.REPLDisplay)
|
|
277
|
+
):
|
|
278
|
+
display.wrapped_display.renderer.spinner_stop()
|
|
279
|
+
display.wrapped_display.renderer.stop_bottom_live()
|
|
280
|
+
|
|
281
|
+
input_provider: ui.InputProviderABC = ui.PromptToolkitInput(
|
|
282
|
+
status_provider=_status_provider,
|
|
283
|
+
pre_prompt=_stop_rich_bottom_ui,
|
|
284
|
+
)
|
|
269
285
|
|
|
270
286
|
# --- Custom Ctrl+C handler: double-press within 2s to exit, single press shows toast ---
|
|
271
287
|
def _show_toast_once() -> None:
|
|
@@ -56,6 +56,14 @@ def _is_gemini_flash_model(model_name: str | None) -> bool:
|
|
|
56
56
|
return "gemini-3-flash" in model_name.lower()
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def should_auto_trigger_thinking(model_name: str | None) -> bool:
|
|
60
|
+
"""Check if model should auto-trigger thinking selection on switch."""
|
|
61
|
+
if not model_name:
|
|
62
|
+
return False
|
|
63
|
+
model_lower = model_name.lower()
|
|
64
|
+
return "gpt-5" in model_lower or "gemini-3" in model_lower or "opus" in model_lower
|
|
65
|
+
|
|
66
|
+
|
|
59
67
|
def _get_levels_for_responses(model_name: str | None) -> list[str]:
|
|
60
68
|
"""Get thinking levels for responses protocol."""
|
|
61
69
|
if _is_codex_max_model(model_name):
|
|
@@ -69,7 +77,7 @@ def _get_levels_for_responses(model_name: str | None) -> list[str]:
|
|
|
69
77
|
return RESPONSES_LEVELS
|
|
70
78
|
|
|
71
79
|
|
|
72
|
-
def
|
|
80
|
+
def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
73
81
|
"""Format the current thinking configuration for display."""
|
|
74
82
|
thinking = config.thinking
|
|
75
83
|
if not thinking:
|
|
@@ -164,6 +172,31 @@ def _select_anthropic_thinking_sync() -> llm_param.Thinking | None:
|
|
|
164
172
|
return None
|
|
165
173
|
|
|
166
174
|
|
|
175
|
+
async def select_thinking_for_protocol(config: llm_param.LLMConfigParameter) -> llm_param.Thinking | None:
|
|
176
|
+
"""Select thinking configuration based on the LLM protocol.
|
|
177
|
+
|
|
178
|
+
Returns the selected Thinking config, or None if user cancelled.
|
|
179
|
+
"""
|
|
180
|
+
protocol = config.protocol
|
|
181
|
+
model_name = config.model
|
|
182
|
+
|
|
183
|
+
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
|
|
184
|
+
return await asyncio.to_thread(_select_responses_thinking_sync, model_name)
|
|
185
|
+
|
|
186
|
+
if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
187
|
+
return await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
188
|
+
|
|
189
|
+
if protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
190
|
+
if _is_openrouter_model_with_reasoning_effort(model_name):
|
|
191
|
+
return await asyncio.to_thread(_select_responses_thinking_sync, model_name)
|
|
192
|
+
return await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
193
|
+
|
|
194
|
+
if protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
195
|
+
return await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
196
|
+
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
167
200
|
class ThinkingCommand(CommandABC):
|
|
168
201
|
"""Configure model thinking/reasoning level."""
|
|
169
202
|
|
|
@@ -185,40 +218,16 @@ class ThinkingCommand(CommandABC):
|
|
|
185
218
|
return self._no_change_result(agent, "No profile configured")
|
|
186
219
|
|
|
187
220
|
config = agent.profile.llm_client.get_llm_config()
|
|
188
|
-
|
|
189
|
-
model_name = config.model
|
|
190
|
-
|
|
191
|
-
current = _format_current_thinking(config)
|
|
192
|
-
|
|
193
|
-
# Select new thinking configuration based on protocol
|
|
194
|
-
new_thinking: llm_param.Thinking | None = None
|
|
195
|
-
|
|
196
|
-
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
|
|
197
|
-
new_thinking = await asyncio.to_thread(_select_responses_thinking_sync, model_name)
|
|
198
|
-
|
|
199
|
-
elif protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
200
|
-
new_thinking = await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
201
|
-
|
|
202
|
-
elif protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
203
|
-
if _is_openrouter_model_with_reasoning_effort(model_name):
|
|
204
|
-
new_thinking = await asyncio.to_thread(_select_responses_thinking_sync, model_name)
|
|
205
|
-
else:
|
|
206
|
-
new_thinking = await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
207
|
-
|
|
208
|
-
elif protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
209
|
-
# openai_compatible uses anthropic style
|
|
210
|
-
new_thinking = await asyncio.to_thread(_select_anthropic_thinking_sync)
|
|
211
|
-
|
|
212
|
-
else:
|
|
213
|
-
return self._no_change_result(agent, f"Unsupported protocol: {protocol}")
|
|
221
|
+
current = format_current_thinking(config)
|
|
214
222
|
|
|
223
|
+
new_thinking = await select_thinking_for_protocol(config)
|
|
215
224
|
if new_thinking is None:
|
|
216
225
|
return self._no_change_result(agent, "(no change)")
|
|
217
226
|
|
|
218
227
|
# Apply the new thinking configuration
|
|
219
228
|
config.thinking = new_thinking
|
|
220
229
|
agent.session.model_thinking = new_thinking
|
|
221
|
-
new_status =
|
|
230
|
+
new_status = format_current_thinking(config)
|
|
222
231
|
|
|
223
232
|
return CommandResult(
|
|
224
233
|
events=[
|
|
@@ -117,16 +117,14 @@ STATUS_DEFAULT_TEXT = "Thinking …"
|
|
|
117
117
|
# Status shimmer animation
|
|
118
118
|
# Horizontal padding used when computing shimmer band position
|
|
119
119
|
STATUS_SHIMMER_PADDING = 10
|
|
120
|
-
# Duration in seconds for one full shimmer sweep across the text
|
|
121
|
-
STATUS_SHIMMER_SWEEP_SECONDS = 2
|
|
122
120
|
# Half-width of the shimmer band in characters
|
|
123
121
|
STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0
|
|
124
122
|
# Scale factor applied to shimmer intensity when blending colors
|
|
125
123
|
STATUS_SHIMMER_ALPHA_SCALE = 0.7
|
|
126
124
|
|
|
127
|
-
# Spinner breathing animation
|
|
128
|
-
# Duration in seconds for one full breathe-in + breathe-out cycle
|
|
129
|
-
#
|
|
125
|
+
# Spinner breathing and shimmer animation period
|
|
126
|
+
# Duration in seconds for one full breathe-in + breathe-out cycle (breathing)
|
|
127
|
+
# and one full shimmer sweep across the text (shimmer)
|
|
130
128
|
SPINNER_BREATH_PERIOD_SECONDS: float = 2.0
|
|
131
129
|
|
|
132
130
|
|
|
@@ -14,6 +14,11 @@ from dataclasses import dataclass
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
from klaude_code.command import dispatch_command
|
|
17
|
+
from klaude_code.command.thinking_cmd import (
|
|
18
|
+
format_current_thinking,
|
|
19
|
+
select_thinking_for_protocol,
|
|
20
|
+
should_auto_trigger_thinking,
|
|
21
|
+
)
|
|
17
22
|
from klaude_code.config import load_config
|
|
18
23
|
from klaude_code.core.agent import Agent, DefaultModelProfileProvider, ModelProfileProvider
|
|
19
24
|
from klaude_code.core.manager import LLMClients, SubAgentManager
|
|
@@ -235,17 +240,55 @@ class ExecutorContext:
|
|
|
235
240
|
agent.session.model_thinking = llm_config.thinking
|
|
236
241
|
|
|
237
242
|
developer_item = model.DeveloperMessageItem(
|
|
238
|
-
content=f"
|
|
243
|
+
content=f"Switched to: {llm_config.model}",
|
|
239
244
|
command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
|
|
240
245
|
)
|
|
241
246
|
agent.session.append_history([developer_item])
|
|
242
247
|
|
|
243
248
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
244
|
-
await self.emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
|
|
245
249
|
|
|
246
250
|
if self._on_model_change is not None:
|
|
247
251
|
self._on_model_change(llm_client.model_name)
|
|
248
252
|
|
|
253
|
+
if should_auto_trigger_thinking(llm_config.model):
|
|
254
|
+
thinking_op = op.ChangeThinkingOperation(session_id=operation.session_id)
|
|
255
|
+
await thinking_op.execute(handler=self)
|
|
256
|
+
# WelcomeEvent is already handled by the thinking change
|
|
257
|
+
else:
|
|
258
|
+
await self.emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
|
|
259
|
+
|
|
260
|
+
async def handle_change_thinking(self, operation: op.ChangeThinkingOperation) -> None:
|
|
261
|
+
"""Handle a change thinking operation by prompting user to select thinking level."""
|
|
262
|
+
agent = await self._ensure_agent(operation.session_id)
|
|
263
|
+
if not agent.profile:
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
config = agent.profile.llm_client.get_llm_config()
|
|
267
|
+
current = format_current_thinking(config)
|
|
268
|
+
|
|
269
|
+
new_thinking = await select_thinking_for_protocol(config)
|
|
270
|
+
|
|
271
|
+
if new_thinking is None:
|
|
272
|
+
developer_item = model.DeveloperMessageItem(
|
|
273
|
+
content="(thinking unchanged)",
|
|
274
|
+
command_output=model.CommandOutput(command_name=commands.CommandName.THINKING),
|
|
275
|
+
)
|
|
276
|
+
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
config.thinking = new_thinking
|
|
280
|
+
agent.session.model_thinking = new_thinking
|
|
281
|
+
new_status = format_current_thinking(config)
|
|
282
|
+
|
|
283
|
+
developer_item = model.DeveloperMessageItem(
|
|
284
|
+
content=f"Thinking changed: {current} -> {new_status}",
|
|
285
|
+
command_output=model.CommandOutput(command_name=commands.CommandName.THINKING),
|
|
286
|
+
)
|
|
287
|
+
agent.session.append_history([developer_item])
|
|
288
|
+
|
|
289
|
+
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
290
|
+
await self.emit_event(events.WelcomeEvent(work_dir=str(agent.session.work_dir), llm_config=config))
|
|
291
|
+
|
|
249
292
|
async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
|
|
250
293
|
agent = await self._ensure_agent(operation.session_id)
|
|
251
294
|
new_session = Session.create(work_dir=agent.session.work_dir)
|
|
@@ -25,6 +25,7 @@ class OperationType(Enum):
|
|
|
25
25
|
USER_INPUT = "user_input"
|
|
26
26
|
RUN_AGENT = "run_agent"
|
|
27
27
|
CHANGE_MODEL = "change_model"
|
|
28
|
+
CHANGE_THINKING = "change_thinking"
|
|
28
29
|
CLEAR_SESSION = "clear_session"
|
|
29
30
|
EXPORT_SESSION = "export_session"
|
|
30
31
|
INTERRUPT = "interrupt"
|
|
@@ -77,6 +78,16 @@ class ChangeModelOperation(Operation):
|
|
|
77
78
|
await handler.handle_change_model(self)
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
class ChangeThinkingOperation(Operation):
|
|
82
|
+
"""Operation for changing the thinking/reasoning configuration."""
|
|
83
|
+
|
|
84
|
+
type: OperationType = OperationType.CHANGE_THINKING
|
|
85
|
+
session_id: str
|
|
86
|
+
|
|
87
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
88
|
+
await handler.handle_change_thinking(self)
|
|
89
|
+
|
|
90
|
+
|
|
80
91
|
class ClearSessionOperation(Operation):
|
|
81
92
|
"""Operation for clearing the active session and starting a new one."""
|
|
82
93
|
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Protocol
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from klaude_code.protocol.op import (
|
|
13
13
|
ChangeModelOperation,
|
|
14
|
+
ChangeThinkingOperation,
|
|
14
15
|
ClearSessionOperation,
|
|
15
16
|
ExportSessionOperation,
|
|
16
17
|
InitAgentOperation,
|
|
@@ -35,6 +36,10 @@ class OperationHandler(Protocol):
|
|
|
35
36
|
"""Handle a change model operation."""
|
|
36
37
|
...
|
|
37
38
|
|
|
39
|
+
async def handle_change_thinking(self, operation: ChangeThinkingOperation) -> None:
|
|
40
|
+
"""Handle a change thinking operation."""
|
|
41
|
+
...
|
|
42
|
+
|
|
38
43
|
async def handle_clear_session(self, operation: ClearSessionOperation) -> None:
|
|
39
44
|
"""Handle a clear session operation."""
|
|
40
45
|
...
|
|
@@ -368,22 +368,20 @@ class DisplayEventHandler:
|
|
|
368
368
|
|
|
369
369
|
first_delta = not self.thinking_stream.is_active
|
|
370
370
|
if first_delta:
|
|
371
|
-
self.renderer.console.push_theme(self.renderer.themes.thinking_markdown_theme)
|
|
372
371
|
mdstream = MarkdownStream(
|
|
373
372
|
mdargs={
|
|
374
373
|
"code_theme": self.renderer.themes.code_theme,
|
|
375
|
-
"style":
|
|
374
|
+
"style": ThemeKey.THINKING,
|
|
376
375
|
},
|
|
377
376
|
theme=self.renderer.themes.thinking_markdown_theme,
|
|
378
377
|
console=self.renderer.console,
|
|
379
|
-
|
|
378
|
+
live_sink=self.renderer.set_stream_renderable,
|
|
380
379
|
mark=THINKING_MESSAGE_MARK,
|
|
381
380
|
mark_style=ThemeKey.THINKING,
|
|
382
381
|
left_margin=const.MARKDOWN_LEFT_MARGIN,
|
|
383
382
|
markdown_class=ThinkingMarkdown,
|
|
384
383
|
)
|
|
385
384
|
self.thinking_stream.start(mdstream)
|
|
386
|
-
self.renderer.spinner_stop()
|
|
387
385
|
|
|
388
386
|
self.thinking_stream.append(event.content)
|
|
389
387
|
|
|
@@ -414,17 +412,13 @@ class DisplayEventHandler:
|
|
|
414
412
|
mdargs={"code_theme": self.renderer.themes.code_theme},
|
|
415
413
|
theme=self.renderer.themes.markdown_theme,
|
|
416
414
|
console=self.renderer.console,
|
|
417
|
-
|
|
415
|
+
live_sink=self.renderer.set_stream_renderable,
|
|
418
416
|
mark=ASSISTANT_MESSAGE_MARK,
|
|
419
417
|
left_margin=const.MARKDOWN_LEFT_MARGIN,
|
|
420
418
|
)
|
|
421
419
|
self.assistant_stream.start(mdstream)
|
|
422
420
|
self.assistant_stream.append(event.content)
|
|
423
421
|
if first_delta and self.assistant_stream.mdstream is not None:
|
|
424
|
-
# Stop spinner and immediately start MarkdownStream's Live
|
|
425
|
-
# to avoid flicker. The update() call starts the Live with
|
|
426
|
-
# the spinner embedded, providing seamless transition.
|
|
427
|
-
self.renderer.spinner_stop()
|
|
428
422
|
self.assistant_stream.mdstream.update(self.assistant_stream.buffer)
|
|
429
423
|
await self.stage_manager.transition_to(Stage.ASSISTANT)
|
|
430
424
|
await self._flush_assistant_buffer(self.assistant_stream)
|
|
@@ -488,7 +482,6 @@ class DisplayEventHandler:
|
|
|
488
482
|
self.spinner_status.reset()
|
|
489
483
|
self.renderer.spinner_stop()
|
|
490
484
|
self.renderer.console.print(Rule(characters="-", style=ThemeKey.LINES))
|
|
491
|
-
self.renderer.print()
|
|
492
485
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
493
486
|
self._maybe_notify_task_finish(event)
|
|
494
487
|
|
|
@@ -552,7 +545,6 @@ class DisplayEventHandler:
|
|
|
552
545
|
assert mdstream is not None
|
|
553
546
|
mdstream.update(normalize_thinking_content(self.thinking_stream.buffer), final=True)
|
|
554
547
|
self.thinking_stream.finish()
|
|
555
|
-
self.renderer.console.pop_theme()
|
|
556
548
|
self.renderer.print()
|
|
557
549
|
self.renderer.spinner_start()
|
|
558
550
|
|
{klaude_code-1.2.24 → klaude_code-1.2.25}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import shutil
|
|
4
5
|
from collections.abc import AsyncIterator, Callable
|
|
5
6
|
from pathlib import Path
|
|
@@ -51,8 +52,12 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
51
52
|
self,
|
|
52
53
|
prompt: str = USER_MESSAGE_MARK,
|
|
53
54
|
status_provider: Callable[[], REPLStatusSnapshot] | None = None,
|
|
55
|
+
pre_prompt: Callable[[], None] | None = None,
|
|
56
|
+
post_prompt: Callable[[], None] | None = None,
|
|
54
57
|
): # ▌
|
|
55
58
|
self._status_provider = status_provider
|
|
59
|
+
self._pre_prompt = pre_prompt
|
|
60
|
+
self._post_prompt = post_prompt
|
|
56
61
|
self._is_light_terminal_background = is_light_terminal_background(timeout=0.2)
|
|
57
62
|
|
|
58
63
|
project = str(Path.cwd()).strip("/").replace("/", "-")
|
|
@@ -80,7 +85,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
80
85
|
[(INPUT_PROMPT_STYLE, prompt)],
|
|
81
86
|
history=FileHistory(str(history_path)),
|
|
82
87
|
multiline=True,
|
|
83
|
-
cursor=CursorShape.
|
|
88
|
+
cursor=CursorShape.BLINKING_BEAM,
|
|
84
89
|
prompt_continuation=[(INPUT_PROMPT_STYLE, " ")],
|
|
85
90
|
key_bindings=kb,
|
|
86
91
|
completer=ThreadedCompleter(create_repl_completer()),
|
|
@@ -202,8 +207,14 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
202
207
|
@override
|
|
203
208
|
async def iter_inputs(self) -> AsyncIterator[UserInputPayload]:
|
|
204
209
|
while True:
|
|
210
|
+
if self._pre_prompt is not None:
|
|
211
|
+
with contextlib.suppress(Exception):
|
|
212
|
+
self._pre_prompt()
|
|
205
213
|
with patch_stdout():
|
|
206
214
|
line: str = await self._session.prompt_async(placeholder=self._render_input_placeholder())
|
|
215
|
+
if self._post_prompt is not None:
|
|
216
|
+
with contextlib.suppress(Exception):
|
|
217
|
+
self._post_prompt()
|
|
207
218
|
|
|
208
219
|
# Extract images referenced in the input text
|
|
209
220
|
images = extract_images_from_text(line)
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from collections.abc import Iterator
|
|
4
5
|
from contextlib import contextmanager
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
|
-
from rich.console import Console
|
|
9
|
+
from rich.console import Console, Group, RenderableType
|
|
10
|
+
from rich.padding import Padding
|
|
9
11
|
from rich.spinner import Spinner
|
|
10
|
-
from rich.status import Status
|
|
11
12
|
from rich.style import Style, StyleType
|
|
12
13
|
from rich.text import Text
|
|
13
14
|
|
|
@@ -23,8 +24,9 @@ from klaude_code.ui.renderers import tools as r_tools
|
|
|
23
24
|
from klaude_code.ui.renderers import user_input as r_user_input
|
|
24
25
|
from klaude_code.ui.renderers.common import truncate_display
|
|
25
26
|
from klaude_code.ui.rich import status as r_status
|
|
27
|
+
from klaude_code.ui.rich.live import CropAboveLive, SingleLine
|
|
26
28
|
from klaude_code.ui.rich.quote import Quote
|
|
27
|
-
from klaude_code.ui.rich.status import ShimmerStatusText
|
|
29
|
+
from klaude_code.ui.rich.status import BreathingSpinner, ShimmerStatusText
|
|
28
30
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
29
31
|
|
|
30
32
|
|
|
@@ -42,10 +44,18 @@ class REPLRenderer:
|
|
|
42
44
|
self.themes = get_theme(theme)
|
|
43
45
|
self.console: Console = Console(theme=self.themes.app_theme)
|
|
44
46
|
self.console.push_theme(self.themes.markdown_theme)
|
|
45
|
-
self.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
self._bottom_live: CropAboveLive | None = None
|
|
48
|
+
self._stream_renderable: RenderableType | None = None
|
|
49
|
+
self._stream_max_height: int = 0
|
|
50
|
+
self._stream_last_height: int = 0
|
|
51
|
+
self._stream_last_width: int = 0
|
|
52
|
+
self._spinner_visible: bool = False
|
|
53
|
+
|
|
54
|
+
self._status_text: ShimmerStatusText = ShimmerStatusText(const.STATUS_DEFAULT_TEXT)
|
|
55
|
+
self._status_spinner: Spinner = BreathingSpinner(
|
|
56
|
+
r_status.spinner_name(),
|
|
57
|
+
text=SingleLine(self._status_text),
|
|
58
|
+
style=ThemeKey.STATUS_SPINNER,
|
|
49
59
|
)
|
|
50
60
|
|
|
51
61
|
self.session_map: dict[str, SessionStatus] = {}
|
|
@@ -235,7 +245,11 @@ class REPLRenderer:
|
|
|
235
245
|
def display_task_finish(self, event: events.TaskFinishEvent) -> None:
|
|
236
246
|
if self.is_sub_agent_session(event.session_id):
|
|
237
247
|
session_status = self.session_map.get(event.session_id)
|
|
238
|
-
description =
|
|
248
|
+
description = (
|
|
249
|
+
session_status.sub_agent_state.sub_agent_desc
|
|
250
|
+
if session_status and session_status.sub_agent_state
|
|
251
|
+
else None
|
|
252
|
+
)
|
|
239
253
|
panel_style = self.get_session_sub_agent_background(event.session_id)
|
|
240
254
|
with self.session_print_context(event.session_id):
|
|
241
255
|
self.print(
|
|
@@ -265,16 +279,89 @@ class REPLRenderer:
|
|
|
265
279
|
|
|
266
280
|
def spinner_start(self) -> None:
|
|
267
281
|
"""Start the spinner animation."""
|
|
268
|
-
self.
|
|
282
|
+
self._spinner_visible = True
|
|
283
|
+
self._ensure_bottom_live_started()
|
|
284
|
+
self._refresh_bottom_live()
|
|
269
285
|
|
|
270
286
|
def spinner_stop(self) -> None:
|
|
271
287
|
"""Stop the spinner animation."""
|
|
272
|
-
self.
|
|
288
|
+
self._spinner_visible = False
|
|
289
|
+
self._refresh_bottom_live()
|
|
273
290
|
|
|
274
291
|
def spinner_update(self, status_text: str | Text, right_text: Text | None = None) -> None:
|
|
275
292
|
"""Update the spinner status text with optional right-aligned text."""
|
|
276
|
-
self.
|
|
293
|
+
self._status_text = ShimmerStatusText(status_text, right_text)
|
|
294
|
+
self._status_spinner.update(text=SingleLine(self._status_text), style=ThemeKey.STATUS_SPINNER)
|
|
295
|
+
self._refresh_bottom_live()
|
|
277
296
|
|
|
278
297
|
def spinner_renderable(self) -> Spinner:
|
|
279
298
|
"""Return the spinner's renderable for embedding in other components."""
|
|
280
|
-
return self.
|
|
299
|
+
return self._status_spinner
|
|
300
|
+
|
|
301
|
+
def set_stream_renderable(self, renderable: RenderableType | None) -> None:
|
|
302
|
+
"""Set the current streaming renderable displayed above the status line."""
|
|
303
|
+
|
|
304
|
+
if renderable is None:
|
|
305
|
+
self._stream_renderable = None
|
|
306
|
+
self._stream_max_height = 0
|
|
307
|
+
self._stream_last_height = 0
|
|
308
|
+
self._stream_last_width = 0
|
|
309
|
+
self._refresh_bottom_live()
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
self._ensure_bottom_live_started()
|
|
313
|
+
self._stream_renderable = renderable
|
|
314
|
+
|
|
315
|
+
height = len(self.console.render_lines(renderable, self.console.options, pad=False))
|
|
316
|
+
self._stream_last_height = height
|
|
317
|
+
self._stream_last_width = self.console.size.width
|
|
318
|
+
self._stream_max_height = max(self._stream_max_height, height)
|
|
319
|
+
self._refresh_bottom_live()
|
|
320
|
+
|
|
321
|
+
def _ensure_bottom_live_started(self) -> None:
|
|
322
|
+
if self._bottom_live is not None:
|
|
323
|
+
return
|
|
324
|
+
self._bottom_live = CropAboveLive(
|
|
325
|
+
Text(""),
|
|
326
|
+
console=self.console,
|
|
327
|
+
refresh_per_second=30,
|
|
328
|
+
transient=True,
|
|
329
|
+
redirect_stdout=False,
|
|
330
|
+
redirect_stderr=False,
|
|
331
|
+
)
|
|
332
|
+
self._bottom_live.start()
|
|
333
|
+
|
|
334
|
+
def _bottom_renderable(self) -> RenderableType:
|
|
335
|
+
stream = self._stream_renderable
|
|
336
|
+
if stream is not None:
|
|
337
|
+
current_width = self.console.size.width
|
|
338
|
+
if self._stream_last_width != current_width:
|
|
339
|
+
height = len(self.console.render_lines(stream, self.console.options, pad=False))
|
|
340
|
+
self._stream_last_height = height
|
|
341
|
+
self._stream_last_width = current_width
|
|
342
|
+
self._stream_max_height = max(self._stream_max_height, height)
|
|
343
|
+
else:
|
|
344
|
+
height = self._stream_last_height
|
|
345
|
+
|
|
346
|
+
pad_lines = max(self._stream_max_height - height, 0)
|
|
347
|
+
if pad_lines:
|
|
348
|
+
stream = Padding(stream, (0, 0, pad_lines, 0))
|
|
349
|
+
|
|
350
|
+
stream_part: RenderableType = stream if stream is not None else Group()
|
|
351
|
+
gap_part: RenderableType = Text("") if self._spinner_visible else Group()
|
|
352
|
+
status_part: RenderableType = SingleLine(self._status_spinner) if self._spinner_visible else Group()
|
|
353
|
+
return Group(stream_part, gap_part, status_part)
|
|
354
|
+
|
|
355
|
+
def _refresh_bottom_live(self) -> None:
|
|
356
|
+
if self._bottom_live is None:
|
|
357
|
+
return
|
|
358
|
+
self._bottom_live.update(self._bottom_renderable(), refresh=True)
|
|
359
|
+
|
|
360
|
+
def stop_bottom_live(self) -> None:
|
|
361
|
+
if self._bottom_live is None:
|
|
362
|
+
return
|
|
363
|
+
with contextlib.suppress(Exception):
|
|
364
|
+
# Avoid cursor restore when stopping right before prompt_toolkit.
|
|
365
|
+
self._bottom_live.transient = False
|
|
366
|
+
self._bottom_live.stop()
|
|
367
|
+
self._bottom_live = None
|
|
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
+
from rich.cells import cell_len
|
|
7
8
|
from rich.console import ConsoleRenderable, RichCast
|
|
8
9
|
from rich.jupyter import JupyterMixin
|
|
9
|
-
from rich.measure import Measurement
|
|
10
|
+
from rich.measure import Measurement
|
|
10
11
|
from rich.segment import Segment
|
|
11
12
|
from rich.style import StyleType
|
|
12
13
|
|
|
@@ -58,17 +59,29 @@ class CodePanel(JupyterMixin):
|
|
|
58
59
|
self.expand = expand
|
|
59
60
|
self.padding = padding
|
|
60
61
|
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _measure_max_line_cells(lines: list[list[Segment]]) -> int:
|
|
64
|
+
max_cells = 0
|
|
65
|
+
for line in lines:
|
|
66
|
+
plain = "".join(segment.text for segment in line).rstrip()
|
|
67
|
+
max_cells = max(max_cells, cell_len(plain))
|
|
68
|
+
return max_cells
|
|
69
|
+
|
|
61
70
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
62
71
|
border_style = console.get_style(self.border_style)
|
|
63
72
|
max_width = options.max_width
|
|
64
73
|
pad = self.padding
|
|
65
74
|
|
|
75
|
+
max_content_width = max(max_width - pad * 2, 1)
|
|
76
|
+
|
|
66
77
|
# Measure the content width (account for padding)
|
|
67
78
|
if self.expand:
|
|
68
|
-
content_width =
|
|
79
|
+
content_width = max_content_width
|
|
69
80
|
else:
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
probe_options = options.update(width=max_content_width)
|
|
82
|
+
probe_lines = console.render_lines(self.renderable, probe_options, pad=False)
|
|
83
|
+
content_width = self._measure_max_line_cells(probe_lines)
|
|
84
|
+
content_width = max(1, min(content_width, max_content_width))
|
|
72
85
|
|
|
73
86
|
# Render content lines
|
|
74
87
|
child_options = options.update(width=content_width)
|
|
@@ -108,5 +121,11 @@ class CodePanel(JupyterMixin):
|
|
|
108
121
|
def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
|
|
109
122
|
if self.expand:
|
|
110
123
|
return Measurement(options.max_width, options.max_width)
|
|
111
|
-
|
|
124
|
+
max_width = options.max_width
|
|
125
|
+
max_content_width = max(max_width - self.padding * 2, 1)
|
|
126
|
+
probe_options = options.update(width=max_content_width)
|
|
127
|
+
probe_lines = console.render_lines(self.renderable, probe_options, pad=False)
|
|
128
|
+
content_width = self._measure_max_line_cells(probe_lines)
|
|
129
|
+
content_width = max(1, min(content_width, max_content_width))
|
|
130
|
+
width = content_width + self.padding * 2
|
|
112
131
|
return Measurement(width, width)
|
|
@@ -63,3 +63,20 @@ class CropAboveLive(Live):
|
|
|
63
63
|
|
|
64
64
|
def update(self, renderable: RenderableType, refresh: bool = True) -> None: # type: ignore[override]
|
|
65
65
|
super().update(CropAbove(renderable, style=self._crop_style), refresh=refresh)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SingleLine:
|
|
69
|
+
"""Render only the first line of a renderable.
|
|
70
|
+
|
|
71
|
+
This is used to ensure dynamic UI elements (spinners / status) never wrap
|
|
72
|
+
to multiple lines, which would appear as a vertical "jump".
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, renderable: RenderableType) -> None:
|
|
76
|
+
self.renderable = renderable
|
|
77
|
+
|
|
78
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
79
|
+
line_options = options.update(no_wrap=True, overflow="ellipsis", height=1)
|
|
80
|
+
lines = console.render_lines(self.renderable, line_options, pad=False)
|
|
81
|
+
if lines:
|
|
82
|
+
yield from lines[0]
|