klaude-code 1.8.0__tar.gz → 1.9.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-1.9.0}/PKG-INFO +25 -5
- {klaude_code-1.8.0 → klaude_code-1.9.0}/README.md +24 -4
- {klaude_code-1.8.0 → klaude_code-1.9.0}/pyproject.toml +1 -1
- klaude_code-1.9.0/src/klaude_code/auth/base.py +101 -0
- klaude_code-1.9.0/src/klaude_code/auth/claude/__init__.py +6 -0
- klaude_code-1.9.0/src/klaude_code/auth/claude/exceptions.py +9 -0
- klaude_code-1.9.0/src/klaude_code/auth/claude/oauth.py +172 -0
- klaude_code-1.9.0/src/klaude_code/auth/claude/token_manager.py +26 -0
- klaude_code-1.9.0/src/klaude_code/auth/codex/token_manager.py +44 -0
- klaude_code-1.9.0/src/klaude_code/cli/auth_cmd.py +154 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/config_cmd.py +4 -2
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/cost_cmd.py +14 -9
- klaude_code-1.9.0/src/klaude_code/cli/list_model.py +355 -0
- klaude_code-1.9.0/src/klaude_code/command/prompt-commit.md +73 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/assets/builtin_config.yaml +36 -3
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/config.py +24 -5
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/thinking.py +4 -4
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompt.py +1 -1
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/client.py +28 -3
- klaude_code-1.9.0/src/klaude_code/llm/claude/__init__.py +3 -0
- klaude_code-1.9.0/src/klaude_code/llm/claude/client.py +95 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/codex/client.py +1 -1
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/registry.py +3 -1
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/llm_param.py +2 -1
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/__init__.py +1 -2
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/session.py +4 -4
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/metadata.py +6 -26
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/theme.py +6 -5
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/utils/common.py +46 -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/core/prompts/prompt-sub-agent-oracle.md +0 -22
- klaude_code-1.8.0/src/klaude_code/protocol/sub_agent/oracle.py +0 -91
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/main.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/runtime.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/fork_session_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/model_select.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/resume_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/const.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/executor.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/bedrock/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/developer.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/markdown.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/selector.py +0 -0
- {klaude_code-1.8.0 → klaude_code-1.9.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: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -120,11 +120,12 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
|
|
|
120
120
|
| Provider | Env Variable | Models |
|
|
121
121
|
|-------------|-----------------------|-------------------------------------------------------------------------------|
|
|
122
122
|
| anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
|
|
123
|
+
| claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
|
|
123
124
|
| openai | `OPENAI_API_KEY` | gpt-5.2 |
|
|
124
125
|
| openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
|
|
125
126
|
| deepseek | `DEEPSEEK_API_KEY` | deepseek |
|
|
126
127
|
| moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
|
|
127
|
-
| codex | N/A (OAuth) | gpt-5.2-codex
|
|
128
|
+
| codex | N/A (OAuth) | gpt-5.2-codex (requires ChatGPT Pro subscription) |
|
|
128
129
|
|
|
129
130
|
List all configured providers and models:
|
|
130
131
|
|
|
@@ -134,6 +135,26 @@ klaude list
|
|
|
134
135
|
|
|
135
136
|
Models from providers without a valid API key are shown as dimmed/unavailable.
|
|
136
137
|
|
|
138
|
+
#### OAuth Login
|
|
139
|
+
|
|
140
|
+
For subscription-based providers (Claude Pro/Max, ChatGPT Pro), use the login command:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Interactive provider selection
|
|
144
|
+
klaude login
|
|
145
|
+
|
|
146
|
+
# Or specify provider directly
|
|
147
|
+
klaude login claude # Claude Pro/Max subscription
|
|
148
|
+
klaude login codex # ChatGPT Pro subscription
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
To logout:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
klaude logout claude
|
|
155
|
+
klaude logout codex
|
|
156
|
+
```
|
|
157
|
+
|
|
137
158
|
#### Custom Configuration
|
|
138
159
|
|
|
139
160
|
User config file: `~/.klaude/klaude-config.yaml`
|
|
@@ -240,7 +261,6 @@ provider_list:
|
|
|
240
261
|
main_model: opus
|
|
241
262
|
|
|
242
263
|
sub_agent_models:
|
|
243
|
-
oracle: gpt-4.1
|
|
244
264
|
explore: sonnet
|
|
245
265
|
task: opus
|
|
246
266
|
webagent: sonnet
|
|
@@ -269,12 +289,13 @@ provider_list:
|
|
|
269
289
|
##### Supported Protocols
|
|
270
290
|
|
|
271
291
|
- `anthropic` - Anthropic Claude API
|
|
292
|
+
- `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
|
|
272
293
|
- `openai` - OpenAI-compatible API
|
|
273
294
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
274
295
|
- `openrouter` - OpenRouter API
|
|
275
296
|
- `google` - Google Gemini API
|
|
276
297
|
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
277
|
-
- `
|
|
298
|
+
- `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
|
|
278
299
|
|
|
279
300
|
List configured providers and models:
|
|
280
301
|
|
|
@@ -374,4 +395,3 @@ The main agent can spawn specialized sub-agents for specific tasks:
|
|
|
374
395
|
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
375
396
|
| **Task** | Handle complex multi-step tasks autonomously |
|
|
376
397
|
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
377
|
-
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|
|
@@ -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,3 @@ 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
|
-
| **Oracle** | Advanced reasoning advisor for code reviews, architecture planning, and bug analysis |
|
|
@@ -0,0 +1,101 @@
|
|
|
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, Generic, TypeVar, cast
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
KLAUDE_AUTH_FILE = Path.home() / ".klaude" / "klaude-auth.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseAuthState(BaseModel):
|
|
16
|
+
"""Base authentication state with common OAuth fields."""
|
|
17
|
+
|
|
18
|
+
access_token: str
|
|
19
|
+
refresh_token: str
|
|
20
|
+
expires_at: int # Unix timestamp
|
|
21
|
+
|
|
22
|
+
def is_expired(self, buffer_seconds: int = 300) -> bool:
|
|
23
|
+
"""Check if token is expired or will expire soon."""
|
|
24
|
+
return time.time() + buffer_seconds >= self.expires_at
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T", bound=BaseAuthState)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseTokenManager(ABC, Generic[T]):
|
|
31
|
+
"""Base class for OAuth token management."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, auth_file: Path | None = None):
|
|
34
|
+
self.auth_file = auth_file or KLAUDE_AUTH_FILE
|
|
35
|
+
self._state: T | None = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def storage_key(self) -> str:
|
|
40
|
+
"""Key used to store this auth state in the JSON file."""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def _create_state(self, data: dict[str, Any]) -> T:
|
|
45
|
+
"""Create state instance from dict data."""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def _load_store(self) -> dict[str, Any]:
|
|
49
|
+
if not self.auth_file.exists():
|
|
50
|
+
return {}
|
|
51
|
+
try:
|
|
52
|
+
data: Any = json.loads(self.auth_file.read_text())
|
|
53
|
+
if isinstance(data, dict):
|
|
54
|
+
return cast(dict[str, Any], data)
|
|
55
|
+
return {}
|
|
56
|
+
except (json.JSONDecodeError, ValueError):
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
def _save_store(self, data: dict[str, Any]) -> None:
|
|
60
|
+
self.auth_file.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
self.auth_file.write_text(json.dumps(data, indent=2))
|
|
62
|
+
|
|
63
|
+
def load(self) -> T | None:
|
|
64
|
+
"""Load authentication state from file."""
|
|
65
|
+
data: Any = self._load_store().get(self.storage_key)
|
|
66
|
+
if not isinstance(data, dict):
|
|
67
|
+
return None
|
|
68
|
+
try:
|
|
69
|
+
self._state = self._create_state(cast(dict[str, Any], data))
|
|
70
|
+
return self._state
|
|
71
|
+
except ValueError:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def save(self, state: T) -> None:
|
|
75
|
+
"""Save authentication state to file."""
|
|
76
|
+
store = self._load_store()
|
|
77
|
+
store[self.storage_key] = state.model_dump(mode="json")
|
|
78
|
+
self._save_store(store)
|
|
79
|
+
self._state = state
|
|
80
|
+
|
|
81
|
+
def delete(self) -> None:
|
|
82
|
+
"""Delete stored tokens."""
|
|
83
|
+
store = self._load_store()
|
|
84
|
+
store.pop(self.storage_key, None)
|
|
85
|
+
if len(store) == 0:
|
|
86
|
+
if self.auth_file.exists():
|
|
87
|
+
self.auth_file.unlink()
|
|
88
|
+
else:
|
|
89
|
+
self._save_store(store)
|
|
90
|
+
self._state = None
|
|
91
|
+
|
|
92
|
+
def is_logged_in(self) -> bool:
|
|
93
|
+
"""Check if user is logged in."""
|
|
94
|
+
state = self._state or self.load()
|
|
95
|
+
return state is not None
|
|
96
|
+
|
|
97
|
+
def get_state(self) -> T | None:
|
|
98
|
+
"""Get current authentication state."""
|
|
99
|
+
if self._state is None:
|
|
100
|
+
self._state = self.load()
|
|
101
|
+
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
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Authentication commands for CLI."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import webbrowser
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from prompt_toolkit.styles import Style
|
|
8
|
+
|
|
9
|
+
from klaude_code.trace import log
|
|
10
|
+
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
|
+
|
|
12
|
+
_SELECT_STYLE = Style(
|
|
13
|
+
[
|
|
14
|
+
("instruction", "ansibrightblack"),
|
|
15
|
+
("pointer", "ansigreen"),
|
|
16
|
+
("highlighted", "ansigreen"),
|
|
17
|
+
("text", "ansibrightblack"),
|
|
18
|
+
("question", "bold"),
|
|
19
|
+
]
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _select_provider() -> str | None:
|
|
24
|
+
"""Display provider selection menu and return selected provider."""
|
|
25
|
+
items: list[SelectItem[str]] = [
|
|
26
|
+
SelectItem(title=[("class:text", "Claude Max/Pro Subscription\n")], value="claude", search_text="claude"),
|
|
27
|
+
SelectItem(title=[("class:text", "ChatGPT Codex Subscription\n")], value="codex", search_text="codex"),
|
|
28
|
+
]
|
|
29
|
+
return select_one(
|
|
30
|
+
message="Select provider to login:",
|
|
31
|
+
items=items,
|
|
32
|
+
pointer="→",
|
|
33
|
+
style=_SELECT_STYLE,
|
|
34
|
+
use_search_filter=False,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def login_command(
|
|
39
|
+
provider: str | None = typer.Argument(None, help="Provider to login (codex|claude)"),
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Login to a provider using OAuth."""
|
|
42
|
+
if provider is None:
|
|
43
|
+
provider = _select_provider()
|
|
44
|
+
if provider is None:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
match provider.lower():
|
|
48
|
+
case "codex":
|
|
49
|
+
from klaude_code.auth.codex.oauth import CodexOAuth
|
|
50
|
+
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
51
|
+
|
|
52
|
+
token_manager = CodexTokenManager()
|
|
53
|
+
|
|
54
|
+
# Check if already logged in
|
|
55
|
+
if token_manager.is_logged_in():
|
|
56
|
+
state = token_manager.get_state()
|
|
57
|
+
if state and not state.is_expired():
|
|
58
|
+
log(("You are already logged in to Codex.", "green"))
|
|
59
|
+
log(f" Account ID: {state.account_id[:8]}...")
|
|
60
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
61
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
62
|
+
if not typer.confirm("Do you want to re-login?"):
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
log("Starting Codex OAuth login flow...")
|
|
66
|
+
log("A browser window will open for authentication.")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
oauth = CodexOAuth(token_manager)
|
|
70
|
+
state = oauth.login()
|
|
71
|
+
log(("Login successful!", "green"))
|
|
72
|
+
log(f" Account ID: {state.account_id[:8]}...")
|
|
73
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
74
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
log((f"Login failed: {e}", "red"))
|
|
77
|
+
raise typer.Exit(1) from None
|
|
78
|
+
case "claude":
|
|
79
|
+
from klaude_code.auth.claude.oauth import ClaudeOAuth
|
|
80
|
+
from klaude_code.auth.claude.token_manager import ClaudeTokenManager
|
|
81
|
+
|
|
82
|
+
token_manager = ClaudeTokenManager()
|
|
83
|
+
|
|
84
|
+
if token_manager.is_logged_in():
|
|
85
|
+
state = token_manager.get_state()
|
|
86
|
+
if state and not state.is_expired():
|
|
87
|
+
log(("You are already logged in to Claude.", "green"))
|
|
88
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
89
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
90
|
+
if not typer.confirm("Do you want to re-login?"):
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
log("Starting Claude OAuth login flow...")
|
|
94
|
+
log("A browser window will open for authentication.")
|
|
95
|
+
log("After login, paste the authorization code in the terminal.")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
oauth = ClaudeOAuth(token_manager)
|
|
99
|
+
state = oauth.login(
|
|
100
|
+
on_auth_url=lambda url: (webbrowser.open(url), None)[1],
|
|
101
|
+
on_prompt_code=lambda: typer.prompt(
|
|
102
|
+
"Paste the authorization code (format: code#state)",
|
|
103
|
+
prompt_suffix=": ",
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
log(("Login successful!", "green"))
|
|
107
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
108
|
+
log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
log((f"Login failed: {e}", "red"))
|
|
111
|
+
raise typer.Exit(1) from None
|
|
112
|
+
case _:
|
|
113
|
+
log((f"Error: Unknown provider '{provider}'. Supported: codex, claude", "red"))
|
|
114
|
+
raise typer.Exit(1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def logout_command(
|
|
118
|
+
provider: str = typer.Argument("codex", help="Provider to logout (codex|claude)"),
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Logout from a provider."""
|
|
121
|
+
match provider.lower():
|
|
122
|
+
case "codex":
|
|
123
|
+
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
124
|
+
|
|
125
|
+
token_manager = CodexTokenManager()
|
|
126
|
+
|
|
127
|
+
if not token_manager.is_logged_in():
|
|
128
|
+
log("You are not logged in to Codex.")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if typer.confirm("Are you sure you want to logout from Codex?"):
|
|
132
|
+
token_manager.delete()
|
|
133
|
+
log(("Logged out from Codex.", "green"))
|
|
134
|
+
case "claude":
|
|
135
|
+
from klaude_code.auth.claude.token_manager import ClaudeTokenManager
|
|
136
|
+
|
|
137
|
+
token_manager = ClaudeTokenManager()
|
|
138
|
+
|
|
139
|
+
if not token_manager.is_logged_in():
|
|
140
|
+
log("You are not logged in to Claude.")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if typer.confirm("Are you sure you want to logout from Claude?"):
|
|
144
|
+
token_manager.delete()
|
|
145
|
+
log(("Logged out from Claude.", "green"))
|
|
146
|
+
case _:
|
|
147
|
+
log((f"Error: Unknown provider '{provider}'. Supported: codex, claude", "red"))
|
|
148
|
+
raise typer.Exit(1)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def register_auth_commands(app: typer.Typer) -> None:
|
|
152
|
+
"""Register auth commands to the given Typer app."""
|
|
153
|
+
app.command("login")(login_command)
|
|
154
|
+
app.command("logout")(logout_command)
|