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.
- {klaude_code-1.2.29 → klaude_code-1.2.30}/PKG-INFO +1 -1
- {klaude_code-1.2.29 → klaude_code-1.2.30}/pyproject.toml +1 -1
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/__init__.py +3 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/clear_cmd.py +4 -1
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/command_abc.py +4 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/fork_session_cmd.py +2 -2
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/model_cmd.py +42 -2
- klaude_code-1.2.30/src/klaude_code/command/resume_cmd.py +51 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/status_cmd.py +1 -1
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/assets/builtin_config.yaml +11 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/executor.py +42 -8
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/task.py +7 -6
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.py +5 -2
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/commands.py +1 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/op.py +12 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/op_handler.py +5 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/common.py +2 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/mermaid_viewer.py +1 -2
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/metadata.py +1 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/thinking.py +1 -1
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/tools.py +175 -11
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/theme.py +15 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/README.md +0 -0
- {klaude_code-1.2.29/src/klaude_code/ui/renderers → klaude_code-1.2.30/src/klaude_code}/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/list_model.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/main.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/runtime.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/config.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/const.py +0 -0
- {klaude_code-1.2.29/src/klaude_code/core/tool/web → klaude_code-1.2.30/src/klaude_code/core}/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {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
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/__init__.py +0 -0
- {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
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {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
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {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
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {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
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.2.29/src/klaude_code/core → klaude_code-1.2.30/src/klaude_code/core/tool/web}/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/export.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/session.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
- {klaude_code-1.2.29/src/klaude_code → klaude_code-1.2.30/src/klaude_code/ui/renderers}/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/developer.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/markdown.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.29 → klaude_code-1.2.30}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -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(
|
|
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=[
|
|
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
|
+
)
|
|
@@ -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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
#
|
|
242
|
-
if
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
)
|
|
@@ -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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|