klaude-code 1.2.3__tar.gz → 1.2.4__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 (161) hide show
  1. {klaude_code-1.2.3 → klaude_code-1.2.4}/PKG-INFO +1 -1
  2. {klaude_code-1.2.3 → klaude_code-1.2.4}/pyproject.toml +1 -1
  3. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/__init__.py +2 -0
  4. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/export_cmd.py +3 -1
  5. klaude_code-1.2.4/src/klaude_code/command/status_cmd.py +111 -0
  6. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/commands.py +1 -0
  7. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/model.py +7 -0
  8. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/session/templates/export_session.html +207 -1
  9. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/developer.py +56 -1
  10. {klaude_code-1.2.3 → klaude_code-1.2.4}/README.md +0 -0
  11. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/__init__.py +0 -0
  12. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/cli/__init__.py +0 -0
  13. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/cli/main.py +0 -0
  14. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/cli/runtime.py +0 -0
  15. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/cli/session_cmd.py +0 -0
  16. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/clear_cmd.py +0 -0
  17. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/command_abc.py +0 -0
  18. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/diff_cmd.py +0 -0
  19. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/help_cmd.py +0 -0
  20. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/model_cmd.py +0 -0
  21. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/prompt-dev-docs-update.md +0 -0
  22. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/prompt-dev-docs.md +0 -0
  23. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/prompt-init.md +0 -0
  24. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/prompt_command.py +0 -0
  25. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/refresh_cmd.py +0 -0
  26. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/registry.py +0 -0
  27. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  28. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/config/__init__.py +0 -0
  29. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/config/config.py +0 -0
  30. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/config/list_model.py +0 -0
  31. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/config/select_model.py +0 -0
  32. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/const/__init__.py +0 -0
  33. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/__init__.py +0 -0
  34. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/agent.py +0 -0
  35. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/executor.py +0 -0
  36. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompt.py +0 -0
  37. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  38. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  39. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  40. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-subagent-explore.md +0 -0
  41. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-subagent-oracle.md +0 -0
  42. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -0
  43. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/prompts/prompt-subagent.md +0 -0
  44. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/reminders.py +0 -0
  45. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/task.py +0 -0
  46. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/__init__.py +0 -0
  47. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/__init__.py +0 -0
  48. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  49. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  50. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  51. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  52. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  53. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/multi_edit_tool.md +0 -0
  54. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/multi_edit_tool.py +0 -0
  55. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  56. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  57. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  58. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  59. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/__init__.py +0 -0
  60. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/memory_tool.md +0 -0
  61. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/memory_tool.py +0 -0
  62. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/skill_loader.py +0 -0
  63. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/skill_tool.md +0 -0
  64. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/memory/skill_tool.py +0 -0
  65. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  66. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  67. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  68. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  69. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  70. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  71. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  72. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  73. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  74. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  75. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/tool_abc.py +0 -0
  76. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/tool_context.py +0 -0
  77. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/tool_registry.py +0 -0
  78. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/tool_runner.py +0 -0
  79. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/truncation.py +0 -0
  80. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/web/__init__.py +0 -0
  81. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  82. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  83. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  84. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  85. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/core/turn.py +0 -0
  86. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/__init__.py +0 -0
  87. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  88. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/anthropic/client.py +0 -0
  89. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/anthropic/input.py +0 -0
  90. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/client.py +0 -0
  91. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/input_common.py +0 -0
  92. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  93. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  94. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  95. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  96. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  97. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openrouter/client.py +0 -0
  98. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openrouter/input.py +0 -0
  99. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/openrouter/reasoning_handler.py +0 -0
  100. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/registry.py +0 -0
  101. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/responses/__init__.py +0 -0
  102. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/responses/client.py +0 -0
  103. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/responses/input.py +0 -0
  104. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/llm/usage.py +0 -0
  105. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/__init__.py +0 -0
  106. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/events.py +0 -0
  107. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/llm_param.py +0 -0
  108. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/op.py +0 -0
  109. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/op_handler.py +0 -0
  110. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/sub_agent.py +0 -0
  111. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/protocol/tools.py +0 -0
  112. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/session/__init__.py +0 -0
  113. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/session/export.py +0 -0
  114. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/session/selector.py +0 -0
  115. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/session/session.py +0 -0
  116. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/trace/__init__.py +0 -0
  117. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/trace/log.py +0 -0
  118. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/__init__.py +0 -0
  119. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/core/__init__.py +0 -0
  120. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/core/display.py +0 -0
  121. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/core/input.py +0 -0
  122. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/core/stage_manager.py +0 -0
  123. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/__init__.py +0 -0
  124. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  125. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/debug/display.py +0 -0
  126. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  127. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/exec/display.py +0 -0
  128. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  129. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  130. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  131. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/display.py +0 -0
  132. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
  133. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
  134. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  135. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
  136. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/__init__.py +0 -0
  137. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/assistant.py +0 -0
  138. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/common.py +0 -0
  139. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/diffs.py +0 -0
  140. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/errors.py +0 -0
  141. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/metadata.py +0 -0
  142. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
  143. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/thinking.py +0 -0
  144. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/tools.py +0 -0
  145. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/renderers/user_input.py +0 -0
  146. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/__init__.py +0 -0
  147. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/live.py +0 -0
  148. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/markdown.py +0 -0
  149. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/quote.py +0 -0
  150. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  151. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/status.py +0 -0
  152. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/rich/theme.py +0 -0
  153. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/terminal/__init__.py +0 -0
  154. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/terminal/color.py +0 -0
  155. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/terminal/control.py +0 -0
  156. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/terminal/notifier.py +0 -0
  157. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  158. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/utils/__init__.py +0 -0
  159. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/utils/common.py +0 -0
  160. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/ui/utils/debouncer.py +0 -0
  161. {klaude_code-1.2.3 → klaude_code-1.2.4}/src/klaude_code/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Add your description here
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: openai>=1.102.0
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.2.3"
7
+ version = "1.2.4"
8
8
  description = "Add your description here"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -16,6 +16,7 @@ from .registry import (
16
16
  load_prompt_commands,
17
17
  register_command,
18
18
  )
19
+ from .status_cmd import StatusCommand
19
20
  from .terminal_setup_cmd import TerminalSetupCommand
20
21
 
21
22
  # Dynamically load prompt commands
@@ -28,6 +29,7 @@ __all__ = [
28
29
  "ModelCommand",
29
30
  "ExportCommand",
30
31
  "RefreshTerminalCommand",
32
+ "StatusCommand",
31
33
  "TerminalSetupCommand",
32
34
  "register_command",
33
35
  "CommandABC",
@@ -46,10 +46,12 @@ class ExportCommand(CommandABC):
46
46
  )
47
47
  return CommandResult(events=[event])
48
48
  except Exception as exc: # pragma: no cover - safeguard for unexpected errors
49
+ import traceback
50
+
49
51
  event = events.DeveloperMessageEvent(
50
52
  session_id=agent.session.id,
51
53
  item=model.DeveloperMessageItem(
52
- content=f"Failed to export session: {exc}",
54
+ content=f"Failed to export session: {exc}\n{traceback.format_exc()}",
53
55
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
54
56
  ),
55
57
  )
@@ -0,0 +1,111 @@
1
+ from klaude_code.command.command_abc import CommandABC, CommandResult
2
+ from klaude_code.command.registry import register_command
3
+ from klaude_code.core.agent import Agent
4
+ from klaude_code.protocol import commands, events, model
5
+ from klaude_code.session.session import Session
6
+
7
+
8
+ def accumulate_session_usage(session: Session) -> tuple[model.Usage, int]:
9
+ """Accumulate usage statistics from all ResponseMetadataItems in session history.
10
+
11
+ Returns:
12
+ A tuple of (accumulated_usage, task_count)
13
+ """
14
+ total = model.Usage()
15
+ task_count = 0
16
+
17
+ for item in session.conversation_history:
18
+ if isinstance(item, model.ResponseMetadataItem) and item.usage:
19
+ task_count += 1
20
+ usage = item.usage
21
+ total.input_tokens += usage.input_tokens
22
+ total.cached_tokens += usage.cached_tokens
23
+ total.reasoning_tokens += usage.reasoning_tokens
24
+ total.output_tokens += usage.output_tokens
25
+ total.total_tokens += usage.total_tokens
26
+
27
+ # Accumulate costs
28
+ if usage.input_cost is not None:
29
+ total.input_cost = (total.input_cost or 0.0) + usage.input_cost
30
+ if usage.output_cost is not None:
31
+ total.output_cost = (total.output_cost or 0.0) + usage.output_cost
32
+ if usage.cache_read_cost is not None:
33
+ total.cache_read_cost = (total.cache_read_cost or 0.0) + usage.cache_read_cost
34
+ if usage.total_cost is not None:
35
+ total.total_cost = (total.total_cost or 0.0) + usage.total_cost
36
+
37
+ # Keep the latest context_usage_percent
38
+ if usage.context_usage_percent is not None:
39
+ total.context_usage_percent = usage.context_usage_percent
40
+
41
+ return total, task_count
42
+
43
+
44
+ def _format_tokens(tokens: int) -> str:
45
+ """Format token count with K/M suffix for readability."""
46
+ if tokens >= 1_000_000:
47
+ return f"{tokens / 1_000_000:.2f}M"
48
+ if tokens >= 1_000:
49
+ return f"{tokens / 1_000:.1f}K"
50
+ return str(tokens)
51
+
52
+
53
+ def _format_cost(cost: float | None) -> str:
54
+ """Format cost in USD."""
55
+ if cost is None:
56
+ return "-"
57
+ if cost < 0.01:
58
+ return f"${cost:.4f}"
59
+ return f"${cost:.2f}"
60
+
61
+
62
+ def format_status_content(usage: model.Usage) -> str:
63
+ """Format session status as comma-separated text."""
64
+ parts: list[str] = []
65
+
66
+ parts.append(f"Input: {_format_tokens(usage.input_tokens)}")
67
+ if usage.cached_tokens > 0:
68
+ parts.append(f"Cached: {_format_tokens(usage.cached_tokens)}")
69
+ parts.append(f"Output: {_format_tokens(usage.output_tokens)}")
70
+ parts.append(f"Total: {_format_tokens(usage.total_tokens)}")
71
+
72
+ if usage.total_cost is not None:
73
+ parts.append(f"Cost: {_format_cost(usage.total_cost)}")
74
+
75
+ return ", ".join(parts)
76
+
77
+
78
+ @register_command
79
+ class StatusCommand(CommandABC):
80
+ """Display session usage statistics."""
81
+
82
+ @property
83
+ def name(self) -> commands.CommandName:
84
+ return commands.CommandName.STATUS
85
+
86
+ @property
87
+ def summary(self) -> str:
88
+ return "Show session usage statistics"
89
+
90
+ async def run(self, raw: str, agent: Agent) -> CommandResult:
91
+ session = agent.session
92
+ usage, task_count = accumulate_session_usage(session)
93
+
94
+ event = events.DeveloperMessageEvent(
95
+ session_id=session.id,
96
+ item=model.DeveloperMessageItem(
97
+ content=format_status_content(usage),
98
+ command_output=model.CommandOutput(
99
+ command_name=self.name,
100
+ ui_extra=model.ToolResultUIExtra(
101
+ type=model.ToolResultUIExtraType.SESSION_STATUS,
102
+ session_status=model.SessionStatusUIExtra(
103
+ usage=usage,
104
+ task_count=task_count,
105
+ ),
106
+ ),
107
+ ),
108
+ ),
109
+ )
110
+
111
+ return CommandResult(events=[event])
@@ -11,6 +11,7 @@ class CommandName(str, Enum):
11
11
  CLEAR = "clear"
12
12
  TERMINAL_SETUP = "terminal-setup"
13
13
  EXPORT = "export"
14
+ STATUS = "status"
14
15
  # PLAN and DOC are dynamically registered now, but kept here if needed for reference
15
16
  # or we can remove them if no code explicitly imports them.
16
17
  # PLAN = "plan"
@@ -45,6 +45,7 @@ class ToolResultUIExtraType(str, Enum):
45
45
  SESSION_ID = "session_id"
46
46
  MERMAID_LINK = "mermaid_link"
47
47
  TRUNCATION = "truncation"
48
+ SESSION_STATUS = "session_status"
48
49
 
49
50
 
50
51
  class ToolSideEffect(str, Enum):
@@ -62,6 +63,11 @@ class TruncationUIExtra(BaseModel):
62
63
  truncated_length: int
63
64
 
64
65
 
66
+ class SessionStatusUIExtra(BaseModel):
67
+ usage: "Usage"
68
+ task_count: int
69
+
70
+
65
71
  class ToolResultUIExtra(BaseModel):
66
72
  type: ToolResultUIExtraType
67
73
  diff_text: str | None = None
@@ -69,6 +75,7 @@ class ToolResultUIExtra(BaseModel):
69
75
  session_id: str | None = None
70
76
  mermaid_link: MermaidLinkUIExtra | None = None
71
77
  truncation: TruncationUIExtra | None = None
78
+ session_status: SessionStatusUIExtra | None = None
72
79
 
73
80
 
74
81
  class AtPatternParseResult(BaseModel):
@@ -908,9 +908,81 @@
908
908
  font-size: var(--font-size-sm);
909
909
  margin-top: 2px;
910
910
  }
911
+ /* TOC Sidebar */
912
+ .toc-sidebar {
913
+ position: fixed;
914
+ top: 33vh;
915
+ left: 20px;
916
+ width: 220px;
917
+ bottom: 33vh;
918
+ overflow-y: auto;
919
+ padding-right: 12px;
920
+ /* Vertical padding to offset mask */
921
+ padding-top: 30px;
922
+ padding-bottom: 30px;
923
+ display: none;
924
+ scrollbar-width: none;
925
+ z-index: 100;
926
+ /* Linear mask for fading edges */
927
+ -webkit-mask-image: linear-gradient(
928
+ to bottom,
929
+ transparent 0%,
930
+ black 30px,
931
+ black calc(100% - 30px),
932
+ transparent 100%
933
+ );
934
+ mask-image: linear-gradient(
935
+ to bottom,
936
+ transparent 0%,
937
+ black 30px,
938
+ black calc(100% - 30px),
939
+ transparent 100%
940
+ );
941
+ }
942
+
943
+ .toc-sidebar::-webkit-scrollbar {
944
+ display: none;
945
+ }
946
+
947
+ /* Show TOC on wide screens */
948
+ @media (min-width: 1400px) {
949
+ .toc-sidebar {
950
+ display: block;
951
+ }
952
+ }
953
+
954
+ .toc-item {
955
+ display: block;
956
+ padding: 3px 10px;
957
+ margin-bottom: 1px;
958
+ font-family: var(--font-mono);
959
+ font-size: 12px;
960
+ line-height: 1.3;
961
+ color: var(--text-dim);
962
+ text-decoration: none;
963
+ cursor: pointer;
964
+ transition: all 0.2s;
965
+ white-space: nowrap;
966
+ overflow: hidden;
967
+ text-overflow: ellipsis;
968
+ border-radius: 4px;
969
+ }
970
+
971
+ .toc-item:hover {
972
+ color: var(--text);
973
+ background: rgba(0, 0, 0, 0.03);
974
+ }
975
+
976
+ .toc-item.active {
977
+ color: var(--text);
978
+ background: var(--bg-card);
979
+ font-weight: bold;
980
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
981
+ }
911
982
  </style>
912
983
  </head>
913
984
  <body>
985
+ <div id="toc-sidebar" class="toc-sidebar"></div>
914
986
  <div class="container">
915
987
  <div class="header">
916
988
  <h1>Klaude Code</h1>
@@ -941,7 +1013,9 @@
941
1013
  <div
942
1014
  class="details-content system-prompt-content"
943
1015
  style="font-family: var(--font-mono); white-space: pre-wrap"
944
- >$system_prompt</div>
1016
+ >
1017
+ $system_prompt
1018
+ </div>
945
1019
  </details>
946
1020
 
947
1021
  <details class="collapsible-section">
@@ -1215,6 +1289,138 @@
1215
1289
  behavior: "smooth",
1216
1290
  });
1217
1291
  });
1292
+
1293
+ // TOC Logic
1294
+ document.addEventListener("DOMContentLoaded", () => {
1295
+ const tocSidebar = document.getElementById("toc-sidebar");
1296
+ if (!tocSidebar) return;
1297
+
1298
+ const sections = [];
1299
+ let userCount = 0;
1300
+ let assistantCount = 0;
1301
+
1302
+ // 1. System Prompt
1303
+ const sysPrompt = document.querySelector(
1304
+ ".collapsible-section summary"
1305
+ );
1306
+ if (sysPrompt && sysPrompt.textContent.includes("System Prompt")) {
1307
+ const details = sysPrompt.parentElement;
1308
+ details.id = details.id || "section-system-prompt";
1309
+ sections.push({
1310
+ id: details.id,
1311
+ label: "System Prompt",
1312
+ el: details,
1313
+ });
1314
+ }
1315
+
1316
+ // 2. Messages and Tools
1317
+ const stream = document.querySelector(".message-stream");
1318
+ if (stream) {
1319
+ // Use a Walker to find top-level relevant items if structure is complex
1320
+ // But assuming flat children of message-stream based on CSS
1321
+ Array.from(stream.children).forEach((child) => {
1322
+ let label = null;
1323
+ let idPrefix = "section";
1324
+
1325
+ if (child.classList.contains("message-group")) {
1326
+ const roleLabel = child.querySelector(".role-label");
1327
+ if (roleLabel) {
1328
+ if (roleLabel.classList.contains("user")) {
1329
+ userCount++;
1330
+ label = `USER $${userCount}`;
1331
+ idPrefix = "user";
1332
+ } else if (roleLabel.classList.contains("assistant")) {
1333
+ assistantCount++;
1334
+ label = `ASSISTANT $${assistantCount}`;
1335
+ idPrefix = "assistant";
1336
+ } else {
1337
+ label = roleLabel.textContent.trim();
1338
+ }
1339
+ }
1340
+ } else if (child.classList.contains("tool-call")) {
1341
+ const toolName = child.querySelector(".tool-name");
1342
+ if (toolName) {
1343
+ label = toolName.textContent.trim();
1344
+ idPrefix = "tool";
1345
+ } else {
1346
+ label = "Tool";
1347
+ }
1348
+ }
1349
+
1350
+ if (label) {
1351
+ child.id =
1352
+ child.id ||
1353
+ `$${idPrefix}-$${Math.random().toString(36).substr(2, 9)}`;
1354
+ sections.push({ id: child.id, label: label, el: child });
1355
+ }
1356
+ });
1357
+ }
1358
+
1359
+ // Render TOC
1360
+ sections.forEach((section) => {
1361
+ const item = document.createElement("div");
1362
+ item.className = "toc-item";
1363
+ item.textContent = section.label;
1364
+ item.dataset.target = section.id;
1365
+ item.addEventListener("click", () => {
1366
+ section.el.scrollIntoView({ behavior: "smooth", block: "start" });
1367
+ });
1368
+ tocSidebar.appendChild(item);
1369
+ });
1370
+
1371
+ // Scroll Spy with throttling
1372
+ let ticking = false;
1373
+ const offset = 150; // Pixel offset for "active" area
1374
+
1375
+ function updateActiveSection() {
1376
+ let currentId = sections.length > 0 ? sections[0].id : null;
1377
+ const scrollY = window.scrollY;
1378
+
1379
+ // We look for the last section that has passed the top threshold (+ offset)
1380
+ for (let i = 0; i < sections.length; i++) {
1381
+ const el = sections[i].el;
1382
+ // If element top is above the "active line" (scrollY + offset)
1383
+ if (el.offsetTop <= scrollY + offset) {
1384
+ currentId = sections[i].id;
1385
+ } else {
1386
+ // Since sections are ordered by position, we can stop once we find one below the line
1387
+ break;
1388
+ }
1389
+ }
1390
+
1391
+ // Update UI
1392
+ document.querySelectorAll(".toc-item").forEach((item) => {
1393
+ const isActive = item.dataset.target === currentId;
1394
+ if (item.classList.contains("active") !== isActive) {
1395
+ item.classList.toggle("active", isActive);
1396
+ if (isActive) {
1397
+ // Auto-scroll sidebar to visible
1398
+ const sidebarRect = tocSidebar.getBoundingClientRect();
1399
+ const itemRect = item.getBoundingClientRect();
1400
+ // Check if item is out of view in sidebar (accounting for mask)
1401
+ if (
1402
+ itemRect.top < sidebarRect.top + 40 ||
1403
+ itemRect.bottom > sidebarRect.bottom - 40
1404
+ ) {
1405
+ item.scrollIntoView({ behavior: "smooth", block: "center" });
1406
+ }
1407
+ }
1408
+ }
1409
+ });
1410
+
1411
+ ticking = false;
1412
+ }
1413
+
1414
+ window.addEventListener("scroll", () => {
1415
+ if (!ticking) {
1416
+ window.requestAnimationFrame(updateActiveSection);
1417
+ ticking = true;
1418
+ }
1419
+ });
1420
+
1421
+ // Initial update
1422
+ updateActiveSection();
1423
+ });
1218
1424
  </script>
1219
1425
  </body>
1220
1426
  </html>
@@ -1,8 +1,9 @@
1
1
  from rich.console import Group, RenderableType
2
2
  from rich.padding import Padding
3
+ from rich.table import Table
3
4
  from rich.text import Text
4
5
 
5
- from klaude_code.protocol import commands, events
6
+ from klaude_code.protocol import commands, events, model
6
7
  from klaude_code.ui.renderers import diffs as r_diffs
7
8
  from klaude_code.ui.renderers.common import create_grid
8
9
  from klaude_code.ui.renderers.tools import render_path
@@ -97,7 +98,61 @@ def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
97
98
  return r_diffs.render_diff_panel(e.item.content, show_file_name=True)
98
99
  case commands.CommandName.HELP:
99
100
  return Padding.indent(Text.from_markup(e.item.content or ""), level=2)
101
+ case commands.CommandName.STATUS:
102
+ return _render_status_output(e.item.command_output)
100
103
  case _:
101
104
  content = e.item.content or "(no content)"
102
105
  style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
103
106
  return Padding.indent(Text(truncate_display(content), style=style), level=2)
107
+
108
+
109
+ def _format_tokens(tokens: int) -> str:
110
+ """Format token count with K/M suffix for readability."""
111
+ if tokens >= 1_000_000:
112
+ return f"{tokens / 1_000_000:.2f}M"
113
+ if tokens >= 1_000:
114
+ return f"{tokens / 1_000:.1f}K"
115
+ return str(tokens)
116
+
117
+
118
+ def _format_cost(cost: float | None) -> str:
119
+ """Format cost in USD."""
120
+ if cost is None:
121
+ return "-"
122
+ if cost < 0.01:
123
+ return f"${cost:.4f}"
124
+ return f"${cost:.2f}"
125
+
126
+
127
+ def _render_status_output(command_output: model.CommandOutput) -> RenderableType:
128
+ """Render session status as a two-column table with sections."""
129
+ if not command_output.ui_extra or not command_output.ui_extra.session_status:
130
+ return Text("(no status data)", style=ThemeKey.TOOL_RESULT)
131
+
132
+ status = command_output.ui_extra.session_status
133
+ usage = status.usage
134
+
135
+ table = Table.grid(padding=(0, 2))
136
+ table.add_column(style=ThemeKey.TOOL_RESULT, no_wrap=True)
137
+ table.add_column(style=ThemeKey.TOOL_RESULT, no_wrap=True)
138
+ # Token Usage section
139
+ table.add_row(Text("Token Usage", style="bold"), "")
140
+ table.add_row("Input Tokens", _format_tokens(usage.input_tokens))
141
+ if usage.cached_tokens > 0:
142
+ table.add_row("Cached Tokens", _format_tokens(usage.cached_tokens))
143
+ if usage.reasoning_tokens > 0:
144
+ table.add_row("Reasoning Tokens", _format_tokens(usage.reasoning_tokens))
145
+ table.add_row("Output Tokens", _format_tokens(usage.output_tokens))
146
+ table.add_row("Total Tokens", _format_tokens(usage.total_tokens))
147
+
148
+ # Cost section
149
+ if usage.total_cost is not None:
150
+ table.add_row("", "") # Empty line
151
+ table.add_row(Text("Cost", style="bold"), "")
152
+ table.add_row("Input Cost", _format_cost(usage.input_cost))
153
+ if usage.cache_read_cost is not None and usage.cache_read_cost > 0:
154
+ table.add_row("Cache Read Cost", _format_cost(usage.cache_read_cost))
155
+ table.add_row("Output Cost", _format_cost(usage.output_cost))
156
+ table.add_row("Total Cost", _format_cost(usage.total_cost))
157
+
158
+ return Padding.indent(table, level=2)
File without changes