klaude-code 1.7.0__tar.gz → 1.8.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-1.7.0 → klaude_code-1.8.0}/PKG-INFO +62 -6
- {klaude_code-1.7.0 → klaude_code-1.8.0}/README.md +61 -5
- {klaude_code-1.7.0 → klaude_code-1.8.0}/pyproject.toml +1 -1
- klaude_code-1.8.0/src/klaude_code/cli/cost_cmd.py +338 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/main.py +12 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/runtime.py +2 -2
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/fork_session_cmd.py +7 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/status_cmd.py +8 -8
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/assets/builtin_config.yaml +24 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/config.py +5 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/const.py +17 -2
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/executor.py +16 -3
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/task.py +5 -3
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/command_safety.py +3 -5
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/events.py +1 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/export.py +14 -2
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/session.py +49 -2
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/store.py +3 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/templates/export_session.html +210 -18
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +6 -46
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/renderer.py +5 -1
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/developer.py +1 -1
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/sub_agent.py +1 -1
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/selector.py +25 -2
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/list_model.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/model_select.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/resume_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/thinking.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/bedrock/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/markdown.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/theme.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -146,20 +146,62 @@ klaude config
|
|
|
146
146
|
|
|
147
147
|
##### Adding Models to Built-in Providers
|
|
148
148
|
|
|
149
|
-
You can add custom models to existing providers without redefining the entire provider
|
|
149
|
+
You can add custom models to existing built-in providers without redefining the entire provider. Just reference the `provider_name` and add your `model_list`:
|
|
150
150
|
|
|
151
151
|
```yaml
|
|
152
|
-
#
|
|
152
|
+
# ~/.klaude/klaude-config.yaml
|
|
153
153
|
provider_list:
|
|
154
|
+
- provider_name: openrouter # Reference existing built-in provider
|
|
155
|
+
model_list:
|
|
156
|
+
- model_name: seed
|
|
157
|
+
model_params:
|
|
158
|
+
model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
|
|
159
|
+
context_limit: 262000
|
|
160
|
+
cost:
|
|
161
|
+
input: 0.25
|
|
162
|
+
output: 2
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**How merging works:**
|
|
166
|
+
- Your models are merged with the built-in models for that provider
|
|
167
|
+
- You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
|
|
168
|
+
- To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
|
|
169
|
+
|
|
170
|
+
**More examples:**
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
provider_list:
|
|
174
|
+
# Add multiple models to OpenRouter
|
|
154
175
|
- provider_name: openrouter
|
|
155
176
|
model_list:
|
|
156
|
-
- model_name:
|
|
177
|
+
- model_name: qwen-coder
|
|
178
|
+
model_params:
|
|
179
|
+
model: qwen/qwen-2.5-coder-32b-instruct
|
|
180
|
+
context_limit: 131072
|
|
181
|
+
cost:
|
|
182
|
+
input: 0.3
|
|
183
|
+
output: 0.9
|
|
184
|
+
- model_name: llama-405b
|
|
157
185
|
model_params:
|
|
158
|
-
model:
|
|
186
|
+
model: meta-llama/llama-3.1-405b-instruct
|
|
187
|
+
context_limit: 131072
|
|
188
|
+
cost:
|
|
189
|
+
input: 0.8
|
|
190
|
+
output: 0.8
|
|
191
|
+
|
|
192
|
+
# Add models to Anthropic provider
|
|
193
|
+
- provider_name: anthropic
|
|
194
|
+
model_list:
|
|
195
|
+
- model_name: haiku@ant
|
|
196
|
+
model_params:
|
|
197
|
+
model: claude-3-5-haiku-20241022
|
|
159
198
|
context_limit: 200000
|
|
199
|
+
cost:
|
|
200
|
+
input: 1.0
|
|
201
|
+
output: 5.0
|
|
160
202
|
```
|
|
161
203
|
|
|
162
|
-
|
|
204
|
+
After adding models, run `klaude list` to verify they appear in the model list.
|
|
163
205
|
|
|
164
206
|
##### Overriding Provider Settings
|
|
165
207
|
|
|
@@ -230,6 +272,8 @@ provider_list:
|
|
|
230
272
|
- `openai` - OpenAI-compatible API
|
|
231
273
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
232
274
|
- `openrouter` - OpenRouter API
|
|
275
|
+
- `google` - Google Gemini API
|
|
276
|
+
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
233
277
|
- `codex` - OpenAI Codex CLI (OAuth-based)
|
|
234
278
|
|
|
235
279
|
List configured providers and models:
|
|
@@ -253,6 +297,18 @@ klaude session clean --min 10
|
|
|
253
297
|
klaude session clean-all
|
|
254
298
|
```
|
|
255
299
|
|
|
300
|
+
### Cost Tracking
|
|
301
|
+
|
|
302
|
+
View aggregated usage statistics across all sessions:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# Show all historical usage data
|
|
306
|
+
klaude cost
|
|
307
|
+
|
|
308
|
+
# Show usage for the last 7 days only
|
|
309
|
+
klaude cost --days 7
|
|
310
|
+
```
|
|
311
|
+
|
|
256
312
|
### Slash Commands
|
|
257
313
|
|
|
258
314
|
Inside the interactive session (`klaude`), use these commands to streamline your workflow:
|
|
@@ -125,20 +125,62 @@ klaude config
|
|
|
125
125
|
|
|
126
126
|
##### Adding Models to Built-in Providers
|
|
127
127
|
|
|
128
|
-
You can add custom models to existing providers without redefining the entire provider
|
|
128
|
+
You can add custom models to existing built-in providers without redefining the entire provider. Just reference the `provider_name` and add your `model_list`:
|
|
129
129
|
|
|
130
130
|
```yaml
|
|
131
|
-
#
|
|
131
|
+
# ~/.klaude/klaude-config.yaml
|
|
132
132
|
provider_list:
|
|
133
|
+
- provider_name: openrouter # Reference existing built-in provider
|
|
134
|
+
model_list:
|
|
135
|
+
- model_name: seed
|
|
136
|
+
model_params:
|
|
137
|
+
model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
|
|
138
|
+
context_limit: 262000
|
|
139
|
+
cost:
|
|
140
|
+
input: 0.25
|
|
141
|
+
output: 2
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**How merging works:**
|
|
145
|
+
- Your models are merged with the built-in models for that provider
|
|
146
|
+
- You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
|
|
147
|
+
- To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
|
|
148
|
+
|
|
149
|
+
**More examples:**
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
provider_list:
|
|
153
|
+
# Add multiple models to OpenRouter
|
|
133
154
|
- provider_name: openrouter
|
|
134
155
|
model_list:
|
|
135
|
-
- model_name:
|
|
156
|
+
- model_name: qwen-coder
|
|
157
|
+
model_params:
|
|
158
|
+
model: qwen/qwen-2.5-coder-32b-instruct
|
|
159
|
+
context_limit: 131072
|
|
160
|
+
cost:
|
|
161
|
+
input: 0.3
|
|
162
|
+
output: 0.9
|
|
163
|
+
- model_name: llama-405b
|
|
136
164
|
model_params:
|
|
137
|
-
model:
|
|
165
|
+
model: meta-llama/llama-3.1-405b-instruct
|
|
166
|
+
context_limit: 131072
|
|
167
|
+
cost:
|
|
168
|
+
input: 0.8
|
|
169
|
+
output: 0.8
|
|
170
|
+
|
|
171
|
+
# Add models to Anthropic provider
|
|
172
|
+
- provider_name: anthropic
|
|
173
|
+
model_list:
|
|
174
|
+
- model_name: haiku@ant
|
|
175
|
+
model_params:
|
|
176
|
+
model: claude-3-5-haiku-20241022
|
|
138
177
|
context_limit: 200000
|
|
178
|
+
cost:
|
|
179
|
+
input: 1.0
|
|
180
|
+
output: 5.0
|
|
139
181
|
```
|
|
140
182
|
|
|
141
|
-
|
|
183
|
+
After adding models, run `klaude list` to verify they appear in the model list.
|
|
142
184
|
|
|
143
185
|
##### Overriding Provider Settings
|
|
144
186
|
|
|
@@ -209,6 +251,8 @@ provider_list:
|
|
|
209
251
|
- `openai` - OpenAI-compatible API
|
|
210
252
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
211
253
|
- `openrouter` - OpenRouter API
|
|
254
|
+
- `google` - Google Gemini API
|
|
255
|
+
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
212
256
|
- `codex` - OpenAI Codex CLI (OAuth-based)
|
|
213
257
|
|
|
214
258
|
List configured providers and models:
|
|
@@ -232,6 +276,18 @@ klaude session clean --min 10
|
|
|
232
276
|
klaude session clean-all
|
|
233
277
|
```
|
|
234
278
|
|
|
279
|
+
### Cost Tracking
|
|
280
|
+
|
|
281
|
+
View aggregated usage statistics across all sessions:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Show all historical usage data
|
|
285
|
+
klaude cost
|
|
286
|
+
|
|
287
|
+
# Show usage for the last 7 days only
|
|
288
|
+
klaude cost --days 7
|
|
289
|
+
```
|
|
290
|
+
|
|
235
291
|
### Slash Commands
|
|
236
292
|
|
|
237
293
|
Inside the interactive session (`klaude`), use these commands to streamline your workflow:
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""Cost command for aggregating usage statistics across all sessions."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.box import Box
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from klaude_code.command.status_cmd import format_cost, format_tokens
|
|
14
|
+
from klaude_code.protocol import model
|
|
15
|
+
from klaude_code.session.codec import decode_jsonl_line
|
|
16
|
+
|
|
17
|
+
ASCII_HORIZONAL = Box(" -- \n \n -- \n \n -- \n -- \n \n -- \n")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ModelUsageStats:
|
|
22
|
+
"""Aggregated usage stats for a single model."""
|
|
23
|
+
|
|
24
|
+
model_name: str
|
|
25
|
+
input_tokens: int = 0
|
|
26
|
+
output_tokens: int = 0
|
|
27
|
+
cached_tokens: int = 0
|
|
28
|
+
cost_usd: float = 0.0
|
|
29
|
+
cost_cny: float = 0.0
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def total_tokens(self) -> int:
|
|
33
|
+
return self.input_tokens + self.output_tokens
|
|
34
|
+
|
|
35
|
+
def add_usage(self, usage: model.Usage) -> None:
|
|
36
|
+
self.input_tokens += usage.input_tokens
|
|
37
|
+
self.output_tokens += usage.output_tokens
|
|
38
|
+
self.cached_tokens += usage.cached_tokens
|
|
39
|
+
if usage.total_cost is not None:
|
|
40
|
+
if usage.currency == "CNY":
|
|
41
|
+
self.cost_cny += usage.total_cost
|
|
42
|
+
else:
|
|
43
|
+
self.cost_usd += usage.total_cost
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class DailyStats:
|
|
48
|
+
"""Aggregated stats for a single day."""
|
|
49
|
+
|
|
50
|
+
date: str
|
|
51
|
+
by_model: dict[str, ModelUsageStats] = field(default_factory=lambda: dict[str, ModelUsageStats]())
|
|
52
|
+
|
|
53
|
+
def add_task_metadata(self, meta: model.TaskMetadata, date_str: str) -> None:
|
|
54
|
+
"""Add a TaskMetadata to this day's stats."""
|
|
55
|
+
del date_str # unused, date is already set
|
|
56
|
+
if not meta.usage or not meta.model_name:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
model_key = meta.model_name
|
|
60
|
+
if model_key not in self.by_model:
|
|
61
|
+
self.by_model[model_key] = ModelUsageStats(model_name=model_key)
|
|
62
|
+
|
|
63
|
+
self.by_model[model_key].add_usage(meta.usage)
|
|
64
|
+
|
|
65
|
+
def get_subtotal(self) -> ModelUsageStats:
|
|
66
|
+
"""Get subtotal across all models for this day."""
|
|
67
|
+
subtotal = ModelUsageStats(model_name="(subtotal)")
|
|
68
|
+
for stats in self.by_model.values():
|
|
69
|
+
subtotal.input_tokens += stats.input_tokens
|
|
70
|
+
subtotal.output_tokens += stats.output_tokens
|
|
71
|
+
subtotal.cached_tokens += stats.cached_tokens
|
|
72
|
+
subtotal.cost_usd += stats.cost_usd
|
|
73
|
+
subtotal.cost_cny += stats.cost_cny
|
|
74
|
+
return subtotal
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def iter_all_sessions() -> list[tuple[str, Path]]:
|
|
78
|
+
"""Iterate over all sessions across all projects.
|
|
79
|
+
|
|
80
|
+
Returns list of (session_id, events_file_path) tuples.
|
|
81
|
+
"""
|
|
82
|
+
projects_dir = Path.home() / ".klaude" / "projects"
|
|
83
|
+
if not projects_dir.exists():
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
sessions: list[tuple[str, Path]] = []
|
|
87
|
+
for project_dir in projects_dir.iterdir():
|
|
88
|
+
if not project_dir.is_dir():
|
|
89
|
+
continue
|
|
90
|
+
sessions_dir = project_dir / "sessions"
|
|
91
|
+
if not sessions_dir.exists():
|
|
92
|
+
continue
|
|
93
|
+
for session_dir in sessions_dir.iterdir():
|
|
94
|
+
if not session_dir.is_dir():
|
|
95
|
+
continue
|
|
96
|
+
events_file = session_dir / "events.jsonl"
|
|
97
|
+
meta_file = session_dir / "meta.json"
|
|
98
|
+
# Skip sub-agent sessions by checking meta.json
|
|
99
|
+
if meta_file.exists():
|
|
100
|
+
import json
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
104
|
+
if meta.get("sub_agent_state") is not None:
|
|
105
|
+
continue
|
|
106
|
+
except (json.JSONDecodeError, OSError):
|
|
107
|
+
pass
|
|
108
|
+
if events_file.exists():
|
|
109
|
+
sessions.append((session_dir.name, events_file))
|
|
110
|
+
|
|
111
|
+
return sessions
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def extract_task_metadata_from_events(events_path: Path) -> list[tuple[str, model.TaskMetadataItem]]:
|
|
115
|
+
"""Extract TaskMetadataItem entries from events.jsonl with their dates.
|
|
116
|
+
|
|
117
|
+
Returns list of (date_str, TaskMetadataItem) tuples.
|
|
118
|
+
"""
|
|
119
|
+
results: list[tuple[str, model.TaskMetadataItem]] = []
|
|
120
|
+
try:
|
|
121
|
+
content = events_path.read_text(encoding="utf-8")
|
|
122
|
+
except OSError:
|
|
123
|
+
return results
|
|
124
|
+
|
|
125
|
+
for line in content.splitlines():
|
|
126
|
+
item = decode_jsonl_line(line)
|
|
127
|
+
if isinstance(item, model.TaskMetadataItem):
|
|
128
|
+
date_str = item.created_at.strftime("%Y-%m-%d")
|
|
129
|
+
results.append((date_str, item))
|
|
130
|
+
|
|
131
|
+
return results
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def aggregate_all_sessions() -> dict[str, DailyStats]:
|
|
135
|
+
"""Aggregate usage stats from all sessions, grouped by date.
|
|
136
|
+
|
|
137
|
+
Returns dict mapping date string to DailyStats.
|
|
138
|
+
"""
|
|
139
|
+
daily_stats: dict[str, DailyStats] = defaultdict(lambda: DailyStats(date=""))
|
|
140
|
+
|
|
141
|
+
sessions = iter_all_sessions()
|
|
142
|
+
for _session_id, events_path in sessions:
|
|
143
|
+
metadata_items = extract_task_metadata_from_events(events_path)
|
|
144
|
+
for date_str, metadata_item in metadata_items:
|
|
145
|
+
if daily_stats[date_str].date == "":
|
|
146
|
+
daily_stats[date_str] = DailyStats(date=date_str)
|
|
147
|
+
|
|
148
|
+
# Process main agent metadata
|
|
149
|
+
daily_stats[date_str].add_task_metadata(metadata_item.main_agent, date_str)
|
|
150
|
+
|
|
151
|
+
# Process sub-agent metadata
|
|
152
|
+
for sub_meta in metadata_item.sub_agent_task_metadata:
|
|
153
|
+
daily_stats[date_str].add_task_metadata(sub_meta, date_str)
|
|
154
|
+
|
|
155
|
+
return dict(daily_stats)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def format_cost_dual(cost_usd: float, cost_cny: float) -> tuple[str, str]:
|
|
159
|
+
"""Format costs for both currencies."""
|
|
160
|
+
usd_str = format_cost(cost_usd if cost_usd > 0 else None, "USD")
|
|
161
|
+
cny_str = format_cost(cost_cny if cost_cny > 0 else None, "CNY")
|
|
162
|
+
return usd_str, cny_str
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def format_date_display(date_str: str) -> str:
|
|
166
|
+
"""Format date string YYYY-MM-DD to 'YYYY M-D' for table display."""
|
|
167
|
+
parts = date_str.split("-")
|
|
168
|
+
if len(parts) == 3:
|
|
169
|
+
month = int(parts[1])
|
|
170
|
+
day = int(parts[2])
|
|
171
|
+
return f"{parts[0]} {month}-{day}"
|
|
172
|
+
return date_str
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
|
|
176
|
+
"""Render the cost table using rich."""
|
|
177
|
+
table = Table(
|
|
178
|
+
title="Usage Statistics",
|
|
179
|
+
show_header=True,
|
|
180
|
+
header_style="bold",
|
|
181
|
+
padding=(0, 1, 0, 2),
|
|
182
|
+
box=ASCII_HORIZONAL,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
table.add_column("Date", style="cyan", no_wrap=True)
|
|
186
|
+
table.add_column("Model", no_wrap=True)
|
|
187
|
+
table.add_column("Input", justify="right", no_wrap=True)
|
|
188
|
+
table.add_column("Output", justify="right", no_wrap=True)
|
|
189
|
+
table.add_column("Cache", justify="right", no_wrap=True)
|
|
190
|
+
table.add_column("Total", justify="right", no_wrap=True)
|
|
191
|
+
table.add_column("USD", justify="right", no_wrap=True)
|
|
192
|
+
table.add_column("CNY", justify="right", no_wrap=True)
|
|
193
|
+
|
|
194
|
+
# Sort dates
|
|
195
|
+
sorted_dates = sorted(daily_stats.keys())
|
|
196
|
+
|
|
197
|
+
# Track global totals by model
|
|
198
|
+
global_by_model: dict[str, ModelUsageStats] = {}
|
|
199
|
+
|
|
200
|
+
def sort_by_cost(stats: ModelUsageStats) -> tuple[float, float]:
|
|
201
|
+
"""Sort key: USD desc, then CNY desc."""
|
|
202
|
+
return (-stats.cost_usd, -stats.cost_cny)
|
|
203
|
+
|
|
204
|
+
for date_str in sorted_dates:
|
|
205
|
+
day = daily_stats[date_str]
|
|
206
|
+
sorted_models = [s.model_name for s in sorted(day.by_model.values(), key=sort_by_cost)]
|
|
207
|
+
|
|
208
|
+
first_row = True
|
|
209
|
+
for model_name in sorted_models:
|
|
210
|
+
stats = day.by_model[model_name]
|
|
211
|
+
usd_str, cny_str = format_cost_dual(stats.cost_usd, stats.cost_cny)
|
|
212
|
+
|
|
213
|
+
# Accumulate to global totals
|
|
214
|
+
if model_name not in global_by_model:
|
|
215
|
+
global_by_model[model_name] = ModelUsageStats(model_name=model_name)
|
|
216
|
+
global_by_model[model_name].input_tokens += stats.input_tokens
|
|
217
|
+
global_by_model[model_name].output_tokens += stats.output_tokens
|
|
218
|
+
global_by_model[model_name].cached_tokens += stats.cached_tokens
|
|
219
|
+
global_by_model[model_name].cost_usd += stats.cost_usd
|
|
220
|
+
global_by_model[model_name].cost_cny += stats.cost_cny
|
|
221
|
+
|
|
222
|
+
table.add_row(
|
|
223
|
+
format_date_display(date_str) if first_row else "",
|
|
224
|
+
f"- {model_name}",
|
|
225
|
+
format_tokens(stats.input_tokens),
|
|
226
|
+
format_tokens(stats.output_tokens),
|
|
227
|
+
format_tokens(stats.cached_tokens),
|
|
228
|
+
format_tokens(stats.total_tokens),
|
|
229
|
+
usd_str,
|
|
230
|
+
cny_str,
|
|
231
|
+
)
|
|
232
|
+
first_row = False
|
|
233
|
+
|
|
234
|
+
# Add subtotal row for this day
|
|
235
|
+
subtotal = day.get_subtotal()
|
|
236
|
+
usd_str, cny_str = format_cost_dual(subtotal.cost_usd, subtotal.cost_cny)
|
|
237
|
+
table.add_row(
|
|
238
|
+
"",
|
|
239
|
+
"[cyan] (subtotal)[/cyan]",
|
|
240
|
+
f"[cyan]{format_tokens(subtotal.input_tokens)}[/cyan]",
|
|
241
|
+
f"[cyan]{format_tokens(subtotal.output_tokens)}[/cyan]",
|
|
242
|
+
f"[cyan]{format_tokens(subtotal.cached_tokens)}[/cyan]",
|
|
243
|
+
f"[cyan]{format_tokens(subtotal.total_tokens)}[/cyan]",
|
|
244
|
+
f"[cyan]{usd_str}[/cyan]",
|
|
245
|
+
f"[cyan]{cny_str}[/cyan]",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Add separator between days
|
|
249
|
+
if date_str != sorted_dates[-1]:
|
|
250
|
+
table.add_section()
|
|
251
|
+
|
|
252
|
+
# Add final section for totals
|
|
253
|
+
table.add_section()
|
|
254
|
+
|
|
255
|
+
# Build date range label for Total
|
|
256
|
+
if sorted_dates:
|
|
257
|
+
first_date = format_date_display(sorted_dates[0])
|
|
258
|
+
last_date = format_date_display(sorted_dates[-1])
|
|
259
|
+
if first_date == last_date:
|
|
260
|
+
total_label = f"[bold]Total[/bold]\n[dim]{first_date}[/dim]"
|
|
261
|
+
else:
|
|
262
|
+
total_label = f"[bold]Total[/bold]\n[dim]{first_date} ~[/dim]\n[dim]{last_date}[/dim]"
|
|
263
|
+
else:
|
|
264
|
+
total_label = "[bold]Total[/bold]"
|
|
265
|
+
|
|
266
|
+
# Add per-model totals
|
|
267
|
+
sorted_global_models = [s.model_name for s in sorted(global_by_model.values(), key=sort_by_cost)]
|
|
268
|
+
first_total_row = True
|
|
269
|
+
for model_name in sorted_global_models:
|
|
270
|
+
stats = global_by_model[model_name]
|
|
271
|
+
usd_str, cny_str = format_cost_dual(stats.cost_usd, stats.cost_cny)
|
|
272
|
+
table.add_row(
|
|
273
|
+
total_label if first_total_row else "",
|
|
274
|
+
f"- {model_name}",
|
|
275
|
+
format_tokens(stats.input_tokens),
|
|
276
|
+
format_tokens(stats.output_tokens),
|
|
277
|
+
format_tokens(stats.cached_tokens),
|
|
278
|
+
format_tokens(stats.total_tokens),
|
|
279
|
+
usd_str,
|
|
280
|
+
cny_str,
|
|
281
|
+
)
|
|
282
|
+
first_total_row = False
|
|
283
|
+
|
|
284
|
+
# Add grand total row
|
|
285
|
+
grand_total = ModelUsageStats(model_name="(total)")
|
|
286
|
+
for stats in global_by_model.values():
|
|
287
|
+
grand_total.input_tokens += stats.input_tokens
|
|
288
|
+
grand_total.output_tokens += stats.output_tokens
|
|
289
|
+
grand_total.cached_tokens += stats.cached_tokens
|
|
290
|
+
grand_total.cost_usd += stats.cost_usd
|
|
291
|
+
grand_total.cost_cny += stats.cost_cny
|
|
292
|
+
|
|
293
|
+
usd_str, cny_str = format_cost_dual(grand_total.cost_usd, grand_total.cost_cny)
|
|
294
|
+
table.add_row(
|
|
295
|
+
"",
|
|
296
|
+
"[bold] (total)[/bold]",
|
|
297
|
+
f"[bold]{format_tokens(grand_total.input_tokens)}[/bold]",
|
|
298
|
+
f"[bold]{format_tokens(grand_total.output_tokens)}[/bold]",
|
|
299
|
+
f"[bold]{format_tokens(grand_total.cached_tokens)}[/bold]",
|
|
300
|
+
f"[bold]{format_tokens(grand_total.total_tokens)}[/bold]",
|
|
301
|
+
f"[bold]{usd_str}[/bold]",
|
|
302
|
+
f"[bold]{cny_str}[/bold]",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return table
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def cost_command(
|
|
309
|
+
days: int | None = typer.Option(None, "--days", "-d", help="Limit to last N days"),
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Display aggregated usage statistics across all sessions."""
|
|
312
|
+
daily_stats = aggregate_all_sessions()
|
|
313
|
+
|
|
314
|
+
if not daily_stats:
|
|
315
|
+
typer.echo("No usage data found.")
|
|
316
|
+
raise typer.Exit(0)
|
|
317
|
+
|
|
318
|
+
# Filter by days if specified
|
|
319
|
+
if days is not None:
|
|
320
|
+
cutoff = datetime.now().strftime("%Y-%m-%d")
|
|
321
|
+
from datetime import timedelta
|
|
322
|
+
|
|
323
|
+
cutoff_date = datetime.now() - timedelta(days=days)
|
|
324
|
+
cutoff = cutoff_date.strftime("%Y-%m-%d")
|
|
325
|
+
daily_stats = {k: v for k, v in daily_stats.items() if k >= cutoff}
|
|
326
|
+
|
|
327
|
+
if not daily_stats:
|
|
328
|
+
typer.echo(f"No usage data found in the last {days} days.")
|
|
329
|
+
raise typer.Exit(0)
|
|
330
|
+
|
|
331
|
+
table = render_cost_table(daily_stats)
|
|
332
|
+
console = Console()
|
|
333
|
+
console.print(table)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def register_cost_commands(app: typer.Typer) -> None:
|
|
337
|
+
"""Register cost command to the given Typer app."""
|
|
338
|
+
app.command("cost")(cost_command)
|
|
@@ -8,6 +8,7 @@ import typer
|
|
|
8
8
|
|
|
9
9
|
from klaude_code.cli.auth_cmd import register_auth_commands
|
|
10
10
|
from klaude_code.cli.config_cmd import register_config_commands
|
|
11
|
+
from klaude_code.cli.cost_cmd import register_cost_commands
|
|
11
12
|
from klaude_code.cli.debug import DEBUG_FILTER_HELP, open_log_file_in_editor, resolve_debug_settings
|
|
12
13
|
from klaude_code.cli.self_update import register_self_update_commands, version_option_callback
|
|
13
14
|
from klaude_code.cli.session_cmd import register_session_commands
|
|
@@ -95,16 +96,27 @@ def read_input_content(cli_argument: str) -> str | None:
|
|
|
95
96
|
return content
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
ENV_HELP = """\
|
|
100
|
+
Environment Variables:
|
|
101
|
+
|
|
102
|
+
KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)
|
|
103
|
+
|
|
104
|
+
KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)
|
|
105
|
+
"""
|
|
106
|
+
|
|
98
107
|
app = typer.Typer(
|
|
99
108
|
add_completion=False,
|
|
100
109
|
pretty_exceptions_enable=False,
|
|
101
110
|
no_args_is_help=False,
|
|
111
|
+
rich_markup_mode="rich",
|
|
112
|
+
epilog=ENV_HELP,
|
|
102
113
|
)
|
|
103
114
|
|
|
104
115
|
# Register subcommands from modules
|
|
105
116
|
register_session_commands(app)
|
|
106
117
|
register_auth_commands(app)
|
|
107
118
|
register_config_commands(app)
|
|
119
|
+
register_cost_commands(app)
|
|
108
120
|
|
|
109
121
|
register_self_update_commands(app)
|
|
110
122
|
|
|
@@ -379,7 +379,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
379
379
|
model_name=model_name,
|
|
380
380
|
save_as_default=False,
|
|
381
381
|
defer_thinking_selection=True,
|
|
382
|
-
emit_welcome_event=
|
|
382
|
+
emit_welcome_event=True,
|
|
383
383
|
emit_switch_message=False,
|
|
384
384
|
)
|
|
385
385
|
)
|
|
@@ -398,7 +398,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
398
398
|
op.ChangeThinkingOperation(
|
|
399
399
|
session_id=sid,
|
|
400
400
|
thinking=thinking,
|
|
401
|
-
emit_welcome_event=
|
|
401
|
+
emit_welcome_event=True,
|
|
402
402
|
emit_switch_message=False,
|
|
403
403
|
)
|
|
404
404
|
)
|
|
@@ -7,6 +7,7 @@ from prompt_toolkit.styles import Style
|
|
|
7
7
|
|
|
8
8
|
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
9
|
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.ui.modes.repl.clipboard import copy_to_clipboard
|
|
10
11
|
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
12
|
|
|
12
13
|
FORK_SELECT_STYLE = Style(
|
|
@@ -215,6 +216,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
215
216
|
new_session = agent.session.fork()
|
|
216
217
|
await new_session.wait_for_flush()
|
|
217
218
|
|
|
219
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
220
|
+
copy_to_clipboard(resume_cmd)
|
|
221
|
+
|
|
218
222
|
event = events.DeveloperMessageEvent(
|
|
219
223
|
session_id=agent.session.id,
|
|
220
224
|
item=model.DeveloperMessageItem(
|
|
@@ -247,6 +251,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
247
251
|
# Build result message
|
|
248
252
|
fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
|
|
249
253
|
|
|
254
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
255
|
+
copy_to_clipboard(resume_cmd)
|
|
256
|
+
|
|
250
257
|
event = events.DeveloperMessageEvent(
|
|
251
258
|
session_id=agent.session.id,
|
|
252
259
|
item=model.DeveloperMessageItem(
|
|
@@ -64,7 +64,7 @@ def accumulate_session_usage(session: Session) -> AggregatedUsage:
|
|
|
64
64
|
return AggregatedUsage(total=total, by_model=by_model, task_count=task_count)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def
|
|
67
|
+
def format_tokens(tokens: int) -> str:
|
|
68
68
|
"""Format token count with K/M suffix for readability."""
|
|
69
69
|
if tokens >= 1_000_000:
|
|
70
70
|
return f"{tokens / 1_000_000:.2f}M"
|
|
@@ -73,7 +73,7 @@ def _format_tokens(tokens: int) -> str:
|
|
|
73
73
|
return str(tokens)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def
|
|
76
|
+
def format_cost(cost: float | None, currency: str = "USD") -> str:
|
|
77
77
|
"""Format cost with currency symbol."""
|
|
78
78
|
if cost is None:
|
|
79
79
|
return "-"
|
|
@@ -93,13 +93,13 @@ def _format_model_usage_line(meta: model.TaskMetadata) -> str:
|
|
|
93
93
|
if not usage:
|
|
94
94
|
return f" {model_label}: no usage data"
|
|
95
95
|
|
|
96
|
-
cost_str =
|
|
96
|
+
cost_str = format_cost(usage.total_cost, usage.currency)
|
|
97
97
|
return (
|
|
98
98
|
f" {model_label}: "
|
|
99
|
-
f"{
|
|
100
|
-
f"{
|
|
101
|
-
f"{
|
|
102
|
-
f"{
|
|
99
|
+
f"{format_tokens(usage.input_tokens)} input, "
|
|
100
|
+
f"{format_tokens(usage.output_tokens)} output, "
|
|
101
|
+
f"{format_tokens(usage.cached_tokens)} cache read, "
|
|
102
|
+
f"{format_tokens(usage.reasoning_tokens)} thinking, "
|
|
103
103
|
f"({cost_str})"
|
|
104
104
|
)
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ def format_status_content(aggregated: AggregatedUsage) -> str:
|
|
|
109
109
|
lines: list[str] = []
|
|
110
110
|
|
|
111
111
|
# Total cost line
|
|
112
|
-
total_cost_str =
|
|
112
|
+
total_cost_str = format_cost(aggregated.total.total_cost, aggregated.total.currency)
|
|
113
113
|
lines.append(f"Total cost: {total_cost_str}")
|
|
114
114
|
|
|
115
115
|
# Per-model breakdown
|