klaude-code 1.8.0__tar.gz → 2.0.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.8.0 → klaude_code-2.0.0}/PKG-INFO +28 -6
- {klaude_code-1.8.0 → klaude_code-2.0.0}/README.md +25 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/pyproject.toml +3 -2
- klaude_code-2.0.0/src/klaude_code/auth/base.py +97 -0
- klaude_code-2.0.0/src/klaude_code/auth/claude/__init__.py +6 -0
- klaude_code-2.0.0/src/klaude_code/auth/claude/exceptions.py +9 -0
- klaude_code-2.0.0/src/klaude_code/auth/claude/oauth.py +172 -0
- klaude_code-2.0.0/src/klaude_code/auth/claude/token_manager.py +26 -0
- klaude_code-2.0.0/src/klaude_code/auth/codex/token_manager.py +44 -0
- klaude_code-2.0.0/src/klaude_code/cli/auth_cmd.py +154 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/config_cmd.py +4 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/cost_cmd.py +14 -9
- klaude_code-2.0.0/src/klaude_code/cli/list_model.py +355 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/main.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/runtime.py +7 -5
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/self_update.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/session_cmd.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/clear_cmd.py +6 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/command_abc.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/debug_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/export_cmd.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/export_online_cmd.py +12 -12
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/fork_session_cmd.py +29 -23
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/help_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/model_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/model_select.py +1 -1
- klaude_code-2.0.0/src/klaude_code/command/prompt-commit.md +82 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/prompt_command.py +3 -3
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/refresh_cmd.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/registry.py +7 -5
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/release_notes_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/resume_cmd.py +15 -11
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/status_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/terminal_setup_cmd.py +8 -8
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/thinking_cmd.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/assets/builtin_config.yaml +52 -3
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/builtin_config.py +16 -5
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/config.py +31 -7
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/thinking.py +4 -4
- klaude_code-2.0.0/src/klaude_code/const.py +219 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/agent.py +3 -12
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/executor.py +21 -13
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/manager/sub_agent_manager.py +71 -7
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompt.py +1 -1
- klaude_code-2.0.0/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/reminders.py +88 -69
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/task.py +44 -45
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +9 -9
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/diff_builder.py +3 -5
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/edit_tool.py +23 -23
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/move_tool.py +43 -43
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/read_tool.py +44 -39
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/write_tool.py +14 -14
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/report_back_tool.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/shell/bash_tool.py +23 -23
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/skill/skill_tool.py +7 -7
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/sub_agent_tool.py +38 -9
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +8 -8
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +6 -6
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/tool_abc.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/tool_context.py +27 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/tool_runner.py +88 -42
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/truncation.py +38 -20
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/mermaid_tool.py +6 -7
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +68 -30
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/web_search_tool.py +15 -17
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/turn.py +120 -73
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/anthropic/client.py +104 -44
- klaude_code-2.0.0/src/klaude_code/llm/anthropic/input.py +232 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/bedrock/client.py +8 -5
- klaude_code-2.0.0/src/klaude_code/llm/claude/__init__.py +3 -0
- klaude_code-2.0.0/src/klaude_code/llm/claude/client.py +105 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/client.py +4 -3
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/codex/client.py +16 -10
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/google/client.py +122 -60
- klaude_code-2.0.0/src/klaude_code/llm/google/input.py +201 -0
- klaude_code-2.0.0/src/klaude_code/llm/image.py +123 -0
- klaude_code-2.0.0/src/klaude_code/llm/input_common.py +180 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openai_compatible/client.py +17 -7
- klaude_code-2.0.0/src/klaude_code/llm/openai_compatible/input.py +81 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openai_compatible/stream.py +119 -67
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openrouter/client.py +34 -9
- klaude_code-2.0.0/src/klaude_code/llm/openrouter/input.py +147 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openrouter/reasoning.py +22 -24
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/registry.py +20 -15
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/responses/client.py +107 -45
- klaude_code-2.0.0/src/klaude_code/llm/responses/input.py +203 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/usage.py +52 -25
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/__init__.py +1 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/events.py +16 -12
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/llm_param.py +22 -3
- klaude_code-2.0.0/src/klaude_code/protocol/message.py +250 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/model.py +94 -281
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/op.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/sub_agent/__init__.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/sub_agent/explore.py +10 -0
- klaude_code-2.0.0/src/klaude_code/protocol/sub_agent/image_gen.py +119 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/sub_agent/task.py +10 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/sub_agent/web.py +10 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/codec.py +6 -6
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/export.py +261 -62
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/selector.py +7 -24
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/session.py +125 -53
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/store.py +5 -32
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/templates/export_session.html +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/templates/mermaid_viewer.html +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/trace/log.py +11 -6
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/core/input.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/core/stage_manager.py +1 -8
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/debug/display.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/clipboard.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/completers.py +18 -10
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/event_handler.py +136 -127
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/key_bindings.py +1 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/renderer.py +107 -15
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/assistant.py +2 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/common.py +65 -7
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/developer.py +7 -6
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/diffs.py +11 -11
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +49 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/metadata.py +39 -31
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/sub_agent.py +57 -16
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/thinking.py +37 -2
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/tools.py +180 -165
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/live.py +3 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/markdown.py +39 -7
- klaude_code-2.0.0/src/klaude_code/ui/rich/quote.py +109 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/status.py +14 -8
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/theme.py +13 -6
- klaude_code-2.0.0/src/klaude_code/ui/terminal/image.py +34 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/notifier.py +2 -1
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/progress_bar.py +4 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/selector.py +22 -4
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/utils/common.py +55 -0
- klaude_code-1.8.0/src/klaude_code/auth/codex/token_manager.py +0 -84
- klaude_code-1.8.0/src/klaude_code/cli/auth_cmd.py +0 -73
- klaude_code-1.8.0/src/klaude_code/cli/list_model.py +0 -307
- klaude_code-1.8.0/src/klaude_code/command/prompt-jj-describe.md +0 -32
- klaude_code-1.8.0/src/klaude_code/const.py +0 -164
- klaude_code-1.8.0/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
- klaude_code-1.8.0/src/klaude_code/llm/anthropic/input.py +0 -224
- klaude_code-1.8.0/src/klaude_code/llm/google/input.py +0 -215
- klaude_code-1.8.0/src/klaude_code/llm/input_common.py +0 -233
- klaude_code-1.8.0/src/klaude_code/llm/openai_compatible/input.py +0 -111
- klaude_code-1.8.0/src/klaude_code/llm/openrouter/input.py +0 -148
- klaude_code-1.8.0/src/klaude_code/llm/responses/input.py +0 -186
- klaude_code-1.8.0/src/klaude_code/protocol/sub_agent/oracle.py +0 -91
- klaude_code-1.8.0/src/klaude_code/ui/rich/quote.py +0 -34
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.8.0 → klaude_code-2.0.0}/src/klaude_code/ui/utils/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -9,11 +9,12 @@ Requires-Dist: diff-match-patch>=20241021
|
|
|
9
9
|
Requires-Dist: google-genai>=1.56.0
|
|
10
10
|
Requires-Dist: markdown-it-py>=4.0.0
|
|
11
11
|
Requires-Dist: openai>=1.102.0
|
|
12
|
-
Requires-Dist: pillow>=
|
|
12
|
+
Requires-Dist: pillow>=9.1,<11.0
|
|
13
13
|
Requires-Dist: prompt-toolkit>=3.0.52
|
|
14
14
|
Requires-Dist: pydantic>=2.11.7
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.2
|
|
16
16
|
Requires-Dist: rich>=14.1.0
|
|
17
|
+
Requires-Dist: term-image>=0.7.2
|
|
17
18
|
Requires-Dist: trafilatura>=2.0.0
|
|
18
19
|
Requires-Dist: typer>=0.17.3
|
|
19
20
|
Requires-Python: >=3.13
|
|
@@ -120,11 +121,12 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
|
|
|
120
121
|
| Provider | Env Variable | Models |
|
|
121
122
|
|-------------|-----------------------|-------------------------------------------------------------------------------|
|
|
122
123
|
| anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
|
|
124
|
+
| claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
|
|
123
125
|
| openai | `OPENAI_API_KEY` | gpt-5.2 |
|
|
124
126
|
| openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
|
|
125
127
|
| deepseek | `DEEPSEEK_API_KEY` | deepseek |
|
|
126
128
|
| moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
|
|
127
|
-
| codex | N/A (OAuth) | gpt-5.2-codex
|
|
129
|
+
| codex | N/A (OAuth) | gpt-5.2-codex (requires ChatGPT Pro subscription) |
|
|
128
130
|
|
|
129
131
|
List all configured providers and models:
|
|
130
132
|
|
|
@@ -134,6 +136,26 @@ klaude list
|
|
|
134
136
|
|
|
135
137
|
Models from providers without a valid API key are shown as dimmed/unavailable.
|
|
136
138
|
|
|
139
|
+
#### OAuth Login
|
|
140
|
+
|
|
141
|
+
For subscription-based providers (Claude Pro/Max, ChatGPT Pro), use the login command:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Interactive provider selection
|
|
145
|
+
klaude login
|
|
146
|
+
|
|
147
|
+
# Or specify provider directly
|
|
148
|
+
klaude login claude # Claude Pro/Max subscription
|
|
149
|
+
klaude login codex # ChatGPT Pro subscription
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
To logout:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
klaude logout claude
|
|
156
|
+
klaude logout codex
|
|
157
|
+
```
|
|
158
|
+
|
|
137
159
|
#### Custom Configuration
|
|
138
160
|
|
|
139
161
|
User config file: `~/.klaude/klaude-config.yaml`
|
|
@@ -240,7 +262,6 @@ provider_list:
|
|
|
240
262
|
main_model: opus
|
|
241
263
|
|
|
242
264
|
sub_agent_models:
|
|
243
|
-
oracle: gpt-4.1
|
|
244
265
|
explore: sonnet
|
|
245
266
|
task: opus
|
|
246
267
|
webagent: sonnet
|
|
@@ -269,12 +290,13 @@ provider_list:
|
|
|
269
290
|
##### Supported Protocols
|
|
270
291
|
|
|
271
292
|
- `anthropic` - Anthropic Claude API
|
|
293
|
+
- `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
|
|
272
294
|
- `openai` - OpenAI-compatible API
|
|
273
295
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
274
296
|
- `openrouter` - OpenRouter API
|
|
275
297
|
- `google` - Google Gemini API
|
|
276
298
|
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
277
|
-
- `
|
|
299
|
+
- `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
|
|
278
300
|
|
|
279
301
|
List configured providers and models:
|
|
280
302
|
|
|
@@ -374,4 +396,4 @@ The main agent can spawn specialized sub-agents for specific tasks:
|
|
|
374
396
|
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
375
397
|
| **Task** | Handle complex multi-step tasks autonomously |
|
|
376
398
|
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
377
|
-
| **
|
|
399
|
+
| **ImageGen** | Generate images from text prompts via OpenRouter Nano Banana Pro |
|
|
@@ -99,11 +99,12 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
|
|
|
99
99
|
| Provider | Env Variable | Models |
|
|
100
100
|
|-------------|-----------------------|-------------------------------------------------------------------------------|
|
|
101
101
|
| anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
|
|
102
|
+
| claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
|
|
102
103
|
| openai | `OPENAI_API_KEY` | gpt-5.2 |
|
|
103
104
|
| openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
|
|
104
105
|
| deepseek | `DEEPSEEK_API_KEY` | deepseek |
|
|
105
106
|
| moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
|
|
106
|
-
| codex | N/A (OAuth) | gpt-5.2-codex
|
|
107
|
+
| codex | N/A (OAuth) | gpt-5.2-codex (requires ChatGPT Pro subscription) |
|
|
107
108
|
|
|
108
109
|
List all configured providers and models:
|
|
109
110
|
|
|
@@ -113,6 +114,26 @@ klaude list
|
|
|
113
114
|
|
|
114
115
|
Models from providers without a valid API key are shown as dimmed/unavailable.
|
|
115
116
|
|
|
117
|
+
#### OAuth Login
|
|
118
|
+
|
|
119
|
+
For subscription-based providers (Claude Pro/Max, ChatGPT Pro), use the login command:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Interactive provider selection
|
|
123
|
+
klaude login
|
|
124
|
+
|
|
125
|
+
# Or specify provider directly
|
|
126
|
+
klaude login claude # Claude Pro/Max subscription
|
|
127
|
+
klaude login codex # ChatGPT Pro subscription
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
To logout:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
klaude logout claude
|
|
134
|
+
klaude logout codex
|
|
135
|
+
```
|
|
136
|
+
|
|
116
137
|
#### Custom Configuration
|
|
117
138
|
|
|
118
139
|
User config file: `~/.klaude/klaude-config.yaml`
|
|
@@ -219,7 +240,6 @@ provider_list:
|
|
|
219
240
|
main_model: opus
|
|
220
241
|
|
|
221
242
|
sub_agent_models:
|
|
222
|
-
oracle: gpt-4.1
|
|
223
243
|
explore: sonnet
|
|
224
244
|
task: opus
|
|
225
245
|
webagent: sonnet
|
|
@@ -248,12 +268,13 @@ provider_list:
|
|
|
248
268
|
##### Supported Protocols
|
|
249
269
|
|
|
250
270
|
- `anthropic` - Anthropic Claude API
|
|
271
|
+
- `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
|
|
251
272
|
- `openai` - OpenAI-compatible API
|
|
252
273
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
253
274
|
- `openrouter` - OpenRouter API
|
|
254
275
|
- `google` - Google Gemini API
|
|
255
276
|
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
256
|
-
- `
|
|
277
|
+
- `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
|
|
257
278
|
|
|
258
279
|
List configured providers and models:
|
|
259
280
|
|
|
@@ -353,4 +374,4 @@ The main agent can spawn specialized sub-agents for specific tasks:
|
|
|
353
374
|
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
354
375
|
| **Task** | Handle complex multi-step tasks autonomously |
|
|
355
376
|
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
356
|
-
| **
|
|
377
|
+
| **ImageGen** | Generate images from text prompts via OpenRouter Nano Banana Pro |
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "klaude-code"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -16,11 +16,12 @@ dependencies = [
|
|
|
16
16
|
"google-genai>=1.56.0",
|
|
17
17
|
"markdown-it-py>=4.0.0",
|
|
18
18
|
"openai>=1.102.0",
|
|
19
|
-
"pillow>=
|
|
19
|
+
"pillow>=9.1,<11.0",
|
|
20
20
|
"prompt-toolkit>=3.0.52",
|
|
21
21
|
"pydantic>=2.11.7",
|
|
22
22
|
"pyyaml>=6.0.2",
|
|
23
23
|
"rich>=14.1.0",
|
|
24
|
+
"term-image>=0.7.2",
|
|
24
25
|
"trafilatura>=2.0.0",
|
|
25
26
|
"typer>=0.17.3",
|
|
26
27
|
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Base classes for authentication token management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
KLAUDE_AUTH_FILE = Path.home() / ".klaude" / "klaude-auth.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseAuthState(BaseModel):
|
|
15
|
+
"""Base authentication state with common OAuth fields."""
|
|
16
|
+
|
|
17
|
+
access_token: str
|
|
18
|
+
refresh_token: str
|
|
19
|
+
expires_at: int # Unix timestamp
|
|
20
|
+
|
|
21
|
+
def is_expired(self, buffer_seconds: int = 300) -> bool:
|
|
22
|
+
"""Check if token is expired or will expire soon."""
|
|
23
|
+
return time.time() + buffer_seconds >= self.expires_at
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseTokenManager[T: BaseAuthState](ABC):
|
|
27
|
+
"""Base class for OAuth token management."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, auth_file: Path | None = None):
|
|
30
|
+
self.auth_file = auth_file or KLAUDE_AUTH_FILE
|
|
31
|
+
self._state: T | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def storage_key(self) -> str:
|
|
36
|
+
"""Key used to store this auth state in the JSON file."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def _create_state(self, data: dict[str, Any]) -> T:
|
|
41
|
+
"""Create state instance from dict data."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
def _load_store(self) -> dict[str, Any]:
|
|
45
|
+
if not self.auth_file.exists():
|
|
46
|
+
return {}
|
|
47
|
+
try:
|
|
48
|
+
data: Any = json.loads(self.auth_file.read_text())
|
|
49
|
+
if isinstance(data, dict):
|
|
50
|
+
return cast(dict[str, Any], data)
|
|
51
|
+
return {}
|
|
52
|
+
except (json.JSONDecodeError, ValueError):
|
|
53
|
+
return {}
|
|
54
|
+
|
|
55
|
+
def _save_store(self, data: dict[str, Any]) -> None:
|
|
56
|
+
self.auth_file.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
self.auth_file.write_text(json.dumps(data, indent=2))
|
|
58
|
+
|
|
59
|
+
def load(self) -> T | None:
|
|
60
|
+
"""Load authentication state from file."""
|
|
61
|
+
data: Any = self._load_store().get(self.storage_key)
|
|
62
|
+
if not isinstance(data, dict):
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
self._state = self._create_state(cast(dict[str, Any], data))
|
|
66
|
+
return self._state
|
|
67
|
+
except ValueError:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def save(self, state: T) -> None:
|
|
71
|
+
"""Save authentication state to file."""
|
|
72
|
+
store = self._load_store()
|
|
73
|
+
store[self.storage_key] = state.model_dump(mode="json")
|
|
74
|
+
self._save_store(store)
|
|
75
|
+
self._state = state
|
|
76
|
+
|
|
77
|
+
def delete(self) -> None:
|
|
78
|
+
"""Delete stored tokens."""
|
|
79
|
+
store = self._load_store()
|
|
80
|
+
store.pop(self.storage_key, None)
|
|
81
|
+
if len(store) == 0:
|
|
82
|
+
if self.auth_file.exists():
|
|
83
|
+
self.auth_file.unlink()
|
|
84
|
+
else:
|
|
85
|
+
self._save_store(store)
|
|
86
|
+
self._state = None
|
|
87
|
+
|
|
88
|
+
def is_logged_in(self) -> bool:
|
|
89
|
+
"""Check if user is logged in."""
|
|
90
|
+
state = self._state or self.load()
|
|
91
|
+
return state is not None
|
|
92
|
+
|
|
93
|
+
def get_state(self) -> T | None:
|
|
94
|
+
"""Get current authentication state."""
|
|
95
|
+
if self._state is None:
|
|
96
|
+
self._state = self.load()
|
|
97
|
+
return self._state
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""OAuth PKCE flow for Claude (Anthropic OAuth) authentication."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import secrets
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from klaude_code.auth.claude.exceptions import ClaudeAuthError, ClaudeNotLoggedInError
|
|
12
|
+
from klaude_code.auth.claude.token_manager import ClaudeAuthState, ClaudeTokenManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _decode_base64(value: str) -> str:
|
|
16
|
+
return base64.b64decode(value).decode()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# OAuth configuration (Claude Pro/Max)
|
|
20
|
+
CLIENT_ID = _decode_base64("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl")
|
|
21
|
+
AUTHORIZE_URL = "https://claude.ai/oauth/authorize"
|
|
22
|
+
TOKEN_URL = "https://console.anthropic.com/v1/oauth/token"
|
|
23
|
+
REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback"
|
|
24
|
+
SCOPE = "org:create_api_key user:profile user:inference"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_code_verifier() -> str:
|
|
28
|
+
"""Generate a random code verifier for PKCE."""
|
|
29
|
+
return secrets.token_urlsafe(64)[:128]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_code_challenge(verifier: str) -> str:
|
|
33
|
+
"""Generate code challenge from verifier using S256 method."""
|
|
34
|
+
digest = hashlib.sha256(verifier.encode()).digest()
|
|
35
|
+
return base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_authorize_url(code_challenge: str, state: str) -> str:
|
|
39
|
+
"""Build the authorization URL with all required parameters."""
|
|
40
|
+
# Note: the `code=true` parameter is required for the console callback flow.
|
|
41
|
+
params = {
|
|
42
|
+
"code": "true",
|
|
43
|
+
"client_id": CLIENT_ID,
|
|
44
|
+
"response_type": "code",
|
|
45
|
+
"redirect_uri": REDIRECT_URI,
|
|
46
|
+
"scope": SCOPE,
|
|
47
|
+
"code_challenge": code_challenge,
|
|
48
|
+
"code_challenge_method": "S256",
|
|
49
|
+
"state": state,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
encoded = httpx.QueryParams(params)
|
|
53
|
+
return f"{AUTHORIZE_URL}?{encoded}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_user_code(value: str) -> tuple[str, str | None]:
|
|
57
|
+
raw = value.strip()
|
|
58
|
+
if "#" in raw:
|
|
59
|
+
code, state = raw.split("#", 1)
|
|
60
|
+
return code.strip(), state.strip()
|
|
61
|
+
return raw, None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ClaudeOAuth:
|
|
65
|
+
"""Handle OAuth PKCE flow for Claude (Anthropic OAuth) authentication."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, token_manager: ClaudeTokenManager | None = None):
|
|
68
|
+
self.token_manager = token_manager or ClaudeTokenManager()
|
|
69
|
+
|
|
70
|
+
def login(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
on_auth_url: Callable[[str], None],
|
|
74
|
+
on_prompt_code: Callable[[], str],
|
|
75
|
+
) -> ClaudeAuthState:
|
|
76
|
+
"""Run the complete OAuth login flow."""
|
|
77
|
+
verifier = generate_code_verifier()
|
|
78
|
+
challenge = generate_code_challenge(verifier)
|
|
79
|
+
|
|
80
|
+
# Some flows require `state` to be echoed back for token exchange.
|
|
81
|
+
state = verifier
|
|
82
|
+
|
|
83
|
+
auth_url = build_authorize_url(challenge, state)
|
|
84
|
+
on_auth_url(auth_url)
|
|
85
|
+
|
|
86
|
+
raw_user_code = on_prompt_code()
|
|
87
|
+
code, returned_state = _parse_user_code(raw_user_code)
|
|
88
|
+
if not code:
|
|
89
|
+
raise ClaudeAuthError("No authorization code provided")
|
|
90
|
+
|
|
91
|
+
exchange_state = returned_state or state
|
|
92
|
+
auth_state = self._exchange_code(code=code, state=exchange_state, verifier=verifier)
|
|
93
|
+
self.token_manager.save(auth_state)
|
|
94
|
+
return auth_state
|
|
95
|
+
|
|
96
|
+
def _exchange_code(self, *, code: str, state: str, verifier: str) -> ClaudeAuthState:
|
|
97
|
+
"""Exchange authorization code for tokens."""
|
|
98
|
+
payload = {
|
|
99
|
+
"grant_type": "authorization_code",
|
|
100
|
+
"client_id": CLIENT_ID,
|
|
101
|
+
"code": code,
|
|
102
|
+
"state": state,
|
|
103
|
+
"redirect_uri": REDIRECT_URI,
|
|
104
|
+
"code_verifier": verifier,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
with httpx.Client() as client:
|
|
108
|
+
response = client.post(
|
|
109
|
+
TOKEN_URL,
|
|
110
|
+
json=payload,
|
|
111
|
+
headers={"Content-Type": "application/json"},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if response.status_code != 200:
|
|
115
|
+
raise ClaudeAuthError(f"Token exchange failed: {response.text}")
|
|
116
|
+
|
|
117
|
+
tokens = response.json()
|
|
118
|
+
access_token = tokens["access_token"]
|
|
119
|
+
refresh_token = tokens["refresh_token"]
|
|
120
|
+
expires_in = tokens.get("expires_in", 3600)
|
|
121
|
+
|
|
122
|
+
return ClaudeAuthState(
|
|
123
|
+
access_token=access_token,
|
|
124
|
+
refresh_token=refresh_token,
|
|
125
|
+
expires_at=int(time.time()) + int(expires_in),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def refresh(self) -> ClaudeAuthState:
|
|
129
|
+
"""Refresh the access token using refresh token."""
|
|
130
|
+
state = self.token_manager.get_state()
|
|
131
|
+
if state is None:
|
|
132
|
+
raise ClaudeNotLoggedInError("Not logged in to Claude. Run 'klaude login claude' first.")
|
|
133
|
+
|
|
134
|
+
payload = {
|
|
135
|
+
"grant_type": "refresh_token",
|
|
136
|
+
"client_id": CLIENT_ID,
|
|
137
|
+
"refresh_token": state.refresh_token,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
with httpx.Client() as client:
|
|
141
|
+
response = client.post(
|
|
142
|
+
TOKEN_URL,
|
|
143
|
+
json=payload,
|
|
144
|
+
headers={"Content-Type": "application/json"},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if response.status_code != 200:
|
|
148
|
+
raise ClaudeAuthError(f"Token refresh failed: {response.text}")
|
|
149
|
+
|
|
150
|
+
tokens = response.json()
|
|
151
|
+
access_token = tokens["access_token"]
|
|
152
|
+
refresh_token = tokens.get("refresh_token", state.refresh_token)
|
|
153
|
+
expires_in = tokens.get("expires_in", 3600)
|
|
154
|
+
|
|
155
|
+
new_state = ClaudeAuthState(
|
|
156
|
+
access_token=access_token,
|
|
157
|
+
refresh_token=refresh_token,
|
|
158
|
+
expires_at=int(time.time()) + int(expires_in),
|
|
159
|
+
)
|
|
160
|
+
self.token_manager.save(new_state)
|
|
161
|
+
return new_state
|
|
162
|
+
|
|
163
|
+
def ensure_valid_token(self) -> str:
|
|
164
|
+
"""Ensure we have a valid access token, refreshing if needed."""
|
|
165
|
+
state = self.token_manager.get_state()
|
|
166
|
+
if state is None:
|
|
167
|
+
raise ClaudeNotLoggedInError("Not logged in to Claude. Run 'klaude login claude' first.")
|
|
168
|
+
|
|
169
|
+
if state.is_expired():
|
|
170
|
+
state = self.refresh()
|
|
171
|
+
|
|
172
|
+
return state.access_token
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Token storage and management for Claude (Anthropic OAuth) authentication."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from klaude_code.auth.base import BaseAuthState, BaseTokenManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClaudeAuthState(BaseAuthState):
|
|
10
|
+
"""Stored authentication state for Claude OAuth."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClaudeTokenManager(BaseTokenManager[ClaudeAuthState]):
|
|
16
|
+
"""Manage Claude OAuth tokens."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, auth_file: Path | None = None):
|
|
19
|
+
super().__init__(auth_file)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def storage_key(self) -> str:
|
|
23
|
+
return "claude"
|
|
24
|
+
|
|
25
|
+
def _create_state(self, data: dict[str, Any]) -> ClaudeAuthState:
|
|
26
|
+
return ClaudeAuthState.model_validate(data)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Token storage and management for Codex authentication."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from klaude_code.auth.base import BaseAuthState, BaseTokenManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodexAuthState(BaseAuthState):
|
|
10
|
+
"""Stored authentication state for Codex."""
|
|
11
|
+
|
|
12
|
+
account_id: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CodexTokenManager(BaseTokenManager[CodexAuthState]):
|
|
16
|
+
"""Manage Codex OAuth tokens."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, auth_file: Path | None = None):
|
|
19
|
+
super().__init__(auth_file)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def storage_key(self) -> str:
|
|
23
|
+
return "codex"
|
|
24
|
+
|
|
25
|
+
def _create_state(self, data: dict[str, Any]) -> CodexAuthState:
|
|
26
|
+
return CodexAuthState.model_validate(data)
|
|
27
|
+
|
|
28
|
+
def get_access_token(self) -> str:
|
|
29
|
+
"""Get access token, raising if not logged in."""
|
|
30
|
+
state = self.get_state()
|
|
31
|
+
if state is None:
|
|
32
|
+
from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
|
|
33
|
+
|
|
34
|
+
raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
|
|
35
|
+
return state.access_token
|
|
36
|
+
|
|
37
|
+
def get_account_id(self) -> str:
|
|
38
|
+
"""Get account ID, raising if not logged in."""
|
|
39
|
+
state = self.get_state()
|
|
40
|
+
if state is None:
|
|
41
|
+
from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
|
|
42
|
+
|
|
43
|
+
raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
|
|
44
|
+
return state.account_id
|