klaude-code 2.8.0__tar.gz → 2.10.1__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 (269) hide show
  1. {klaude_code-2.8.0 → klaude_code-2.10.1}/PKG-INFO +3 -6
  2. {klaude_code-2.8.0 → klaude_code-2.10.1}/README.md +1 -5
  3. {klaude_code-2.8.0 → klaude_code-2.10.1}/pyproject.toml +2 -1
  4. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/app/runtime.py +7 -2
  5. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/antigravity/oauth.py +33 -38
  6. klaude_code-2.10.1/src/klaude_code/auth/antigravity/token_manager.py +27 -0
  7. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/base.py +53 -0
  8. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/claude/oauth.py +34 -49
  9. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/codex/exceptions.py +0 -4
  10. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/codex/oauth.py +32 -28
  11. klaude_code-2.10.1/src/klaude_code/auth/codex/token_manager.py +26 -0
  12. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/cost_cmd.py +128 -39
  13. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/list_model.py +28 -12
  14. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/main.py +25 -4
  15. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/assets/builtin_config.yaml +39 -37
  16. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/config.py +47 -25
  17. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/sub_agent_model_helper.py +18 -13
  18. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/thinking.py +0 -8
  19. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/const.py +6 -5
  20. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/agent_profile.py +35 -57
  21. klaude_code-2.10.1/src/klaude_code/core/bash_mode.py +276 -0
  22. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/compaction/compaction.py +4 -6
  23. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/compaction/overflow.py +0 -4
  24. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/executor.py +91 -12
  25. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/manager/llm_clients.py +10 -1
  26. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/manager/llm_clients_builder.py +2 -2
  27. klaude_code-2.10.1/src/klaude_code/core/memory.py +140 -0
  28. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-claude-code.md +4 -4
  29. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +2 -2
  30. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/reminders.py +38 -112
  31. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/task.py +1 -5
  32. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/__init__.py +3 -2
  33. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/apply_patch.py +0 -27
  34. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/edit_tool.py +1 -2
  35. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/read_tool.md +3 -2
  36. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/read_tool.py +27 -3
  37. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/offload.py +4 -39
  38. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/shell/bash_tool.py +1 -1
  39. klaude_code-2.10.1/src/klaude_code/core/tool/sub_agent/__init__.py +6 -0
  40. klaude_code-2.10.1/src/klaude_code/core/tool/sub_agent/image_gen.md +16 -0
  41. klaude_code-2.10.1/src/klaude_code/core/tool/sub_agent/image_gen.py +146 -0
  42. klaude_code-2.10.1/src/klaude_code/core/tool/sub_agent/task.md +20 -0
  43. klaude_code-2.10.1/src/klaude_code/core/tool/sub_agent/task.py +205 -0
  44. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/tool_registry.py +0 -16
  45. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/web_fetch_tool.md +2 -1
  46. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/web_fetch_tool.py +1 -1
  47. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/turn.py +10 -5
  48. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/anthropic/input.py +6 -5
  49. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/antigravity/input.py +14 -7
  50. klaude_code-2.10.1/src/klaude_code/llm/bedrock_anthropic/__init__.py +3 -0
  51. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/google/client.py +8 -6
  52. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/google/input.py +20 -12
  53. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/image.py +18 -11
  54. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/input_common.py +32 -6
  55. klaude_code-2.10.1/src/klaude_code/llm/json_stable.py +37 -0
  56. {klaude_code-2.8.0/src/klaude_code/llm/codex → klaude_code-2.10.1/src/klaude_code/llm/openai_codex}/__init__.py +1 -1
  57. {klaude_code-2.8.0/src/klaude_code/llm/codex → klaude_code-2.10.1/src/klaude_code/llm/openai_codex}/client.py +24 -2
  58. klaude_code-2.10.1/src/klaude_code/llm/openai_codex/prompt_sync.py +237 -0
  59. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openai_compatible/client.py +3 -1
  60. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openai_compatible/input.py +0 -10
  61. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openai_compatible/stream.py +35 -10
  62. {klaude_code-2.8.0/src/klaude_code/llm/responses → klaude_code-2.10.1/src/klaude_code/llm/openai_responses}/client.py +1 -1
  63. {klaude_code-2.8.0/src/klaude_code/llm/responses → klaude_code-2.10.1/src/klaude_code/llm/openai_responses}/input.py +15 -5
  64. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/registry.py +3 -8
  65. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/stream_parts.py +3 -1
  66. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/usage.py +1 -9
  67. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/commands.py +1 -0
  68. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/events.py +19 -2
  69. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/message.py +3 -2
  70. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/model.py +34 -2
  71. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/op.py +39 -0
  72. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/op_handler.py +15 -0
  73. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/sub_agent/AGENTS.md +5 -5
  74. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/sub_agent/__init__.py +13 -34
  75. klaude_code-2.10.1/src/klaude_code/protocol/sub_agent/explore.py +21 -0
  76. klaude_code-2.10.1/src/klaude_code/protocol/sub_agent/image_gen.py +38 -0
  77. klaude_code-2.10.1/src/klaude_code/protocol/sub_agent/task.py +17 -0
  78. klaude_code-2.10.1/src/klaude_code/protocol/sub_agent/web.py +21 -0
  79. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/tools.py +2 -0
  80. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/export.py +308 -299
  81. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/session.py +80 -22
  82. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/store.py +0 -4
  83. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/templates/export_session.html +430 -134
  84. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/templates/mermaid_viewer.html +85 -0
  85. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/assets/deslop/SKILL.md +9 -0
  86. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/system_skills.py +0 -20
  87. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/__init__.py +3 -0
  88. klaude_code-2.10.1/src/klaude_code/tui/command/continue_cmd.py +34 -0
  89. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/fork_session_cmd.py +5 -2
  90. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/resume_cmd.py +10 -3
  91. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
  92. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/commands.py +15 -0
  93. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/bash_syntax.py +4 -0
  94. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/command_output.py +7 -6
  95. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/developer.py +4 -3
  96. klaude_code-2.10.1/src/klaude_code/tui/components/diffs.py +91 -0
  97. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/errors.py +4 -0
  98. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/mermaid_viewer.py +2 -2
  99. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/metadata.py +28 -28
  100. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/code_panel.py +31 -16
  101. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/markdown.py +104 -88
  102. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/status.py +2 -2
  103. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/theme.py +33 -18
  104. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/sub_agent.py +2 -46
  105. klaude_code-2.10.1/src/klaude_code/tui/components/thinking.py +28 -0
  106. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/tools.py +69 -23
  107. klaude_code-2.10.1/src/klaude_code/tui/components/user_input.py +99 -0
  108. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/welcome.py +47 -2
  109. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/display.py +14 -6
  110. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/completers.py +8 -0
  111. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/images.py +21 -18
  112. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/key_bindings.py +39 -3
  113. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/prompt_toolkit.py +95 -69
  114. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/machine.py +136 -74
  115. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/renderer.py +164 -50
  116. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/runner.py +24 -1
  117. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/image.py +27 -34
  118. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/notifier.py +11 -12
  119. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/selector.py +1 -1
  120. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/common.py +0 -70
  121. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/terminal/title.py +4 -2
  122. klaude_code-2.8.0/src/klaude_code/auth/antigravity/token_manager.py +0 -45
  123. klaude_code-2.8.0/src/klaude_code/auth/codex/token_manager.py +0 -44
  124. klaude_code-2.8.0/src/klaude_code/core/tool/sub_agent_tool.py +0 -126
  125. klaude_code-2.8.0/src/klaude_code/llm/bedrock/__init__.py +0 -3
  126. klaude_code-2.8.0/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
  127. klaude_code-2.8.0/src/klaude_code/protocol/sub_agent/explore.py +0 -48
  128. klaude_code-2.8.0/src/klaude_code/protocol/sub_agent/image_gen.py +0 -109
  129. klaude_code-2.8.0/src/klaude_code/protocol/sub_agent/task.py +0 -61
  130. klaude_code-2.8.0/src/klaude_code/protocol/sub_agent/web.py +0 -65
  131. klaude_code-2.8.0/src/klaude_code/tui/components/assistant.py +0 -28
  132. klaude_code-2.8.0/src/klaude_code/tui/components/diffs.py +0 -296
  133. klaude_code-2.8.0/src/klaude_code/tui/components/rich/searchable_text.py +0 -68
  134. klaude_code-2.8.0/src/klaude_code/tui/components/thinking.py +0 -96
  135. klaude_code-2.8.0/src/klaude_code/tui/components/user_input.py +0 -107
  136. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/.DS_Store +0 -0
  137. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/__init__.py +0 -0
  138. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/app/__init__.py +0 -0
  139. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/AGENTS.md +0 -0
  140. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/__init__.py +0 -0
  141. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/antigravity/__init__.py +0 -0
  142. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/antigravity/exceptions.py +0 -0
  143. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/antigravity/pkce.py +0 -0
  144. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/claude/__init__.py +0 -0
  145. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/claude/exceptions.py +0 -0
  146. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/claude/token_manager.py +0 -0
  147. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/codex/__init__.py +0 -0
  148. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  149. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/auth/env.py +0 -0
  150. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/__init__.py +0 -0
  151. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/auth_cmd.py +0 -0
  152. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/config_cmd.py +0 -0
  153. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/debug.py +0 -0
  154. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/cli/self_update.py +0 -0
  155. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/__init__.py +0 -0
  156. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/assets/__init__.py +0 -0
  157. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/builtin_config.py +0 -0
  158. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/config/model_matcher.py +0 -0
  159. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/__init__.py +0 -0
  160. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/agent.py +0 -0
  161. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/compaction/AGENTS.md +0 -0
  162. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/compaction/__init__.py +0 -0
  163. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/compaction/prompts.py +0 -0
  164. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/loaded_skills.py +0 -0
  165. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/manager/__init__.py +0 -0
  166. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  167. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-antigravity.md +0 -0
  168. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  169. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2.md +0 -0
  170. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  171. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  172. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  173. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  174. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +0 -0
  175. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  176. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/context.py +0 -0
  177. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/__init__.py +0 -0
  178. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/_utils.py +0 -0
  179. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  180. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  181. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  182. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  183. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  184. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  185. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  186. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  187. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  188. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  189. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  190. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  191. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  192. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  193. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  194. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  195. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/tool_abc.py +0 -0
  196. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/tool_runner.py +0 -0
  197. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/__init__.py +0 -0
  198. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  199. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  200. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  201. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  202. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/__init__.py +0 -0
  203. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  204. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/anthropic/client.py +0 -0
  205. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/antigravity/__init__.py +0 -0
  206. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/antigravity/client.py +0 -0
  207. {klaude_code-2.8.0/src/klaude_code/llm/bedrock → klaude_code-2.10.1/src/klaude_code/llm/bedrock_anthropic}/client.py +0 -0
  208. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/claude/__init__.py +0 -0
  209. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/claude/client.py +0 -0
  210. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/client.py +0 -0
  211. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/google/__init__.py +0 -0
  212. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  213. {klaude_code-2.8.0/src/klaude_code/llm/responses → klaude_code-2.10.1/src/klaude_code/llm/openai_responses}/__init__.py +0 -0
  214. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  215. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openrouter/client.py +0 -0
  216. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openrouter/input.py +0 -0
  217. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  218. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/llm/partial_message.py +0 -0
  219. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/log.py +0 -0
  220. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/__init__.py +0 -0
  221. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/protocol/llm_param.py +0 -0
  222. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/__init__.py +0 -0
  223. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/codec.py +0 -0
  224. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/session/selector.py +0 -0
  225. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/.DS_Store +0 -0
  226. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/__init__.py +0 -0
  227. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/assets/.DS_Store +0 -0
  228. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/assets/create-plan/SKILL.md +0 -0
  229. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  230. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  231. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/loader.py +0 -0
  232. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/skill/manager.py +0 -0
  233. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/__init__.py +0 -0
  234. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/clear_cmd.py +0 -0
  235. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/command_abc.py +0 -0
  236. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/compact_cmd.py +0 -0
  237. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/copy_cmd.py +0 -0
  238. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/debug_cmd.py +0 -0
  239. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/export_cmd.py +0 -0
  240. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/export_online_cmd.py +0 -0
  241. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/model_cmd.py +0 -0
  242. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/model_picker.py +0 -0
  243. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/prompt-init.md +0 -0
  244. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/prompt_command.py +0 -0
  245. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/refresh_cmd.py +0 -0
  246. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/registry.py +0 -0
  247. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/status_cmd.py +0 -0
  248. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/command/thinking_cmd.py +0 -0
  249. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/__init__.py +0 -0
  250. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/common.py +0 -0
  251. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/__init__.py +0 -0
  252. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/cjk_wrap.py +0 -0
  253. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/live.py +0 -0
  254. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/components/rich/quote.py +0 -0
  255. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/AGENTS.md +0 -0
  256. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/__init__.py +0 -0
  257. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/drag_drop.py +0 -0
  258. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/input/paste.py +0 -0
  259. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/__init__.py +0 -0
  260. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/color.py +0 -0
  261. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/control.py +0 -0
  262. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/tui/terminal/progress_bar.py +0 -0
  263. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/__init__.py +0 -0
  264. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/core/__init__.py +0 -0
  265. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/core/display.py +0 -0
  266. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/core/input.py +0 -0
  267. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/debug_mode.py +0 -0
  268. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/ui/terminal/__init__.py +0 -0
  269. {klaude_code-2.8.0 → klaude_code-2.10.1}/src/klaude_code/update.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 2.8.0
3
+ Version: 2.10.1
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
7
7
  Requires-Dist: ddgs>=9.9.3
8
8
  Requires-Dist: diff-match-patch>=20241021
9
+ Requires-Dist: filelock>=3.20.3
9
10
  Requires-Dist: google-genai>=1.56.0
10
11
  Requires-Dist: markdown-it-py>=4.0.0
11
12
  Requires-Dist: openai>=1.102.0
@@ -23,7 +24,7 @@ Description-Content-Type: text/markdown
23
24
  Minimal code agent CLI.
24
25
 
25
26
  ## Features
26
- - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, Claude Max OAuth and ChatGPT Codex OAuth etc.
27
+ - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, ChatGPT Codex OAuth etc.
27
28
  - **Keep reasoning item in context**: Interleaved thinking support
28
29
  - **Model-aware tools**: Claude Code tool set for Opus, `apply_patch` for GPT-5/Codex
29
30
  - **Reminders**: Cooldown-based todo tracking, instruction reinforcement and external file change reminder
@@ -107,7 +108,6 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
107
108
  | Provider | Env Variable | Models |
108
109
  |-------------|-----------------------|-------------------------------------------------------------------------------|
109
110
  | anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
110
- | claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
111
111
  | openai | `OPENAI_API_KEY` | gpt-5.2 |
112
112
  | openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
113
113
  | deepseek | `DEEPSEEK_API_KEY` | deepseek |
@@ -139,7 +139,6 @@ klaude auth login deepseek # Set DEEPSEEK_API_KEY
139
139
  klaude auth login moonshot # Set MOONSHOT_API_KEY
140
140
 
141
141
  # OAuth login for subscription-based providers
142
- klaude auth login claude # Claude Pro/Max subscription
143
142
  klaude auth login codex # ChatGPT Pro subscription
144
143
  ```
145
144
 
@@ -148,7 +147,6 @@ API keys are stored in `~/.klaude/klaude-auth.json` and used as fallback when en
148
147
  To logout from OAuth providers:
149
148
 
150
149
  ```bash
151
- klaude auth logout claude
152
150
  klaude auth logout codex
153
151
  ```
154
152
 
@@ -201,7 +199,6 @@ provider_list:
201
199
  ##### Supported Protocols
202
200
 
203
201
  - `anthropic` - Anthropic Messages API
204
- - `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
205
202
  - `openai` - OpenAI Chat Completion API
206
203
  - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
207
204
  - `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
@@ -3,7 +3,7 @@
3
3
  Minimal code agent CLI.
4
4
 
5
5
  ## Features
6
- - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, Claude Max OAuth and ChatGPT Codex OAuth etc.
6
+ - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, ChatGPT Codex OAuth etc.
7
7
  - **Keep reasoning item in context**: Interleaved thinking support
8
8
  - **Model-aware tools**: Claude Code tool set for Opus, `apply_patch` for GPT-5/Codex
9
9
  - **Reminders**: Cooldown-based todo tracking, instruction reinforcement and external file change reminder
@@ -87,7 +87,6 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
87
87
  | Provider | Env Variable | Models |
88
88
  |-------------|-----------------------|-------------------------------------------------------------------------------|
89
89
  | anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
90
- | claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
91
90
  | openai | `OPENAI_API_KEY` | gpt-5.2 |
92
91
  | openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
93
92
  | deepseek | `DEEPSEEK_API_KEY` | deepseek |
@@ -119,7 +118,6 @@ klaude auth login deepseek # Set DEEPSEEK_API_KEY
119
118
  klaude auth login moonshot # Set MOONSHOT_API_KEY
120
119
 
121
120
  # OAuth login for subscription-based providers
122
- klaude auth login claude # Claude Pro/Max subscription
123
121
  klaude auth login codex # ChatGPT Pro subscription
124
122
  ```
125
123
 
@@ -128,7 +126,6 @@ API keys are stored in `~/.klaude/klaude-auth.json` and used as fallback when en
128
126
  To logout from OAuth providers:
129
127
 
130
128
  ```bash
131
- klaude auth logout claude
132
129
  klaude auth logout codex
133
130
  ```
134
131
 
@@ -181,7 +178,6 @@ provider_list:
181
178
  ##### Supported Protocols
182
179
 
183
180
  - `anthropic` - Anthropic Messages API
184
- - `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
185
181
  - `openai` - OpenAI Chat Completion API
186
182
  - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
187
183
  - `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "2.8.0"
7
+ version = "2.10.1"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "chardet>=5.2.0",
14
14
  "ddgs>=9.9.3",
15
15
  "diff-match-patch>=20241021",
16
+ "filelock>=3.20.3",
16
17
  "google-genai>=1.56.0",
17
18
  "markdown-it-py>=4.0.0",
18
19
  "openai>=1.102.0",
@@ -12,6 +12,7 @@ from klaude_code.core.agent import Agent
12
12
  from klaude_code.core.agent_profile import (
13
13
  DefaultModelProfileProvider,
14
14
  VanillaModelProfileProvider,
15
+ WebModelProfileProvider,
15
16
  )
16
17
  from klaude_code.core.executor import Executor
17
18
  from klaude_code.core.manager import build_llm_clients
@@ -27,6 +28,7 @@ class AppInitConfig:
27
28
  model: str | None
28
29
  debug: bool
29
30
  vanilla: bool
31
+ web: bool = False
30
32
  debug_filters: set[DebugType] | None = None
31
33
 
32
34
 
@@ -74,6 +76,8 @@ async def initialize_app_components(
74
76
 
75
77
  if init_config.vanilla:
76
78
  model_profile_provider = VanillaModelProfileProvider()
79
+ elif init_config.web:
80
+ model_profile_provider = WebModelProfileProvider(config=config)
77
81
  else:
78
82
  model_profile_provider = DefaultModelProfileProvider(config=config)
79
83
 
@@ -87,7 +91,7 @@ async def initialize_app_components(
87
91
  )
88
92
 
89
93
  if on_model_change is not None:
90
- on_model_change(llm_clients.main.model_name)
94
+ on_model_change(llm_clients.main_model_alias)
91
95
 
92
96
  executor_task = asyncio.create_task(executor.start())
93
97
 
@@ -178,6 +182,7 @@ async def handle_keyboard_interrupt(executor: Executor) -> None:
178
182
  log("Bye!")
179
183
  session_id = executor.context.current_session_id()
180
184
  if session_id and Session.exists(session_id):
181
- log(("Resume with:", "dim"), (f"klaude --resume {session_id}", "green"))
185
+ short_id = Session.shortest_unique_prefix(session_id)
186
+ log(("Resume with:", "dim"), (f"klaude -r {short_id}", "green"))
182
187
  with contextlib.suppress(Exception):
183
188
  await executor.submit(op.InterruptOperation(target_session_id=None))
@@ -258,42 +258,46 @@ class AntigravityOAuth:
258
258
  )
259
259
 
260
260
  def refresh(self) -> AntigravityAuthState:
261
- """Refresh the access token using refresh token."""
262
- state = self.token_manager.get_state()
263
- if state is None:
264
- raise AntigravityNotLoggedInError("Not logged in to Antigravity. Run 'klaude login antigravity' first.")
261
+ """Refresh the access token using refresh token with file locking.
265
262
 
266
- data = {
267
- "client_id": CLIENT_ID,
268
- "client_secret": CLIENT_SECRET,
269
- "refresh_token": state.refresh_token,
270
- "grant_type": "refresh_token",
271
- }
263
+ Uses file locking to prevent multiple instances from refreshing simultaneously.
264
+ If another instance has already refreshed, returns the updated state.
265
+ """
272
266
 
273
- with httpx.Client() as client:
274
- response = client.post(TOKEN_URL, data=data, timeout=30)
267
+ def do_refresh(current_state: AntigravityAuthState) -> AntigravityAuthState:
268
+ data = {
269
+ "client_id": CLIENT_ID,
270
+ "client_secret": CLIENT_SECRET,
271
+ "refresh_token": current_state.refresh_token,
272
+ "grant_type": "refresh_token",
273
+ }
275
274
 
276
- if response.status_code != 200:
277
- raise AntigravityTokenExpiredError(f"Token refresh failed: {response.text}")
275
+ with httpx.Client() as client:
276
+ response = client.post(TOKEN_URL, data=data, timeout=30)
278
277
 
279
- tokens = response.json()
280
- access_token = tokens["access_token"]
281
- refresh_token = tokens.get("refresh_token", state.refresh_token)
282
- expires_in = tokens.get("expires_in", 3600)
278
+ if response.status_code != 200:
279
+ raise AntigravityTokenExpiredError(f"Token refresh failed: {response.text}")
283
280
 
284
- # Calculate expiry time with 5 minute buffer
285
- expires_at = int(time.time()) + expires_in - 300
281
+ tokens = response.json()
282
+ access_token = tokens["access_token"]
283
+ refresh_token = tokens.get("refresh_token", current_state.refresh_token)
284
+ expires_in = tokens.get("expires_in", 3600)
286
285
 
287
- new_state = AntigravityAuthState(
288
- access_token=access_token,
289
- refresh_token=refresh_token,
290
- expires_at=expires_at,
291
- project_id=state.project_id,
292
- email=state.email,
293
- )
286
+ # Calculate expiry time with 5 minute buffer
287
+ expires_at = int(time.time()) + expires_in - 300
294
288
 
295
- self.token_manager.save(new_state)
296
- return new_state
289
+ return AntigravityAuthState(
290
+ access_token=access_token,
291
+ refresh_token=refresh_token,
292
+ expires_at=expires_at,
293
+ project_id=current_state.project_id,
294
+ email=current_state.email,
295
+ )
296
+
297
+ try:
298
+ return self.token_manager.refresh_with_lock(do_refresh)
299
+ except ValueError as e:
300
+ raise AntigravityNotLoggedInError(str(e)) from e
297
301
 
298
302
  def ensure_valid_token(self) -> tuple[str, str]:
299
303
  """Ensure we have a valid access token, refreshing if needed.
@@ -309,12 +313,3 @@ class AntigravityOAuth:
309
313
  state = self.refresh()
310
314
 
311
315
  return state.access_token, state.project_id
312
-
313
- def get_api_key_json(self) -> str:
314
- """Get API key as JSON string for LLM client.
315
-
316
- Returns:
317
- JSON string with token and projectId.
318
- """
319
- access_token, project_id = self.ensure_valid_token()
320
- return json.dumps({"token": access_token, "projectId": project_id})
@@ -0,0 +1,27 @@
1
+ """Token storage and management for Antigravity 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 AntigravityAuthState(BaseAuthState):
10
+ """Stored authentication state for Antigravity."""
11
+
12
+ project_id: str
13
+ email: str | None = None
14
+
15
+
16
+ class AntigravityTokenManager(BaseTokenManager[AntigravityAuthState]):
17
+ """Manage Antigravity OAuth tokens."""
18
+
19
+ def __init__(self, auth_file: Path | None = None):
20
+ super().__init__(auth_file)
21
+
22
+ @property
23
+ def storage_key(self) -> str:
24
+ return "antigravity"
25
+
26
+ def _create_state(self, data: dict[str, Any]) -> AntigravityAuthState:
27
+ return AntigravityAuthState.model_validate(data)
@@ -3,12 +3,15 @@
3
3
  import json
4
4
  import time
5
5
  from abc import ABC, abstractmethod
6
+ from collections.abc import Callable
6
7
  from pathlib import Path
7
8
  from typing import Any, cast
8
9
 
10
+ from filelock import FileLock, Timeout
9
11
  from pydantic import BaseModel
10
12
 
11
13
  KLAUDE_AUTH_FILE = Path.home() / ".klaude" / "klaude-auth.json"
14
+ LOCK_TIMEOUT_SECONDS = 30 # Maximum time to wait for lock acquisition
12
15
 
13
16
 
14
17
  class BaseAuthState(BaseModel):
@@ -99,3 +102,53 @@ class BaseTokenManager[T: BaseAuthState](ABC):
99
102
  def clear_cached_state(self) -> None:
100
103
  """Clear in-memory cached state to force reload from file on next access."""
101
104
  self._state = None
105
+
106
+ def _get_lock_file(self) -> Path:
107
+ """Get the lock file path for this auth file."""
108
+ return self.auth_file.with_suffix(".lock")
109
+
110
+ def refresh_with_lock(self, refresh_fn: Callable[[T], T]) -> T:
111
+ """Refresh token with file locking to prevent concurrent refresh.
112
+
113
+ This prevents multiple instances from simultaneously refreshing the same token.
114
+ If another instance has already refreshed, returns the updated state.
115
+
116
+ Args:
117
+ refresh_fn: Function that takes current state and returns new state.
118
+
119
+ Returns:
120
+ The new or already-refreshed authentication state.
121
+
122
+ Raises:
123
+ Timeout: If unable to acquire the lock within timeout.
124
+ ValueError: If not logged in.
125
+ """
126
+ lock_file = self._get_lock_file()
127
+ lock = FileLock(lock_file, timeout=LOCK_TIMEOUT_SECONDS)
128
+
129
+ try:
130
+ with lock:
131
+ # Re-read file after acquiring lock - another instance may have refreshed
132
+ self.clear_cached_state()
133
+ state = self.load()
134
+
135
+ if state is None:
136
+ raise ValueError(f"Not logged in to {self.storage_key}")
137
+
138
+ # Check if token is still expired after re-reading
139
+ if not state.is_expired():
140
+ # Another instance already refreshed, use their result
141
+ return state
142
+
143
+ # Token still expired, we need to refresh
144
+ new_state = refresh_fn(state)
145
+ self.save(new_state)
146
+ return new_state
147
+
148
+ except Timeout:
149
+ # Lock timeout - try to re-read file in case another instance succeeded
150
+ self.clear_cached_state()
151
+ state = self.load()
152
+ if state and not state.is_expired():
153
+ return state
154
+ raise
@@ -125,60 +125,45 @@ class ClaudeOAuth:
125
125
  expires_at=int(time.time()) + int(expires_in),
126
126
  )
127
127
 
128
- def _do_refresh_request(self, refresh_token: str) -> httpx.Response:
129
- """Send token refresh request to OAuth server."""
130
- payload = {
131
- "grant_type": "refresh_token",
132
- "client_id": CLIENT_ID,
133
- "refresh_token": refresh_token,
134
- }
135
- with httpx.Client() as client:
136
- return client.post(
137
- TOKEN_URL,
138
- json=payload,
139
- headers={"Content-Type": "application/json"},
140
- )
141
-
142
128
  def refresh(self) -> ClaudeAuthState:
143
- """Refresh the access token using refresh token.
129
+ """Refresh the access token using refresh token with file locking.
144
130
 
145
- Handles concurrent refresh race conditions by retrying with freshly loaded token
146
- if the first attempt fails with invalid_grant error.
131
+ Uses file locking to prevent multiple instances from refreshing simultaneously.
132
+ If another instance has already refreshed, returns the updated state.
147
133
  """
148
- state = self.token_manager.get_state()
149
- if state is None:
150
- raise ClaudeNotLoggedInError("Not logged in to Claude. Run 'klaude login claude' first.")
151
134
 
152
- response = self._do_refresh_request(state.refresh_token)
153
-
154
- # Handle race condition: another process may have refreshed the token already
155
- if response.status_code != 200 and "invalid_grant" in response.text:
156
- # Reload token from file (another process may have updated it)
157
- self.token_manager.clear_cached_state()
158
- fresh_state = self.token_manager.load()
159
- if fresh_state and fresh_state.refresh_token != state.refresh_token:
160
- # Token was updated by another process
161
- if not fresh_state.is_expired():
162
- # New token is still valid, use it directly
163
- return fresh_state
164
- # New token expired, try refreshing with the new refresh_token
165
- response = self._do_refresh_request(fresh_state.refresh_token)
166
-
167
- if response.status_code != 200:
168
- raise ClaudeAuthError(f"Token refresh failed: {response.text}")
169
-
170
- tokens = response.json()
171
- access_token = tokens["access_token"]
172
- refresh_token = tokens.get("refresh_token", state.refresh_token)
173
- expires_in = tokens.get("expires_in", 3600)
135
+ def do_refresh(current_state: ClaudeAuthState) -> ClaudeAuthState:
136
+ payload = {
137
+ "grant_type": "refresh_token",
138
+ "client_id": CLIENT_ID,
139
+ "refresh_token": current_state.refresh_token,
140
+ }
141
+
142
+ with httpx.Client() as client:
143
+ response = client.post(
144
+ TOKEN_URL,
145
+ json=payload,
146
+ headers={"Content-Type": "application/json"},
147
+ )
148
+
149
+ if response.status_code != 200:
150
+ raise ClaudeAuthError(f"Token refresh failed: {response.text}")
151
+
152
+ tokens = response.json()
153
+ access_token = tokens["access_token"]
154
+ refresh_token = tokens.get("refresh_token", current_state.refresh_token)
155
+ expires_in = tokens.get("expires_in", 3600)
156
+
157
+ return ClaudeAuthState(
158
+ access_token=access_token,
159
+ refresh_token=refresh_token,
160
+ expires_at=int(time.time()) + int(expires_in),
161
+ )
174
162
 
175
- new_state = ClaudeAuthState(
176
- access_token=access_token,
177
- refresh_token=refresh_token,
178
- expires_at=int(time.time()) + int(expires_in),
179
- )
180
- self.token_manager.save(new_state)
181
- return new_state
163
+ try:
164
+ return self.token_manager.refresh_with_lock(do_refresh)
165
+ except ValueError as e:
166
+ raise ClaudeNotLoggedInError(str(e)) from e
182
167
 
183
168
  def ensure_valid_token(self) -> str:
184
169
  """Ensure we have a valid access token, refreshing if needed."""
@@ -15,7 +15,3 @@ class CodexTokenExpiredError(CodexAuthError):
15
15
 
16
16
  class CodexOAuthError(CodexAuthError):
17
17
  """OAuth flow failed."""
18
-
19
-
20
- class CodexUnsupportedModelError(CodexAuthError):
21
- """Model is not supported by codex_oauth protocol."""
@@ -177,43 +177,47 @@ class CodexOAuth:
177
177
  )
178
178
 
179
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
180
+ """Refresh the access token using refresh token with file locking.
184
181
 
185
- raise CodexNotLoggedInError("Not logged in to Codex. Run 'klaude login codex' first.")
182
+ Uses file locking to prevent multiple instances from refreshing simultaneously.
183
+ If another instance has already refreshed, returns the updated state.
184
+ """
186
185
 
187
- data = {
188
- "grant_type": "refresh_token",
189
- "client_id": CLIENT_ID,
190
- "refresh_token": state.refresh_token,
191
- }
186
+ def do_refresh(current_state: CodexAuthState) -> CodexAuthState:
187
+ data = {
188
+ "grant_type": "refresh_token",
189
+ "client_id": CLIENT_ID,
190
+ "refresh_token": current_state.refresh_token,
191
+ }
192
192
 
193
- with httpx.Client() as client:
194
- response = client.post(TOKEN_URL, data=data)
193
+ with httpx.Client() as client:
194
+ response = client.post(TOKEN_URL, data=data)
195
195
 
196
- if response.status_code != 200:
197
- from klaude_code.auth.codex.exceptions import CodexTokenExpiredError
196
+ if response.status_code != 200:
197
+ from klaude_code.auth.codex.exceptions import CodexTokenExpiredError
198
198
 
199
- raise CodexTokenExpiredError(f"Token refresh failed: {response.text}")
199
+ raise CodexTokenExpiredError(f"Token refresh failed: {response.text}")
200
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)
201
+ tokens = response.json()
202
+ access_token = tokens["access_token"]
203
+ refresh_token = tokens.get("refresh_token", current_state.refresh_token)
204
+ expires_in = tokens.get("expires_in", 3600)
205
205
 
206
- account_id = extract_account_id(access_token)
206
+ account_id = extract_account_id(access_token)
207
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
- )
208
+ return 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
+ try:
216
+ return self.token_manager.refresh_with_lock(do_refresh)
217
+ except ValueError as e:
218
+ from klaude_code.auth.codex.exceptions import CodexNotLoggedInError
214
219
 
215
- self.token_manager.save(new_state)
216
- return new_state
220
+ raise CodexNotLoggedInError(str(e)) from e
217
221
 
218
222
  def ensure_valid_token(self) -> str:
219
223
  """Ensure we have a valid access token, refreshing if needed."""
@@ -0,0 +1,26 @@
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)