klaude-code 2.8.1__tar.gz → 2.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {klaude_code-2.8.1 → klaude_code-2.9.0}/PKG-INFO +3 -6
- {klaude_code-2.8.1 → klaude_code-2.9.0}/README.md +1 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/pyproject.toml +2 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/app/runtime.py +2 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/antigravity/oauth.py +0 -9
- klaude_code-2.9.0/src/klaude_code/auth/antigravity/token_manager.py +27 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/base.py +53 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/codex/exceptions.py +0 -4
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/codex/oauth.py +32 -28
- klaude_code-2.9.0/src/klaude_code/auth/codex/token_manager.py +26 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/cost_cmd.py +128 -39
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/list_model.py +27 -10
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/main.py +14 -3
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/assets/builtin_config.yaml +8 -24
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/config.py +47 -25
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/sub_agent_model_helper.py +18 -13
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/thinking.py +0 -8
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/const.py +1 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/agent_profile.py +10 -52
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/compaction/overflow.py +0 -4
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/executor.py +33 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/manager/llm_clients.py +9 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-claude-code.md +4 -4
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/reminders.py +21 -23
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/task.py +0 -4
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/__init__.py +3 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -27
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/read_tool.md +3 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/read_tool.py +15 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/offload.py +0 -35
- klaude_code-2.9.0/src/klaude_code/core/tool/sub_agent/__init__.py +6 -0
- klaude_code-2.9.0/src/klaude_code/core/tool/sub_agent/image_gen.md +16 -0
- klaude_code-2.9.0/src/klaude_code/core/tool/sub_agent/image_gen.py +146 -0
- klaude_code-2.9.0/src/klaude_code/core/tool/sub_agent/task.md +20 -0
- klaude_code-2.9.0/src/klaude_code/core/tool/sub_agent/task.py +205 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/tool_registry.py +0 -16
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/turn.py +1 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/anthropic/input.py +6 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/antigravity/input.py +14 -7
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/codex/client.py +22 -0
- klaude_code-2.9.0/src/klaude_code/llm/codex/prompt_sync.py +237 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/google/client.py +8 -6
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/google/input.py +20 -12
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/image.py +18 -11
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/input_common.py +14 -6
- klaude_code-2.9.0/src/klaude_code/llm/json_stable.py +37 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openai_compatible/input.py +0 -10
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openai_compatible/stream.py +16 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/registry.py +0 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/responses/input.py +15 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/usage.py +0 -8
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/events.py +2 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/message.py +2 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/model.py +20 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/op.py +13 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/op_handler.py +5 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/sub_agent/AGENTS.md +5 -5
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/sub_agent/__init__.py +13 -34
- klaude_code-2.9.0/src/klaude_code/protocol/sub_agent/explore.py +21 -0
- klaude_code-2.9.0/src/klaude_code/protocol/sub_agent/image_gen.py +38 -0
- klaude_code-2.9.0/src/klaude_code/protocol/sub_agent/task.py +17 -0
- klaude_code-2.9.0/src/klaude_code/protocol/sub_agent/web.py +21 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/tools.py +2 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/session.py +58 -21
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/store.py +0 -4
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/assets/deslop/SKILL.md +9 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/system_skills.py +0 -20
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/fork_session_cmd.py +5 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/resume_cmd.py +9 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
- klaude_code-2.9.0/src/klaude_code/tui/components/assistant.py +2 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/command_output.py +3 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/developer.py +3 -0
- klaude_code-2.9.0/src/klaude_code/tui/components/diffs.py +90 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/errors.py +4 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/mermaid_viewer.py +2 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/markdown.py +0 -54
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/theme.py +2 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/sub_agent.py +2 -46
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/thinking.py +0 -33
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/tools.py +43 -21
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/images.py +21 -18
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/key_bindings.py +2 -2
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/prompt_toolkit.py +49 -49
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/machine.py +15 -11
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/renderer.py +11 -20
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/runner.py +2 -1
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/image.py +6 -34
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/common.py +0 -70
- klaude_code-2.8.1/src/klaude_code/auth/antigravity/token_manager.py +0 -45
- klaude_code-2.8.1/src/klaude_code/auth/codex/token_manager.py +0 -44
- klaude_code-2.8.1/src/klaude_code/core/tool/sub_agent_tool.py +0 -126
- klaude_code-2.8.1/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
- klaude_code-2.8.1/src/klaude_code/protocol/sub_agent/explore.py +0 -48
- klaude_code-2.8.1/src/klaude_code/protocol/sub_agent/image_gen.py +0 -109
- klaude_code-2.8.1/src/klaude_code/protocol/sub_agent/task.py +0 -61
- klaude_code-2.8.1/src/klaude_code/protocol/sub_agent/web.py +0 -65
- klaude_code-2.8.1/src/klaude_code/tui/components/assistant.py +0 -28
- klaude_code-2.8.1/src/klaude_code/tui/components/diffs.py +0 -296
- klaude_code-2.8.1/src/klaude_code/tui/components/rich/searchable_text.py +0 -68
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/.DS_Store +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/app/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/AGENTS.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/antigravity/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/antigravity/exceptions.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/antigravity/pkce.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/claude/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/claude/exceptions.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/claude/oauth.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/claude/token_manager.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/auth/env.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/config/model_matcher.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/compaction/AGENTS.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/compaction/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/compaction/compaction.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/compaction/prompts.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/loaded_skills.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-antigravity.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/context.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/antigravity/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/antigravity/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/bedrock/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/claude/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/claude/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/partial_message.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/llm/stream_parts.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/log.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/.DS_Store +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/assets/.DS_Store +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/assets/create-plan/SKILL.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/clear_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/command_abc.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/compact_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/continue_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/copy_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/debug_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/export_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/export_online_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/model_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/model_picker.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/prompt-init.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/prompt_command.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/refresh_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/registry.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/status_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/command/thinking_cmd.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/commands.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/bash_syntax.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/common.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/metadata.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/cjk_wrap.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/code_panel.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/live.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/quote.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/rich/status.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/user_input.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/components/welcome.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/display.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/AGENTS.md +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/completers.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/drag_drop.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/input/paste.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/color.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/control.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/notifier.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/progress_bar.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/tui/terminal/selector.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/debug_mode.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/src/klaude_code/ui/terminal/title.py +0 -0
- {klaude_code-2.8.1 → klaude_code-2.9.0}/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.
|
|
3
|
+
Version: 2.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
|
|
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,
|
|
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,
|
|
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.
|
|
7
|
+
version = "2.9.0"
|
|
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",
|
|
@@ -178,6 +178,7 @@ async def handle_keyboard_interrupt(executor: Executor) -> None:
|
|
|
178
178
|
log("Bye!")
|
|
179
179
|
session_id = executor.context.current_session_id()
|
|
180
180
|
if session_id and Session.exists(session_id):
|
|
181
|
-
|
|
181
|
+
short_id = Session.shortest_unique_prefix(session_id)
|
|
182
|
+
log(("Resume with:", "dim"), (f"klaude -r {short_id}", "green"))
|
|
182
183
|
with contextlib.suppress(Exception):
|
|
183
184
|
await executor.submit(op.InterruptOperation(target_session_id=None))
|
|
@@ -309,12 +309,3 @@ class AntigravityOAuth:
|
|
|
309
309
|
state = self.refresh()
|
|
310
310
|
|
|
311
311
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
193
|
+
with httpx.Client() as client:
|
|
194
|
+
response = client.post(TOKEN_URL, data=data)
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
if response.status_code != 200:
|
|
197
|
+
from klaude_code.auth.codex.exceptions import CodexTokenExpiredError
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
raise CodexTokenExpiredError(f"Token refresh failed: {response.text}")
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
206
|
+
account_id = extract_account_id(access_token)
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -34,6 +34,16 @@ class ModelUsageStats:
|
|
|
34
34
|
def total_tokens(self) -> int:
|
|
35
35
|
return self.input_tokens + self.output_tokens
|
|
36
36
|
|
|
37
|
+
@property
|
|
38
|
+
def non_cached_input_tokens(self) -> int:
|
|
39
|
+
"""Non-cached prompt tokens.
|
|
40
|
+
|
|
41
|
+
We store `input_tokens` as the provider-reported prompt token count, which
|
|
42
|
+
includes cached tokens for providers that support prompt caching.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return max(0, self.input_tokens - self.cached_tokens)
|
|
46
|
+
|
|
37
47
|
def add_usage(self, usage: model.Usage) -> None:
|
|
38
48
|
self.input_tokens += usage.input_tokens
|
|
39
49
|
self.output_tokens += usage.output_tokens
|
|
@@ -48,41 +58,99 @@ class ModelUsageStats:
|
|
|
48
58
|
ModelKey = tuple[str, str] # (model_name, provider)
|
|
49
59
|
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
@dataclass
|
|
62
|
+
class SubProviderGroup:
|
|
63
|
+
"""Group of models under a sub-provider."""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
models: list[ModelUsageStats]
|
|
67
|
+
total: ModelUsageStats
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ProviderGroup:
|
|
72
|
+
"""Group of models/sub-providers under a top-level provider."""
|
|
73
|
+
|
|
74
|
+
name: str
|
|
75
|
+
sub_providers: dict[str, SubProviderGroup] # empty if no sub-providers
|
|
76
|
+
models: list[ModelUsageStats] # direct models (when no sub-provider)
|
|
77
|
+
total: ModelUsageStats
|
|
78
|
+
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
def _sort_by_cost(stats: ModelUsageStats) -> tuple[float, float]:
|
|
81
|
+
return (-stats.cost_usd, -stats.cost_cny)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def group_models_by_provider(models: dict[ModelKey, ModelUsageStats]) -> dict[str, ProviderGroup]:
|
|
85
|
+
"""Group models by provider with three-level hierarchy.
|
|
86
|
+
|
|
87
|
+
Provider strings like "openrouter/Anthropic" are split into:
|
|
88
|
+
- Top-level: "openrouter"
|
|
89
|
+
- Sub-provider: "Anthropic"
|
|
90
|
+
|
|
91
|
+
Returns dict of ProviderGroup sorted by cost desc.
|
|
57
92
|
"""
|
|
58
|
-
|
|
59
|
-
provider_totals: dict[str, ModelUsageStats] = {}
|
|
93
|
+
provider_groups: dict[str, ProviderGroup] = {}
|
|
60
94
|
|
|
61
95
|
for stats in models.values():
|
|
62
|
-
|
|
63
|
-
if provider_key not in models_by_provider:
|
|
64
|
-
models_by_provider[provider_key] = []
|
|
65
|
-
provider_totals[provider_key] = ModelUsageStats(model_name=provider_key, provider=provider_key)
|
|
66
|
-
models_by_provider[provider_key].append(stats)
|
|
67
|
-
provider_totals[provider_key].input_tokens += stats.input_tokens
|
|
68
|
-
provider_totals[provider_key].output_tokens += stats.output_tokens
|
|
69
|
-
provider_totals[provider_key].cached_tokens += stats.cached_tokens
|
|
70
|
-
provider_totals[provider_key].cost_usd += stats.cost_usd
|
|
71
|
-
provider_totals[provider_key].cost_cny += stats.cost_cny
|
|
96
|
+
provider_raw = stats.provider or "(unknown)"
|
|
72
97
|
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
# Split provider by first "/"
|
|
99
|
+
if "/" in provider_raw:
|
|
100
|
+
parts = provider_raw.split("/", 1)
|
|
101
|
+
top_provider, sub_provider = parts[0], parts[1]
|
|
102
|
+
else:
|
|
103
|
+
top_provider, sub_provider = provider_raw, ""
|
|
104
|
+
|
|
105
|
+
# Initialize top-level provider group
|
|
106
|
+
if top_provider not in provider_groups:
|
|
107
|
+
provider_groups[top_provider] = ProviderGroup(
|
|
108
|
+
name=top_provider,
|
|
109
|
+
sub_providers={},
|
|
110
|
+
models=[],
|
|
111
|
+
total=ModelUsageStats(model_name=top_provider),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
group = provider_groups[top_provider]
|
|
115
|
+
|
|
116
|
+
# Accumulate to top-level total
|
|
117
|
+
group.total.input_tokens += stats.input_tokens
|
|
118
|
+
group.total.output_tokens += stats.output_tokens
|
|
119
|
+
group.total.cached_tokens += stats.cached_tokens
|
|
120
|
+
group.total.cost_usd += stats.cost_usd
|
|
121
|
+
group.total.cost_cny += stats.cost_cny
|
|
122
|
+
|
|
123
|
+
if sub_provider:
|
|
124
|
+
# Has sub-provider, add to sub-provider group
|
|
125
|
+
if sub_provider not in group.sub_providers:
|
|
126
|
+
group.sub_providers[sub_provider] = SubProviderGroup(
|
|
127
|
+
name=sub_provider,
|
|
128
|
+
models=[],
|
|
129
|
+
total=ModelUsageStats(model_name=sub_provider),
|
|
130
|
+
)
|
|
131
|
+
sub_group = group.sub_providers[sub_provider]
|
|
132
|
+
sub_group.models.append(stats)
|
|
133
|
+
sub_group.total.input_tokens += stats.input_tokens
|
|
134
|
+
sub_group.total.output_tokens += stats.output_tokens
|
|
135
|
+
sub_group.total.cached_tokens += stats.cached_tokens
|
|
136
|
+
sub_group.total.cost_usd += stats.cost_usd
|
|
137
|
+
sub_group.total.cost_cny += stats.cost_cny
|
|
138
|
+
else:
|
|
139
|
+
# No sub-provider, add directly to models
|
|
140
|
+
group.models.append(stats)
|
|
75
141
|
|
|
76
|
-
# Sort
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
142
|
+
# Sort everything by cost
|
|
143
|
+
for group in provider_groups.values():
|
|
144
|
+
group.models.sort(key=_sort_by_cost)
|
|
145
|
+
for sub_group in group.sub_providers.values():
|
|
146
|
+
sub_group.models.sort(key=_sort_by_cost)
|
|
147
|
+
# Sort sub-providers by cost
|
|
148
|
+
group.sub_providers = dict(sorted(group.sub_providers.items(), key=lambda x: _sort_by_cost(x[1].total)))
|
|
80
149
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
sorted_provider_totals = {p: provider_totals[p] for p in sorted_providers}
|
|
150
|
+
# Sort top-level providers by cost
|
|
151
|
+
sorted_groups = dict(sorted(provider_groups.items(), key=lambda x: _sort_by_cost(x[1].total)))
|
|
84
152
|
|
|
85
|
-
return
|
|
153
|
+
return sorted_groups
|
|
86
154
|
|
|
87
155
|
|
|
88
156
|
@dataclass
|
|
@@ -223,8 +291,8 @@ def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
|
|
|
223
291
|
table.add_column("Date", style="cyan")
|
|
224
292
|
table.add_column("Model", overflow="ellipsis")
|
|
225
293
|
table.add_column("Input", justify="right")
|
|
226
|
-
table.add_column("Output", justify="right")
|
|
227
294
|
table.add_column("Cache", justify="right")
|
|
295
|
+
table.add_column("Output", justify="right")
|
|
228
296
|
table.add_column("Total", justify="right")
|
|
229
297
|
table.add_column("USD", justify="right")
|
|
230
298
|
table.add_column("CNY", justify="right")
|
|
@@ -248,9 +316,9 @@ def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
|
|
|
248
316
|
table.add_row(
|
|
249
317
|
date_label,
|
|
250
318
|
model_col,
|
|
251
|
-
fmt(format_tokens(stats.
|
|
252
|
-
fmt(format_tokens(stats.output_tokens)),
|
|
319
|
+
fmt(format_tokens(stats.non_cached_input_tokens)),
|
|
253
320
|
fmt(format_tokens(stats.cached_tokens)),
|
|
321
|
+
fmt(format_tokens(stats.output_tokens)),
|
|
254
322
|
fmt(format_tokens(stats.total_tokens)),
|
|
255
323
|
fmt(usd_str),
|
|
256
324
|
fmt(cny_str),
|
|
@@ -261,19 +329,40 @@ def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
|
|
|
261
329
|
date_label: str = "",
|
|
262
330
|
show_subtotal: bool = True,
|
|
263
331
|
) -> None:
|
|
264
|
-
"""Render models grouped by provider with tree structure."""
|
|
265
|
-
|
|
332
|
+
"""Render models grouped by provider with three-level tree structure."""
|
|
333
|
+
provider_groups = group_models_by_provider(models)
|
|
266
334
|
|
|
267
335
|
first_row = True
|
|
268
|
-
for
|
|
269
|
-
|
|
270
|
-
add_stats_row(
|
|
336
|
+
for group in provider_groups.values():
|
|
337
|
+
# Top-level provider
|
|
338
|
+
add_stats_row(group.total, date_label=date_label if first_row else "", bold=True)
|
|
271
339
|
first_row = False
|
|
272
340
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
341
|
+
if group.sub_providers:
|
|
342
|
+
# Has sub-providers: render three-level tree
|
|
343
|
+
sub_list = list(group.sub_providers.values())
|
|
344
|
+
for sub_idx, sub_group in enumerate(sub_list):
|
|
345
|
+
is_last_sub = sub_idx == len(sub_list) - 1
|
|
346
|
+
sub_prefix = " └─ " if is_last_sub else " ├─ "
|
|
347
|
+
|
|
348
|
+
# Sub-provider row
|
|
349
|
+
add_stats_row(sub_group.total, prefix=sub_prefix, bold=True)
|
|
350
|
+
|
|
351
|
+
# Models under sub-provider
|
|
352
|
+
for model_idx, stats in enumerate(sub_group.models):
|
|
353
|
+
is_last_model = model_idx == len(sub_group.models) - 1
|
|
354
|
+
# Indent based on whether sub-provider is last
|
|
355
|
+
if is_last_sub:
|
|
356
|
+
model_prefix = " └─ " if is_last_model else " ├─ "
|
|
357
|
+
else:
|
|
358
|
+
model_prefix = " │ └─ " if is_last_model else " │ ├─ "
|
|
359
|
+
add_stats_row(stats, prefix=model_prefix)
|
|
360
|
+
else:
|
|
361
|
+
# No sub-providers: render two-level tree (direct models)
|
|
362
|
+
for model_idx, stats in enumerate(group.models):
|
|
363
|
+
is_last_model = model_idx == len(group.models) - 1
|
|
364
|
+
model_prefix = " └─ " if is_last_model else " ├─ "
|
|
365
|
+
add_stats_row(stats, prefix=model_prefix)
|
|
277
366
|
|
|
278
367
|
if show_subtotal:
|
|
279
368
|
subtotal = ModelUsageStats(model_name="(subtotal)")
|
|
@@ -234,10 +234,15 @@ def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
|
234
234
|
return [Text("")]
|
|
235
235
|
|
|
236
236
|
|
|
237
|
-
def _build_provider_info_panel(provider: ProviderConfig, available: bool) -> Quote:
|
|
237
|
+
def _build_provider_info_panel(provider: ProviderConfig, available: bool, *, disabled: bool) -> Quote:
|
|
238
238
|
"""Build a Quote containing provider name and information using a two-column grid."""
|
|
239
239
|
# Provider name as title
|
|
240
|
-
if
|
|
240
|
+
if disabled:
|
|
241
|
+
title = Text.assemble(
|
|
242
|
+
(provider.provider_name, ThemeKey.CONFIG_PROVIDER),
|
|
243
|
+
(" (Disabled)", "dim"),
|
|
244
|
+
)
|
|
245
|
+
elif available:
|
|
241
246
|
title = Text(provider.provider_name, style=ThemeKey.CONFIG_PROVIDER)
|
|
242
247
|
else:
|
|
243
248
|
title = Text.assemble(
|
|
@@ -297,7 +302,8 @@ def _build_models_table(
|
|
|
297
302
|
config: Config,
|
|
298
303
|
) -> Table:
|
|
299
304
|
"""Build a table for models under a provider."""
|
|
300
|
-
|
|
305
|
+
provider_disabled = provider.disabled
|
|
306
|
+
provider_available = (not provider_disabled) and (not provider.is_api_key_missing())
|
|
301
307
|
|
|
302
308
|
def _resolve_selector(value: str | None) -> str | None:
|
|
303
309
|
if not value:
|
|
@@ -334,7 +340,15 @@ def _build_models_table(
|
|
|
334
340
|
is_last = i == model_count - 1
|
|
335
341
|
prefix = " └─ " if is_last else " ├─ "
|
|
336
342
|
|
|
337
|
-
if
|
|
343
|
+
if provider_disabled:
|
|
344
|
+
name = Text.assemble(
|
|
345
|
+
(prefix, ThemeKey.LINES),
|
|
346
|
+
(model.model_name, "dim strike"),
|
|
347
|
+
(" (provider disabled)", "dim"),
|
|
348
|
+
)
|
|
349
|
+
model_id = Text(model.model_id or "", style="dim")
|
|
350
|
+
params = Text("(disabled)", style="dim")
|
|
351
|
+
elif not provider_available:
|
|
338
352
|
name = Text.assemble((prefix, ThemeKey.LINES), (model.model_name, "dim"))
|
|
339
353
|
model_id = Text(model.model_id or "", style="dim")
|
|
340
354
|
params = Text("(unavailable)", style="dim")
|
|
@@ -408,19 +422,22 @@ def display_models_and_providers(config: Config, *, show_all: bool = False):
|
|
|
408
422
|
_display_agent_models_table(config, console)
|
|
409
423
|
console.print()
|
|
410
424
|
|
|
411
|
-
# Sort providers: available
|
|
412
|
-
sorted_providers = sorted(
|
|
425
|
+
# Sort providers: enabled+available first, disabled/unavailable last
|
|
426
|
+
sorted_providers = sorted(
|
|
427
|
+
config.provider_list,
|
|
428
|
+
key=lambda p: (p.disabled, p.is_api_key_missing(), p.provider_name),
|
|
429
|
+
)
|
|
413
430
|
|
|
414
|
-
# Filter out unavailable providers unless show_all is True
|
|
431
|
+
# Filter out disabled/unavailable providers unless show_all is True
|
|
415
432
|
if not show_all:
|
|
416
|
-
sorted_providers = [p for p in sorted_providers if not p.is_api_key_missing()]
|
|
433
|
+
sorted_providers = [p for p in sorted_providers if (not p.disabled) and (not p.is_api_key_missing())]
|
|
417
434
|
|
|
418
435
|
# Display each provider with its models table
|
|
419
436
|
for provider in sorted_providers:
|
|
420
|
-
provider_available = not provider.is_api_key_missing()
|
|
437
|
+
provider_available = (not provider.disabled) and (not provider.is_api_key_missing())
|
|
421
438
|
|
|
422
439
|
# Provider info panel
|
|
423
|
-
provider_panel = _build_provider_info_panel(provider, provider_available)
|
|
440
|
+
provider_panel = _build_provider_info_panel(provider, provider_available, disabled=provider.disabled)
|
|
424
441
|
console.print(provider_panel)
|
|
425
442
|
console.print()
|
|
426
443
|
|
|
@@ -227,10 +227,21 @@ def main_callback(
|
|
|
227
227
|
log(("Error: --resume <id> cannot be combined with --continue or interactive --resume", "red"))
|
|
228
228
|
raise typer.Exit(2)
|
|
229
229
|
|
|
230
|
+
# Resolve resume_by_id with prefix matching support
|
|
230
231
|
if resume_by_id_value is not None and not Session.exists(resume_by_id_value):
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
matches = Session.find_sessions_by_prefix(resume_by_id_value)
|
|
233
|
+
if not matches:
|
|
234
|
+
log((f"Error: session id '{resume_by_id_value}' not found for this project", "red"))
|
|
235
|
+
log(("Hint: run `klaude --resume` to select an existing session", "yellow"))
|
|
236
|
+
raise typer.Exit(2)
|
|
237
|
+
if len(matches) == 1:
|
|
238
|
+
resume_by_id_value = matches[0]
|
|
239
|
+
else:
|
|
240
|
+
# Multiple matches: show interactive selection with filtered list
|
|
241
|
+
selected = select_session_sync(session_ids=matches)
|
|
242
|
+
if selected is None:
|
|
243
|
+
raise typer.Exit(1)
|
|
244
|
+
resume_by_id_value = selected
|
|
234
245
|
|
|
235
246
|
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
236
247
|
log(("Error: interactive mode requires a TTY", "red"))
|