klaude-code 1.2.12__tar.gz → 1.2.14__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.12 → klaude_code-1.2.14}/PKG-INFO +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/pyproject.toml +9 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/codex/oauth.py +3 -3
- klaude_code-1.2.14/src/klaude_code/cli/auth_cmd.py +73 -0
- klaude_code-1.2.14/src/klaude_code/cli/config_cmd.py +88 -0
- klaude_code-1.2.14/src/klaude_code/cli/debug.py +72 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/cli/main.py +31 -142
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/cli/runtime.py +19 -58
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/cli/session_cmd.py +9 -9
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/export_cmd.py +3 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/model_cmd.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/registry.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/terminal_setup_cmd.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/thinking_cmd.py +8 -6
- klaude_code-1.2.14/src/klaude_code/config/__init__.py +7 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/config/config.py +31 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/config/list_model.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/const/__init__.py +8 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/agent.py +14 -62
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/executor.py +11 -10
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/manager/agent_manager.py +4 -4
- klaude_code-1.2.14/src/klaude_code/core/manager/llm_clients.py +28 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/manager/llm_clients_builder.py +8 -21
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/manager/sub_agent_manager.py +3 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompt.py +12 -7
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/reminders.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/task.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/__init__.py +16 -25
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/_utils.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/apply_patch.py +17 -25
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/apply_patch_tool.py +4 -7
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/edit_tool.py +4 -11
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/multi_edit_tool.py +2 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/read_tool.py +3 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/write_tool.py +2 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/memory_tool.py +2 -8
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/skill_loader.py +3 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/shell/command_safety.py +0 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/tool_context.py +1 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/tool_registry.py +2 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/tool_runner.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/truncation.py +2 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/turn.py +9 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/anthropic/client.py +6 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/client.py +5 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/codex/client.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/input_common.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openai_compatible/client.py +11 -8
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openai_compatible/stream_processor.py +2 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openrouter/client.py +22 -9
- klaude_code-1.2.14/src/klaude_code/llm/openrouter/reasoning_handler.py +96 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/registry.py +6 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/responses/client.py +10 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/events.py +9 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/model.py +7 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/sub_agent.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/session/export.py +58 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/session/selector.py +2 -2
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/session/session.py +37 -7
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/session/templates/export_session.html +46 -0
- klaude_code-1.2.14/src/klaude_code/trace/__init__.py +3 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/trace/log.py +144 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/__init__.py +4 -9
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/core/stage_manager.py +7 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/debug/display.py +2 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/__init__.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/completers.py +6 -7
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/display.py +3 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/event_handler.py +63 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/key_bindings.py +2 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/renderer.py +52 -62
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/diffs.py +1 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/tools.py +4 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/markdown.py +3 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/searchable_text.py +6 -6
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/status.py +3 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/theme.py +2 -5
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/terminal/control.py +7 -16
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/terminal/notifier.py +2 -4
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/utils/common.py +1 -1
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/utils/debouncer.py +2 -2
- klaude_code-1.2.12/src/klaude_code/config/__init__.py +0 -11
- klaude_code-1.2.12/src/klaude_code/core/manager/llm_clients.py +0 -67
- klaude_code-1.2.12/src/klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code-1.2.12/src/klaude_code/trace/__init__.py +0 -3
- {klaude_code-1.2.12 → klaude_code-1.2.14}/README.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/__init__.py +6 -6
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/diff_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/prompt-deslop.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/prompt-dev-docs-update.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/prompt-dev-docs.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-subagent-explore.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-subagent-oracle.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/prompts/prompt-subagent.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/multi_edit_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/memory_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/skill_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/memory/skill_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/developer.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.12 → klaude_code-1.2.14}/src/klaude_code/version.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.14"
|
|
8
8
|
description = "Add your description here"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -30,7 +30,6 @@ module-name = "klaude_code"
|
|
|
30
30
|
[dependency-groups]
|
|
31
31
|
dev = [
|
|
32
32
|
"import-linter>=2.6",
|
|
33
|
-
"isort>=6.0.1",
|
|
34
33
|
"pyright>=1.1.407",
|
|
35
34
|
"pytest>=8.4.1",
|
|
36
35
|
"pytest-cov>=7.0.0",
|
|
@@ -45,6 +44,14 @@ markers = [
|
|
|
45
44
|
|
|
46
45
|
[tool.ruff]
|
|
47
46
|
line-length = 120
|
|
47
|
+
src = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "B", "I", "UP", "SIM", "RUF"]
|
|
51
|
+
ignore = ["RUF001", "RUF002", "RUF003", "E501"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint.isort]
|
|
54
|
+
known-first-party = ["klaude_code"]
|
|
48
55
|
|
|
49
56
|
[tool.pyright]
|
|
50
57
|
typeCheckingMode = "strict"
|
|
@@ -78,13 +78,13 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
|
78
78
|
self.end_headers()
|
|
79
79
|
|
|
80
80
|
if OAuthCallbackHandler.error:
|
|
81
|
-
html = """
|
|
81
|
+
html = f"""
|
|
82
82
|
<html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
83
83
|
<h1>Authentication Failed</h1>
|
|
84
|
-
<p>Error: {}</p>
|
|
84
|
+
<p>Error: {OAuthCallbackHandler.error}</p>
|
|
85
85
|
<p>Please close this window and try again.</p>
|
|
86
86
|
</body></html>
|
|
87
|
-
"""
|
|
87
|
+
"""
|
|
88
88
|
else:
|
|
89
89
|
html = """
|
|
90
90
|
<html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Authentication commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from klaude_code.trace import log
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def login_command(
|
|
11
|
+
provider: str = typer.Argument("codex", help="Provider to login (codex)"),
|
|
12
|
+
) -> None:
|
|
13
|
+
"""Login to a provider using OAuth."""
|
|
14
|
+
if provider.lower() != "codex":
|
|
15
|
+
log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
|
|
16
|
+
raise typer.Exit(1)
|
|
17
|
+
|
|
18
|
+
from klaude_code.auth.codex.oauth import CodexOAuth
|
|
19
|
+
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
20
|
+
|
|
21
|
+
token_manager = CodexTokenManager()
|
|
22
|
+
|
|
23
|
+
# Check if already logged in
|
|
24
|
+
if token_manager.is_logged_in():
|
|
25
|
+
state = token_manager.get_state()
|
|
26
|
+
if state and not state.is_expired():
|
|
27
|
+
log(("You are already logged in to Codex.", "green"))
|
|
28
|
+
log(f" Account ID: {state.account_id[:8]}...")
|
|
29
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
30
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
31
|
+
if not typer.confirm("Do you want to re-login?"):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
log("Starting Codex OAuth login flow...")
|
|
35
|
+
log("A browser window will open for authentication.")
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
oauth = CodexOAuth(token_manager)
|
|
39
|
+
state = oauth.login()
|
|
40
|
+
log(("Login successful!", "green"))
|
|
41
|
+
log(f" Account ID: {state.account_id[:8]}...")
|
|
42
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
43
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
log((f"Login failed: {e}", "red"))
|
|
46
|
+
raise typer.Exit(1) from None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def logout_command(
|
|
50
|
+
provider: str = typer.Argument("codex", help="Provider to logout (codex)"),
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Logout from a provider."""
|
|
53
|
+
if provider.lower() != "codex":
|
|
54
|
+
log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
58
|
+
|
|
59
|
+
token_manager = CodexTokenManager()
|
|
60
|
+
|
|
61
|
+
if not token_manager.is_logged_in():
|
|
62
|
+
log("You are not logged in to Codex.")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if typer.confirm("Are you sure you want to logout from Codex?"):
|
|
66
|
+
token_manager.delete()
|
|
67
|
+
log(("Logged out from Codex.", "green"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def register_auth_commands(app: typer.Typer) -> None:
|
|
71
|
+
"""Register auth commands to the given Typer app."""
|
|
72
|
+
app.command("login")(login_command)
|
|
73
|
+
app.command("logout")(logout_command)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Configuration commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from klaude_code.config import config_path, load_config
|
|
10
|
+
from klaude_code.trace import log
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def list_models() -> None:
|
|
14
|
+
"""List all models and providers configuration"""
|
|
15
|
+
from klaude_code.config.list_model import display_models_and_providers
|
|
16
|
+
from klaude_code.ui.terminal.color import is_light_terminal_background
|
|
17
|
+
|
|
18
|
+
config = load_config()
|
|
19
|
+
if config is None:
|
|
20
|
+
raise typer.Exit(1)
|
|
21
|
+
|
|
22
|
+
# Auto-detect theme when not explicitly set in config, to match other CLI entrypoints.
|
|
23
|
+
if config.theme is None:
|
|
24
|
+
detected = is_light_terminal_background()
|
|
25
|
+
if detected is True:
|
|
26
|
+
config.theme = "light"
|
|
27
|
+
elif detected is False:
|
|
28
|
+
config.theme = "dark"
|
|
29
|
+
|
|
30
|
+
display_models_and_providers(config)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def edit_config() -> None:
|
|
34
|
+
"""Open the configuration file in $EDITOR or default system editor"""
|
|
35
|
+
editor = os.environ.get("EDITOR")
|
|
36
|
+
|
|
37
|
+
# If no EDITOR is set, prioritize TextEdit on macOS
|
|
38
|
+
if not editor:
|
|
39
|
+
# Try common editors in order of preference on other platforms
|
|
40
|
+
for cmd in [
|
|
41
|
+
"code",
|
|
42
|
+
"nvim",
|
|
43
|
+
"vim",
|
|
44
|
+
"nano",
|
|
45
|
+
]:
|
|
46
|
+
try:
|
|
47
|
+
subprocess.run(["which", cmd], check=True, capture_output=True)
|
|
48
|
+
editor = cmd
|
|
49
|
+
break
|
|
50
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
# If no editor found, try platform-specific defaults
|
|
54
|
+
if not editor:
|
|
55
|
+
if sys.platform == "darwin": # macOS
|
|
56
|
+
editor = "open"
|
|
57
|
+
elif sys.platform == "win32": # Windows
|
|
58
|
+
editor = "notepad"
|
|
59
|
+
else: # Linux and other Unix systems
|
|
60
|
+
editor = "xdg-open"
|
|
61
|
+
|
|
62
|
+
# Ensure config file exists
|
|
63
|
+
config = load_config()
|
|
64
|
+
if config is None:
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
if editor == "open -a TextEdit":
|
|
69
|
+
subprocess.run(["open", "-a", "TextEdit", str(config_path)], check=True)
|
|
70
|
+
elif editor in ["open", "xdg-open"]:
|
|
71
|
+
# For open/xdg-open, we need to pass the file directly
|
|
72
|
+
subprocess.run([editor, str(config_path)], check=True)
|
|
73
|
+
else:
|
|
74
|
+
subprocess.run([editor, str(config_path)], check=True)
|
|
75
|
+
except subprocess.CalledProcessError as e:
|
|
76
|
+
log((f"Error: Failed to open editor: {e}", "red"))
|
|
77
|
+
raise typer.Exit(1) from None
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
log((f"Error: Editor '{editor}' not found", "red"))
|
|
80
|
+
log("Please install a text editor or set your $EDITOR environment variable")
|
|
81
|
+
raise typer.Exit(1) from None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def register_config_commands(app: typer.Typer) -> None:
|
|
85
|
+
"""Register config commands to the given Typer app."""
|
|
86
|
+
app.command("list")(list_models)
|
|
87
|
+
app.command("config")(edit_config)
|
|
88
|
+
app.command("conf", hidden=True)(edit_config)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Debug utilities for CLI."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from klaude_code.trace import DebugType, log
|
|
11
|
+
|
|
12
|
+
DEBUG_FILTER_HELP = "Comma-separated debug types: " + ", ".join(dt.value for dt in DebugType)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_debug_filters(raw: str | None) -> set[DebugType] | None:
|
|
16
|
+
"""Parse comma-separated debug filter string into a set of DebugType."""
|
|
17
|
+
if raw is None:
|
|
18
|
+
return None
|
|
19
|
+
filters: set[DebugType] = set()
|
|
20
|
+
for chunk in raw.split(","):
|
|
21
|
+
normalized = chunk.strip().lower().replace("-", "_")
|
|
22
|
+
if not normalized:
|
|
23
|
+
continue
|
|
24
|
+
try:
|
|
25
|
+
filters.add(DebugType(normalized))
|
|
26
|
+
except ValueError: # pragma: no cover - user input validation
|
|
27
|
+
valid_options = ", ".join(dt.value for dt in DebugType)
|
|
28
|
+
log(
|
|
29
|
+
(
|
|
30
|
+
f"Invalid debug filter '{normalized}'. Valid options: {valid_options}",
|
|
31
|
+
"red",
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
raise typer.Exit(2) from None
|
|
35
|
+
return filters or None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def resolve_debug_settings(flag: bool, raw_filters: str | None) -> tuple[bool, set[DebugType] | None]:
|
|
39
|
+
"""Resolve debug flag and filters into effective settings."""
|
|
40
|
+
filters = parse_debug_filters(raw_filters)
|
|
41
|
+
effective_flag = flag or (filters is not None)
|
|
42
|
+
return effective_flag, filters
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def open_log_file_in_editor(path: Path) -> None:
|
|
46
|
+
"""Open the given log file in a text editor without blocking the CLI."""
|
|
47
|
+
|
|
48
|
+
editor = os.environ.get("EDITOR")
|
|
49
|
+
|
|
50
|
+
if not editor:
|
|
51
|
+
for cmd in ["open", "xdg-open", "code", "nvim", "vim", "nano"]:
|
|
52
|
+
try:
|
|
53
|
+
subprocess.run(["which", cmd], check=True, capture_output=True)
|
|
54
|
+
editor = cmd
|
|
55
|
+
break
|
|
56
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
if not editor:
|
|
60
|
+
if sys.platform == "darwin":
|
|
61
|
+
editor = "open"
|
|
62
|
+
elif sys.platform == "win32":
|
|
63
|
+
editor = "notepad"
|
|
64
|
+
else:
|
|
65
|
+
editor = "xdg-open"
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
subprocess.Popen([editor, str(path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
69
|
+
except FileNotFoundError:
|
|
70
|
+
log((f"Error: Editor '{editor}' not found", "red"))
|
|
71
|
+
except Exception as exc: # pragma: no cover - best effort
|
|
72
|
+
log((f"Warning: failed to open log file in editor: {exc}", "yellow"))
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import datetime
|
|
3
2
|
import os
|
|
4
|
-
import subprocess
|
|
5
3
|
import sys
|
|
6
4
|
from importlib.metadata import PackageNotFoundError
|
|
7
5
|
from importlib.metadata import version as pkg_version
|
|
6
|
+
from pathlib import Path
|
|
8
7
|
|
|
9
8
|
import typer
|
|
10
9
|
|
|
11
|
-
from klaude_code.cli.
|
|
10
|
+
from klaude_code.cli.auth_cmd import register_auth_commands
|
|
11
|
+
from klaude_code.cli.config_cmd import register_config_commands
|
|
12
|
+
from klaude_code.cli.debug import DEBUG_FILTER_HELP, open_log_file_in_editor, resolve_debug_settings
|
|
12
13
|
from klaude_code.cli.session_cmd import register_session_commands
|
|
13
|
-
from klaude_code.config import
|
|
14
|
+
from klaude_code.config import load_config
|
|
14
15
|
from klaude_code.session import Session, resume_select_session
|
|
15
|
-
from klaude_code.trace import
|
|
16
|
-
from klaude_code.ui.terminal.color import is_light_terminal_background
|
|
16
|
+
from klaude_code.trace import prepare_debug_log_file
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def set_terminal_title(title: str) -> None:
|
|
@@ -42,142 +42,10 @@ app = typer.Typer(
|
|
|
42
42
|
no_args_is_help=False,
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
register_session_commands(
|
|
47
|
-
app
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@app.command("login")
|
|
51
|
-
def login_command(
|
|
52
|
-
provider: str = typer.Argument("codex", help="Provider to login (codex)"),
|
|
53
|
-
) -> None:
|
|
54
|
-
"""Login to a provider using OAuth."""
|
|
55
|
-
if provider.lower() != "codex":
|
|
56
|
-
log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
|
|
57
|
-
raise typer.Exit(1)
|
|
58
|
-
|
|
59
|
-
from klaude_code.auth.codex.oauth import CodexOAuth
|
|
60
|
-
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
61
|
-
|
|
62
|
-
token_manager = CodexTokenManager()
|
|
63
|
-
|
|
64
|
-
# Check if already logged in
|
|
65
|
-
if token_manager.is_logged_in():
|
|
66
|
-
state = token_manager.get_state()
|
|
67
|
-
if state and not state.is_expired():
|
|
68
|
-
log(("You are already logged in to Codex.", "green"))
|
|
69
|
-
log(f" Account ID: {state.account_id[:8]}...")
|
|
70
|
-
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.timezone.utc)
|
|
71
|
-
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
72
|
-
if not typer.confirm("Do you want to re-login?"):
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
log("Starting Codex OAuth login flow...")
|
|
76
|
-
log("A browser window will open for authentication.")
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
oauth = CodexOAuth(token_manager)
|
|
80
|
-
state = oauth.login()
|
|
81
|
-
log(("Login successful!", "green"))
|
|
82
|
-
log(f" Account ID: {state.account_id[:8]}...")
|
|
83
|
-
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.timezone.utc)
|
|
84
|
-
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
85
|
-
except Exception as e:
|
|
86
|
-
log((f"Login failed: {e}", "red"))
|
|
87
|
-
raise typer.Exit(1)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@app.command("logout")
|
|
91
|
-
def logout_command(
|
|
92
|
-
provider: str = typer.Argument("codex", help="Provider to logout (codex)"),
|
|
93
|
-
) -> None:
|
|
94
|
-
"""Logout from a provider."""
|
|
95
|
-
if provider.lower() != "codex":
|
|
96
|
-
log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
|
|
97
|
-
raise typer.Exit(1)
|
|
98
|
-
|
|
99
|
-
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
100
|
-
|
|
101
|
-
token_manager = CodexTokenManager()
|
|
102
|
-
|
|
103
|
-
if not token_manager.is_logged_in():
|
|
104
|
-
log("You are not logged in to Codex.")
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
if typer.confirm("Are you sure you want to logout from Codex?"):
|
|
108
|
-
token_manager.delete()
|
|
109
|
-
log(("Logged out from Codex.", "green"))
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@app.command("list")
|
|
113
|
-
def list_models() -> None:
|
|
114
|
-
"""List all models and providers configuration"""
|
|
115
|
-
config = load_config()
|
|
116
|
-
if config is None:
|
|
117
|
-
raise typer.Exit(1)
|
|
118
|
-
|
|
119
|
-
# Auto-detect theme when not explicitly set in config, to match other CLI entrypoints.
|
|
120
|
-
if config.theme is None:
|
|
121
|
-
detected = is_light_terminal_background()
|
|
122
|
-
if detected is True:
|
|
123
|
-
config.theme = "light"
|
|
124
|
-
elif detected is False:
|
|
125
|
-
config.theme = "dark"
|
|
126
|
-
|
|
127
|
-
display_models_and_providers(config)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@app.command("config")
|
|
131
|
-
@app.command("conf", hidden=True)
|
|
132
|
-
def edit_config() -> None:
|
|
133
|
-
"""Open the configuration file in $EDITOR or default system editor"""
|
|
134
|
-
editor = os.environ.get("EDITOR")
|
|
135
|
-
|
|
136
|
-
# If no EDITOR is set, prioritize TextEdit on macOS
|
|
137
|
-
if not editor:
|
|
138
|
-
# Try common editors in order of preference on other platforms
|
|
139
|
-
for cmd in [
|
|
140
|
-
"code",
|
|
141
|
-
"nvim",
|
|
142
|
-
"vim",
|
|
143
|
-
"nano",
|
|
144
|
-
]:
|
|
145
|
-
try:
|
|
146
|
-
subprocess.run(["which", cmd], check=True, capture_output=True)
|
|
147
|
-
editor = cmd
|
|
148
|
-
break
|
|
149
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
150
|
-
continue
|
|
151
|
-
|
|
152
|
-
# If no editor found, try platform-specific defaults
|
|
153
|
-
if not editor:
|
|
154
|
-
if sys.platform == "darwin": # macOS
|
|
155
|
-
editor = "open"
|
|
156
|
-
elif sys.platform == "win32": # Windows
|
|
157
|
-
editor = "notepad"
|
|
158
|
-
else: # Linux and other Unix systems
|
|
159
|
-
editor = "xdg-open"
|
|
160
|
-
|
|
161
|
-
# Ensure config file exists
|
|
162
|
-
config = load_config()
|
|
163
|
-
if config is None:
|
|
164
|
-
raise typer.Exit(1)
|
|
165
|
-
|
|
166
|
-
try:
|
|
167
|
-
if editor == "open -a TextEdit":
|
|
168
|
-
subprocess.run(["open", "-a", "TextEdit", str(config_path)], check=True)
|
|
169
|
-
elif editor in ["open", "xdg-open"]:
|
|
170
|
-
# For open/xdg-open, we need to pass the file directly
|
|
171
|
-
subprocess.run([editor, str(config_path)], check=True)
|
|
172
|
-
else:
|
|
173
|
-
subprocess.run([editor, str(config_path)], check=True)
|
|
174
|
-
except subprocess.CalledProcessError as e:
|
|
175
|
-
log((f"Error: Failed to open editor: {e}", "red"))
|
|
176
|
-
raise typer.Exit(1)
|
|
177
|
-
except FileNotFoundError:
|
|
178
|
-
log((f"Error: Editor '{editor}' not found", "red"))
|
|
179
|
-
log("Please install a text editor or set your $EDITOR environment variable")
|
|
180
|
-
raise typer.Exit(1)
|
|
45
|
+
# Register subcommands from modules
|
|
46
|
+
register_session_commands(app)
|
|
47
|
+
register_auth_commands(app)
|
|
48
|
+
register_config_commands(app)
|
|
181
49
|
|
|
182
50
|
|
|
183
51
|
@app.command("exec")
|
|
@@ -222,6 +90,7 @@ def exec_command(
|
|
|
222
90
|
),
|
|
223
91
|
) -> None:
|
|
224
92
|
"""Execute non-interactively with provided input."""
|
|
93
|
+
from klaude_code.trace import log
|
|
225
94
|
|
|
226
95
|
# Set terminal title with current folder name
|
|
227
96
|
folder_name = os.path.basename(os.getcwd())
|
|
@@ -250,6 +119,9 @@ def exec_command(
|
|
|
250
119
|
log(("Error: No input content provided", "red"))
|
|
251
120
|
raise typer.Exit(1)
|
|
252
121
|
|
|
122
|
+
from klaude_code.cli.runtime import AppInitConfig, run_exec
|
|
123
|
+
from klaude_code.config.select_model import select_model_from_config
|
|
124
|
+
|
|
253
125
|
chosen_model = model
|
|
254
126
|
if select_model:
|
|
255
127
|
# Prefer the explicitly provided model as default; otherwise main model
|
|
@@ -263,6 +135,10 @@ def exec_command(
|
|
|
263
135
|
|
|
264
136
|
debug_enabled, debug_filters = resolve_debug_settings(debug, debug_filter)
|
|
265
137
|
|
|
138
|
+
log_path: Path | None = None
|
|
139
|
+
if debug_enabled:
|
|
140
|
+
log_path = prepare_debug_log_file()
|
|
141
|
+
|
|
266
142
|
init_config = AppInitConfig(
|
|
267
143
|
model=chosen_model,
|
|
268
144
|
debug=debug_enabled,
|
|
@@ -272,6 +148,9 @@ def exec_command(
|
|
|
272
148
|
stream_json=stream_json,
|
|
273
149
|
)
|
|
274
150
|
|
|
151
|
+
if log_path:
|
|
152
|
+
open_log_file_in_editor(log_path)
|
|
153
|
+
|
|
275
154
|
asyncio.run(
|
|
276
155
|
run_exec(
|
|
277
156
|
init_config=init_config,
|
|
@@ -328,6 +207,9 @@ def main_callback(
|
|
|
328
207
|
) -> None:
|
|
329
208
|
# Only run interactive mode when no subcommand is invoked
|
|
330
209
|
if ctx.invoked_subcommand is None:
|
|
210
|
+
from klaude_code.cli.runtime import AppInitConfig, run_interactive
|
|
211
|
+
from klaude_code.config.select_model import select_model_from_config
|
|
212
|
+
|
|
331
213
|
# Set terminal title with current folder name
|
|
332
214
|
folder_name = os.path.basename(os.getcwd())
|
|
333
215
|
set_terminal_title(f"{folder_name}: klaude")
|
|
@@ -352,6 +234,10 @@ def main_callback(
|
|
|
352
234
|
|
|
353
235
|
debug_enabled, debug_filters = resolve_debug_settings(debug, debug_filter)
|
|
354
236
|
|
|
237
|
+
log_path: Path | None = None
|
|
238
|
+
if debug_enabled:
|
|
239
|
+
log_path = prepare_debug_log_file()
|
|
240
|
+
|
|
355
241
|
init_config = AppInitConfig(
|
|
356
242
|
model=chosen_model,
|
|
357
243
|
debug=debug_enabled,
|
|
@@ -359,6 +245,9 @@ def main_callback(
|
|
|
359
245
|
debug_filters=debug_filters,
|
|
360
246
|
)
|
|
361
247
|
|
|
248
|
+
if log_path:
|
|
249
|
+
open_log_file_in_editor(log_path)
|
|
250
|
+
|
|
362
251
|
asyncio.run(
|
|
363
252
|
run_interactive(
|
|
364
253
|
init_config=init_config,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import sys
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import Any, Protocol
|
|
@@ -29,37 +30,6 @@ class PrintCapable(Protocol):
|
|
|
29
30
|
def print(self, *objects: Any, style: Any | None = None, end: str = "\n") -> None: ...
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
DEBUG_FILTER_HELP = "Comma-separated debug types: " + ", ".join(dt.value for dt in DebugType)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _parse_debug_filters(raw: str | None) -> set[DebugType] | None:
|
|
36
|
-
if raw is None:
|
|
37
|
-
return None
|
|
38
|
-
filters: set[DebugType] = set()
|
|
39
|
-
for chunk in raw.split(","):
|
|
40
|
-
normalized = chunk.strip().lower().replace("-", "_")
|
|
41
|
-
if not normalized:
|
|
42
|
-
continue
|
|
43
|
-
try:
|
|
44
|
-
filters.add(DebugType(normalized))
|
|
45
|
-
except ValueError: # pragma: no cover - user input validation
|
|
46
|
-
valid_options = ", ".join(dt.value for dt in DebugType)
|
|
47
|
-
log(
|
|
48
|
-
(
|
|
49
|
-
f"Invalid debug filter '{normalized}'. Valid options: {valid_options}",
|
|
50
|
-
"red",
|
|
51
|
-
)
|
|
52
|
-
)
|
|
53
|
-
raise typer.Exit(2) from None
|
|
54
|
-
return filters or None
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def resolve_debug_settings(flag: bool, raw_filters: str | None) -> tuple[bool, set[DebugType] | None]:
|
|
58
|
-
filters = _parse_debug_filters(raw_filters)
|
|
59
|
-
effective_flag = flag or (filters is not None)
|
|
60
|
-
return effective_flag, filters
|
|
61
|
-
|
|
62
|
-
|
|
63
33
|
@dataclass
|
|
64
34
|
class AppInitConfig:
|
|
65
35
|
"""Configuration for initializing the application components."""
|
|
@@ -169,32 +139,26 @@ async def cleanup_app_components(components: AppComponents) -> None:
|
|
|
169
139
|
await components.display_task
|
|
170
140
|
finally:
|
|
171
141
|
# Always attempt to clear Ghostty progress bar and restore cursor visibility
|
|
172
|
-
|
|
142
|
+
# Best-effort only; never fail cleanup due to OSC errors
|
|
143
|
+
with contextlib.suppress(Exception):
|
|
173
144
|
emit_osc94(OSC94States.HIDDEN)
|
|
174
|
-
except Exception:
|
|
175
|
-
# Best-effort only; never fail cleanup due to OSC errors
|
|
176
|
-
pass
|
|
177
145
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
146
|
+
# Ensure the terminal cursor is visible even if Rich's Status spinner
|
|
147
|
+
# did not get a chance to stop cleanly (e.g. on KeyboardInterrupt).
|
|
148
|
+
# If this fails the shell can still recover via `reset`/`stty sane`.
|
|
149
|
+
with contextlib.suppress(Exception):
|
|
181
150
|
stream = getattr(sys, "__stdout__", None) or sys.stdout
|
|
182
151
|
stream.write("\033[?25h")
|
|
183
152
|
stream.flush()
|
|
184
|
-
except Exception:
|
|
185
|
-
# If this fails the shell can still recover via `reset`/`stty sane`.
|
|
186
|
-
pass
|
|
187
153
|
|
|
188
154
|
|
|
189
155
|
async def _handle_keyboard_interrupt(executor: Executor) -> None:
|
|
190
156
|
"""Handle Ctrl+C by logging and sending a global interrupt."""
|
|
191
157
|
|
|
192
158
|
log("Bye!")
|
|
193
|
-
|
|
159
|
+
# Executor might already be stopping
|
|
160
|
+
with contextlib.suppress(Exception):
|
|
194
161
|
await executor.submit(op.InterruptOperation(target_session_id=None))
|
|
195
|
-
except Exception:
|
|
196
|
-
# Executor might already be stopping
|
|
197
|
-
pass
|
|
198
162
|
|
|
199
163
|
|
|
200
164
|
async def run_exec(init_config: AppInitConfig, input_content: str) -> None:
|
|
@@ -259,9 +223,12 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
259
223
|
if isinstance(components.display, ui.REPLDisplay):
|
|
260
224
|
printer = components.display.renderer
|
|
261
225
|
# Check if it's a DebugEventDisplay wrapping a REPLDisplay
|
|
262
|
-
elif
|
|
263
|
-
|
|
264
|
-
|
|
226
|
+
elif (
|
|
227
|
+
isinstance(components.display, ui.DebugEventDisplay)
|
|
228
|
+
and components.display.wrapped_display
|
|
229
|
+
and isinstance(components.display.wrapped_display, ui.REPLDisplay)
|
|
230
|
+
):
|
|
231
|
+
printer = components.display.wrapped_display.renderer
|
|
265
232
|
|
|
266
233
|
if printer is not None:
|
|
267
234
|
printer.print(Text(f" {MSG} ", style="bold yellow reverse"))
|
|
@@ -272,10 +239,8 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
272
239
|
print(MSG, file=sys.stderr)
|
|
273
240
|
|
|
274
241
|
def _hide_progress() -> None:
|
|
275
|
-
|
|
242
|
+
with contextlib.suppress(Exception):
|
|
276
243
|
emit_osc94(OSC94States.HIDDEN)
|
|
277
|
-
except Exception:
|
|
278
|
-
pass
|
|
279
244
|
|
|
280
245
|
restore_sigint = install_sigint_double_press_exit(_show_toast_once, _hide_progress)
|
|
281
246
|
|
|
@@ -315,17 +280,13 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
315
280
|
finally:
|
|
316
281
|
# Stop ESC monitor and wait for it to finish cleaning up TTY
|
|
317
282
|
stop_event.set()
|
|
318
|
-
|
|
283
|
+
with contextlib.suppress(Exception):
|
|
319
284
|
await esc_task
|
|
320
|
-
except Exception:
|
|
321
|
-
pass
|
|
322
285
|
|
|
323
286
|
except KeyboardInterrupt:
|
|
324
287
|
await _handle_keyboard_interrupt(components.executor)
|
|
325
288
|
finally:
|
|
326
|
-
|
|
327
|
-
|
|
289
|
+
# Restore original SIGINT handler
|
|
290
|
+
with contextlib.suppress(Exception):
|
|
328
291
|
restore_sigint()
|
|
329
|
-
except Exception:
|
|
330
|
-
pass
|
|
331
292
|
await cleanup_app_components(components)
|