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.
Files changed (218) hide show
  1. {klaude_code-1.7.0 → klaude_code-1.8.0}/PKG-INFO +62 -6
  2. {klaude_code-1.7.0 → klaude_code-1.8.0}/README.md +61 -5
  3. {klaude_code-1.7.0 → klaude_code-1.8.0}/pyproject.toml +1 -1
  4. klaude_code-1.8.0/src/klaude_code/cli/cost_cmd.py +338 -0
  5. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/main.py +12 -0
  6. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/runtime.py +2 -2
  7. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/fork_session_cmd.py +7 -0
  8. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/status_cmd.py +8 -8
  9. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/assets/builtin_config.yaml +24 -0
  10. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/config.py +5 -0
  11. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/const.py +17 -2
  12. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/executor.py +16 -3
  13. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/task.py +5 -3
  14. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/command_safety.py +3 -5
  15. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/events.py +1 -0
  16. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/export.py +14 -2
  17. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/session.py +49 -2
  18. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/store.py +3 -0
  19. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/templates/export_session.html +210 -18
  20. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +6 -46
  21. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/renderer.py +5 -1
  22. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/developer.py +1 -1
  23. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/sub_agent.py +1 -1
  24. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/selector.py +25 -2
  25. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/__init__.py +0 -0
  26. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/__init__.py +0 -0
  27. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/__init__.py +0 -0
  28. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
  29. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  30. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/oauth.py +0 -0
  31. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
  32. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/__init__.py +0 -0
  33. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/auth_cmd.py +0 -0
  34. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/config_cmd.py +0 -0
  35. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/debug.py +0 -0
  36. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/list_model.py +0 -0
  37. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/self_update.py +0 -0
  38. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/cli/session_cmd.py +0 -0
  39. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/__init__.py +0 -0
  40. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/clear_cmd.py +0 -0
  41. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/command_abc.py +0 -0
  42. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/debug_cmd.py +0 -0
  43. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/export_cmd.py +0 -0
  44. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/export_online_cmd.py +0 -0
  45. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/help_cmd.py +0 -0
  46. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/model_cmd.py +0 -0
  47. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/model_select.py +0 -0
  48. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt-init.md +0 -0
  49. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt-jj-describe.md +0 -0
  50. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/prompt_command.py +0 -0
  51. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/refresh_cmd.py +0 -0
  52. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/registry.py +0 -0
  53. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
  54. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/resume_cmd.py +0 -0
  55. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  56. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/command/thinking_cmd.py +0 -0
  57. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/__init__.py +0 -0
  58. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/assets/__init__.py +0 -0
  59. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/builtin_config.py +0 -0
  60. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/select_model.py +0 -0
  61. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/config/thinking.py +0 -0
  62. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/__init__.py +0 -0
  63. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/agent.py +0 -0
  64. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/__init__.py +0 -0
  65. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
  66. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  67. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  68. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompt.py +0 -0
  69. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  70. {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
  71. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  72. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  73. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  74. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  75. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  76. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
  77. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  78. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  79. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/reminders.py +0 -0
  80. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/__init__.py +0 -0
  81. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
  82. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
  83. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  84. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  85. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  86. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  87. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  88. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  89. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
  90. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
  91. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  92. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  93. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  94. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  95. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  96. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  97. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  98. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  99. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
  100. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  101. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  102. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  103. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  104. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  105. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  106. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  107. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  108. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  109. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
  110. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_context.py +0 -0
  111. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
  112. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
  113. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/truncation.py +0 -0
  114. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
  115. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  116. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  117. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  118. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  119. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  120. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  121. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/core/turn.py +0 -0
  122. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/__init__.py +0 -0
  123. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  124. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/client.py +0 -0
  125. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/anthropic/input.py +0 -0
  126. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
  127. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/bedrock/client.py +0 -0
  128. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/client.py +0 -0
  129. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/codex/__init__.py +0 -0
  130. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/codex/client.py +0 -0
  131. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/__init__.py +0 -0
  132. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/client.py +0 -0
  133. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/google/input.py +0 -0
  134. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/input_common.py +0 -0
  135. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  136. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  137. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  138. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  139. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  140. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  141. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/client.py +0 -0
  142. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/input.py +0 -0
  143. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  144. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/registry.py +0 -0
  145. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/__init__.py +0 -0
  146. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/client.py +0 -0
  147. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/responses/input.py +0 -0
  148. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/llm/usage.py +0 -0
  149. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/__init__.py +0 -0
  150. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/commands.py +0 -0
  151. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/llm_param.py +0 -0
  152. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/model.py +0 -0
  153. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/op.py +0 -0
  154. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/op_handler.py +0 -0
  155. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  156. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  157. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
  158. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  159. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  160. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/protocol/tools.py +0 -0
  161. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/__init__.py +0 -0
  162. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/codec.py +0 -0
  163. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/selector.py +0 -0
  164. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  165. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/__init__.py +0 -0
  166. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  167. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  168. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  169. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  170. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  171. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/loader.py +0 -0
  172. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/manager.py +0 -0
  173. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/skill/system_skills.py +0 -0
  174. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/trace/__init__.py +0 -0
  175. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/trace/log.py +0 -0
  176. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/__init__.py +0 -0
  177. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/__init__.py +0 -0
  178. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/display.py +0 -0
  179. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/input.py +0 -0
  180. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
  181. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/__init__.py +0 -0
  182. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  183. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
  184. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  185. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
  186. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  187. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  188. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  189. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
  190. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
  191. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  192. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
  193. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
  194. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
  195. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/common.py +0 -0
  196. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
  197. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/errors.py +0 -0
  198. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
  199. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/metadata.py +0 -0
  200. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
  201. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/tools.py +0 -0
  202. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
  203. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/__init__.py +0 -0
  204. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  205. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
  206. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/live.py +0 -0
  207. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/markdown.py +0 -0
  208. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/quote.py +0 -0
  209. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  210. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/status.py +0 -0
  211. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/rich/theme.py +0 -0
  212. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
  213. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/color.py +0 -0
  214. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/control.py +0 -0
  215. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
  216. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  217. {klaude_code-1.7.0 → klaude_code-1.8.0}/src/klaude_code/ui/utils/__init__.py +0 -0
  218. {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.7.0
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
- # Just specify provider_name and your new models - no need for protocol/api_key
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: my-custom-model
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: some-provider/some-model-id
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
- Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
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
- # Just specify provider_name and your new models - no need for protocol/api_key
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: my-custom-model
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: some-provider/some-model-id
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
- Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
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:
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.7.0"
7
+ version = "1.8.0"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -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=False,
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=False,
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 _format_tokens(tokens: int) -> str:
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 _format_cost(cost: float | None, currency: str = "USD") -> str:
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 = _format_cost(usage.total_cost, usage.currency)
96
+ cost_str = format_cost(usage.total_cost, usage.currency)
97
97
  return (
98
98
  f" {model_label}: "
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, "
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 = _format_cost(aggregated.total.total_cost, aggregated.total.currency)
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