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.
Files changed (229) hide show
  1. {klaude_code-1.8.0 → klaude_code-1.9.0}/PKG-INFO +25 -5
  2. {klaude_code-1.8.0 → klaude_code-1.9.0}/README.md +24 -4
  3. {klaude_code-1.8.0 → klaude_code-1.9.0}/pyproject.toml +1 -1
  4. klaude_code-1.9.0/src/klaude_code/auth/base.py +101 -0
  5. klaude_code-1.9.0/src/klaude_code/auth/claude/__init__.py +6 -0
  6. klaude_code-1.9.0/src/klaude_code/auth/claude/exceptions.py +9 -0
  7. klaude_code-1.9.0/src/klaude_code/auth/claude/oauth.py +172 -0
  8. klaude_code-1.9.0/src/klaude_code/auth/claude/token_manager.py +26 -0
  9. klaude_code-1.9.0/src/klaude_code/auth/codex/token_manager.py +44 -0
  10. klaude_code-1.9.0/src/klaude_code/cli/auth_cmd.py +154 -0
  11. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/config_cmd.py +4 -2
  12. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/cost_cmd.py +14 -9
  13. klaude_code-1.9.0/src/klaude_code/cli/list_model.py +355 -0
  14. klaude_code-1.9.0/src/klaude_code/command/prompt-commit.md +73 -0
  15. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/assets/builtin_config.yaml +36 -3
  16. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/config.py +24 -5
  17. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/thinking.py +4 -4
  18. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompt.py +1 -1
  19. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/client.py +28 -3
  20. klaude_code-1.9.0/src/klaude_code/llm/claude/__init__.py +3 -0
  21. klaude_code-1.9.0/src/klaude_code/llm/claude/client.py +95 -0
  22. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/codex/client.py +1 -1
  23. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/registry.py +3 -1
  24. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/llm_param.py +2 -1
  25. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/__init__.py +1 -2
  26. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/session.py +4 -4
  27. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/metadata.py +6 -26
  28. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/theme.py +6 -5
  29. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/utils/common.py +46 -0
  30. klaude_code-1.8.0/src/klaude_code/auth/codex/token_manager.py +0 -84
  31. klaude_code-1.8.0/src/klaude_code/cli/auth_cmd.py +0 -73
  32. klaude_code-1.8.0/src/klaude_code/cli/list_model.py +0 -307
  33. klaude_code-1.8.0/src/klaude_code/command/prompt-jj-describe.md +0 -32
  34. klaude_code-1.8.0/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
  35. klaude_code-1.8.0/src/klaude_code/protocol/sub_agent/oracle.py +0 -91
  36. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/__init__.py +0 -0
  37. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/__init__.py +0 -0
  38. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/__init__.py +0 -0
  39. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
  40. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  41. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/auth/codex/oauth.py +0 -0
  42. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/__init__.py +0 -0
  43. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/debug.py +0 -0
  44. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/main.py +0 -0
  45. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/runtime.py +0 -0
  46. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/self_update.py +0 -0
  47. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/cli/session_cmd.py +0 -0
  48. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/__init__.py +0 -0
  49. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/clear_cmd.py +0 -0
  50. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/command_abc.py +0 -0
  51. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/debug_cmd.py +0 -0
  52. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/export_cmd.py +0 -0
  53. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/export_online_cmd.py +0 -0
  54. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/fork_session_cmd.py +0 -0
  55. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/help_cmd.py +0 -0
  56. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/model_cmd.py +0 -0
  57. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/model_select.py +0 -0
  58. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/prompt-init.md +0 -0
  59. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/prompt_command.py +0 -0
  60. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/refresh_cmd.py +0 -0
  61. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/registry.py +0 -0
  62. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
  63. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/resume_cmd.py +0 -0
  64. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/status_cmd.py +0 -0
  65. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  66. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/command/thinking_cmd.py +0 -0
  67. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/__init__.py +0 -0
  68. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/assets/__init__.py +0 -0
  69. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/builtin_config.py +0 -0
  70. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/config/select_model.py +0 -0
  71. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/const.py +0 -0
  72. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/__init__.py +0 -0
  73. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/agent.py +0 -0
  74. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/executor.py +0 -0
  75. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/__init__.py +0 -0
  76. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
  77. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  78. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  79. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  80. {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
  81. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  82. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  83. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  84. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  85. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  86. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  87. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  88. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/reminders.py +0 -0
  89. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/task.py +0 -0
  90. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/__init__.py +0 -0
  91. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
  92. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
  93. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  94. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  95. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  96. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  97. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  98. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  99. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
  100. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
  101. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  102. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  103. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  104. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  105. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  106. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  107. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  108. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  109. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  110. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
  111. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  112. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  113. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  114. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  115. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  116. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  117. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  118. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  119. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  120. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
  121. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_context.py +0 -0
  122. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
  123. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
  124. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/truncation.py +0 -0
  125. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
  126. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  127. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  128. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  129. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  130. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  131. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  132. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/core/turn.py +0 -0
  133. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/__init__.py +0 -0
  134. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  135. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/anthropic/input.py +0 -0
  136. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
  137. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/bedrock/client.py +0 -0
  138. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/client.py +0 -0
  139. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/codex/__init__.py +0 -0
  140. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/__init__.py +0 -0
  141. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/client.py +0 -0
  142. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/google/input.py +0 -0
  143. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/input_common.py +0 -0
  144. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  145. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  146. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  147. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  148. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  149. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  150. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/client.py +0 -0
  151. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/input.py +0 -0
  152. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  153. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/__init__.py +0 -0
  154. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/client.py +0 -0
  155. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/responses/input.py +0 -0
  156. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/llm/usage.py +0 -0
  157. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/__init__.py +0 -0
  158. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/commands.py +0 -0
  159. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/events.py +0 -0
  160. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/model.py +0 -0
  161. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/op.py +0 -0
  162. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/op_handler.py +0 -0
  163. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  164. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  165. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  166. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/protocol/tools.py +0 -0
  167. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/__init__.py +0 -0
  168. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/codec.py +0 -0
  169. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/export.py +0 -0
  170. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/selector.py +0 -0
  171. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/store.py +0 -0
  172. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/templates/export_session.html +0 -0
  173. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  174. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/__init__.py +0 -0
  175. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  176. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  177. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  178. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  179. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  180. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/loader.py +0 -0
  181. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/manager.py +0 -0
  182. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/skill/system_skills.py +0 -0
  183. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/trace/__init__.py +0 -0
  184. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/trace/log.py +0 -0
  185. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/__init__.py +0 -0
  186. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/__init__.py +0 -0
  187. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/display.py +0 -0
  188. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/input.py +0 -0
  189. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
  190. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/__init__.py +0 -0
  191. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  192. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
  193. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  194. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
  195. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  196. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  197. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  198. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
  199. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
  200. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
  201. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  202. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
  203. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
  204. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
  205. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
  206. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/common.py +0 -0
  207. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/developer.py +0 -0
  208. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
  209. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/errors.py +0 -0
  210. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
  211. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
  212. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
  213. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/tools.py +0 -0
  214. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
  215. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/__init__.py +0 -0
  216. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  217. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
  218. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/live.py +0 -0
  219. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/markdown.py +0 -0
  220. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/quote.py +0 -0
  221. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  222. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/rich/status.py +0 -0
  223. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
  224. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/color.py +0 -0
  225. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/control.py +0 -0
  226. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
  227. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  228. {klaude_code-1.8.0 → klaude_code-1.9.0}/src/klaude_code/ui/terminal/selector.py +0 -0
  229. {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.8.0
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
- - `codex` - OpenAI Codex CLI (OAuth-based)
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
- - `codex` - OpenAI Codex CLI (OAuth-based)
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 |
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.8.0"
7
+ version = "1.9.0"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -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,6 @@
1
+ """Claude (Anthropic OAuth) authentication helpers."""
2
+
3
+ from .oauth import ClaudeOAuth
4
+ from .token_manager import ClaudeAuthState, ClaudeTokenManager
5
+
6
+ __all__ = ["ClaudeAuthState", "ClaudeOAuth", "ClaudeTokenManager"]
@@ -0,0 +1,9 @@
1
+ """Exceptions for Claude OAuth authentication."""
2
+
3
+
4
+ class ClaudeAuthError(Exception):
5
+ """Base class for Claude auth errors."""
6
+
7
+
8
+ class ClaudeNotLoggedInError(ClaudeAuthError):
9
+ """Raised when no valid Claude OAuth session is available."""
@@ -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)