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