claude-code-log 1.2.0__tar.gz → 1.3.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 (221) hide show
  1. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.claude/skills/tool-renderer/SKILL.md +27 -1
  2. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.github/workflows/ci.yml +3 -3
  3. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CHANGELOG.md +33 -0
  4. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CLAUDE.md +29 -6
  5. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CONTRIBUTING.md +31 -8
  6. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/PKG-INFO +14 -2
  7. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/README.md +13 -1
  8. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/cache.py +75 -6
  9. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/cli.py +62 -13
  10. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/converter.py +440 -44
  11. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/dag.py +313 -70
  12. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/__init__.py +21 -0
  13. claude_code_log-1.3.0/claude_code_log/factories/agent_metadata_factory.py +107 -0
  14. claude_code_log-1.3.0/claude_code_log/factories/attachment_factory.py +126 -0
  15. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/meta_factory.py +2 -0
  16. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/system_factory.py +52 -4
  17. claude_code_log-1.3.0/claude_code_log/factories/task_notification_factory.py +161 -0
  18. claude_code_log-1.3.0/claude_code_log/factories/teammate_factory.py +140 -0
  19. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/tool_factory.py +520 -2
  20. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/transcript_factory.py +4 -0
  21. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/user_factory.py +104 -3
  22. claude_code_log-1.3.0/claude_code_log/html/async_formatter.py +175 -0
  23. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/renderer.py +514 -13
  24. claude_code_log-1.3.0/claude_code_log/html/system_formatters.py +299 -0
  25. claude_code_log-1.3.0/claude_code_log/html/teammate_formatter.py +568 -0
  26. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/filter_styles.css +4 -0
  27. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/global_styles.css +87 -0
  28. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/message_styles.css +171 -4
  29. claude_code_log-1.3.0/claude_code_log/html/templates/components/teammate_styles.css +374 -0
  30. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timeline.html +23 -3
  31. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/index.html +7 -0
  32. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/transcript.html +81 -0
  33. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/tool_formatters.py +235 -1
  34. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/user_formatters.py +42 -5
  35. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/utils.py +164 -6
  36. claude_code_log-1.3.0/claude_code_log/json/__init__.py +5 -0
  37. claude_code_log-1.3.0/claude_code_log/json/renderer.py +222 -0
  38. claude_code_log-1.3.0/claude_code_log/markdown/renderer.py +1875 -0
  39. claude_code_log-1.3.0/claude_code_log/migrations/005_session_team_name.sql +14 -0
  40. claude_code_log-1.3.0/claude_code_log/migrations/006_session_ai_title.sql +14 -0
  41. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/models.py +716 -7
  42. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/renderer.py +1387 -96
  43. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/tui.py +28 -16
  44. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/utils.py +18 -16
  45. claude_code_log-1.3.0/dev-docs/agents.md +191 -0
  46. claude_code_log-1.3.0/dev-docs/application_model.md +448 -0
  47. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/css-classes.md +5 -0
  48. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/dag.md +119 -6
  49. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/implementing-a-tool-renderer.md +17 -0
  50. claude_code_log-1.2.0/dev-docs/FOLD_STATE_DIAGRAM.md → claude_code_log-1.3.0/dev-docs/message-hierarchy.md +16 -12
  51. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages.md +80 -5
  52. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/rendering-architecture.md +43 -8
  53. claude_code_log-1.3.0/dev-docs/teammates.md +978 -0
  54. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/justfile +18 -20
  55. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/pyproject.toml +6 -1
  56. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/uv.lock +1 -1
  57. claude_code_log-1.3.0/work/async-agents.md +215 -0
  58. claude_code_log-1.3.0/work/refactor-reindex-with-ghosting.md +164 -0
  59. {claude_code_log-1.2.0/dev-docs → claude_code_log-1.3.0/work}/rendering-next.md +2 -2
  60. claude_code_log-1.2.0/.github/workflows/claude.yml +0 -67
  61. claude_code_log-1.2.0/claude_code_log/html/system_formatters.py +0 -141
  62. claude_code_log-1.2.0/claude_code_log/markdown/renderer.py +0 -1010
  63. claude_code_log-1.2.0/dev-docs/restoring-archived-sessions.md +0 -105
  64. claude_code_log-1.2.0/work/phase-c-agent-transcripts.md +0 -87
  65. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.claude/settings.json +0 -0
  66. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.gitignore +0 -0
  67. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.vscode/settings.json +0 -0
  68. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/LICENSE +0 -0
  69. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/__init__.py +0 -0
  70. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/assistant_factory.py +0 -0
  71. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/__init__.py +0 -0
  72. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/ansi_colors.py +0 -0
  73. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/assistant_formatters.py +0 -0
  74. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/renderer_code.py +0 -0
  75. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/edit_diff_styles.css +0 -0
  76. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/page_nav_styles.css +0 -0
  77. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/project_card_styles.css +0 -0
  78. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/pygments_styles.css +0 -0
  79. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search.html +0 -0
  80. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_inline.html +0 -0
  81. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_inline_script.html +0 -0
  82. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_results_panel.html +0 -0
  83. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_styles.css +0 -0
  84. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/session_nav.html +0 -0
  85. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/session_nav_styles.css +0 -0
  86. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timeline_styles.css +0 -0
  87. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timezone_converter.js +0 -0
  88. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/todo_styles.css +0 -0
  89. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/image_export.py +0 -0
  90. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/markdown/__init__.py +0 -0
  91. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/001_initial_schema.sql +0 -0
  92. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/002_html_cache.sql +0 -0
  93. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/003_html_pagination.sql +0 -0
  94. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/004_html_pagination_variant.sql +0 -0
  95. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/__init__.py +0 -0
  96. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/runner.py +0 -0
  97. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/parser.py +0 -0
  98. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/py.typed +0 -0
  99. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/renderer_timings.py +0 -0
  100. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant.json +0 -0
  101. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant.jsonl +0 -0
  102. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant_sidechain.json +0 -0
  103. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant_sidechain.jsonl +0 -0
  104. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/thinking.json +0 -0
  105. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/thinking.jsonl +0 -0
  106. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/file_history_snapshot.json +0 -0
  107. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/file_history_snapshot.jsonl +0 -0
  108. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/queue_operation.json +0 -0
  109. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/queue_operation.jsonl +0 -0
  110. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/summary.json +0 -0
  111. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/summary.jsonl +0 -0
  112. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/system_info.json +0 -0
  113. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/system_info.jsonl +0 -0
  114. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.json +0 -0
  115. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.jsonl +0 -0
  116. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.json +0 -0
  117. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.jsonl +0 -0
  118. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.json +0 -0
  119. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.jsonl +0 -0
  120. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result.json +0 -0
  121. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result.jsonl +0 -0
  122. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result_error.json +0 -0
  123. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result_error.jsonl +0 -0
  124. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_use.json +0 -0
  125. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_use.jsonl +0 -0
  126. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_result.json +0 -0
  127. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_result.jsonl +0 -0
  128. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_use.json +0 -0
  129. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_use.jsonl +0 -0
  130. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result.json +0 -0
  131. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result.jsonl +0 -0
  132. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result_error.json +0 -0
  133. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result_error.jsonl +0 -0
  134. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_use.json +0 -0
  135. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_use.jsonl +0 -0
  136. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.json +0 -0
  137. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.jsonl +0 -0
  138. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.json +0 -0
  139. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.jsonl +0 -0
  140. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.json +0 -0
  141. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.jsonl +0 -0
  142. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_result.json +0 -0
  143. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_result.jsonl +0 -0
  144. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_use.json +0 -0
  145. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_use.jsonl +0 -0
  146. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_result.json +0 -0
  147. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_result.jsonl +0 -0
  148. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_use.json +0 -0
  149. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_use.jsonl +0 -0
  150. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result.json +0 -0
  151. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result.jsonl +0 -0
  152. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result_error.json +0 -0
  153. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result_error.jsonl +0 -0
  154. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_use.json +0 -0
  155. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_use.jsonl +0 -0
  156. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_result.json +0 -0
  157. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_result.jsonl +0 -0
  158. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_use.json +0 -0
  159. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_use.jsonl +0 -0
  160. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result.json +0 -0
  161. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result.jsonl +0 -0
  162. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.json +0 -0
  163. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.jsonl +0 -0
  164. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_use.json +0 -0
  165. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_use.jsonl +0 -0
  166. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result.json +0 -0
  167. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result.jsonl +0 -0
  168. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result_error.json +0 -0
  169. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result_error.jsonl +0 -0
  170. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_use.json +0 -0
  171. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_use.jsonl +0 -0
  172. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_result.json +0 -0
  173. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_result.jsonl +0 -0
  174. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_use.json +0 -0
  175. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_use.jsonl +0 -0
  176. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_result.json +0 -0
  177. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_result.jsonl +0 -0
  178. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_use.json +0 -0
  179. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_use.jsonl +0 -0
  180. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_result.json +0 -0
  181. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_result.jsonl +0 -0
  182. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_use.json +0 -0
  183. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_use.jsonl +0 -0
  184. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_result.json +0 -0
  185. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_result.jsonl +0 -0
  186. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_use.json +0 -0
  187. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_use.jsonl +0 -0
  188. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result.json +0 -0
  189. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result.jsonl +0 -0
  190. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result_error.json +0 -0
  191. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result_error.jsonl +0 -0
  192. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_use.json +0 -0
  193. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_use.jsonl +0 -0
  194. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.json +0 -0
  195. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.jsonl +0 -0
  196. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.json +0 -0
  197. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.jsonl +0 -0
  198. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_input.json +0 -0
  199. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_input.jsonl +0 -0
  200. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_output.json +0 -0
  201. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_output.jsonl +0 -0
  202. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/command_output.json +0 -0
  203. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/command_output.jsonl +0 -0
  204. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/image.json +0 -0
  205. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/image.jsonl +0 -0
  206. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user.json +0 -0
  207. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user.jsonl +0 -0
  208. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_command.json +0 -0
  209. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_command.jsonl +0 -0
  210. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_sidechain.json +0 -0
  211. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_sidechain.jsonl +0 -0
  212. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_slash_command.json +0 -0
  213. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_slash_command.jsonl +0 -0
  214. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/mise.toml +0 -0
  215. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/__init__.pyi +0 -0
  216. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/formatter.pyi +0 -0
  217. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/formatters/__init__.pyi +0 -0
  218. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/lexer.pyi +0 -0
  219. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/lexers/__init__.pyi +0 -0
  220. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/util.pyi +0 -0
  221. {claude_code_log-1.2.0 → claude_code_log-1.3.0}/work/session-state-propagation.md +0 -0
@@ -260,6 +260,32 @@ def title_WebSearchInput(self, input: WebSearchInput, message: TemplateMessage)
260
260
  return self._tool_title(message, "🔎", f'"{input.query}"')
261
261
  ```
262
262
 
263
+ ### Watch out: the template's wrench-suppression is emoji-range-gated
264
+
265
+ Tool-use messages get a default `🛠️` prefix prepended by
266
+ `templates/transcript.html` *unless* the title already starts with
267
+ an emoji that `html/utils.py::starts_with_emoji` recognises. That
268
+ function whitelists specific Unicode ranges:
269
+
270
+ - `0x2300-0x23FF` Misc Technical (`⏰ ⏳ ⏱️ ⏲️ ⏸ ⏹ ⏺ ⏏` …)
271
+ - `0x2600-0x26FF` Misc Symbols
272
+ - `0x2700-0x27BF` Dingbats
273
+ - `0x1F300-0x1F5FF` Misc Symbols and Pictographs
274
+ - `0x1F600-0x1F64F` Emoticons
275
+ - `0x1F680-0x1F6FF` Transport and Map Symbols
276
+ - `0x1F900-0x1F9FF` Supplemental Symbols
277
+
278
+ If the icon you pass to `_tool_title` falls **outside** these
279
+ ranges, the template will helpfully add a `🛠️` in front of it,
280
+ producing a redundant double-icon title like
281
+ `🛠️ <your-icon> <ToolName>`. Verify by rendering a fixture and
282
+ grepping for `🛠️` co-occurring with your icon, or by checking
283
+ `ord(your_icon)` against the ranges above.
284
+
285
+ If your icon is a real emoji that lives in a Unicode range not
286
+ listed there, **add the range** to `starts_with_emoji` rather than
287
+ picking a different icon.
288
+
263
289
  ## Step 5: Implement Markdown Renderer
264
290
 
265
291
  In `markdown/renderer.py`:
@@ -369,7 +395,7 @@ class Test{ToolName}OutputFormatting:
369
395
  uv run pytest test/test_{toolname}_rendering.py -v
370
396
 
371
397
  # Run full test suite to check for regressions
372
- uv run pytest -n auto -m "not (tui or browser)" -v
398
+ uv run pytest -m "not (tui or browser)" -v
373
399
  ```
374
400
 
375
401
  ## Checklist
@@ -33,13 +33,13 @@ jobs:
33
33
  run: uv sync --all-extras --dev && uv run playwright install chromium
34
34
 
35
35
  - name: Run unit tests with coverage
36
- run: uv run pytest -n auto -m "not (tui or browser or benchmark)" --cov=claude_code_log --cov-report=xml --cov-report=html --cov-report=term
36
+ run: uv run pytest -m "not (tui or browser or benchmark)" --cov=claude_code_log --cov-report=xml --cov-report=html --cov-report=term
37
37
 
38
38
  - name: Run TUI tests with coverage append
39
- run: uv run pytest -n auto -m tui --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term
39
+ run: uv run pytest -m tui --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term
40
40
 
41
41
  - name: Run browser tests with coverage append
42
- run: uv run pytest -n auto -m browser --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term
42
+ run: uv run pytest -m browser --cov=claude_code_log --cov-append --cov-report=xml --cov-report=html --cov-report=term
43
43
 
44
44
  - name: Run benchmark tests with coverage append (primary only)
45
45
  if: matrix.is-primary
@@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [1.3.0] - 2026-05-14
10
+
11
+ ### Changed
12
+
13
+ - **Render ScheduleWakeup and Cron* tools (#148) (#152)**
14
+ - **Render hook attachment entries at FULL detail (#128) (#149)**
15
+ - **Style sidechain filter toggle with dashed border**
16
+ - **scrub_surrogates: handle high surrogate range (CR follow-up) (#150)**
17
+ - **Render the built-in Monitor tool with Task-end backlink (#142) (#147)**
18
+ - **Add support for ai-title and prefer it over legacy summary (#136)**
19
+ - **fix: add errors='replace' to read_text/write_text for Unicode safety (#139) (#146)**
20
+ - **Fix UnicodeEncodeError on JSONL with lone surrogates (#139) (#144)**
21
+ - **Use `--dist=worksteal` to speed up tests + move `-n auto` to config to make it default (#145)**
22
+ - **Fix/prevent dag cycle (#138)**
23
+ - **Render away_summary recap entries (#111) (#141)**
24
+ - **System info cosmetic improvements + chain-pairing fix (#137) (#140)**
25
+ - **dev-docs: introduce application_model.md as entry point, normalize naming, clean work/ (#134)**
26
+ - **export conversations to json (#36)**
27
+ - **Suppress noise in system-info messages (#129) (#133)**
28
+ - **Fix DAG cyclic-children hang and add SIGUSR1 stack dump (#135)**
29
+ - **Support async agents (#90) (#132)**
30
+ - **Robust within-session fork rendering: collapse parallel-tool_use forks, consistent labels (#131)**
31
+ - **Render user content as Markdown with raw fallback toggle (#119)**
32
+ - **Add --detail user-only level (#118)**
33
+ - **Pair Slash Command with User (slash command) (#126) (#127)**
34
+ - **Fold Skill name into tool_use title and drop the params row**
35
+ - **Fold Skill body into its tool_use block (#121)**
36
+ - **docs: add Community Extensions section (#120)**
37
+ - **Support teammates (#91): stitching + session headers + index (PR 3 of 3) (#125)**
38
+ - **Support teammates (#91): rendering (PR 2 of 3) (#122)**
39
+ - **Support teammates (#91): parsing + data model (draft) (#117)**
40
+
41
+
9
42
  ## [1.2.0] - 2026-04-19
10
43
 
11
44
  ### Changed
@@ -51,26 +51,26 @@ See @CONTRIBUTING.md for detailed development setup, testing, architecture, and
51
51
 
52
52
  ### Claude-Specific Testing Tips
53
53
 
54
- **Always use `-n auto` for parallel test execution:**
54
+ **Config in `pyproject.toml` sets `-n auto --dist=worksteal` so you might need to unset for pdb, etc**
55
55
 
56
56
  ```bash
57
57
  # Unit tests (fast, recommended for development)
58
58
  just test
59
- # or: uv run pytest -n auto -m "not (tui or browser)" -v
59
+ # or: uv run pytest -m "not (tui or browser)" -v
60
60
 
61
61
  # TUI tests
62
62
  just test-tui
63
- # or: uv run pytest -n auto -m tui
63
+ # or: uv run pytest -m tui
64
64
 
65
65
  # Browser tests
66
66
  just test-browser
67
- # or: uv run pytest -n auto -m browser
67
+ # or: uv run pytest -m browser
68
68
 
69
69
  # All tests
70
70
  just test-all
71
71
  ```
72
72
 
73
- **Tip:** Add `-x` to stop on first failure (e.g., `uv run pytest -n auto -m "not (tui or browser)" -v -x`).
73
+ **Tip:** Add `-x` to stop on first failure (e.g., `uv run pytest -m "not (tui or browser)" -v -x`).
74
74
 
75
75
  ### Code Quality
76
76
 
@@ -95,7 +95,30 @@ The interactive timeline is implemented in JavaScript within `claude_code_log/te
95
95
 
96
96
  ## Architecture
97
97
 
98
- For detailed architecture documentation, see:
98
+ Start with [dev-docs/application_model.md](dev-docs/application_model.md)
99
+ — the entry point covering subsystems, data lifecycle, and a glossary,
100
+ with pointers to the deep-dive docs:
101
+
99
102
  - [dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md) - Data flow and rendering pipeline
100
103
  - [dev-docs/messages.md](dev-docs/messages.md) - Message type reference
101
104
  - [dev-docs/css-classes.md](dev-docs/css-classes.md) - CSS class combinations
105
+ - [dev-docs/dag.md](dev-docs/dag.md) - DAG-based session/fork architecture
106
+ - [dev-docs/agents.md](dev-docs/agents.md) - Sync/async/teammate agent integration
107
+ - [dev-docs/teammates.md](dev-docs/teammates.md) - Teammates feature deep-dive
108
+ - [dev-docs/message-hierarchy.md](dev-docs/message-hierarchy.md) - Fold/unfold state machine
109
+ - [dev-docs/implementing-a-tool-renderer.md](dev-docs/implementing-a-tool-renderer.md) - How-to: add a new tool
110
+
111
+ User-facing docs live in [docs/](docs/); plans and TODOs live in [work/](work/).
112
+
113
+ ### Keeping dev-docs/ in sync
114
+
115
+ `dev-docs/` is **as-built reference** — the code is the authoritative
116
+ source. When a non-trivial change alters behavior, structure, or
117
+ invariants documented in a deep-dive, update the relevant page in
118
+ the same commit (or as a prompt follow-up). If `dev-docs/` and the
119
+ code disagree, the doc is wrong.
120
+
121
+ Typical lifecycle: a feature begins as a spec in `work/`, evolves
122
+ into a WIP scratchpad as the code adapts to reality, then graduates
123
+ into `dev-docs/` (new page or merged into an existing one) once the
124
+ implementation has stabilized.
@@ -50,7 +50,9 @@ claude_code_log/
50
50
 
51
51
  scripts/ # Development utilities
52
52
  test/test_data/ # Representative JSONL samples
53
- dev-docs/ # Architecture documentation
53
+ dev-docs/ # Architecture / dev documentation (start in application_model.md)
54
+ docs/ # User-facing operations docs
55
+ work/ # Plans, TODOs, in-flight design docs
54
56
  ```
55
57
 
56
58
  ## Development Setup
@@ -94,7 +96,7 @@ The project uses a categorized test system to avoid async event loop conflicts.
94
96
  ```bash
95
97
  # Unit tests only (fast, recommended for development)
96
98
  just test
97
- # or: uv run pytest -n auto -m "not (tui or browser)" -v
99
+ # or: uv run pytest -m "not (tui or browser)" -v
98
100
 
99
101
  # TUI tests (isolated event loop)
100
102
  just test-tui
@@ -114,16 +116,23 @@ just test-cov
114
116
  Snapshot tests detect unintended HTML output changes using [syrupy](https://github.com/syrupy-project/syrupy):
115
117
 
116
118
  ```bash
117
- # Run snapshot tests
118
- uv run pytest -n auto test/test_snapshot_html.py -v
119
+ # Run snapshot tests (parallel mode is fine for read-only runs)
120
+ uv run pytest test/test_snapshot_html.py -v
119
121
 
120
122
  # Update snapshots after intentional HTML changes
121
- uv run pytest -n auto test/test_snapshot_html.py --snapshot-update
123
+ # IMPORTANT: run --snapshot-update with -n0 (see warning below)
124
+ uv run pytest test/test_snapshot_html.py -n0 --snapshot-update
122
125
  ```
123
126
 
127
+ > **Warning — don't let `--snapshot-update` run with `-n auto`.** Syrupy
128
+ > and pytest-xdist race when writing snapshot files in parallel: the
129
+ > `.ambr` file ends up truncated (observed: ~6000 lines silently
130
+ > deleted on a single run, leaving the file structurally broken but
131
+ > still passing on next read). Run `--snapshot-update` serially.
132
+
124
133
  When snapshot tests fail:
125
134
  1. Review the diff to verify changes are intentional
126
- 2. If intentional, run `--snapshot-update` to accept new output
135
+ 2. If intentional, run `--snapshot-update` (serially) to accept new output
127
136
  3. If unintentional, fix your code and re-run tests
128
137
 
129
138
  ### Test Prerequisites
@@ -151,7 +160,7 @@ Running all tests together can cause "RuntimeError: This event loop is already r
151
160
  just test-cov
152
161
 
153
162
  # Or manually:
154
- uv run pytest -n auto --cov=claude_code_log --cov-report=html --cov-report=term
163
+ uv run pytest --cov=claude_code_log --cov-report=html --cov-report=term
155
164
  ```
156
165
 
157
166
  HTML coverage reports are generated in `htmlcov/index.html`.
@@ -186,9 +195,23 @@ CLAUDE_CODE_LOG_DEBUG_TIMING=1 claude-code-log path/to/file.jsonl
186
195
 
187
196
  This outputs detailed timing for each rendering phase. The timing module is in `claude_code_log/renderer_timings.py`.
188
197
 
198
+ ## Diagnosing Hangs
199
+
200
+ If `claude-code-log` appears stuck (100% CPU, no output), send `SIGUSR1` to print the live Python stack to stderr without killing the process:
201
+
202
+ ```bash
203
+ # In another terminal
204
+ kill -USR1 $(pgrep -f claude-code-log | head -1)
205
+ ```
206
+
207
+ The handler is installed in `cli.py` via `faulthandler.register(SIGUSR1)`. POSIX-only; no-op on Windows. Unlike `py-spy`, it needs no root and no extra install.
208
+
189
209
  ## Architecture
190
210
 
191
- For detailed architecture documentation, see [dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md).
211
+ Start with [dev-docs/application_model.md](dev-docs/application_model.md)
212
+ for the system overview (subsystems, data lifecycle, glossary). For
213
+ the rendering pipeline specifically, see
214
+ [dev-docs/rendering-architecture.md](dev-docs/rendering-architecture.md).
192
215
 
193
216
  ### Data Flow Overview
194
217
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-log
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Convert Claude Code transcript JSONL files to HTML
5
5
  Project-URL: Homepage, https://github.com/daaain/claude-code-log
6
6
  Project-URL: Issues, https://github.com/daaain/claude-code-log/issues
@@ -66,7 +66,7 @@ uvx claude-code-log@latest --open-browser
66
66
  - **Rich Message Types**: Support for user/assistant messages, tool use/results, thinking content, images
67
67
  - **System Command Visibility**: Show system commands (like `init`) in expandable details with structured parsing
68
68
  - **Markdown Rendering**: Server-side markdown rendering with syntax highlighting using mistune
69
- - **Detail Levels & Compact Mode**: `--detail full|high|low|minimal` filters by verbosity and `--compact` merges repeated section headings — pairs well with `--format md` to feed past conversations back to an LLM for analysis or experience building
69
+ - **Detail Levels & Compact Mode**: `--detail full|high|low|minimal|user-only` filters by verbosity and `--compact` merges repeated section headings — pairs well with `--format md` to feed past conversations back to an LLM for analysis or experience building
70
70
  - **Floating Navigation**: Always-available back-to-top button and filter controls
71
71
  - **CLI Interface**: Simple command-line tool using Click
72
72
 
@@ -180,6 +180,7 @@ claude-code-log /path/to/project --detail low --format md --compact
180
180
 
181
181
  `--detail` levels (smallest → largest output):
182
182
 
183
+ - `user-only` — just user prompts and steering (useful as input to a downstream agent, e.g. building a requirements doc)
183
184
  - `minimal` — user + assistant text only
184
185
  - `low` — interaction-focused; keeps WebSearch, WebFetch, and Task (agent delegations) as key signals
185
186
  - `high` — detailed but cleaned; drops system/hook noise
@@ -280,6 +281,17 @@ uv run claude-code-log
280
281
 
281
282
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and architecture documentation.
282
283
 
284
+ ## Community Extensions
285
+
286
+ Projects built on top of `claude-code-log`:
287
+
288
+ - **[archive-session](https://github.com/lifeinchords/claude-code-skills#archive-session-skill--slash-command--optional-hook)** by [@lifeinchords](https://github.com/lifeinchords). Wraps the CLI as three integration surfaces:
289
+ - a Claude Code [Skill](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/skills/archive-session/SKILL.md)
290
+ - a Claude Code slash [Command](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/commands/archive-session.md) `/archive-session` for explicit in-chat invocation
291
+ - a Claude Code PreCompact [Hook](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/hooks/pre-compact-archive.sh) that auto-archives transcripts and subagent logs right before context compaction
292
+
293
+ Cross-platform (macOS and Windows/MSYS).
294
+
283
295
  ## TODO
284
296
 
285
297
  - tutorial overlay
@@ -42,7 +42,7 @@ uvx claude-code-log@latest --open-browser
42
42
  - **Rich Message Types**: Support for user/assistant messages, tool use/results, thinking content, images
43
43
  - **System Command Visibility**: Show system commands (like `init`) in expandable details with structured parsing
44
44
  - **Markdown Rendering**: Server-side markdown rendering with syntax highlighting using mistune
45
- - **Detail Levels & Compact Mode**: `--detail full|high|low|minimal` filters by verbosity and `--compact` merges repeated section headings — pairs well with `--format md` to feed past conversations back to an LLM for analysis or experience building
45
+ - **Detail Levels & Compact Mode**: `--detail full|high|low|minimal|user-only` filters by verbosity and `--compact` merges repeated section headings — pairs well with `--format md` to feed past conversations back to an LLM for analysis or experience building
46
46
  - **Floating Navigation**: Always-available back-to-top button and filter controls
47
47
  - **CLI Interface**: Simple command-line tool using Click
48
48
 
@@ -156,6 +156,7 @@ claude-code-log /path/to/project --detail low --format md --compact
156
156
 
157
157
  `--detail` levels (smallest → largest output):
158
158
 
159
+ - `user-only` — just user prompts and steering (useful as input to a downstream agent, e.g. building a requirements doc)
159
160
  - `minimal` — user + assistant text only
160
161
  - `low` — interaction-focused; keeps WebSearch, WebFetch, and Task (agent delegations) as key signals
161
162
  - `high` — detailed but cleaned; drops system/hook noise
@@ -256,6 +257,17 @@ uv run claude-code-log
256
257
 
257
258
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and architecture documentation.
258
259
 
260
+ ## Community Extensions
261
+
262
+ Projects built on top of `claude-code-log`:
263
+
264
+ - **[archive-session](https://github.com/lifeinchords/claude-code-skills#archive-session-skill--slash-command--optional-hook)** by [@lifeinchords](https://github.com/lifeinchords). Wraps the CLI as three integration surfaces:
265
+ - a Claude Code [Skill](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/skills/archive-session/SKILL.md)
266
+ - a Claude Code slash [Command](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/commands/archive-session.md) `/archive-session` for explicit in-chat invocation
267
+ - a Claude Code PreCompact [Hook](https://github.com/lifeinchords/claude-code-skills/blob/main/.claude/hooks/pre-compact-archive.sh) that auto-archives transcripts and subagent logs right before context compaction
268
+
269
+ Cross-platform (macOS and Windows/MSYS).
270
+
259
271
  ## TODO
260
272
 
261
273
  - tutorial overlay
@@ -47,6 +47,10 @@ class SessionCacheData(BaseModel):
47
47
 
48
48
  session_id: str
49
49
  summary: Optional[str] = None
50
+ # Claude Code's AI-generated short session title, sourced from
51
+ # `ai-title` JSONL entries (last one wins). Preferred over `summary`
52
+ # for display when present.
53
+ ai_title: Optional[str] = None
50
54
  first_timestamp: str
51
55
  last_timestamp: str
52
56
  message_count: int
@@ -56,6 +60,9 @@ class SessionCacheData(BaseModel):
56
60
  total_output_tokens: int = 0
57
61
  total_cache_creation_tokens: int = 0
58
62
  total_cache_read_tokens: int = 0
63
+ # Teammates feature — set when the session was active in a team.
64
+ # First non-None ``teamName`` of any entry in the session.
65
+ team_name: Optional[str] = None
59
66
 
60
67
 
61
68
  class HtmlCacheEntry(BaseModel):
@@ -122,6 +129,48 @@ class ProjectCache(BaseModel):
122
129
  # ========== Helper Functions ==========
123
130
 
124
131
 
132
+ # Lone HIGH surrogates (U+D800–U+DBFF) are not in surrogateescape's
133
+ # back-mapping range (which only covers low surrogates U+DC80–U+DCFF
134
+ # produced from raw byte decoding), so the surrogateescape-encode step
135
+ # in scrub_surrogates would raise on them. Pre-substitute high
136
+ # surrogates directly with U+FFFD before the encode step.
137
+ _HIGH_SURROGATE_RE = re.compile(r"[\ud800-\udbff]")
138
+
139
+
140
+ def scrub_surrogates(s: Optional[str]) -> Optional[str]:
141
+ """Replace lone surrogates (U+D800…U+DFFF) with U+FFFD.
142
+
143
+ Public helper used wherever ``surrogateescape``-decoded JSONL data
144
+ might cross a strict-UTF-8 boundary (issue #139). Examples:
145
+ sqlite3's text-binding path (``cache.update_session_cache``) and
146
+ TUI export (``tui.SessionBrowser._ensure_session_file``) — both
147
+ raise ``UnicodeEncodeError`` on lone surrogates the moment we try
148
+ to persist them.
149
+
150
+ Two ranges to handle, with two different mechanisms:
151
+
152
+ - **Low surrogates** (U+DC80…U+DCFF) — what ``surrogateescape``
153
+ uses when decoding raw bytes. ``encode("utf-8",
154
+ errors="surrogateescape")`` reverses the mapping back to the
155
+ original raw byte (``\\udcb2`` → ``b"\\xb2"``); the subsequent
156
+ ``decode("utf-8", errors="replace")`` then substitutes those
157
+ invalid bytes with U+FFFD on the decode side.
158
+ - **High surrogates** (U+D800…U+DBFF) — ``surrogateescape`` has
159
+ no mapping for these (they're not produced by raw-byte decoding),
160
+ so the encode step would raise ``UnicodeEncodeError``. Pre-scrub
161
+ them directly via regex substitution before the encode step.
162
+
163
+ Why not the simpler ``encode(..., errors="replace")``? That emits
164
+ ASCII ``?`` (U+003F) on encode rather than the canonical Unicode
165
+ replacement U+FFFD on decode — also valid UTF-8, but a less
166
+ informative sentinel.
167
+ """
168
+ if s is None:
169
+ return None
170
+ s = _HIGH_SURROGATE_RE.sub("�", s)
171
+ return s.encode("utf-8", errors="surrogateescape").decode("utf-8", errors="replace")
172
+
173
+
125
174
  def get_library_version() -> str:
126
175
  """Get the current library version from package metadata or pyproject.toml."""
127
176
  # First try to get version from installed package metadata
@@ -597,8 +646,9 @@ class CacheManager:
597
646
  project_id, session_id, summary, first_timestamp, last_timestamp,
598
647
  message_count, first_user_message, cwd,
599
648
  total_input_tokens, total_output_tokens,
600
- total_cache_creation_tokens, total_cache_read_tokens
601
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
649
+ total_cache_creation_tokens, total_cache_read_tokens,
650
+ team_name, ai_title
651
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
602
652
  ON CONFLICT(project_id, session_id) DO UPDATE SET
603
653
  summary = excluded.summary,
604
654
  first_timestamp = excluded.first_timestamp,
@@ -609,21 +659,29 @@ class CacheManager:
609
659
  total_input_tokens = excluded.total_input_tokens,
610
660
  total_output_tokens = excluded.total_output_tokens,
611
661
  total_cache_creation_tokens = excluded.total_cache_creation_tokens,
612
- total_cache_read_tokens = excluded.total_cache_read_tokens
662
+ total_cache_read_tokens = excluded.total_cache_read_tokens,
663
+ team_name = excluded.team_name,
664
+ ai_title = excluded.ai_title
613
665
  """,
614
666
  (
615
667
  self._project_id,
616
668
  session_id,
617
- data.summary,
669
+ # Scrub surrogate-bearing strings before binding —
670
+ # sqlite3 raises UnicodeEncodeError on lone
671
+ # surrogates that surrogateescape-decoded JSONL
672
+ # may have leaked into these text fields. (#139)
673
+ scrub_surrogates(data.summary),
618
674
  data.first_timestamp,
619
675
  data.last_timestamp,
620
676
  data.message_count,
621
- data.first_user_message,
622
- data.cwd,
677
+ scrub_surrogates(data.first_user_message),
678
+ scrub_surrogates(data.cwd),
623
679
  data.total_input_tokens,
624
680
  data.total_output_tokens,
625
681
  data.total_cache_creation_tokens,
626
682
  data.total_cache_read_tokens,
683
+ scrub_surrogates(data.team_name),
684
+ scrub_surrogates(data.ai_title),
627
685
  ),
628
686
  )
629
687
 
@@ -742,6 +800,7 @@ class CacheManager:
742
800
  sessions[row["session_id"]] = SessionCacheData(
743
801
  session_id=row["session_id"],
744
802
  summary=row["summary"],
803
+ ai_title=row["ai_title"] if "ai_title" in row.keys() else None,
745
804
  first_timestamp=row["first_timestamp"],
746
805
  last_timestamp=row["last_timestamp"],
747
806
  message_count=row["message_count"],
@@ -751,6 +810,7 @@ class CacheManager:
751
810
  total_output_tokens=row["total_output_tokens"],
752
811
  total_cache_creation_tokens=row["total_cache_creation_tokens"],
753
812
  total_cache_read_tokens=row["total_cache_read_tokens"],
813
+ team_name=row["team_name"] if "team_name" in row.keys() else None,
754
814
  )
755
815
 
756
816
  return ProjectCache(
@@ -790,6 +850,11 @@ class CacheManager:
790
850
  # 0.9.0 introduced _compact_ide_tags_for_preview() which transforms
791
851
  # first_user_message to use emoji indicators instead of raw IDE tags
792
852
  "0.8.0": "0.9.0",
853
+ # 1.3.0 added handling for `ai-title` JSONL entries: existing
854
+ # caches have a NULL `ai_title` column for every session until
855
+ # JSONLs are re-ingested, so the project index keeps showing
856
+ # the old session-id title until the cache is rebuilt.
857
+ "1.2.0": "1.3.0",
793
858
  }
794
859
 
795
860
  cache_ver = version.parse(cache_version)
@@ -1059,6 +1124,7 @@ class CacheManager:
1059
1124
  archived_sessions[session_id] = SessionCacheData(
1060
1125
  session_id=session_id,
1061
1126
  summary=row["summary"],
1127
+ ai_title=row["ai_title"] if "ai_title" in row.keys() else None,
1062
1128
  first_timestamp=row["first_timestamp"],
1063
1129
  last_timestamp=row["last_timestamp"],
1064
1130
  message_count=row["message_count"],
@@ -1068,6 +1134,9 @@ class CacheManager:
1068
1134
  total_output_tokens=row["total_output_tokens"],
1069
1135
  total_cache_creation_tokens=row["total_cache_creation_tokens"],
1070
1136
  total_cache_read_tokens=row["total_cache_read_tokens"],
1137
+ team_name=row["team_name"]
1138
+ if "team_name" in row.keys()
1139
+ else None,
1071
1140
  )
1072
1141
 
1073
1142
  return archived_sessions
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
  """CLI interface for claude-code-log."""
3
3
 
4
+ import faulthandler
4
5
  import logging
5
6
  import os
7
+ import signal
6
8
  import sys
7
9
  from pathlib import Path
8
10
  from typing import Optional
@@ -16,6 +18,7 @@ from .converter import (
16
18
  ensure_fresh_cache,
17
19
  generate_single_session_file,
18
20
  get_file_extension,
21
+ get_index_filename,
19
22
  process_projects_hierarchy,
20
23
  )
21
24
  from .cache import (
@@ -27,6 +30,25 @@ from .cache import (
27
30
  )
28
31
 
29
32
 
33
+ def _install_stack_dump_signal() -> None:
34
+ """Make ``kill -USR1 <pid>`` print the live Python stack to stderr.
35
+
36
+ Useful for diagnosing apparent hangs without killing the process —
37
+ py-spy needs root on macOS, but this only needs the signal. SIGUSR1
38
+ is POSIX-only; on Windows we silently skip.
39
+ """
40
+ sigusr1 = getattr(signal, "SIGUSR1", None)
41
+ if sigusr1 is None:
42
+ return
43
+ try:
44
+ faulthandler.register(sigusr1, all_threads=True, chain=False)
45
+ except (RuntimeError, ValueError, OSError):
46
+ # E.g. signal already taken, no-tty environments, or platforms
47
+ # where faulthandler.register raises. Diagnostics shouldn't
48
+ # break the CLI — silently skip.
49
+ pass
50
+
51
+
30
52
  def get_default_projects_dir() -> Path:
31
53
  """Get the default Claude projects directory path."""
32
54
  return Path.home() / ".claude" / "projects"
@@ -360,8 +382,25 @@ def _clear_caches(input_path: Path, all_projects: bool) -> None:
360
382
  click.echo(f"Warning: Failed to clear cache: {e}")
361
383
 
362
384
 
363
- def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) -> None:
364
- """Clear generated output files (HTML or Markdown) for the specified path."""
385
+ def _list_generated_outputs(directory: Path, file_ext: str) -> list[Path]:
386
+ """Return only files this tool generates, not every file with the extension.
387
+
388
+ Safe for JSON in particular, where the project directory may contain
389
+ unrelated user `.json` files that must not be deleted.
390
+ """
391
+ if file_ext == "json":
392
+ return [
393
+ *directory.glob("combined_transcripts*.json"),
394
+ *directory.glob("session-*.json"),
395
+ ]
396
+ return list(directory.glob(f"*.{file_ext}"))
397
+
398
+
399
+ def _clear_output_files(
400
+ input_path: Path, all_projects: bool, output_format: str
401
+ ) -> None:
402
+ """Clear generated output files (HTML/Markdown/JSON) for the specified path."""
403
+ file_ext = get_file_extension(output_format)
365
404
  ext_upper = file_ext.upper()
366
405
  try:
367
406
  if all_projects:
@@ -377,7 +416,7 @@ def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) ->
377
416
  for project_dir in project_dirs:
378
417
  try:
379
418
  # Remove output files in project directory
380
- output_files = list(project_dir.glob(f"*.{file_ext}"))
419
+ output_files = _list_generated_outputs(project_dir, file_ext)
381
420
  for output_file in output_files:
382
421
  output_file.unlink()
383
422
  total_removed += 1
@@ -391,12 +430,14 @@ def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) ->
391
430
  f" Warning: Failed to clear {ext_upper} files for {project_dir.name}: {e}"
392
431
  )
393
432
 
394
- # Also remove top-level index file
395
- index_file = input_path / f"index.{file_ext}"
433
+ # Also remove top-level index file (shared helper keeps this in
434
+ # sync with the generator, which uses a different name for JSON).
435
+ index_filename = get_index_filename(output_format)
436
+ index_file = input_path / index_filename
396
437
  if index_file.exists():
397
438
  index_file.unlink()
398
439
  total_removed += 1
399
- click.echo(f" Removed top-level index.{file_ext}")
440
+ click.echo(f" Removed top-level {index_filename}")
400
441
 
401
442
  if total_removed > 0:
402
443
  click.echo(f"Total: Removed {total_removed} {ext_upper} files")
@@ -406,7 +447,7 @@ def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) ->
406
447
  elif input_path.is_dir():
407
448
  # Clear output files for single directory
408
449
  click.echo(f"Clearing {ext_upper} files for {input_path}...")
409
- output_files = list(input_path.glob(f"*.{file_ext}"))
450
+ output_files = _list_generated_outputs(input_path, file_ext)
410
451
  for output_file in output_files:
411
452
  output_file.unlink()
412
453
 
@@ -492,9 +533,9 @@ def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) ->
492
533
  "-f",
493
534
  "--format",
494
535
  "output_format",
495
- type=click.Choice(["html", "md", "markdown"]),
536
+ type=click.Choice(["html", "md", "markdown", "json"]),
496
537
  default="html",
497
- help="Output format (default: html). Supports html, md, or markdown.",
538
+ help="Output format (default: html). Supports html, md/markdown, or json.",
498
539
  )
499
540
  @click.option(
500
541
  "--image-export-mode",
@@ -515,14 +556,18 @@ def _clear_output_files(input_path: Path, all_projects: bool, file_ext: str) ->
515
556
  )
516
557
  @click.option(
517
558
  "--detail",
518
- type=click.Choice(["full", "high", "low", "minimal"], case_sensitive=False),
559
+ type=click.Choice(
560
+ ["full", "high", "low", "minimal", "user-only"], case_sensitive=False
561
+ ),
519
562
  default="full",
520
563
  help=(
521
564
  "Detail level for output. "
522
565
  "full: everything; "
523
566
  "high: detailed but cleaned (no system/hook noise); "
524
567
  "low: interaction-focused + key signals; "
525
- "minimal: user + assistant messages only."
568
+ "minimal: user + assistant messages only; "
569
+ "user-only: only user prompts and steering (for feeding to "
570
+ "downstream agents, e.g. building a requirements doc)."
526
571
  ),
527
572
  )
528
573
  @click.option(
@@ -564,6 +609,10 @@ def main(
564
609
 
565
610
  INPUT_PATH: Path to a Claude transcript JSONL file, directory containing JSONL files, or project path to convert. If not provided, defaults to ~/.claude/projects/ and --all-projects is used.
566
611
  """
612
+ # Install signal-based stack dumper before any heavy work, so a hang
613
+ # can be diagnosed with `kill -USR1 <pid>` without root or restart.
614
+ _install_stack_dump_signal()
615
+
567
616
  # Configure logging to show warnings and above
568
617
  logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
569
618
 
@@ -751,10 +800,10 @@ def main(
751
800
 
752
801
  # Handle output files clearing
753
802
  if clear_output:
754
- file_ext = get_file_extension(output_format)
755
- _clear_output_files(input_path, all_projects, file_ext)
803
+ _clear_output_files(input_path, all_projects, output_format)
756
804
  if clear_output and not (from_date or to_date or input_path.is_file()):
757
805
  # If only clearing output files, exit after clearing
806
+ file_ext = get_file_extension(output_format)
758
807
  click.echo(f"{file_ext.upper()} files cleared successfully.")
759
808
  return
760
809