klaude-code 1.2.27__tar.gz → 1.2.29__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.27 → klaude_code-1.2.29}/PKG-INFO +1 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/pyproject.toml +2 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/config_cmd.py +13 -6
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/debug.py +9 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/list_model.py +1 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/main.py +39 -14
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/runtime.py +11 -5
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/__init__.py +3 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/export_online_cmd.py +15 -12
- klaude_code-1.2.29/src/klaude_code/command/fork_session_cmd.py +42 -0
- klaude_code-1.2.29/src/klaude_code/config/__init__.py +19 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/config/config.py +21 -17
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/config/select_model.py +1 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/executor.py +2 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/reminders.py +52 -16
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/mermaid_tool.md +17 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/mermaid_tool.py +2 -2
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/commands.py +1 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/model.py +2 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/export.py +61 -17
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/session.py +23 -1
- klaude_code-1.2.29/src/klaude_code/session/templates/mermaid_viewer.html +926 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/trace/log.py +7 -1
- klaude_code-1.2.29/src/klaude_code/ui/modes/repl/__init__.py +6 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/completers.py +35 -3
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/event_handler.py +9 -5
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +32 -65
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/renderer.py +1 -6
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/assistant.py +4 -2
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/common.py +11 -4
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/developer.py +26 -7
- klaude_code-1.2.29/src/klaude_code/ui/renderers/errors.py +21 -0
- klaude_code-1.2.29/src/klaude_code/ui/renderers/mermaid_viewer.py +58 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/tools.py +46 -18
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/markdown.py +4 -4
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/theme.py +12 -2
- klaude_code-1.2.27/src/klaude_code/config/__init__.py +0 -9
- klaude_code-1.2.27/src/klaude_code/ui/modes/repl/__init__.py +0 -47
- klaude_code-1.2.27/src/klaude_code/ui/renderers/errors.py +0 -16
- {klaude_code-1.2.27 → klaude_code-1.2.29}/README.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/const.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.29}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -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.29"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -27,6 +27,7 @@ dependencies = [
|
|
|
27
27
|
|
|
28
28
|
[project.scripts]
|
|
29
29
|
klaude = "klaude_code.cli.main:app"
|
|
30
|
+
klaude-code = "klaude_code.cli.main:app"
|
|
30
31
|
|
|
31
32
|
[tool.uv.build-backend]
|
|
32
33
|
module-name = "klaude_code"
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
|
-
from klaude_code.config import config_path, load_config
|
|
9
|
+
from klaude_code.config import config_path, create_example_config, example_config_path, load_config
|
|
10
10
|
from klaude_code.trace import log
|
|
11
11
|
|
|
12
12
|
|
|
@@ -57,17 +57,24 @@ def edit_config() -> None:
|
|
|
57
57
|
else: # Linux and other Unix systems
|
|
58
58
|
editor = "xdg-open"
|
|
59
59
|
|
|
60
|
-
# Ensure config
|
|
61
|
-
|
|
60
|
+
# Ensure config directory exists and create example config if needed
|
|
61
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
if create_example_config():
|
|
63
|
+
log(f"Created example config: {example_config_path}", style="dim")
|
|
64
|
+
|
|
65
|
+
# Decide which file to open
|
|
66
|
+
target_path = config_path if config_path.exists() else example_config_path
|
|
67
|
+
if target_path == example_config_path:
|
|
68
|
+
log(f"Opening example config (copy to {config_path.name} to use)", style="yellow")
|
|
62
69
|
|
|
63
70
|
try:
|
|
64
71
|
if editor == "open -a TextEdit":
|
|
65
|
-
subprocess.run(["open", "-a", "TextEdit", str(
|
|
72
|
+
subprocess.run(["open", "-a", "TextEdit", str(target_path)], check=True)
|
|
66
73
|
elif editor in ["open", "xdg-open"]:
|
|
67
74
|
# For open/xdg-open, we need to pass the file directly
|
|
68
|
-
subprocess.run([editor, str(
|
|
75
|
+
subprocess.run([editor, str(target_path)], check=True)
|
|
69
76
|
else:
|
|
70
|
-
subprocess.run([editor, str(
|
|
77
|
+
subprocess.run([editor, str(target_path)], check=True)
|
|
71
78
|
except subprocess.CalledProcessError as e:
|
|
72
79
|
log((f"Error: Failed to open editor: {e}", "red"))
|
|
73
80
|
raise typer.Exit(1) from None
|
|
@@ -63,7 +63,15 @@ def open_log_file_in_editor(path: Path) -> None:
|
|
|
63
63
|
editor = "xdg-open"
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
|
-
|
|
66
|
+
# Detach stdin to prevent the editor from interfering with terminal input state.
|
|
67
|
+
# Without this, the spawned process inherits the parent's TTY and can disrupt
|
|
68
|
+
# prompt_toolkit's keyboard handling (e.g., history navigation with up/down keys).
|
|
69
|
+
subprocess.Popen(
|
|
70
|
+
[editor, str(path)],
|
|
71
|
+
stdin=subprocess.DEVNULL,
|
|
72
|
+
stdout=subprocess.DEVNULL,
|
|
73
|
+
stderr=subprocess.DEVNULL,
|
|
74
|
+
)
|
|
67
75
|
except FileNotFoundError:
|
|
68
76
|
log((f"Error: Editor '{editor}' not found", "red"))
|
|
69
77
|
except Exception as exc: # pragma: no cover - best effort
|
|
@@ -218,7 +218,7 @@ def display_models_and_providers(config: Config):
|
|
|
218
218
|
|
|
219
219
|
panel = Panel(
|
|
220
220
|
panel_content,
|
|
221
|
-
title=Text(f"Provider: {provider.provider_name}", style=
|
|
221
|
+
title=Text(f"Provider: {provider.provider_name}", style=ThemeKey.CONFIG_PROVIDER),
|
|
222
222
|
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
223
223
|
padding=(0, 1),
|
|
224
224
|
title_align="left",
|
|
@@ -16,6 +16,10 @@ from klaude_code.trace import DebugType, prepare_debug_log_file
|
|
|
16
16
|
|
|
17
17
|
def set_terminal_title(title: str) -> None:
|
|
18
18
|
"""Set terminal window title using ANSI escape sequence."""
|
|
19
|
+
# Never write terminal control sequences when stdout is not a TTY (pipes/CI/redirects).
|
|
20
|
+
# This avoids corrupting machine-readable output (e.g., JSON streaming) and log captures.
|
|
21
|
+
if not sys.stdout.isatty():
|
|
22
|
+
return
|
|
19
23
|
sys.stdout.write(f"\033]0;{title}\007")
|
|
20
24
|
sys.stdout.flush()
|
|
21
25
|
|
|
@@ -242,9 +246,43 @@ def main_callback(
|
|
|
242
246
|
) -> None:
|
|
243
247
|
# Only run interactive mode when no subcommand is invoked
|
|
244
248
|
if ctx.invoked_subcommand is None:
|
|
249
|
+
from klaude_code.trace import log
|
|
250
|
+
|
|
251
|
+
resume_by_id_value = resume_by_id.strip() if resume_by_id is not None else None
|
|
252
|
+
if resume_by_id_value == "":
|
|
253
|
+
log(("Error: --resume-by-id cannot be empty", "red"))
|
|
254
|
+
raise typer.Exit(2)
|
|
255
|
+
|
|
256
|
+
if resume_by_id_value is not None and (resume or continue_):
|
|
257
|
+
log(("Error: --resume-by-id cannot be combined with --resume/--continue", "red"))
|
|
258
|
+
raise typer.Exit(2)
|
|
259
|
+
|
|
260
|
+
if resume_by_id_value is not None and not Session.exists(resume_by_id_value):
|
|
261
|
+
log((f"Error: session id '{resume_by_id_value}' not found for this project", "red"))
|
|
262
|
+
log(("Hint: run `klaude --resume` to select an existing session", "yellow"))
|
|
263
|
+
raise typer.Exit(2)
|
|
264
|
+
|
|
265
|
+
# In non-interactive environments, default to exec-mode behavior.
|
|
266
|
+
# This allows: echo "..." | klaude
|
|
267
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
268
|
+
if continue_ or resume or resume_by_id is not None:
|
|
269
|
+
log(("Error: --continue/--resume options require a TTY", "red"))
|
|
270
|
+
log(("Hint: use `klaude exec` for non-interactive usage", "yellow"))
|
|
271
|
+
raise typer.Exit(2)
|
|
272
|
+
|
|
273
|
+
exec_command(
|
|
274
|
+
input_content="",
|
|
275
|
+
model=model,
|
|
276
|
+
select_model=select_model,
|
|
277
|
+
debug=debug,
|
|
278
|
+
debug_filter=debug_filter,
|
|
279
|
+
vanilla=vanilla,
|
|
280
|
+
stream_json=False,
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
|
|
245
284
|
from klaude_code.cli.runtime import AppInitConfig, run_interactive
|
|
246
285
|
from klaude_code.config.select_model import select_model_from_config
|
|
247
|
-
from klaude_code.trace import log
|
|
248
286
|
|
|
249
287
|
update_terminal_title()
|
|
250
288
|
|
|
@@ -258,15 +296,6 @@ def main_callback(
|
|
|
258
296
|
# session_id=None means create a new session
|
|
259
297
|
session_id: str | None = None
|
|
260
298
|
|
|
261
|
-
resume_by_id_value = resume_by_id.strip() if resume_by_id is not None else None
|
|
262
|
-
if resume_by_id_value == "":
|
|
263
|
-
log(("Error: --resume-by-id cannot be empty", "red"))
|
|
264
|
-
raise typer.Exit(2)
|
|
265
|
-
|
|
266
|
-
if resume_by_id_value is not None and (resume or continue_):
|
|
267
|
-
log(("Error: --resume-by-id cannot be combined with --resume/--continue", "red"))
|
|
268
|
-
raise typer.Exit(2)
|
|
269
|
-
|
|
270
299
|
if resume:
|
|
271
300
|
session_id = resume_select_session()
|
|
272
301
|
if session_id is None:
|
|
@@ -276,10 +305,6 @@ def main_callback(
|
|
|
276
305
|
session_id = Session.most_recent_session_id()
|
|
277
306
|
|
|
278
307
|
if resume_by_id_value is not None:
|
|
279
|
-
if not Session.exists(resume_by_id_value):
|
|
280
|
-
log((f"Error: session id '{resume_by_id_value}' not found for this project", "red"))
|
|
281
|
-
log(("Hint: run `klaude --resume` to select an existing session", "yellow"))
|
|
282
|
-
raise typer.Exit(2)
|
|
283
308
|
session_id = resume_by_id_value
|
|
284
309
|
# If still no session_id, leave as None to create a new session
|
|
285
310
|
|
|
@@ -255,12 +255,8 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
255
255
|
|
|
256
256
|
# Create status provider for bottom toolbar
|
|
257
257
|
def _status_provider() -> REPLStatusSnapshot:
|
|
258
|
-
# Check for updates (returns None if uv not available)
|
|
259
258
|
update_message = get_update_message()
|
|
260
|
-
|
|
261
|
-
return build_repl_status_snapshot(
|
|
262
|
-
agent=components.executor.context.current_agent, update_message=update_message
|
|
263
|
-
)
|
|
259
|
+
return build_repl_status_snapshot(update_message)
|
|
264
260
|
|
|
265
261
|
# Set up input provider for interactive mode
|
|
266
262
|
def _stop_rich_bottom_ui() -> None:
|
|
@@ -276,9 +272,19 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
276
272
|
display.wrapped_display.renderer.spinner_stop()
|
|
277
273
|
display.wrapped_display.renderer.stop_bottom_live()
|
|
278
274
|
|
|
275
|
+
# Pass the pre-detected theme to avoid redundant TTY queries.
|
|
276
|
+
# Querying the terminal background again after questionary's interactive selection
|
|
277
|
+
# can interfere with prompt_toolkit's terminal state and break history navigation.
|
|
278
|
+
is_light_background: bool | None = None
|
|
279
|
+
if components.theme == "light":
|
|
280
|
+
is_light_background = True
|
|
281
|
+
elif components.theme == "dark":
|
|
282
|
+
is_light_background = False
|
|
283
|
+
|
|
279
284
|
input_provider: ui.InputProviderABC = ui.PromptToolkitInput(
|
|
280
285
|
status_provider=_status_provider,
|
|
281
286
|
pre_prompt=_stop_rich_bottom_ui,
|
|
287
|
+
is_light_background=is_light_background,
|
|
282
288
|
)
|
|
283
289
|
|
|
284
290
|
# --- Custom Ctrl+C handler: double-press within 2s to exit, single press shows toast ---
|
|
@@ -31,6 +31,7 @@ def ensure_commands_loaded() -> None:
|
|
|
31
31
|
from .debug_cmd import DebugCommand
|
|
32
32
|
from .export_cmd import ExportCommand
|
|
33
33
|
from .export_online_cmd import ExportOnlineCommand
|
|
34
|
+
from .fork_session_cmd import ForkSessionCommand
|
|
34
35
|
from .help_cmd import HelpCommand
|
|
35
36
|
from .model_cmd import ModelCommand
|
|
36
37
|
from .refresh_cmd import RefreshTerminalCommand
|
|
@@ -45,6 +46,7 @@ def ensure_commands_loaded() -> None:
|
|
|
45
46
|
register(RefreshTerminalCommand())
|
|
46
47
|
register(ThinkingCommand())
|
|
47
48
|
register(ModelCommand())
|
|
49
|
+
register(ForkSessionCommand())
|
|
48
50
|
load_prompt_commands()
|
|
49
51
|
register(StatusCommand())
|
|
50
52
|
register(HelpCommand())
|
|
@@ -63,6 +65,7 @@ def __getattr__(name: str) -> object:
|
|
|
63
65
|
"DebugCommand": "debug_cmd",
|
|
64
66
|
"ExportCommand": "export_cmd",
|
|
65
67
|
"ExportOnlineCommand": "export_online_cmd",
|
|
68
|
+
"ForkSessionCommand": "fork_session_cmd",
|
|
66
69
|
"HelpCommand": "help_cmd",
|
|
67
70
|
"ModelCommand": "model_cmd",
|
|
68
71
|
"RefreshTerminalCommand": "refresh_cmd",
|
|
@@ -47,20 +47,23 @@ class ExportOnlineCommand(CommandABC):
|
|
|
47
47
|
)
|
|
48
48
|
return CommandResult(events=[event])
|
|
49
49
|
|
|
50
|
-
# Check if user is logged in to surge
|
|
51
|
-
if not self._is_surge_logged_in(surge_cmd):
|
|
52
|
-
login_cmd = " ".join([*surge_cmd, "login"])
|
|
53
|
-
event = events.DeveloperMessageEvent(
|
|
54
|
-
session_id=agent.session.id,
|
|
55
|
-
item=model.DeveloperMessageItem(
|
|
56
|
-
content=f"Not logged in to surge.sh. Please run: {login_cmd}",
|
|
57
|
-
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
58
|
-
),
|
|
59
|
-
)
|
|
60
|
-
return CommandResult(events=[event])
|
|
61
|
-
|
|
62
50
|
try:
|
|
63
51
|
console = Console()
|
|
52
|
+
# Check login status inside status context since npx surge whoami can be slow
|
|
53
|
+
with console.status(Text("Checking surge.sh login status...", style="dim"), spinner_style="dim"):
|
|
54
|
+
logged_in = self._is_surge_logged_in(surge_cmd)
|
|
55
|
+
|
|
56
|
+
if not logged_in:
|
|
57
|
+
login_cmd = " ".join([*surge_cmd, "login"])
|
|
58
|
+
event = events.DeveloperMessageEvent(
|
|
59
|
+
session_id=agent.session.id,
|
|
60
|
+
item=model.DeveloperMessageItem(
|
|
61
|
+
content=f"Not logged in to surge.sh. Please run: {login_cmd}",
|
|
62
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
return CommandResult(events=[event])
|
|
66
|
+
|
|
64
67
|
with console.status(Text("Deploying to surge.sh...", style="dim"), spinner_style="dim"):
|
|
65
68
|
html_doc = self._build_html(agent)
|
|
66
69
|
domain = self._generate_domain()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
2
|
+
from klaude_code.protocol import commands, events, model
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ForkSessionCommand(CommandABC):
|
|
6
|
+
"""Fork current session to a new session id and show a resume command."""
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def name(self) -> commands.CommandName:
|
|
10
|
+
return commands.CommandName.FORK_SESSION
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def summary(self) -> str:
|
|
14
|
+
return "Fork the current session and show a resume-by-id command"
|
|
15
|
+
|
|
16
|
+
async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
|
|
17
|
+
del user_input # unused
|
|
18
|
+
|
|
19
|
+
if agent.session.messages_count == 0:
|
|
20
|
+
event = events.DeveloperMessageEvent(
|
|
21
|
+
session_id=agent.session.id,
|
|
22
|
+
item=model.DeveloperMessageItem(
|
|
23
|
+
content="(no messages to fork)",
|
|
24
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
return CommandResult(events=[event])
|
|
28
|
+
|
|
29
|
+
new_session = agent.session.fork()
|
|
30
|
+
await new_session.wait_for_flush()
|
|
31
|
+
|
|
32
|
+
event = events.DeveloperMessageEvent(
|
|
33
|
+
session_id=agent.session.id,
|
|
34
|
+
item=model.DeveloperMessageItem(
|
|
35
|
+
content=f"Session forked successfully. New session id: {new_session.id}",
|
|
36
|
+
command_output=model.CommandOutput(
|
|
37
|
+
command_name=self.name,
|
|
38
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
return CommandResult(events=[event])
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .config import (
|
|
2
|
+
Config,
|
|
3
|
+
UserConfig,
|
|
4
|
+
config_path,
|
|
5
|
+
create_example_config,
|
|
6
|
+
example_config_path,
|
|
7
|
+
load_config,
|
|
8
|
+
print_no_available_models_hint,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Config",
|
|
13
|
+
"UserConfig",
|
|
14
|
+
"config_path",
|
|
15
|
+
"create_example_config",
|
|
16
|
+
"example_config_path",
|
|
17
|
+
"load_config",
|
|
18
|
+
"print_no_available_models_hint",
|
|
19
|
+
]
|
|
@@ -51,6 +51,7 @@ def resolve_api_key(value: str | None) -> str | None:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
config_path = Path.home() / ".klaude" / "klaude-config.yaml"
|
|
54
|
+
example_config_path = Path.home() / ".klaude" / "klaude-config.example.yaml"
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class ModelConfig(BaseModel):
|
|
@@ -227,8 +228,8 @@ class Config(BaseModel):
|
|
|
227
228
|
def get_example_config() -> UserConfig:
|
|
228
229
|
"""Generate example config for user reference (will be commented out)."""
|
|
229
230
|
return UserConfig(
|
|
230
|
-
main_model="
|
|
231
|
-
sub_agent_models={"explore": "
|
|
231
|
+
main_model="opus",
|
|
232
|
+
sub_agent_models={"explore": "haiku", "oracle": "gpt-5.2", "webagent": "sonnet", "task": "sonnet"},
|
|
232
233
|
provider_list=[
|
|
233
234
|
UserProviderConfig(
|
|
234
235
|
provider_name="my-provider",
|
|
@@ -357,30 +358,32 @@ def _load_user_config() -> UserConfig | None:
|
|
|
357
358
|
raise ValueError(f"Invalid config file: {config_path}") from e
|
|
358
359
|
|
|
359
360
|
|
|
360
|
-
def
|
|
361
|
-
"""
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
def create_example_config() -> bool:
|
|
362
|
+
"""Create example config file if it doesn't exist.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
True if file was created, False if it already exists.
|
|
366
|
+
"""
|
|
367
|
+
if example_config_path.exists():
|
|
368
|
+
return False
|
|
364
369
|
|
|
365
370
|
example_config = get_example_config()
|
|
366
|
-
|
|
371
|
+
example_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
367
372
|
config_dict = example_config.model_dump(mode="json", exclude_none=True)
|
|
368
373
|
|
|
369
|
-
# Comment out all example config lines
|
|
370
374
|
yaml_str = yaml.dump(config_dict, default_flow_style=False, sort_keys=False) or ""
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
_ =
|
|
375
|
+
header = "# Example configuration for klaude-code\n"
|
|
376
|
+
header += "# Copy this file to klaude-config.yaml and modify as needed.\n"
|
|
377
|
+
header += "# Run `klaude list` to see available models.\n"
|
|
378
|
+
header += "#\n"
|
|
379
|
+
header += "# Built-in providers (anthropic, openai, openrouter, deepseek) are available automatically.\n"
|
|
380
|
+
header += "# Just set the corresponding API key environment variable to use them.\n\n"
|
|
381
|
+
_ = example_config_path.write_text(header + yaml_str)
|
|
382
|
+
return True
|
|
378
383
|
|
|
379
384
|
|
|
380
385
|
def _load_config_uncached() -> Config:
|
|
381
386
|
"""Load and merge builtin + user config. Always returns a valid Config."""
|
|
382
|
-
_ensure_config_file_exists()
|
|
383
|
-
|
|
384
387
|
builtin_config = _get_builtin_config()
|
|
385
388
|
user_config = _load_user_config()
|
|
386
389
|
|
|
@@ -417,6 +420,7 @@ def print_no_available_models_hint() -> None:
|
|
|
417
420
|
log(f" export {env_var}=<your-api-key>", style="dim")
|
|
418
421
|
log("")
|
|
419
422
|
log(f"Or add custom providers in: {config_path}", style="dim")
|
|
423
|
+
log(f"See example config: {example_config_path}", style="dim")
|
|
420
424
|
|
|
421
425
|
|
|
422
426
|
# Expose cache control for tests and callers that need to invalidate the cache.
|
|
@@ -96,6 +96,7 @@ def select_model_from_config(preferred: str | None = None) -> str | None:
|
|
|
96
96
|
else:
|
|
97
97
|
# No matches: show all models without filter hint
|
|
98
98
|
preferred = None
|
|
99
|
+
log(("No matching models found. Showing all models.", "yellow"))
|
|
99
100
|
|
|
100
101
|
# Non-interactive environments (CI/pipes) should never enter an interactive prompt.
|
|
101
102
|
# If we couldn't resolve to a single model deterministically above, fail with a clear hint.
|
|
@@ -404,7 +404,8 @@ class ExecutorContext:
|
|
|
404
404
|
|
|
405
405
|
def _open_file(self, path: Path) -> None:
|
|
406
406
|
try:
|
|
407
|
-
|
|
407
|
+
# Detach stdin to prevent interference with prompt_toolkit's terminal state
|
|
408
|
+
subprocess.run(["open", str(path)], stdin=subprocess.DEVNULL, check=True)
|
|
408
409
|
except FileNotFoundError as exc: # pragma: no cover
|
|
409
410
|
msg = "`open` command not found; please open the HTML manually."
|
|
410
411
|
raise RuntimeError(msg) from exc
|
|
@@ -46,6 +46,17 @@ class AtPatternSource:
|
|
|
46
46
|
mentioned_in: str | None = None
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def _extract_at_patterns(content: str) -> list[str]:
|
|
50
|
+
"""Extract @ patterns from content."""
|
|
51
|
+
patterns: list[str] = []
|
|
52
|
+
if "@" in content:
|
|
53
|
+
for match in AT_FILE_PATTERN.finditer(content):
|
|
54
|
+
path_str = match.group("quoted") or match.group("plain")
|
|
55
|
+
if path_str:
|
|
56
|
+
patterns.append(path_str)
|
|
57
|
+
return patterns
|
|
58
|
+
|
|
59
|
+
|
|
49
60
|
def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
50
61
|
"""Get @ patterns from last user input and developer messages, preserving source info."""
|
|
51
62
|
patterns: list[AtPatternSource] = []
|
|
@@ -56,24 +67,14 @@ def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
|
56
67
|
|
|
57
68
|
if isinstance(item, model.UserMessageItem):
|
|
58
69
|
content = item.content or ""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
path_str = match.group("quoted") or match.group("plain")
|
|
62
|
-
if path_str:
|
|
63
|
-
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
70
|
+
for path_str in _extract_at_patterns(content):
|
|
71
|
+
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
64
72
|
break
|
|
65
73
|
|
|
66
|
-
if isinstance(item, model.DeveloperMessageItem):
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Use first memory_path as the source if available
|
|
71
|
-
source = item.memory_paths[0] if item.memory_paths else None
|
|
72
|
-
for match in AT_FILE_PATTERN.finditer(content):
|
|
73
|
-
path_str = match.group("quoted") or match.group("plain")
|
|
74
|
-
if path_str:
|
|
75
|
-
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=source))
|
|
76
|
-
|
|
74
|
+
if isinstance(item, model.DeveloperMessageItem) and item.memory_mentioned:
|
|
75
|
+
for memory_path, mentioned_patterns in item.memory_mentioned.items():
|
|
76
|
+
for pattern in mentioned_patterns:
|
|
77
|
+
patterns.append(AtPatternSource(pattern=pattern, mentioned_in=memory_path))
|
|
77
78
|
return patterns
|
|
78
79
|
|
|
79
80
|
|
|
@@ -92,6 +93,23 @@ def get_skill_from_user_input(session: Session) -> str | None:
|
|
|
92
93
|
return None
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def _is_tracked_file_unchanged(session: Session, path: str) -> bool:
|
|
97
|
+
status = session.file_tracker.get(path)
|
|
98
|
+
if status is None or status.content_sha256 is None:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
current_mtime = Path(path).stat().st_mtime
|
|
103
|
+
except (OSError, FileNotFoundError):
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
if current_mtime == status.mtime:
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
current_sha256 = _compute_file_content_sha256(path)
|
|
110
|
+
return current_sha256 is not None and current_sha256 == status.content_sha256
|
|
111
|
+
|
|
112
|
+
|
|
95
113
|
async def _load_at_file_recursive(
|
|
96
114
|
session: Session,
|
|
97
115
|
pattern: str,
|
|
@@ -112,6 +130,8 @@ async def _load_at_file_recursive(
|
|
|
112
130
|
context_token = set_tool_context_from_session(session)
|
|
113
131
|
try:
|
|
114
132
|
if path.exists() and path.is_file():
|
|
133
|
+
if _is_tracked_file_unchanged(session, path_str):
|
|
134
|
+
return
|
|
115
135
|
args = ReadTool.ReadArguments(file_path=path_str)
|
|
116
136
|
tool_result = await ReadTool.call_with_args(args)
|
|
117
137
|
at_files[path_str] = model.AtPatternParseResult(
|
|
@@ -458,6 +478,13 @@ async def memory_reminder(session: Session) -> model.DeveloperMessageItem | None
|
|
|
458
478
|
memories_str = "\n\n".join(
|
|
459
479
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
460
480
|
)
|
|
481
|
+
# Build memory_mentioned: extract @ patterns from each memory's content
|
|
482
|
+
memory_mentioned: dict[str, list[str]] = {}
|
|
483
|
+
for memory in memories:
|
|
484
|
+
patterns = _extract_at_patterns(memory.content)
|
|
485
|
+
if patterns:
|
|
486
|
+
memory_mentioned[memory.path] = patterns
|
|
487
|
+
|
|
461
488
|
return model.DeveloperMessageItem(
|
|
462
489
|
content=f"""<system-reminder>As you answer the user's questions, you can use the following context:
|
|
463
490
|
|
|
@@ -474,6 +501,7 @@ NEVER proactively create documentation files (*.md) or README files. Only create
|
|
|
474
501
|
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
|
|
475
502
|
</system-reminder>""",
|
|
476
503
|
memory_paths=[memory.path for memory in memories],
|
|
504
|
+
memory_mentioned=memory_mentioned or None,
|
|
477
505
|
)
|
|
478
506
|
return None
|
|
479
507
|
|
|
@@ -544,10 +572,18 @@ async def last_path_memory_reminder(
|
|
|
544
572
|
memories_str = "\n\n".join(
|
|
545
573
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
546
574
|
)
|
|
575
|
+
# Build memory_mentioned: extract @ patterns from each memory's content
|
|
576
|
+
memory_mentioned: dict[str, list[str]] = {}
|
|
577
|
+
for memory in memories:
|
|
578
|
+
patterns = _extract_at_patterns(memory.content)
|
|
579
|
+
if patterns:
|
|
580
|
+
memory_mentioned[memory.path] = patterns
|
|
581
|
+
|
|
547
582
|
return model.DeveloperMessageItem(
|
|
548
583
|
content=f"""<system-reminder>{memories_str}
|
|
549
584
|
</system-reminder>""",
|
|
550
585
|
memory_paths=[memory.path for memory in memories],
|
|
586
|
+
memory_mentioned=memory_mentioned or None,
|
|
551
587
|
)
|
|
552
588
|
|
|
553
589
|
|
|
@@ -45,3 +45,20 @@ sequenceDiagram
|
|
|
45
45
|
|
|
46
46
|
# Styling
|
|
47
47
|
- When defining custom classDefs, always define fill color, stroke color, and text color ("fill", "stroke", "color") explicitly
|
|
48
|
+
- Use colors to distinguish node types and improve readability
|
|
49
|
+
|
|
50
|
+
## Color Palette
|
|
51
|
+
- Cyan #e0f0f0 - information, data flow
|
|
52
|
+
- Green #e0f0e0 - success, completion
|
|
53
|
+
- Blue #e0e8f5 - primary actions, main flow
|
|
54
|
+
- Purple #ede0f5 - highlights, special nodes
|
|
55
|
+
- Orange #f5ebe0 - warnings, pending
|
|
56
|
+
- Red #f5e0e0 - errors, critical
|
|
57
|
+
- Grey #e8e8e8 - neutral elements
|
|
58
|
+
- Yellow #f5f5e0 - attention, notes
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
```mermaid
|
|
62
|
+
classDef primary fill:#e0e8f5,stroke:#3078C5,color:#1a1a1a
|
|
63
|
+
classDef success fill:#e0f0e0,stroke:#00875f,color:#1a1a1a
|
|
64
|
+
```
|
|
@@ -31,7 +31,7 @@ class MermaidTool(ToolABC):
|
|
|
31
31
|
"type": "object",
|
|
32
32
|
"properties": {
|
|
33
33
|
"code": {
|
|
34
|
-
"description": "The Mermaid diagram code to render (DO NOT
|
|
34
|
+
"description": "The Mermaid diagram code to render (DO NOT use HTML tags in node labels)",
|
|
35
35
|
"type": "string",
|
|
36
36
|
},
|
|
37
37
|
},
|
|
@@ -49,7 +49,7 @@ class MermaidTool(ToolABC):
|
|
|
49
49
|
|
|
50
50
|
link = cls._build_link(args.code)
|
|
51
51
|
line_count = cls._count_lines(args.code)
|
|
52
|
-
ui_extra = model.MermaidLinkUIExtra(link=link, line_count=line_count)
|
|
52
|
+
ui_extra = model.MermaidLinkUIExtra(code=args.code, link=link, line_count=line_count)
|
|
53
53
|
output = f"Mermaid diagram rendered successfully ({line_count} lines)."
|
|
54
54
|
return model.ToolResultItem(status="success", output=output, ui_extra=ui_extra)
|
|
55
55
|
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from abc import ABC, abstractmethod
|
|
2
3
|
|
|
3
4
|
from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
6
7
|
from klaude_code.protocol import model
|
|
8
|
+
from klaude_code.trace.log import log_debug
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_tool_name(name: str) -> str:
|
|
12
|
+
"""Normalize tool name from Gemini-3 format.
|
|
13
|
+
|
|
14
|
+
Gemini-3 sometimes returns tool names in format like 'tool_Edit_mUoY2p3W3r3z8uO5P2nZ'.
|
|
15
|
+
This function extracts the actual tool name (e.g., 'Edit').
|
|
16
|
+
"""
|
|
17
|
+
match = re.match(r"^tool_([A-Za-z]+)_[A-Za-z0-9]+$", name)
|
|
18
|
+
if match:
|
|
19
|
+
normalized = match.group(1)
|
|
20
|
+
log_debug(f"Gemini-3 tool name normalized: {name} -> {normalized}", style="yellow")
|
|
21
|
+
return normalized
|
|
22
|
+
return name
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
class ToolCallAccumulatorABC(ABC):
|
|
@@ -74,7 +90,7 @@ class BasicToolCallAccumulator(ToolCallAccumulatorABC, BaseModel):
|
|
|
74
90
|
if first_chunk.function is None:
|
|
75
91
|
continue
|
|
76
92
|
if first_chunk.function.name:
|
|
77
|
-
result[-1].name = first_chunk.function.name
|
|
93
|
+
result[-1].name = normalize_tool_name(first_chunk.function.name)
|
|
78
94
|
if first_chunk.function.arguments:
|
|
79
95
|
result[-1].arguments += first_chunk.function.arguments
|
|
80
96
|
return result
|
|
@@ -15,6 +15,7 @@ class CommandName(str, Enum):
|
|
|
15
15
|
STATUS = "status"
|
|
16
16
|
RELEASE_NOTES = "release-notes"
|
|
17
17
|
THINKING = "thinking"
|
|
18
|
+
FORK_SESSION = "fork-session"
|
|
18
19
|
# PLAN and DOC are dynamically registered now, but kept here if needed for reference
|
|
19
20
|
# or we can remove them if no code explicitly imports them.
|
|
20
21
|
# PLAN = "plan"
|
|
@@ -127,6 +127,7 @@ class SessionIdUIExtra(BaseModel):
|
|
|
127
127
|
|
|
128
128
|
class MermaidLinkUIExtra(BaseModel):
|
|
129
129
|
type: Literal["mermaid_link"] = "mermaid_link"
|
|
130
|
+
code: str = ""
|
|
130
131
|
link: str
|
|
131
132
|
line_count: int
|
|
132
133
|
|
|
@@ -260,6 +261,7 @@ class DeveloperMessageItem(BaseModel):
|
|
|
260
261
|
|
|
261
262
|
# Special fields for reminders UI
|
|
262
263
|
memory_paths: list[str] | None = None
|
|
264
|
+
memory_mentioned: dict[str, list[str]] | None = None # memory_path -> list of @ patterns mentioned in it
|
|
263
265
|
external_file_changes: list[str] | None = None
|
|
264
266
|
todo_use: bool | None = None
|
|
265
267
|
at_files: list[AtPatternParseResult] | None = None
|