klaude-code 1.2.0__tar.gz → 1.2.11__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 (267) hide show
  1. {klaude_code-1.2.0 → klaude_code-1.2.11}/PKG-INFO +31 -17
  2. {klaude_code-1.2.0 → klaude_code-1.2.11}/README.md +28 -14
  3. klaude_code-1.2.11/pyproject.toml +78 -0
  4. klaude_code-1.2.11/src/klaude_code/auth/__init__.py +24 -0
  5. klaude_code-1.2.11/src/klaude_code/auth/codex/__init__.py +20 -0
  6. klaude_code-1.2.11/src/klaude_code/auth/codex/exceptions.py +17 -0
  7. klaude_code-1.2.11/src/klaude_code/auth/codex/jwt_utils.py +45 -0
  8. klaude_code-1.2.11/src/klaude_code/auth/codex/oauth.py +229 -0
  9. klaude_code-1.2.11/src/klaude_code/auth/codex/token_manager.py +84 -0
  10. klaude_code-1.2.11/src/klaude_code/cli/main.py +367 -0
  11. klaude_code-1.2.11/src/klaude_code/cli/runtime.py +331 -0
  12. klaude_code-1.2.11/src/klaude_code/cli/session_cmd.py +80 -0
  13. klaude_code-1.2.11/src/klaude_code/command/__init__.py +90 -0
  14. klaude_code-1.2.11/src/klaude_code/command/clear_cmd.py +24 -0
  15. klaude_code-1.2.11/src/klaude_code/command/command_abc.py +95 -0
  16. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/diff_cmd.py +37 -28
  17. klaude_code-1.2.11/src/klaude_code/command/export_cmd.py +89 -0
  18. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/help_cmd.py +15 -9
  19. klaude_code-1.2.11/src/klaude_code/command/model_cmd.py +46 -0
  20. klaude_code-1.2.11/src/klaude_code/command/prompt-deslop.md +14 -0
  21. klaude_code-1.2.0/src/klaude_code/command/prompt-update-dev-doc.md → klaude_code-1.2.11/src/klaude_code/command/prompt-dev-docs-update.md +3 -2
  22. klaude_code-1.2.0/src/klaude_code/command/prompt-dev-doc.md → klaude_code-1.2.11/src/klaude_code/command/prompt-dev-docs.md +3 -2
  23. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/prompt-init.md +2 -5
  24. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/prompt_command.py +13 -8
  25. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/refresh_cmd.py +9 -6
  26. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/registry.py +33 -24
  27. klaude_code-1.2.11/src/klaude_code/command/release_notes_cmd.py +89 -0
  28. klaude_code-1.2.11/src/klaude_code/command/status_cmd.py +161 -0
  29. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/command/terminal_setup_cmd.py +18 -14
  30. klaude_code-1.2.11/src/klaude_code/config/__init__.py +11 -0
  31. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/config/config.py +25 -26
  32. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/config/list_model.py +61 -3
  33. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/config/select_model.py +1 -1
  34. klaude_code-1.2.0/src/klaude_code/config/constants.py → klaude_code-1.2.11/src/klaude_code/const/__init__.py +2 -2
  35. klaude_code-1.2.11/src/klaude_code/core/agent.py +212 -0
  36. klaude_code-1.2.11/src/klaude_code/core/executor.py +482 -0
  37. klaude_code-1.2.11/src/klaude_code/core/manager/__init__.py +19 -0
  38. klaude_code-1.2.11/src/klaude_code/core/manager/agent_manager.py +132 -0
  39. klaude_code-1.2.11/src/klaude_code/core/manager/llm_clients.py +67 -0
  40. klaude_code-1.2.11/src/klaude_code/core/manager/llm_clients_builder.py +58 -0
  41. klaude_code-1.2.11/src/klaude_code/core/manager/sub_agent_manager.py +90 -0
  42. klaude_code-1.2.11/src/klaude_code/core/prompt.py +95 -0
  43. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-claude-code.md +1 -12
  44. klaude_code-1.2.11/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
  45. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-gemini.md +1 -1
  46. klaude_code-1.2.11/src/klaude_code/core/prompts/prompt-minimal.md +12 -0
  47. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-subagent-explore.md +3 -1
  48. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-subagent-webfetch.md +19 -2
  49. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/core/reminders.py +96 -112
  50. klaude_code-1.2.11/src/klaude_code/core/task.py +257 -0
  51. klaude_code-1.2.11/src/klaude_code/core/tool/__init__.py +77 -0
  52. klaude_code-1.2.11/src/klaude_code/core/tool/file/_utils.py +30 -0
  53. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/file}/apply_patch.py +7 -3
  54. klaude_code-1.2.11/src/klaude_code/core/tool/file/apply_patch_tool.md +1 -0
  55. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/file}/apply_patch_tool.py +20 -22
  56. klaude_code-1.2.11/src/klaude_code/core/tool/file/edit_tool.md +9 -0
  57. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/file}/edit_tool.py +63 -124
  58. klaude_code-1.2.11/src/klaude_code/core/tool/file/multi_edit_tool.md +42 -0
  59. klaude_code-1.2.11/src/klaude_code/core/tool/file/multi_edit_tool.py +174 -0
  60. klaude_code-1.2.11/src/klaude_code/core/tool/file/read_tool.md +14 -0
  61. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/file}/read_tool.py +52 -73
  62. klaude_code-1.2.11/src/klaude_code/core/tool/file/write_tool.md +8 -0
  63. klaude_code-1.2.11/src/klaude_code/core/tool/file/write_tool.py +121 -0
  64. klaude_code-1.2.11/src/klaude_code/core/tool/memory/__init__.py +5 -0
  65. klaude_code-1.2.11/src/klaude_code/core/tool/memory/memory_tool.md +20 -0
  66. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/memory}/memory_tool.py +87 -86
  67. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/memory}/skill_loader.py +16 -2
  68. klaude_code-1.2.11/src/klaude_code/core/tool/memory/skill_tool.md +24 -0
  69. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/memory}/skill_tool.py +28 -38
  70. klaude_code-1.2.11/src/klaude_code/core/tool/shell/bash_tool.md +43 -0
  71. klaude_code-1.2.11/src/klaude_code/core/tool/shell/bash_tool.py +123 -0
  72. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/shell}/command_safety.py +12 -268
  73. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/core/tool/sub_agent_tool.py +12 -12
  74. klaude_code-1.2.11/src/klaude_code/core/tool/todo/__init__.py +0 -0
  75. klaude_code-1.2.11/src/klaude_code/core/tool/todo/todo_write_tool.md +25 -0
  76. klaude_code-1.2.11/src/klaude_code/core/tool/todo/todo_write_tool.py +121 -0
  77. klaude_code-1.2.0/src/klaude_code/core/tool/todo_write_tool.py → klaude_code-1.2.11/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +1 -126
  78. klaude_code-1.2.11/src/klaude_code/core/tool/todo/update_plan_tool.md +3 -0
  79. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/todo}/update_plan_tool.py +24 -35
  80. klaude_code-1.2.11/src/klaude_code/core/tool/tool_abc.py +25 -0
  81. klaude_code-1.2.11/src/klaude_code/core/tool/tool_context.py +123 -0
  82. klaude_code-1.2.11/src/klaude_code/core/tool/tool_registry.py +77 -0
  83. klaude_code-1.2.11/src/klaude_code/core/tool/tool_runner.py +249 -0
  84. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/core/tool/truncation.py +55 -21
  85. klaude_code-1.2.11/src/klaude_code/core/tool/web/__init__.py +0 -0
  86. klaude_code-1.2.11/src/klaude_code/core/tool/web/mermaid_tool.md +21 -0
  87. klaude_code-1.2.11/src/klaude_code/core/tool/web/mermaid_tool.py +73 -0
  88. klaude_code-1.2.11/src/klaude_code/core/tool/web/web_fetch_tool.md +8 -0
  89. {klaude_code-1.2.0/src/klaude_code/core/tool → klaude_code-1.2.11/src/klaude_code/core/tool/web}/web_fetch_tool.py +18 -26
  90. klaude_code-1.2.11/src/klaude_code/core/turn.py +248 -0
  91. klaude_code-1.2.11/src/klaude_code/llm/__init__.py +13 -0
  92. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/anthropic/client.py +37 -83
  93. klaude_code-1.2.11/src/klaude_code/llm/anthropic/input.py +215 -0
  94. klaude_code-1.2.11/src/klaude_code/llm/client.py +49 -0
  95. klaude_code-1.2.11/src/klaude_code/llm/codex/__init__.py +5 -0
  96. klaude_code-1.2.11/src/klaude_code/llm/codex/client.py +130 -0
  97. klaude_code-1.2.11/src/klaude_code/llm/input_common.py +233 -0
  98. klaude_code-1.2.11/src/klaude_code/llm/openai_compatible/client.py +168 -0
  99. klaude_code-1.2.11/src/klaude_code/llm/openai_compatible/input.py +111 -0
  100. klaude_code-1.2.11/src/klaude_code/llm/openai_compatible/stream_processor.py +82 -0
  101. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  102. klaude_code-1.2.11/src/klaude_code/llm/openrouter/client.py +170 -0
  103. klaude_code-1.2.11/src/klaude_code/llm/openrouter/input.py +137 -0
  104. klaude_code-1.2.11/src/klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  105. klaude_code-1.2.11/src/klaude_code/llm/registry.py +48 -0
  106. klaude_code-1.2.11/src/klaude_code/llm/responses/client.py +202 -0
  107. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/responses/input.py +32 -35
  108. klaude_code-1.2.11/src/klaude_code/llm/usage.py +162 -0
  109. klaude_code-1.2.11/src/klaude_code/protocol/__init__.py +4 -0
  110. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/protocol/commands.py +2 -0
  111. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/protocol/events.py +15 -4
  112. klaude_code-1.2.0/src/klaude_code/protocol/llm_parameter.py → klaude_code-1.2.11/src/klaude_code/protocol/llm_param.py +14 -36
  113. klaude_code-1.2.11/src/klaude_code/protocol/model.py +412 -0
  114. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/protocol/op.py +23 -17
  115. klaude_code-1.2.11/src/klaude_code/protocol/op_handler.py +28 -0
  116. {klaude_code-1.2.0/src/klaude_code/core → klaude_code-1.2.11/src/klaude_code/protocol}/sub_agent.py +16 -3
  117. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/protocol/tools.py +1 -0
  118. klaude_code-1.2.11/src/klaude_code/session/export.py +653 -0
  119. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/session/session.py +127 -40
  120. klaude_code-1.2.11/src/klaude_code/session/templates/export_session.html +1597 -0
  121. klaude_code-1.2.11/src/klaude_code/trace/__init__.py +3 -0
  122. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/trace/log.py +11 -5
  123. klaude_code-1.2.11/src/klaude_code/ui/__init__.py +91 -0
  124. klaude_code-1.2.11/src/klaude_code/ui/core/__init__.py +1 -0
  125. klaude_code-1.2.11/src/klaude_code/ui/core/display.py +103 -0
  126. klaude_code-1.2.11/src/klaude_code/ui/core/input.py +71 -0
  127. klaude_code-1.2.11/src/klaude_code/ui/modes/__init__.py +1 -0
  128. klaude_code-1.2.11/src/klaude_code/ui/modes/debug/__init__.py +1 -0
  129. klaude_code-1.2.0/src/klaude_code/ui/base/debug_event_display.py → klaude_code-1.2.11/src/klaude_code/ui/modes/debug/display.py +9 -5
  130. klaude_code-1.2.11/src/klaude_code/ui/modes/exec/__init__.py +1 -0
  131. klaude_code-1.2.0/src/klaude_code/ui/base/exec_display.py → klaude_code-1.2.11/src/klaude_code/ui/modes/exec/display.py +28 -2
  132. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/__init__.py +47 -0
  133. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/clipboard.py +152 -0
  134. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/completers.py +462 -0
  135. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/display.py +60 -0
  136. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/event_handler.py +462 -0
  137. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +167 -0
  138. klaude_code-1.2.11/src/klaude_code/ui/modes/repl/key_bindings.py +170 -0
  139. {klaude_code-1.2.0/src/klaude_code/ui → klaude_code-1.2.11/src/klaude_code/ui/modes}/repl/renderer.py +113 -132
  140. klaude_code-1.2.11/src/klaude_code/ui/renderers/__init__.py +0 -0
  141. klaude_code-1.2.11/src/klaude_code/ui/renderers/assistant.py +21 -0
  142. klaude_code-1.2.11/src/klaude_code/ui/renderers/common.py +8 -0
  143. klaude_code-1.2.11/src/klaude_code/ui/renderers/developer.py +169 -0
  144. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/ui/renderers/diffs.py +36 -14
  145. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/ui/renderers/errors.py +1 -1
  146. klaude_code-1.2.11/src/klaude_code/ui/renderers/metadata.py +253 -0
  147. klaude_code-1.2.11/src/klaude_code/ui/renderers/sub_agent.py +71 -0
  148. klaude_code-1.2.11/src/klaude_code/ui/renderers/thinking.py +39 -0
  149. klaude_code-1.2.11/src/klaude_code/ui/renderers/tools.py +528 -0
  150. klaude_code-1.2.11/src/klaude_code/ui/renderers/user_input.py +78 -0
  151. klaude_code-1.2.11/src/klaude_code/ui/rich/__init__.py +1 -0
  152. {klaude_code-1.2.0/src/klaude_code/ui/rich_ext → klaude_code-1.2.11/src/klaude_code/ui/rich}/markdown.py +11 -8
  153. {klaude_code-1.2.0/src/klaude_code/ui/rich_ext → klaude_code-1.2.11/src/klaude_code/ui/rich}/searchable_text.py +3 -1
  154. {klaude_code-1.2.0/src/klaude_code/ui/renderers → klaude_code-1.2.11/src/klaude_code/ui/rich}/status.py +35 -24
  155. {klaude_code-1.2.0/src/klaude_code/ui/base → klaude_code-1.2.11/src/klaude_code/ui/rich}/theme.py +10 -2
  156. klaude_code-1.2.11/src/klaude_code/ui/terminal/__init__.py +56 -0
  157. klaude_code-1.2.0/src/klaude_code/ui/base/terminal_color.py → klaude_code-1.2.11/src/klaude_code/ui/terminal/color.py +4 -1
  158. klaude_code-1.2.11/src/klaude_code/ui/terminal/control.py +147 -0
  159. klaude_code-1.2.0/src/klaude_code/ui/base/terminal_notifier.py → klaude_code-1.2.11/src/klaude_code/ui/terminal/notifier.py +5 -2
  160. klaude_code-1.2.11/src/klaude_code/ui/utils/__init__.py +1 -0
  161. klaude_code-1.2.0/src/klaude_code/ui/base/utils.py → klaude_code-1.2.11/src/klaude_code/ui/utils/common.py +35 -3
  162. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/version.py +3 -3
  163. klaude_code-1.2.0/.claude/skills/publish/SKILL.md +0 -90
  164. klaude_code-1.2.0/.claude/skills/publish/scripts/bump_version.py +0 -74
  165. klaude_code-1.2.0/.claude/skills/publish/scripts/update_changelog.py +0 -187
  166. klaude_code-1.2.0/.crush/.gitignore +0 -1
  167. klaude_code-1.2.0/.crush/crush.db +0 -0
  168. klaude_code-1.2.0/.crush/logs/crush.log +0 -13
  169. klaude_code-1.2.0/.gitignore +0 -14
  170. klaude_code-1.2.0/.python-version +0 -1
  171. klaude_code-1.2.0/AGENTS.md +0 -137
  172. klaude_code-1.2.0/CHANGELOG.md +0 -150
  173. klaude_code-1.2.0/dev/finished/memory-tool/memory-tool-context.md +0 -209
  174. klaude_code-1.2.0/dev/finished/memory-tool/memory-tool-plan.md +0 -302
  175. klaude_code-1.2.0/dev/finished/memory-tool/memory-tool-tasks.md +0 -74
  176. klaude_code-1.2.0/dev/finished/smart-truncation/smart-truncation-context.md +0 -177
  177. klaude_code-1.2.0/dev/finished/smart-truncation/smart-truncation-plan.md +0 -239
  178. klaude_code-1.2.0/dev/finished/smart-truncation/smart-truncation-tasks.md +0 -80
  179. klaude_code-1.2.0/dev/finished/web-fetch-agent/web-fetch-agent.md +0 -126
  180. klaude_code-1.2.0/docs/at_files.md +0 -38
  181. klaude_code-1.2.0/docs/read_edit_tool.md +0 -323
  182. klaude_code-1.2.0/pyproject.toml +0 -61
  183. klaude_code-1.2.0/pyrightconfig.json +0 -17
  184. klaude_code-1.2.0/src/klaude_code/cli/main.py +0 -727
  185. klaude_code-1.2.0/src/klaude_code/command/__init__.py +0 -39
  186. klaude_code-1.2.0/src/klaude_code/command/clear_cmd.py +0 -43
  187. klaude_code-1.2.0/src/klaude_code/command/command_abc.py +0 -56
  188. klaude_code-1.2.0/src/klaude_code/command/export_cmd.py +0 -1149
  189. klaude_code-1.2.0/src/klaude_code/command/model_cmd.py +0 -71
  190. klaude_code-1.2.0/src/klaude_code/config/__init__.py +0 -49
  191. klaude_code-1.2.0/src/klaude_code/core/__init__.py +0 -3
  192. klaude_code-1.2.0/src/klaude_code/core/agent.py +0 -667
  193. klaude_code-1.2.0/src/klaude_code/core/clipboard_manifest.py +0 -155
  194. klaude_code-1.2.0/src/klaude_code/core/executor.py +0 -455
  195. klaude_code-1.2.0/src/klaude_code/core/prompt.py +0 -88
  196. klaude_code-1.2.0/src/klaude_code/core/tool/__init__.py +0 -39
  197. klaude_code-1.2.0/src/klaude_code/core/tool/apply_patch_tool_instructions.md +0 -75
  198. klaude_code-1.2.0/src/klaude_code/core/tool/bash_tool.py +0 -166
  199. klaude_code-1.2.0/src/klaude_code/core/tool/mermaid_tool.py +0 -96
  200. klaude_code-1.2.0/src/klaude_code/core/tool/multi_edit_tool.py +0 -236
  201. klaude_code-1.2.0/src/klaude_code/core/tool/tool_abc.py +0 -16
  202. klaude_code-1.2.0/src/klaude_code/core/tool/tool_context.py +0 -20
  203. klaude_code-1.2.0/src/klaude_code/core/tool/tool_registry.py +0 -98
  204. klaude_code-1.2.0/src/klaude_code/core/tool/tool_runner.py +0 -57
  205. klaude_code-1.2.0/src/klaude_code/llm/__init__.py +0 -21
  206. klaude_code-1.2.0/src/klaude_code/llm/anthropic/input.py +0 -218
  207. klaude_code-1.2.0/src/klaude_code/llm/client.py +0 -28
  208. klaude_code-1.2.0/src/klaude_code/llm/openai_compatible/client.py +0 -253
  209. klaude_code-1.2.0/src/klaude_code/llm/openai_compatible/input.py +0 -140
  210. klaude_code-1.2.0/src/klaude_code/llm/openrouter/client.py +0 -449
  211. klaude_code-1.2.0/src/klaude_code/llm/openrouter/input.py +0 -190
  212. klaude_code-1.2.0/src/klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
  213. klaude_code-1.2.0/src/klaude_code/llm/registry.py +0 -22
  214. klaude_code-1.2.0/src/klaude_code/llm/responses/client.py +0 -237
  215. klaude_code-1.2.0/src/klaude_code/protocol/model.py +0 -301
  216. klaude_code-1.2.0/src/klaude_code/trace/__init__.py +0 -3
  217. klaude_code-1.2.0/src/klaude_code/ui/__init__.py +0 -8
  218. klaude_code-1.2.0/src/klaude_code/ui/base/__init__.py +0 -1
  219. klaude_code-1.2.0/src/klaude_code/ui/base/display_abc.py +0 -36
  220. klaude_code-1.2.0/src/klaude_code/ui/base/input_abc.py +0 -20
  221. klaude_code-1.2.0/src/klaude_code/ui/renderers/common.py +0 -24
  222. klaude_code-1.2.0/src/klaude_code/ui/renderers/developer.py +0 -103
  223. klaude_code-1.2.0/src/klaude_code/ui/renderers/metadata.py +0 -167
  224. klaude_code-1.2.0/src/klaude_code/ui/renderers/sub_agent.py +0 -37
  225. klaude_code-1.2.0/src/klaude_code/ui/renderers/thinking.py +0 -7
  226. klaude_code-1.2.0/src/klaude_code/ui/renderers/tools.py +0 -334
  227. klaude_code-1.2.0/src/klaude_code/ui/renderers/user_input.py +0 -69
  228. klaude_code-1.2.0/src/klaude_code/ui/repl/__init__.py +0 -1
  229. klaude_code-1.2.0/src/klaude_code/ui/repl/display.py +0 -36
  230. klaude_code-1.2.0/src/klaude_code/ui/repl/event_handler.py +0 -247
  231. klaude_code-1.2.0/src/klaude_code/ui/repl/input.py +0 -745
  232. klaude_code-1.2.0/src/klaude_code/ui/rich_ext/__init__.py +0 -1
  233. klaude_code-1.2.0/tests/conftest.py +0 -12
  234. klaude_code-1.2.0/tests/gpt-5-reasoning-input.log +0 -557
  235. klaude_code-1.2.0/tests/run_tests.py +0 -40
  236. klaude_code-1.2.0/tests/test_apply_patch.py +0 -305
  237. klaude_code-1.2.0/tests/test_apply_patch_tool.py +0 -114
  238. klaude_code-1.2.0/tests/test_clipboard_manifest.py +0 -38
  239. klaude_code-1.2.0/tests/test_command_safety.py +0 -283
  240. klaude_code-1.2.0/tests/test_memory_tool.py +0 -377
  241. klaude_code-1.2.0/tests/test_mermaid_tool.py +0 -43
  242. klaude_code-1.2.0/tests/test_model.py +0 -310
  243. klaude_code-1.2.0/tests/test_openrouter_reasoning.py +0 -120
  244. klaude_code-1.2.0/tests/test_read_edit_multiedit.py +0 -464
  245. klaude_code-1.2.0/tests/test_subagent_registry.py +0 -23
  246. klaude_code-1.2.0/tests/test_terminal_notifier.py +0 -56
  247. klaude_code-1.2.0/tests/test_web_fetch_tool.py +0 -137
  248. klaude_code-1.2.0/uv.lock +0 -899
  249. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/__init__.py +0 -0
  250. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/cli/__init__.py +0 -0
  251. {klaude_code-1.2.0/src/klaude_code/protocol → klaude_code-1.2.11/src/klaude_code/core}/__init__.py +0 -0
  252. /klaude_code-1.2.0/src/klaude_code/core/prompt/prompt-codex.md → /klaude_code-1.2.11/src/klaude_code/core/prompts/prompt-codex-gpt-5-1.md +0 -0
  253. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-subagent-oracle.md +0 -0
  254. {klaude_code-1.2.0/src/klaude_code/core/prompt → klaude_code-1.2.11/src/klaude_code/core/prompts}/prompt-subagent.md +0 -0
  255. {klaude_code-1.2.0/src/klaude_code/ui/renderers → klaude_code-1.2.11/src/klaude_code/core/tool/file}/__init__.py +0 -0
  256. {klaude_code-1.2.0/tests → klaude_code-1.2.11/src/klaude_code/core/tool/shell}/__init__.py +0 -0
  257. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  258. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  259. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  260. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/llm/responses/__init__.py +0 -0
  261. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/session/__init__.py +0 -0
  262. {klaude_code-1.2.0 → klaude_code-1.2.11}/src/klaude_code/session/selector.py +0 -0
  263. {klaude_code-1.2.0/src/klaude_code/ui/base → klaude_code-1.2.11/src/klaude_code/ui/core}/stage_manager.py +0 -0
  264. {klaude_code-1.2.0/src/klaude_code/ui/rich_ext → klaude_code-1.2.11/src/klaude_code/ui/rich}/live.py +0 -0
  265. {klaude_code-1.2.0/src/klaude_code/ui/rich_ext → klaude_code-1.2.11/src/klaude_code/ui/rich}/quote.py +0 -0
  266. {klaude_code-1.2.0/src/klaude_code/ui/base → klaude_code-1.2.11/src/klaude_code/ui/terminal}/progress_bar.py +0 -0
  267. {klaude_code-1.2.0/src/klaude_code/ui/base → klaude_code-1.2.11/src/klaude_code/ui/utils}/debouncer.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.0
3
+ Version: 1.2.11
4
4
  Summary: Add your description here
5
- Requires-Python: >=3.13
6
5
  Requires-Dist: anthropic>=0.66.0
7
6
  Requires-Dist: openai>=1.102.0
8
7
  Requires-Dist: pillow>=12.0.0
@@ -13,6 +12,7 @@ Requires-Dist: questionary>=2.1.1
13
12
  Requires-Dist: rich>=14.1.0
14
13
  Requires-Dist: trafilatura>=2.0.0
15
14
  Requires-Dist: typer>=0.17.3
15
+ Requires-Python: >=3.13
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  # Minimal Code Agent CLI (Klaude Code)
@@ -27,20 +27,6 @@ An minimal and opinionated code agent with multi-model support.
27
27
  - **Simple TUI**: Clean interface offering full visibility into model responses, reasoning and actions.
28
28
  - **Core Utilities**: Slash commands, sub-agents, image pasting, terminal notifications, file mentioning, and auto-theming.
29
29
 
30
- ### Input Shortcuts
31
-
32
- | Key | Action |
33
- |-----|--------|
34
- | `Enter` | Submit input |
35
- | `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
36
- | `Ctrl+J` | Insert newline |
37
- | `Ctrl+V` | Paste image from clipboard |
38
- | `Left/Right` | Move cursor (wraps across lines) |
39
- | `Backspace` | Delete character or selected text |
40
- | `c` (with selection) | Copy selected text to clipboard |
41
-
42
- Mouse support is automatically enabled when input spans multiple lines.
43
-
44
30
  ## Installation
45
31
 
46
32
  ```bash
@@ -133,6 +119,21 @@ List configured providers and models:
133
119
  klaude list
134
120
  ```
135
121
 
122
+ ### Session Management
123
+
124
+ Clean up sessions with few messages:
125
+
126
+ ```bash
127
+ # Remove sessions with fewer than 5 messages (default)
128
+ klaude session clean
129
+
130
+ # Remove sessions with fewer than 10 messages
131
+ klaude session clean --min 10
132
+
133
+ # Remove all sessions for the current project
134
+ klaude session clean-all
135
+ ```
136
+
136
137
  ### Slash Commands
137
138
 
138
139
  Inside the interactive session (`klaude`), use these commands to streamline your workflow:
@@ -145,6 +146,19 @@ Inside the interactive session (`klaude`), use these commands to streamline your
145
146
  - `/diff` - Show local git diff changes.
146
147
  - `/help` - List all available commands.
147
148
 
149
+
150
+ ### Input Shortcuts
151
+
152
+ | Key | Action |
153
+ | -------------------- | ------------------------------------------- |
154
+ | `Enter` | Submit input |
155
+ | `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
156
+ | `Ctrl+J` | Insert newline |
157
+ | `Ctrl+V` | Paste image from clipboard |
158
+ | `Left/Right` | Move cursor (wraps across lines) |
159
+ | `Backspace` | Delete character or selected text |
160
+ | `c` (with selection) | Copy selected text to clipboard |
161
+
148
162
  ### Non-Interactive Headless Mode (exec)
149
163
 
150
164
  Execute a single command without starting the interactive REPL:
@@ -10,20 +10,6 @@ An minimal and opinionated code agent with multi-model support.
10
10
  - **Simple TUI**: Clean interface offering full visibility into model responses, reasoning and actions.
11
11
  - **Core Utilities**: Slash commands, sub-agents, image pasting, terminal notifications, file mentioning, and auto-theming.
12
12
 
13
- ### Input Shortcuts
14
-
15
- | Key | Action |
16
- |-----|--------|
17
- | `Enter` | Submit input |
18
- | `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
19
- | `Ctrl+J` | Insert newline |
20
- | `Ctrl+V` | Paste image from clipboard |
21
- | `Left/Right` | Move cursor (wraps across lines) |
22
- | `Backspace` | Delete character or selected text |
23
- | `c` (with selection) | Copy selected text to clipboard |
24
-
25
- Mouse support is automatically enabled when input spans multiple lines.
26
-
27
13
  ## Installation
28
14
 
29
15
  ```bash
@@ -116,6 +102,21 @@ List configured providers and models:
116
102
  klaude list
117
103
  ```
118
104
 
105
+ ### Session Management
106
+
107
+ Clean up sessions with few messages:
108
+
109
+ ```bash
110
+ # Remove sessions with fewer than 5 messages (default)
111
+ klaude session clean
112
+
113
+ # Remove sessions with fewer than 10 messages
114
+ klaude session clean --min 10
115
+
116
+ # Remove all sessions for the current project
117
+ klaude session clean-all
118
+ ```
119
+
119
120
  ### Slash Commands
120
121
 
121
122
  Inside the interactive session (`klaude`), use these commands to streamline your workflow:
@@ -128,6 +129,19 @@ Inside the interactive session (`klaude`), use these commands to streamline your
128
129
  - `/diff` - Show local git diff changes.
129
130
  - `/help` - List all available commands.
130
131
 
132
+
133
+ ### Input Shortcuts
134
+
135
+ | Key | Action |
136
+ | -------------------- | ------------------------------------------- |
137
+ | `Enter` | Submit input |
138
+ | `Shift+Enter` | Insert newline (requires `/terminal-setup`) |
139
+ | `Ctrl+J` | Insert newline |
140
+ | `Ctrl+V` | Paste image from clipboard |
141
+ | `Left/Right` | Move cursor (wraps across lines) |
142
+ | `Backspace` | Delete character or selected text |
143
+ | `c` (with selection) | Copy selected text to clipboard |
144
+
131
145
  ### Non-Interactive Headless Mode (exec)
132
146
 
133
147
  Execute a single command without starting the interactive REPL:
@@ -0,0 +1,78 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.8.5,<0.9.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "klaude-code"
7
+ version = "1.2.11"
8
+ description = "Add your description here"
9
+ readme = "README.md"
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+ "anthropic>=0.66.0",
13
+ "openai>=1.102.0",
14
+ "pillow>=12.0.0",
15
+ "prompt-toolkit>=3.0.52",
16
+ "pydantic>=2.11.7",
17
+ "pyyaml>=6.0.2",
18
+ "questionary>=2.1.1",
19
+ "rich>=14.1.0",
20
+ "trafilatura>=2.0.0",
21
+ "typer>=0.17.3",
22
+ ]
23
+
24
+ [project.scripts]
25
+ klaude = "klaude_code.cli.main:app"
26
+
27
+ [tool.uv.build-backend]
28
+ module-name = "klaude_code"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "import-linter>=2.6",
33
+ "isort>=6.0.1",
34
+ "pyright>=1.1.407",
35
+ "pytest>=8.4.1",
36
+ "pytest-cov>=7.0.0",
37
+ ]
38
+
39
+
40
+
41
+ [tool.pytest.ini_options]
42
+ markers = [
43
+ "network: marks tests as requiring network access (deselect with '-m \"not network\"')",
44
+ ]
45
+
46
+ [tool.ruff]
47
+ line-length = 120
48
+
49
+ [tool.pyright]
50
+ typeCheckingMode = "strict"
51
+ pythonVersion = "3.13"
52
+ venvPath = "."
53
+ venv = ".venv"
54
+ extraPaths = ["src"]
55
+ reportMissingImports = "warning"
56
+ reportMissingModuleSource = "warning"
57
+ exclude = [".venv/"]
58
+
59
+ [[tool.pyright.executionEnvironments]]
60
+ root = "."
61
+ extraPaths = ["src"]
62
+
63
+ [tool.importlinter]
64
+ root_packages = ["klaude_code"]
65
+ include_external_packages = false
66
+
67
+ [[tool.importlinter.contracts]]
68
+ name = "Layered architecture"
69
+ type = "layers"
70
+ layers = [
71
+ "klaude_code.cli",
72
+ "klaude_code.core",
73
+ "klaude_code.session",
74
+ "klaude_code.config",
75
+ "klaude_code.llm",
76
+ "klaude_code.protocol",
77
+ "klaude_code.const",
78
+ ]
@@ -0,0 +1,24 @@
1
+ """Authentication module.
2
+
3
+ Currently includes Codex OAuth helpers in ``klaude_code.auth.codex``.
4
+ """
5
+
6
+ from klaude_code.auth.codex import (
7
+ CodexAuthError,
8
+ CodexAuthState,
9
+ CodexNotLoggedInError,
10
+ CodexOAuth,
11
+ CodexOAuthError,
12
+ CodexTokenExpiredError,
13
+ CodexTokenManager,
14
+ )
15
+
16
+ __all__ = [
17
+ "CodexAuthError",
18
+ "CodexAuthState",
19
+ "CodexNotLoggedInError",
20
+ "CodexOAuth",
21
+ "CodexOAuthError",
22
+ "CodexTokenExpiredError",
23
+ "CodexTokenManager",
24
+ ]
@@ -0,0 +1,20 @@
1
+ """Codex authentication helpers."""
2
+
3
+ from klaude_code.auth.codex.exceptions import (
4
+ CodexAuthError,
5
+ CodexNotLoggedInError,
6
+ CodexOAuthError,
7
+ CodexTokenExpiredError,
8
+ )
9
+ from klaude_code.auth.codex.oauth import CodexOAuth
10
+ from klaude_code.auth.codex.token_manager import CodexAuthState, CodexTokenManager
11
+
12
+ __all__ = [
13
+ "CodexAuthError",
14
+ "CodexAuthState",
15
+ "CodexNotLoggedInError",
16
+ "CodexOAuth",
17
+ "CodexOAuthError",
18
+ "CodexTokenExpiredError",
19
+ "CodexTokenManager",
20
+ ]
@@ -0,0 +1,17 @@
1
+ """Exceptions for Codex authentication."""
2
+
3
+
4
+ class CodexAuthError(Exception):
5
+ """Base exception for Codex authentication errors."""
6
+
7
+
8
+ class CodexNotLoggedInError(CodexAuthError):
9
+ """User has not logged in to Codex."""
10
+
11
+
12
+ class CodexTokenExpiredError(CodexAuthError):
13
+ """Token expired and refresh failed."""
14
+
15
+
16
+ class CodexOAuthError(CodexAuthError):
17
+ """OAuth flow failed."""
@@ -0,0 +1,45 @@
1
+ """JWT parsing utilities for Codex authentication."""
2
+
3
+ import base64
4
+ import json
5
+ from typing import Any
6
+
7
+
8
+ def decode_jwt_payload(token: str) -> dict[str, Any]:
9
+ """Decode JWT payload without verification.
10
+
11
+ Args:
12
+ token: JWT token string
13
+
14
+ Returns:
15
+ Decoded payload as a dictionary
16
+ """
17
+ parts = token.split(".")
18
+ if len(parts) != 3:
19
+ raise ValueError("Invalid JWT format")
20
+
21
+ payload = parts[1]
22
+ # Add padding if needed
23
+ padding = 4 - len(payload) % 4
24
+ if padding != 4:
25
+ payload += "=" * padding
26
+
27
+ decoded = base64.urlsafe_b64decode(payload)
28
+ return json.loads(decoded)
29
+
30
+
31
+ def extract_account_id(token: str) -> str:
32
+ """Extract ChatGPT account ID from JWT token.
33
+
34
+ Args:
35
+ token: JWT access token from OpenAI OAuth
36
+
37
+ Returns:
38
+ The chatgpt_account_id from the token claims
39
+ """
40
+ payload = decode_jwt_payload(token)
41
+ auth_claim = payload.get("https://api.openai.com/auth", {})
42
+ account_id = auth_claim.get("chatgpt_account_id")
43
+ if not account_id:
44
+ raise ValueError("chatgpt_account_id not found in token")
45
+ return account_id
@@ -0,0 +1,229 @@
1
+ """OAuth PKCE flow for Codex authentication."""
2
+
3
+ import base64
4
+ import hashlib
5
+ import secrets
6
+ import time
7
+ import webbrowser
8
+ from http.server import BaseHTTPRequestHandler, HTTPServer
9
+ from threading import Thread
10
+ from typing import Any
11
+ from urllib.parse import parse_qs, urlencode, urlparse
12
+
13
+ import httpx
14
+
15
+ from klaude_code.auth.codex.exceptions import CodexOAuthError
16
+ from klaude_code.auth.codex.jwt_utils import extract_account_id
17
+ from klaude_code.auth.codex.token_manager import CodexAuthState, CodexTokenManager
18
+
19
+ # OAuth configuration
20
+ CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
21
+ AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"
22
+ TOKEN_URL = "https://auth.openai.com/oauth/token"
23
+ REDIRECT_URI = "http://localhost:1455/auth/callback"
24
+ REDIRECT_PORT = 1455
25
+ SCOPE = "openid profile email offline_access"
26
+
27
+
28
+ def generate_code_verifier() -> str:
29
+ """Generate a random code verifier for PKCE."""
30
+ return secrets.token_urlsafe(64)[:128]
31
+
32
+
33
+ def generate_code_challenge(verifier: str) -> str:
34
+ """Generate code challenge from verifier using S256 method."""
35
+ digest = hashlib.sha256(verifier.encode()).digest()
36
+ return base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
37
+
38
+
39
+ def build_authorize_url(code_challenge: str, state: str) -> str:
40
+ """Build the authorization URL with all required parameters."""
41
+ params = {
42
+ "response_type": "code",
43
+ "client_id": CLIENT_ID,
44
+ "redirect_uri": REDIRECT_URI,
45
+ "scope": SCOPE,
46
+ "code_challenge": code_challenge,
47
+ "code_challenge_method": "S256",
48
+ "state": state,
49
+ "id_token_add_organizations": "true",
50
+ "codex_cli_simplified_flow": "true",
51
+ "originator": "codex_cli_rs",
52
+ }
53
+ return f"{AUTHORIZE_URL}?{urlencode(params)}"
54
+
55
+
56
+ class OAuthCallbackHandler(BaseHTTPRequestHandler):
57
+ """HTTP request handler for OAuth callback."""
58
+
59
+ code: str | None = None
60
+ state: str | None = None
61
+ error: str | None = None
62
+
63
+ def log_message(self, format: str, *args: Any) -> None:
64
+ """Suppress HTTP server logs."""
65
+ pass
66
+
67
+ def do_GET(self) -> None:
68
+ """Handle GET request from OAuth callback."""
69
+ parsed = urlparse(self.path)
70
+ params = parse_qs(parsed.query)
71
+
72
+ OAuthCallbackHandler.code = params.get("code", [None])[0]
73
+ OAuthCallbackHandler.state = params.get("state", [None])[0]
74
+ OAuthCallbackHandler.error = params.get("error", [None])[0]
75
+
76
+ self.send_response(200)
77
+ self.send_header("Content-Type", "text/html")
78
+ self.end_headers()
79
+
80
+ if OAuthCallbackHandler.error:
81
+ html = """
82
+ <html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
83
+ <h1>Authentication Failed</h1>
84
+ <p>Error: {}</p>
85
+ <p>Please close this window and try again.</p>
86
+ </body></html>
87
+ """.format(OAuthCallbackHandler.error)
88
+ else:
89
+ html = """
90
+ <html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
91
+ <h1>Authentication Successful!</h1>
92
+ <p>You can close this window now.</p>
93
+ <script>setTimeout(function() { window.close(); }, 2000);</script>
94
+ </body></html>
95
+ """
96
+ self.wfile.write(html.encode())
97
+
98
+
99
+ class CodexOAuth:
100
+ """Handle OAuth PKCE flow for Codex authentication."""
101
+
102
+ def __init__(self, token_manager: CodexTokenManager | None = None):
103
+ self.token_manager = token_manager or CodexTokenManager()
104
+
105
+ def login(self) -> CodexAuthState:
106
+ """Run the complete OAuth login flow."""
107
+ # Generate PKCE parameters
108
+ verifier = generate_code_verifier()
109
+ challenge = generate_code_challenge(verifier)
110
+ state = secrets.token_urlsafe(32)
111
+
112
+ # Build authorization URL
113
+ auth_url = build_authorize_url(challenge, state)
114
+
115
+ # Start callback server
116
+ OAuthCallbackHandler.code = None
117
+ OAuthCallbackHandler.state = None
118
+ OAuthCallbackHandler.error = None
119
+
120
+ server = HTTPServer(("localhost", REDIRECT_PORT), OAuthCallbackHandler)
121
+ server_thread = Thread(target=server.handle_request)
122
+ server_thread.start()
123
+
124
+ # Open browser for user to authenticate
125
+ webbrowser.open(auth_url)
126
+
127
+ # Wait for callback
128
+ server_thread.join(timeout=300) # 5 minute timeout
129
+ server.server_close()
130
+
131
+ # Check for errors
132
+ if OAuthCallbackHandler.error:
133
+ raise CodexOAuthError(f"OAuth error: {OAuthCallbackHandler.error}")
134
+
135
+ if not OAuthCallbackHandler.code:
136
+ raise CodexOAuthError("No authorization code received")
137
+
138
+ if OAuthCallbackHandler.state is None or OAuthCallbackHandler.state != state:
139
+ raise CodexOAuthError("OAuth state mismatch")
140
+
141
+ # Exchange code for tokens
142
+ auth_state = self._exchange_code(OAuthCallbackHandler.code, verifier)
143
+
144
+ # Save tokens
145
+ self.token_manager.save(auth_state)
146
+
147
+ return auth_state
148
+
149
+ def _exchange_code(self, code: str, verifier: str) -> CodexAuthState:
150
+ """Exchange authorization code for tokens."""
151
+ data = {
152
+ "grant_type": "authorization_code",
153
+ "client_id": CLIENT_ID,
154
+ "code": code,
155
+ "redirect_uri": REDIRECT_URI,
156
+ "code_verifier": verifier,
157
+ }
158
+
159
+ with httpx.Client() as client:
160
+ response = client.post(TOKEN_URL, data=data)
161
+
162
+ if response.status_code != 200:
163
+ raise CodexOAuthError(f"Token exchange failed: {response.text}")
164
+
165
+ tokens = response.json()
166
+ access_token = tokens["access_token"]
167
+ refresh_token = tokens["refresh_token"]
168
+ expires_in = tokens.get("expires_in", 3600)
169
+
170
+ account_id = extract_account_id(access_token)
171
+
172
+ return CodexAuthState(
173
+ access_token=access_token,
174
+ refresh_token=refresh_token,
175
+ expires_at=int(time.time()) + expires_in,
176
+ account_id=account_id,
177
+ )
178
+
179
+ def refresh(self) -> CodexAuthState:
180
+ """Refresh the access token using refresh token."""
181
+ state = self.token_manager.get_state()
182
+ if state is None:
183
+ from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
184
+
185
+ raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
186
+
187
+ data = {
188
+ "grant_type": "refresh_token",
189
+ "client_id": CLIENT_ID,
190
+ "refresh_token": state.refresh_token,
191
+ }
192
+
193
+ with httpx.Client() as client:
194
+ response = client.post(TOKEN_URL, data=data)
195
+
196
+ if response.status_code != 200:
197
+ from klaude_code.auth.codex.exceptions import CodexTokenExpiredError
198
+
199
+ raise CodexTokenExpiredError(f"Token refresh failed: {response.text}")
200
+
201
+ tokens = response.json()
202
+ access_token = tokens["access_token"]
203
+ refresh_token = tokens.get("refresh_token", state.refresh_token)
204
+ expires_in = tokens.get("expires_in", 3600)
205
+
206
+ account_id = extract_account_id(access_token)
207
+
208
+ new_state = CodexAuthState(
209
+ access_token=access_token,
210
+ refresh_token=refresh_token,
211
+ expires_at=int(time.time()) + expires_in,
212
+ account_id=account_id,
213
+ )
214
+
215
+ self.token_manager.save(new_state)
216
+ return new_state
217
+
218
+ def ensure_valid_token(self) -> str:
219
+ """Ensure we have a valid access token, refreshing if needed."""
220
+ state = self.token_manager.get_state()
221
+ if state is None:
222
+ from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
223
+
224
+ raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
225
+
226
+ if state.is_expired():
227
+ state = self.refresh()
228
+
229
+ return state.access_token
@@ -0,0 +1,84 @@
1
+ """Token storage and management for Codex authentication."""
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class CodexAuthState(BaseModel):
11
+ """Stored authentication state for Codex."""
12
+
13
+ access_token: str
14
+ refresh_token: str
15
+ expires_at: int # Unix timestamp
16
+ account_id: str
17
+
18
+ def is_expired(self, buffer_seconds: int = 300) -> bool:
19
+ """Check if token is expired or will expire soon."""
20
+ return time.time() + buffer_seconds >= self.expires_at
21
+
22
+
23
+ CODEX_AUTH_FILE = Path.home() / ".klaude" / "codex-auth.json"
24
+
25
+
26
+ class CodexTokenManager:
27
+ """Manage Codex OAuth tokens."""
28
+
29
+ def __init__(self, auth_file: Path | None = None):
30
+ self.auth_file = auth_file or CODEX_AUTH_FILE
31
+ self._state: CodexAuthState | None = None
32
+
33
+ def load(self) -> CodexAuthState | None:
34
+ """Load authentication state from file."""
35
+ if not self.auth_file.exists():
36
+ return None
37
+
38
+ try:
39
+ data = json.loads(self.auth_file.read_text())
40
+ self._state = CodexAuthState.model_validate(data)
41
+ return self._state
42
+ except (json.JSONDecodeError, ValueError):
43
+ return None
44
+
45
+ def save(self, state: CodexAuthState) -> None:
46
+ """Save authentication state to file."""
47
+ self.auth_file.parent.mkdir(parents=True, exist_ok=True)
48
+ self.auth_file.write_text(state.model_dump_json(indent=2))
49
+ self._state = state
50
+
51
+ def delete(self) -> None:
52
+ """Delete stored tokens."""
53
+ if self.auth_file.exists():
54
+ self.auth_file.unlink()
55
+ self._state = None
56
+
57
+ def is_logged_in(self) -> bool:
58
+ """Check if user is logged in."""
59
+ state = self._state or self.load()
60
+ return state is not None
61
+
62
+ def get_state(self) -> CodexAuthState | None:
63
+ """Get current authentication state."""
64
+ if self._state is None:
65
+ self._state = self.load()
66
+ return self._state
67
+
68
+ def get_access_token(self) -> str:
69
+ """Get access token, raising if not logged in."""
70
+ state = self.get_state()
71
+ if state is None:
72
+ from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
73
+
74
+ raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
75
+ return state.access_token
76
+
77
+ def get_account_id(self) -> str:
78
+ """Get account ID, raising if not logged in."""
79
+ state = self.get_state()
80
+ if state is None:
81
+ from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
82
+
83
+ raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
84
+ return state.account_id