klaude-code 1.5.0__tar.gz → 1.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {klaude_code-1.5.0 → klaude_code-1.7.0}/PKG-INFO +33 -5
- {klaude_code-1.5.0 → klaude_code-1.7.0}/README.md +31 -4
- {klaude_code-1.5.0 → klaude_code-1.7.0}/pyproject.toml +3 -1
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/list_model.py +55 -4
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/main.py +3 -56
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/session_cmd.py +3 -2
- klaude_code-1.7.0/src/klaude_code/command/fork_session_cmd.py +260 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/refresh_cmd.py +4 -4
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/resume_cmd.py +21 -11
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/assets/builtin_config.yaml +37 -2
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/builtin_config.py +1 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/config.py +14 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/thinking.py +14 -0
- klaude_code-1.7.0/src/klaude_code/llm/anthropic/client.py +233 -0
- klaude_code-1.7.0/src/klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code-1.7.0/src/klaude_code/llm/bedrock/client.py +60 -0
- klaude_code-1.7.0/src/klaude_code/llm/google/__init__.py +3 -0
- klaude_code-1.7.0/src/klaude_code/llm/google/client.py +309 -0
- klaude_code-1.7.0/src/klaude_code/llm/google/input.py +215 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/registry.py +10 -5
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/usage.py +1 -1
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/llm_param.py +9 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/__init__.py +2 -2
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/selector.py +32 -4
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/session.py +20 -12
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/event_handler.py +22 -32
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/renderer.py +1 -1
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/developer.py +2 -2
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/metadata.py +8 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/markdown.py +41 -9
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/status.py +83 -22
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/selector.py +72 -3
- klaude_code-1.5.0/src/klaude_code/command/fork_session_cmd.py +0 -42
- klaude_code-1.5.0/src/klaude_code/llm/anthropic/client.py +0 -220
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/runtime.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/model_select.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/const.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/executor.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/rich/theme.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.7.0}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
7
7
|
Requires-Dist: ddgs>=9.9.3
|
|
8
8
|
Requires-Dist: diff-match-patch>=20241021
|
|
9
|
+
Requires-Dist: google-genai>=1.56.0
|
|
9
10
|
Requires-Dist: markdown-it-py>=4.0.0
|
|
10
11
|
Requires-Dist: openai>=1.102.0
|
|
11
12
|
Requires-Dist: pillow>=12.0.0
|
|
@@ -34,6 +35,10 @@ Minimal code agent CLI.
|
|
|
34
35
|
- **Output truncation**: Large outputs saved to file system with snapshot links
|
|
35
36
|
- **Skills**: Built-in + user + project Agent Skills (with implicit invocation by Skill tool or explicit invocation by typing `$`)
|
|
36
37
|
- **Sessions**: Resumable with `--continue`
|
|
38
|
+
- **Cost tracking**: Automatic API cost calculation and display (USD/CNY)
|
|
39
|
+
- **Version update check**: Background PyPI version check with upgrade prompts
|
|
40
|
+
- **Terminal title**: Shows current directory and model name
|
|
41
|
+
- **Mermaid diagrams**: Interactive local HTML viewer with zoom, pan, and SVG export
|
|
37
42
|
- **Extras**: Slash commands, sub-agents, image paste, terminal notifications, auto-theming
|
|
38
43
|
|
|
39
44
|
## Installation
|
|
@@ -77,6 +82,7 @@ klaude [--model <name>] [--select-model]
|
|
|
77
82
|
- `--select-model`/`-s`: Open the interactive model selector at startup (shows all models unless `--model` is also provided).
|
|
78
83
|
- `--continue`/`-c`: Resume the most recent session.
|
|
79
84
|
- `--resume`/`-r`: Select a session to resume for this project.
|
|
85
|
+
- `--resume-by-id <id>`: Resume a session by its ID directly.
|
|
80
86
|
- `--vanilla`: Minimal mode with only basic tools (Bash, Read, Edit) and no system prompts.
|
|
81
87
|
|
|
82
88
|
**Model selection behavior:**
|
|
@@ -251,12 +257,18 @@ klaude session clean-all
|
|
|
251
257
|
|
|
252
258
|
Inside the interactive session (`klaude`), use these commands to streamline your workflow:
|
|
253
259
|
|
|
254
|
-
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
255
|
-
- `/export` - Export last assistant message to a temp Markdown file.
|
|
256
|
-
- `/init` - Bootstrap a new project structure or module.
|
|
257
260
|
- `/model` - Switch the active LLM during the session.
|
|
261
|
+
- `/thinking` - Configure model thinking/reasoning level.
|
|
258
262
|
- `/clear` - Clear the current conversation context.
|
|
259
|
-
- `/
|
|
263
|
+
- `/status` - Show session usage statistics (cost, tokens, model breakdown).
|
|
264
|
+
- `/resume` - Select and resume a previous session.
|
|
265
|
+
- `/fork-session` - Fork current session to a new session ID (supports interactive fork point selection).
|
|
266
|
+
- `/export` - Export last assistant message to a temp Markdown file.
|
|
267
|
+
- `/export-online` - Export and deploy session to surge.sh as a static webpage.
|
|
268
|
+
- `/debug [filters]` - Toggle debug mode and configure debug filters.
|
|
269
|
+
- `/init` - Bootstrap a new project structure or module.
|
|
270
|
+
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
271
|
+
- `/terminal-setup` - Configure terminal for Shift+Enter support.
|
|
260
272
|
- `/help` - List all available commands.
|
|
261
273
|
|
|
262
274
|
|
|
@@ -267,6 +279,8 @@ Inside the interactive session (`klaude`), use these commands to streamline your
|
|
|
267
279
|
| `Enter` | Submit input |
|
|
268
280
|
| `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
|
|
269
281
|
| `Ctrl+J` | Insert newline |
|
|
282
|
+
| `Ctrl+L` | Open model picker overlay |
|
|
283
|
+
| `Ctrl+T` | Open thinking level picker overlay |
|
|
270
284
|
| `Ctrl+V` | Paste image from clipboard |
|
|
271
285
|
| `Left/Right` | Move cursor (wraps across lines) |
|
|
272
286
|
| `Backspace` | Delete character or selected text |
|
|
@@ -290,4 +304,18 @@ echo "generate quicksort in python" | klaude exec --model gpt-5.1
|
|
|
290
304
|
|
|
291
305
|
# Partial/ambiguous name opens the interactive selector (filtered)
|
|
292
306
|
echo "generate quicksort in python" | klaude exec --model gpt
|
|
307
|
+
|
|
308
|
+
# Stream all events as JSON lines (for programmatic processing)
|
|
309
|
+
klaude exec "what is 2+2?" --stream-json
|
|
293
310
|
```
|
|
311
|
+
|
|
312
|
+
### Sub-Agents
|
|
313
|
+
|
|
314
|
+
The main agent can spawn specialized sub-agents for specific tasks:
|
|
315
|
+
|
|
316
|
+
| Sub-Agent | Purpose |
|
|
317
|
+
|-----------|---------|
|
|
318
|
+
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
319
|
+
| **Task** | Handle complex multi-step tasks autonomously |
|
|
320
|
+
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
321
|
+
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|
|
@@ -14,6 +14,10 @@ Minimal code agent CLI.
|
|
|
14
14
|
- **Output truncation**: Large outputs saved to file system with snapshot links
|
|
15
15
|
- **Skills**: Built-in + user + project Agent Skills (with implicit invocation by Skill tool or explicit invocation by typing `$`)
|
|
16
16
|
- **Sessions**: Resumable with `--continue`
|
|
17
|
+
- **Cost tracking**: Automatic API cost calculation and display (USD/CNY)
|
|
18
|
+
- **Version update check**: Background PyPI version check with upgrade prompts
|
|
19
|
+
- **Terminal title**: Shows current directory and model name
|
|
20
|
+
- **Mermaid diagrams**: Interactive local HTML viewer with zoom, pan, and SVG export
|
|
17
21
|
- **Extras**: Slash commands, sub-agents, image paste, terminal notifications, auto-theming
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
@@ -57,6 +61,7 @@ klaude [--model <name>] [--select-model]
|
|
|
57
61
|
- `--select-model`/`-s`: Open the interactive model selector at startup (shows all models unless `--model` is also provided).
|
|
58
62
|
- `--continue`/`-c`: Resume the most recent session.
|
|
59
63
|
- `--resume`/`-r`: Select a session to resume for this project.
|
|
64
|
+
- `--resume-by-id <id>`: Resume a session by its ID directly.
|
|
60
65
|
- `--vanilla`: Minimal mode with only basic tools (Bash, Read, Edit) and no system prompts.
|
|
61
66
|
|
|
62
67
|
**Model selection behavior:**
|
|
@@ -231,12 +236,18 @@ klaude session clean-all
|
|
|
231
236
|
|
|
232
237
|
Inside the interactive session (`klaude`), use these commands to streamline your workflow:
|
|
233
238
|
|
|
234
|
-
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
235
|
-
- `/export` - Export last assistant message to a temp Markdown file.
|
|
236
|
-
- `/init` - Bootstrap a new project structure or module.
|
|
237
239
|
- `/model` - Switch the active LLM during the session.
|
|
240
|
+
- `/thinking` - Configure model thinking/reasoning level.
|
|
238
241
|
- `/clear` - Clear the current conversation context.
|
|
239
|
-
- `/
|
|
242
|
+
- `/status` - Show session usage statistics (cost, tokens, model breakdown).
|
|
243
|
+
- `/resume` - Select and resume a previous session.
|
|
244
|
+
- `/fork-session` - Fork current session to a new session ID (supports interactive fork point selection).
|
|
245
|
+
- `/export` - Export last assistant message to a temp Markdown file.
|
|
246
|
+
- `/export-online` - Export and deploy session to surge.sh as a static webpage.
|
|
247
|
+
- `/debug [filters]` - Toggle debug mode and configure debug filters.
|
|
248
|
+
- `/init` - Bootstrap a new project structure or module.
|
|
249
|
+
- `/dev-doc [feature]` - Generate a comprehensive execution plan for a feature.
|
|
250
|
+
- `/terminal-setup` - Configure terminal for Shift+Enter support.
|
|
240
251
|
- `/help` - List all available commands.
|
|
241
252
|
|
|
242
253
|
|
|
@@ -247,6 +258,8 @@ Inside the interactive session (`klaude`), use these commands to streamline your
|
|
|
247
258
|
| `Enter` | Submit input |
|
|
248
259
|
| `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
|
|
249
260
|
| `Ctrl+J` | Insert newline |
|
|
261
|
+
| `Ctrl+L` | Open model picker overlay |
|
|
262
|
+
| `Ctrl+T` | Open thinking level picker overlay |
|
|
250
263
|
| `Ctrl+V` | Paste image from clipboard |
|
|
251
264
|
| `Left/Right` | Move cursor (wraps across lines) |
|
|
252
265
|
| `Backspace` | Delete character or selected text |
|
|
@@ -270,4 +283,18 @@ echo "generate quicksort in python" | klaude exec --model gpt-5.1
|
|
|
270
283
|
|
|
271
284
|
# Partial/ambiguous name opens the interactive selector (filtered)
|
|
272
285
|
echo "generate quicksort in python" | klaude exec --model gpt
|
|
286
|
+
|
|
287
|
+
# Stream all events as JSON lines (for programmatic processing)
|
|
288
|
+
klaude exec "what is 2+2?" --stream-json
|
|
273
289
|
```
|
|
290
|
+
|
|
291
|
+
### Sub-Agents
|
|
292
|
+
|
|
293
|
+
The main agent can spawn specialized sub-agents for specific tasks:
|
|
294
|
+
|
|
295
|
+
| Sub-Agent | Purpose |
|
|
296
|
+
|-----------|---------|
|
|
297
|
+
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
298
|
+
| **Task** | Handle complex multi-step tasks autonomously |
|
|
299
|
+
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
300
|
+
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "klaude-code"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.7.0"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -13,6 +13,7 @@ dependencies = [
|
|
|
13
13
|
"chardet>=5.2.0",
|
|
14
14
|
"ddgs>=9.9.3",
|
|
15
15
|
"diff-match-patch>=20241021",
|
|
16
|
+
"google-genai>=1.56.0",
|
|
16
17
|
"markdown-it-py>=4.0.0",
|
|
17
18
|
"openai>=1.102.0",
|
|
18
19
|
"pillow>=12.0.0",
|
|
@@ -33,6 +34,7 @@ module-name = "klaude_code"
|
|
|
33
34
|
|
|
34
35
|
[dependency-groups]
|
|
35
36
|
dev = [
|
|
37
|
+
"hypothesis>=6.148.8",
|
|
36
38
|
"import-linter>=2.6",
|
|
37
39
|
"pyright>=1.1.407",
|
|
38
40
|
"pytest>=8.4.1",
|
|
@@ -6,7 +6,7 @@ from rich.table import Table
|
|
|
6
6
|
from rich.text import Text
|
|
7
7
|
|
|
8
8
|
from klaude_code.config import Config
|
|
9
|
-
from klaude_code.config.config import ModelConfig, ProviderConfig
|
|
9
|
+
from klaude_code.config.config import ModelConfig, ProviderConfig, parse_env_var_syntax
|
|
10
10
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
11
11
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
12
12
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
@@ -94,6 +94,29 @@ def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
|
94
94
|
return Text("N/A")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def format_env_var_display(value: str | None) -> Text:
|
|
98
|
+
"""Format environment variable display with warning if not set."""
|
|
99
|
+
env_var, resolved = parse_env_var_syntax(value)
|
|
100
|
+
|
|
101
|
+
if env_var:
|
|
102
|
+
# Using ${ENV_VAR} syntax
|
|
103
|
+
if resolved:
|
|
104
|
+
return Text.assemble(
|
|
105
|
+
(f"${{{env_var}}} = ", "dim"),
|
|
106
|
+
(mask_api_key(resolved), ""),
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
return Text.assemble(
|
|
110
|
+
(f"${{{env_var}}} ", ""),
|
|
111
|
+
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
112
|
+
)
|
|
113
|
+
elif value:
|
|
114
|
+
# Plain value
|
|
115
|
+
return Text(mask_api_key(value))
|
|
116
|
+
else:
|
|
117
|
+
return Text("N/A")
|
|
118
|
+
|
|
119
|
+
|
|
97
120
|
def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
98
121
|
"""Get display elements for model parameters."""
|
|
99
122
|
params: list[Text] = []
|
|
@@ -162,15 +185,43 @@ def display_models_and_providers(config: Config):
|
|
|
162
185
|
format_api_key_display(provider),
|
|
163
186
|
)
|
|
164
187
|
|
|
188
|
+
# AWS Bedrock parameters
|
|
189
|
+
if provider.protocol == LLMClientProtocol.BEDROCK:
|
|
190
|
+
if provider.aws_access_key:
|
|
191
|
+
provider_info.add_row(
|
|
192
|
+
Text("AWS Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
193
|
+
format_env_var_display(provider.aws_access_key),
|
|
194
|
+
)
|
|
195
|
+
if provider.aws_secret_key:
|
|
196
|
+
provider_info.add_row(
|
|
197
|
+
Text("AWS Secret:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
198
|
+
format_env_var_display(provider.aws_secret_key),
|
|
199
|
+
)
|
|
200
|
+
if provider.aws_region:
|
|
201
|
+
provider_info.add_row(
|
|
202
|
+
Text("AWS Region:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
203
|
+
format_env_var_display(provider.aws_region),
|
|
204
|
+
)
|
|
205
|
+
if provider.aws_session_token:
|
|
206
|
+
provider_info.add_row(
|
|
207
|
+
Text("AWS Token:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
208
|
+
format_env_var_display(provider.aws_session_token),
|
|
209
|
+
)
|
|
210
|
+
if provider.aws_profile:
|
|
211
|
+
provider_info.add_row(
|
|
212
|
+
Text("AWS Profile:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
213
|
+
format_env_var_display(provider.aws_profile),
|
|
214
|
+
)
|
|
215
|
+
|
|
165
216
|
# Check if provider has valid API key
|
|
166
217
|
provider_available = not provider.is_api_key_missing()
|
|
167
218
|
|
|
168
219
|
# Models table for this provider
|
|
169
220
|
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
170
221
|
models_table.add_column(width=2, no_wrap=True) # Status
|
|
171
|
-
models_table.add_column(overflow="fold", ratio=
|
|
172
|
-
models_table.add_column(overflow="fold", ratio=
|
|
173
|
-
models_table.add_column(overflow="fold", ratio=
|
|
222
|
+
models_table.add_column(overflow="fold", ratio=2) # Name
|
|
223
|
+
models_table.add_column(overflow="fold", ratio=3) # Model
|
|
224
|
+
models_table.add_column(overflow="fold", ratio=4) # Params
|
|
174
225
|
|
|
175
226
|
# Add header
|
|
176
227
|
models_table.add_row(
|
|
@@ -11,64 +11,11 @@ from klaude_code.cli.config_cmd import register_config_commands
|
|
|
11
11
|
from klaude_code.cli.debug import DEBUG_FILTER_HELP, open_log_file_in_editor, resolve_debug_settings
|
|
12
12
|
from klaude_code.cli.self_update import register_self_update_commands, version_option_callback
|
|
13
13
|
from klaude_code.cli.session_cmd import register_session_commands
|
|
14
|
-
from klaude_code.
|
|
14
|
+
from klaude_code.command.resume_cmd import select_session_sync
|
|
15
|
+
from klaude_code.session import Session
|
|
15
16
|
from klaude_code.trace import DebugType, prepare_debug_log_file
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def select_session_interactive() -> str | None:
|
|
19
|
-
"""Interactive session selection for CLI.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
Selected session ID, or None if no session selected or no sessions exist.
|
|
23
|
-
"""
|
|
24
|
-
from klaude_code.trace import log
|
|
25
|
-
|
|
26
|
-
options = build_session_select_options()
|
|
27
|
-
if not options:
|
|
28
|
-
log("No sessions found for this project.")
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
from prompt_toolkit.styles import Style
|
|
32
|
-
|
|
33
|
-
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
34
|
-
|
|
35
|
-
items: list[SelectItem[str]] = []
|
|
36
|
-
for opt in options:
|
|
37
|
-
title = [
|
|
38
|
-
("class:msg", f"{opt.first_user_message}\n"),
|
|
39
|
-
("class:meta", f" {opt.messages_count} · {opt.relative_time} · {opt.model_name} · {opt.session_id}\n\n"),
|
|
40
|
-
]
|
|
41
|
-
items.append(
|
|
42
|
-
SelectItem(
|
|
43
|
-
title=title,
|
|
44
|
-
value=opt.session_id,
|
|
45
|
-
search_text=f"{opt.first_user_message} {opt.model_name} {opt.session_id}",
|
|
46
|
-
)
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
return select_one(
|
|
51
|
-
message="Select a session to resume:",
|
|
52
|
-
items=items,
|
|
53
|
-
pointer="→",
|
|
54
|
-
style=Style(
|
|
55
|
-
[
|
|
56
|
-
("msg", ""),
|
|
57
|
-
("meta", "fg:ansibrightblack"),
|
|
58
|
-
("pointer", "bold fg:ansigreen"),
|
|
59
|
-
("highlighted", "fg:ansigreen"),
|
|
60
|
-
("search_prefix", "fg:ansibrightblack"),
|
|
61
|
-
("search_success", "noinherit fg:ansigreen"),
|
|
62
|
-
("search_none", "noinherit fg:ansired"),
|
|
63
|
-
("question", "bold"),
|
|
64
|
-
("text", ""),
|
|
65
|
-
]
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
except KeyboardInterrupt:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
|
|
72
19
|
def set_terminal_title(title: str) -> None:
|
|
73
20
|
"""Set terminal window title using ANSI escape sequence."""
|
|
74
21
|
# Never write terminal control sequences when stdout is not a TTY (pipes/CI/redirects).
|
|
@@ -361,7 +308,7 @@ def main_callback(
|
|
|
361
308
|
session_id: str | None = None
|
|
362
309
|
|
|
363
310
|
if resume:
|
|
364
|
-
session_id =
|
|
311
|
+
session_id = select_session_sync()
|
|
365
312
|
if session_id is None:
|
|
366
313
|
return
|
|
367
314
|
# If user didn't pick, allow fallback to --continue
|
|
@@ -22,8 +22,9 @@ def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) ->
|
|
|
22
22
|
log(f"Sessions to delete ({len(sessions)}):")
|
|
23
23
|
for s in sessions:
|
|
24
24
|
msg_count_display = "N/A" if s.messages_count == -1 else str(s.messages_count)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
first_msg_text = s.user_messages[0] if s.user_messages else ""
|
|
26
|
+
first_msg = first_msg_text.strip().replace("\n", " ")[:50]
|
|
27
|
+
if len(first_msg_text) > 50:
|
|
27
28
|
first_msg += "..."
|
|
28
29
|
log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
|
|
29
30
|
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from prompt_toolkit.styles import Style
|
|
7
|
+
|
|
8
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
|
+
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
|
+
|
|
12
|
+
FORK_SELECT_STYLE = Style(
|
|
13
|
+
[
|
|
14
|
+
("msg", ""),
|
|
15
|
+
("meta", "fg:ansibrightblack"),
|
|
16
|
+
("separator", "fg:ansibrightblack"),
|
|
17
|
+
("assistant", "fg:ansiblue"),
|
|
18
|
+
("pointer", "bold fg:ansigreen"),
|
|
19
|
+
("search_prefix", "fg:ansibrightblack"),
|
|
20
|
+
("search_success", "noinherit fg:ansigreen"),
|
|
21
|
+
("search_none", "noinherit fg:ansired"),
|
|
22
|
+
("question", "bold"),
|
|
23
|
+
("text", ""),
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ForkPoint:
|
|
30
|
+
"""A fork point in conversation history."""
|
|
31
|
+
|
|
32
|
+
history_index: int | None # None means fork entire conversation
|
|
33
|
+
user_message: str
|
|
34
|
+
tool_call_stats: dict[str, int] # tool_name -> count
|
|
35
|
+
last_assistant_summary: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _truncate(text: str, max_len: int = 60) -> str:
|
|
39
|
+
"""Truncate text to max_len, adding ellipsis if needed."""
|
|
40
|
+
text = text.replace("\n", " ").strip()
|
|
41
|
+
if len(text) <= max_len:
|
|
42
|
+
return text
|
|
43
|
+
return text[: max_len - 3] + "..."
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _build_fork_points(conversation_history: list[model.ConversationItem]) -> list[ForkPoint]:
|
|
47
|
+
"""Build list of fork points from conversation history.
|
|
48
|
+
|
|
49
|
+
Fork points are:
|
|
50
|
+
- Each UserMessageItem position (for UI display, including first which would be empty session)
|
|
51
|
+
- The end of the conversation (fork entire conversation)
|
|
52
|
+
"""
|
|
53
|
+
fork_points: list[ForkPoint] = []
|
|
54
|
+
user_indices: list[int] = []
|
|
55
|
+
|
|
56
|
+
for i, item in enumerate(conversation_history):
|
|
57
|
+
if isinstance(item, model.UserMessageItem):
|
|
58
|
+
user_indices.append(i)
|
|
59
|
+
|
|
60
|
+
# For each UserMessageItem, create a fork point at that position
|
|
61
|
+
for i, user_idx in enumerate(user_indices):
|
|
62
|
+
user_item = conversation_history[user_idx]
|
|
63
|
+
assert isinstance(user_item, model.UserMessageItem)
|
|
64
|
+
|
|
65
|
+
# Find the end of this "task" (next UserMessageItem or end of history)
|
|
66
|
+
next_user_idx = user_indices[i + 1] if i + 1 < len(user_indices) else len(conversation_history)
|
|
67
|
+
|
|
68
|
+
# Count tool calls by name and find last assistant message in this segment
|
|
69
|
+
tool_stats: dict[str, int] = {}
|
|
70
|
+
last_assistant_content = ""
|
|
71
|
+
for j in range(user_idx, next_user_idx):
|
|
72
|
+
item = conversation_history[j]
|
|
73
|
+
if isinstance(item, model.ToolCallItem):
|
|
74
|
+
tool_stats[item.name] = tool_stats.get(item.name, 0) + 1
|
|
75
|
+
elif isinstance(item, model.AssistantMessageItem) and item.content:
|
|
76
|
+
last_assistant_content = item.content
|
|
77
|
+
|
|
78
|
+
fork_points.append(
|
|
79
|
+
ForkPoint(
|
|
80
|
+
history_index=user_idx,
|
|
81
|
+
user_message=user_item.content or "(empty)",
|
|
82
|
+
tool_call_stats=tool_stats,
|
|
83
|
+
last_assistant_summary=_truncate(last_assistant_content) if last_assistant_content else "",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Add the "fork entire conversation" option at the end
|
|
88
|
+
if user_indices:
|
|
89
|
+
fork_points.append(
|
|
90
|
+
ForkPoint(
|
|
91
|
+
history_index=None, # None means fork entire conversation
|
|
92
|
+
user_message="", # No specific message, this represents the end
|
|
93
|
+
tool_call_stats={},
|
|
94
|
+
last_assistant_summary="",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return fork_points
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int | None]]:
|
|
102
|
+
"""Build SelectItem list from fork points."""
|
|
103
|
+
items: list[SelectItem[int | None]] = []
|
|
104
|
+
|
|
105
|
+
for i, fp in enumerate(fork_points):
|
|
106
|
+
is_first = i == 0
|
|
107
|
+
is_last = i == len(fork_points) - 1
|
|
108
|
+
|
|
109
|
+
# Build the title
|
|
110
|
+
title_parts: list[tuple[str, str]] = []
|
|
111
|
+
|
|
112
|
+
# First line: separator (with special markers for first/last fork points)
|
|
113
|
+
if is_first and not is_last:
|
|
114
|
+
title_parts.append(("class:separator", "----- fork from here (empty session) -----\n\n"))
|
|
115
|
+
elif is_last:
|
|
116
|
+
title_parts.append(("class:separator", "----- fork from here (entire session) -----\n\n"))
|
|
117
|
+
else:
|
|
118
|
+
title_parts.append(("class:separator", "----- fork from here -----\n\n"))
|
|
119
|
+
|
|
120
|
+
if not is_last:
|
|
121
|
+
# Second line: user message
|
|
122
|
+
title_parts.append(("class:msg", f"user: {_truncate(fp.user_message, 70)}\n"))
|
|
123
|
+
|
|
124
|
+
# Third line: tool call stats (if any)
|
|
125
|
+
if fp.tool_call_stats:
|
|
126
|
+
tool_parts = [f"{name} × {count}" for name, count in fp.tool_call_stats.items()]
|
|
127
|
+
title_parts.append(("class:meta", f"tools: {', '.join(tool_parts)}\n"))
|
|
128
|
+
|
|
129
|
+
# Fourth line: last assistant message summary (if any)
|
|
130
|
+
if fp.last_assistant_summary:
|
|
131
|
+
title_parts.append(("class:assistant", f"ai: {fp.last_assistant_summary}\n"))
|
|
132
|
+
|
|
133
|
+
# Empty line at the end
|
|
134
|
+
title_parts.append(("class:text", "\n"))
|
|
135
|
+
|
|
136
|
+
items.append(
|
|
137
|
+
SelectItem(
|
|
138
|
+
title=title_parts,
|
|
139
|
+
value=fp.history_index,
|
|
140
|
+
search_text=fp.user_message if not is_last else "fork entire conversation",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return items
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _select_fork_point_sync(fork_points: list[ForkPoint]) -> int | None | Literal["cancelled"]:
|
|
148
|
+
"""Interactive fork point selection (sync version for asyncio.to_thread).
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
- int: history index to fork at (exclusive)
|
|
152
|
+
- None: fork entire conversation
|
|
153
|
+
- "cancelled": user cancelled selection
|
|
154
|
+
"""
|
|
155
|
+
items = _build_select_items(fork_points)
|
|
156
|
+
if not items:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
# Default to the last option (fork entire conversation)
|
|
160
|
+
last_value = items[-1].value
|
|
161
|
+
|
|
162
|
+
# Non-interactive environments default to forking entire conversation
|
|
163
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
164
|
+
return last_value
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
result = select_one(
|
|
168
|
+
message="Select fork point (messages before this point will be included):",
|
|
169
|
+
items=items,
|
|
170
|
+
pointer="→",
|
|
171
|
+
style=FORK_SELECT_STYLE,
|
|
172
|
+
initial_value=last_value,
|
|
173
|
+
highlight_pointed_item=False,
|
|
174
|
+
)
|
|
175
|
+
if result is None:
|
|
176
|
+
return "cancelled"
|
|
177
|
+
return result
|
|
178
|
+
except KeyboardInterrupt:
|
|
179
|
+
return "cancelled"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ForkSessionCommand(CommandABC):
|
|
183
|
+
"""Fork current session to a new session id and show a resume command."""
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def name(self) -> commands.CommandName:
|
|
187
|
+
return commands.CommandName.FORK_SESSION
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def summary(self) -> str:
|
|
191
|
+
return "Fork the current session and show a resume-by-id command"
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def is_interactive(self) -> bool:
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
|
|
198
|
+
del user_input # unused
|
|
199
|
+
|
|
200
|
+
if agent.session.messages_count == 0:
|
|
201
|
+
event = events.DeveloperMessageEvent(
|
|
202
|
+
session_id=agent.session.id,
|
|
203
|
+
item=model.DeveloperMessageItem(
|
|
204
|
+
content="(no messages to fork)",
|
|
205
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
209
|
+
|
|
210
|
+
# Build fork points from conversation history
|
|
211
|
+
fork_points = _build_fork_points(agent.session.conversation_history)
|
|
212
|
+
|
|
213
|
+
if not fork_points:
|
|
214
|
+
# Only one user message, just fork entirely
|
|
215
|
+
new_session = agent.session.fork()
|
|
216
|
+
await new_session.wait_for_flush()
|
|
217
|
+
|
|
218
|
+
event = events.DeveloperMessageEvent(
|
|
219
|
+
session_id=agent.session.id,
|
|
220
|
+
item=model.DeveloperMessageItem(
|
|
221
|
+
content=f"Session forked successfully. New session id: {new_session.id}",
|
|
222
|
+
command_output=model.CommandOutput(
|
|
223
|
+
command_name=self.name,
|
|
224
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
229
|
+
|
|
230
|
+
# Interactive selection
|
|
231
|
+
selected = await asyncio.to_thread(_select_fork_point_sync, fork_points)
|
|
232
|
+
|
|
233
|
+
if selected == "cancelled":
|
|
234
|
+
event = events.DeveloperMessageEvent(
|
|
235
|
+
session_id=agent.session.id,
|
|
236
|
+
item=model.DeveloperMessageItem(
|
|
237
|
+
content="(fork cancelled)",
|
|
238
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
242
|
+
|
|
243
|
+
# Perform the fork
|
|
244
|
+
new_session = agent.session.fork(until_index=selected)
|
|
245
|
+
await new_session.wait_for_flush()
|
|
246
|
+
|
|
247
|
+
# Build result message
|
|
248
|
+
fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
|
|
249
|
+
|
|
250
|
+
event = events.DeveloperMessageEvent(
|
|
251
|
+
session_id=agent.session.id,
|
|
252
|
+
item=model.DeveloperMessageItem(
|
|
253
|
+
content=f"Session forked ({fork_description}). New session id: {new_session.id}",
|
|
254
|
+
command_output=model.CommandOutput(
|
|
255
|
+
command_name=self.name,
|
|
256
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -23,7 +23,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
23
23
|
|
|
24
24
|
os.system("cls" if os.name == "nt" else "clear")
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
return CommandResult(
|
|
27
27
|
events=[
|
|
28
28
|
events.WelcomeEvent(
|
|
29
29
|
work_dir=str(agent.session.work_dir),
|
|
@@ -35,7 +35,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
35
35
|
updated_at=agent.session.updated_at,
|
|
36
36
|
is_load=False,
|
|
37
37
|
),
|
|
38
|
-
]
|
|
38
|
+
],
|
|
39
|
+
persist_user_input=False,
|
|
40
|
+
persist_events=False,
|
|
39
41
|
)
|
|
40
|
-
|
|
41
|
-
return result
|