claude-code-log 1.3.0__tar.gz → 1.4.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 (227) hide show
  1. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/.github/workflows/ci.yml +6 -0
  2. claude_code_log-1.4.0/.github/workflows/docs.yml +68 -0
  3. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/.gitignore +4 -3
  4. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/CHANGELOG.md +48 -0
  5. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/CLAUDE.md +7 -0
  6. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/CONTRIBUTING.md +41 -0
  7. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/PKG-INFO +18 -3
  8. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/README.md +16 -1
  9. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/cli.py +247 -5
  10. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/converter.py +515 -550
  11. claude_code_log-1.4.0/claude_code_log/factories/priorities.py +46 -0
  12. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/tool_factory.py +303 -46
  13. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/user_factory.py +25 -0
  14. claude_code_log-1.4.0/claude_code_log/git_remote.py +343 -0
  15. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/renderer.py +409 -22
  16. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/edit_diff_styles.css +1 -1
  17. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/global_styles.css +1 -2
  18. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/message_styles.css +93 -10
  19. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/project_card_styles.css +32 -0
  20. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/session_nav.html +6 -1
  21. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/teammate_styles.css +6 -7
  22. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/todo_styles.css +19 -0
  23. claude_code_log-1.4.0/claude_code_log/html/templates/index.html +157 -0
  24. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/tool_formatters.py +143 -43
  25. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/utils.py +47 -0
  26. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/json/renderer.py +4 -1
  27. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/markdown/renderer.py +334 -23
  28. claude_code_log-1.4.0/claude_code_log/markdown_plugins.py +440 -0
  29. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/models.py +232 -4
  30. claude_code_log-1.4.0/claude_code_log/plugins.py +371 -0
  31. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/renderer.py +727 -355
  32. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/utils.py +272 -3
  33. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/application_model.md +22 -12
  34. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/dag.md +2 -3
  35. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/implementing-a-tool-renderer.md +37 -0
  36. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages.md +2 -0
  37. claude_code_log-1.4.0/dev-docs/plugins.md +741 -0
  38. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/rendering-architecture.md +50 -10
  39. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/justfile +22 -11
  40. claude_code_log-1.4.0/mkdocs.yml +87 -0
  41. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/pyproject.toml +16 -2
  42. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/uv.lock +330 -2
  43. claude_code_log-1.4.0/work/obsidian-friendly-output.md +81 -0
  44. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/work/refactor-reindex-with-ghosting.md +39 -6
  45. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/work/rendering-next.md +1 -34
  46. claude_code_log-1.4.0/work/simplify-converter-renderer.md +312 -0
  47. claude_code_log-1.4.0/work/tool-renderer-plugins.md +29 -0
  48. claude_code_log-1.3.0/claude_code_log/html/templates/index.html +0 -118
  49. claude_code_log-1.3.0/work/async-agents.md +0 -215
  50. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/.claude/settings.json +0 -0
  51. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/.claude/skills/tool-renderer/SKILL.md +0 -0
  52. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/.vscode/settings.json +0 -0
  53. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/LICENSE +0 -0
  54. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/__init__.py +0 -0
  55. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/cache.py +0 -0
  56. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/dag.py +0 -0
  57. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/__init__.py +0 -0
  58. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/agent_metadata_factory.py +0 -0
  59. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/assistant_factory.py +0 -0
  60. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/attachment_factory.py +0 -0
  61. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/meta_factory.py +0 -0
  62. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/system_factory.py +0 -0
  63. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/task_notification_factory.py +0 -0
  64. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/teammate_factory.py +0 -0
  65. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/factories/transcript_factory.py +0 -0
  66. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/__init__.py +0 -0
  67. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/ansi_colors.py +0 -0
  68. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/assistant_formatters.py +0 -0
  69. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/async_formatter.py +0 -0
  70. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/renderer_code.py +0 -0
  71. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/system_formatters.py +0 -0
  72. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/teammate_formatter.py +0 -0
  73. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/filter_styles.css +0 -0
  74. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/page_nav_styles.css +0 -0
  75. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/pygments_styles.css +0 -0
  76. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/search.html +0 -0
  77. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/search_inline.html +0 -0
  78. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/search_inline_script.html +0 -0
  79. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/search_results_panel.html +0 -0
  80. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/search_styles.css +0 -0
  81. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/session_nav_styles.css +0 -0
  82. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/timeline.html +0 -0
  83. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/timeline_styles.css +0 -0
  84. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/components/timezone_converter.js +0 -0
  85. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/templates/transcript.html +0 -0
  86. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/html/user_formatters.py +0 -0
  87. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/image_export.py +0 -0
  88. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/json/__init__.py +0 -0
  89. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/markdown/__init__.py +0 -0
  90. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/001_initial_schema.sql +0 -0
  91. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/002_html_cache.sql +0 -0
  92. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/003_html_pagination.sql +0 -0
  93. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/004_html_pagination_variant.sql +0 -0
  94. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/005_session_team_name.sql +0 -0
  95. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/006_session_ai_title.sql +0 -0
  96. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/__init__.py +0 -0
  97. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/migrations/runner.py +0 -0
  98. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/parser.py +0 -0
  99. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/py.typed +0 -0
  100. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/renderer_timings.py +0 -0
  101. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/claude_code_log/tui.py +0 -0
  102. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/agents.md +0 -0
  103. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/css-classes.md +0 -0
  104. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/message-hierarchy.md +0 -0
  105. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/assistant.json +0 -0
  106. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/assistant.jsonl +0 -0
  107. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/assistant_sidechain.json +0 -0
  108. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/assistant_sidechain.jsonl +0 -0
  109. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/thinking.json +0 -0
  110. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/assistant/thinking.jsonl +0 -0
  111. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/file_history_snapshot.json +0 -0
  112. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/file_history_snapshot.jsonl +0 -0
  113. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/queue_operation.json +0 -0
  114. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/queue_operation.jsonl +0 -0
  115. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/summary.json +0 -0
  116. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/summary.jsonl +0 -0
  117. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/system_info.json +0 -0
  118. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/system/system_info.jsonl +0 -0
  119. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.json +0 -0
  120. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.jsonl +0 -0
  121. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.json +0 -0
  122. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.jsonl +0 -0
  123. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.json +0 -0
  124. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.jsonl +0 -0
  125. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_result.json +0 -0
  126. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_result.jsonl +0 -0
  127. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_result_error.json +0 -0
  128. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_result_error.jsonl +0 -0
  129. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_use.json +0 -0
  130. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Bash-tool_use.jsonl +0 -0
  131. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/BashOutput-tool_result.json +0 -0
  132. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/BashOutput-tool_result.jsonl +0 -0
  133. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/BashOutput-tool_use.json +0 -0
  134. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/BashOutput-tool_use.jsonl +0 -0
  135. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_result.json +0 -0
  136. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_result.jsonl +0 -0
  137. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_result_error.json +0 -0
  138. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_result_error.jsonl +0 -0
  139. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_use.json +0 -0
  140. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Edit-tool_use.jsonl +0 -0
  141. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.json +0 -0
  142. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.jsonl +0 -0
  143. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.json +0 -0
  144. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.jsonl +0 -0
  145. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.json +0 -0
  146. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.jsonl +0 -0
  147. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Glob-tool_result.json +0 -0
  148. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Glob-tool_result.jsonl +0 -0
  149. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Glob-tool_use.json +0 -0
  150. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Glob-tool_use.jsonl +0 -0
  151. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Grep-tool_result.json +0 -0
  152. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Grep-tool_result.jsonl +0 -0
  153. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Grep-tool_use.json +0 -0
  154. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Grep-tool_use.jsonl +0 -0
  155. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_result.json +0 -0
  156. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_result.jsonl +0 -0
  157. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_result_error.json +0 -0
  158. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_result_error.jsonl +0 -0
  159. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_use.json +0 -0
  160. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/KillShell-tool_use.jsonl +0 -0
  161. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/LS-tool_result.json +0 -0
  162. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/LS-tool_result.jsonl +0 -0
  163. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/LS-tool_use.json +0 -0
  164. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/LS-tool_use.jsonl +0 -0
  165. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_result.json +0 -0
  166. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_result.jsonl +0 -0
  167. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.json +0 -0
  168. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.jsonl +0 -0
  169. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_use.json +0 -0
  170. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/MultiEdit-tool_use.jsonl +0 -0
  171. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_result.json +0 -0
  172. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_result.jsonl +0 -0
  173. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_result_error.json +0 -0
  174. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_result_error.jsonl +0 -0
  175. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_use.json +0 -0
  176. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Read-tool_use.jsonl +0 -0
  177. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Task-tool_result.json +0 -0
  178. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Task-tool_result.jsonl +0 -0
  179. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Task-tool_use.json +0 -0
  180. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Task-tool_use.jsonl +0 -0
  181. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/TodoWrite-tool_result.json +0 -0
  182. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/TodoWrite-tool_result.jsonl +0 -0
  183. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/TodoWrite-tool_use.json +0 -0
  184. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/TodoWrite-tool_use.jsonl +0 -0
  185. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebFetch-tool_result.json +0 -0
  186. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebFetch-tool_result.jsonl +0 -0
  187. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebFetch-tool_use.json +0 -0
  188. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebFetch-tool_use.jsonl +0 -0
  189. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebSearch-tool_result.json +0 -0
  190. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebSearch-tool_result.jsonl +0 -0
  191. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebSearch-tool_use.json +0 -0
  192. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/WebSearch-tool_use.jsonl +0 -0
  193. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_result.json +0 -0
  194. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_result.jsonl +0 -0
  195. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_result_error.json +0 -0
  196. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_result_error.jsonl +0 -0
  197. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_use.json +0 -0
  198. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/Write-tool_use.jsonl +0 -0
  199. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.json +0 -0
  200. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.jsonl +0 -0
  201. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.json +0 -0
  202. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.jsonl +0 -0
  203. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/bash_input.json +0 -0
  204. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/bash_input.jsonl +0 -0
  205. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/bash_output.json +0 -0
  206. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/bash_output.jsonl +0 -0
  207. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/command_output.json +0 -0
  208. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/command_output.jsonl +0 -0
  209. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/image.json +0 -0
  210. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/image.jsonl +0 -0
  211. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user.json +0 -0
  212. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user.jsonl +0 -0
  213. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_command.json +0 -0
  214. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_command.jsonl +0 -0
  215. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_sidechain.json +0 -0
  216. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_sidechain.jsonl +0 -0
  217. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_slash_command.json +0 -0
  218. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/messages/user/user_slash_command.jsonl +0 -0
  219. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/dev-docs/teammates.md +0 -0
  220. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/mise.toml +0 -0
  221. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/__init__.pyi +0 -0
  222. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/formatter.pyi +0 -0
  223. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/formatters/__init__.pyi +0 -0
  224. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/lexer.pyi +0 -0
  225. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/lexers/__init__.pyi +0 -0
  226. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/stubs/pygments/util.pyi +0 -0
  227. {claude_code_log-1.3.0 → claude_code_log-1.4.0}/work/session-state-propagation.md +0 -0
@@ -20,6 +20,12 @@ jobs:
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v4
23
+ with:
24
+ # Full history so test/test_commit_linkifier.py's
25
+ # TestIntegrationLocalRepo can resolve SHAs older than the
26
+ # current tip (`git branch -r --contains` reads local refs
27
+ # only and needs the commits in the object DB).
28
+ fetch-depth: 0
23
29
 
24
30
  - name: Install uv
25
31
  uses: astral-sh/setup-uv@v4
@@ -0,0 +1,68 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ # Allow the deploy job to publish to GitHub Pages.
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ # Avoid overlapping deploys; let an in-progress run finish.
16
+ concurrency:
17
+ group: pages
18
+ cancel-in-progress: false
19
+
20
+ env:
21
+ # Silence the Material-for-MkDocs vendor banner about a future MkDocs 2.0.
22
+ DISABLE_MKDOCS_2_WARNING: "true"
23
+
24
+ jobs:
25
+ build:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v5
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v8.2.0
32
+ with:
33
+ enable-cache: true
34
+
35
+ - name: Set up Python
36
+ run: uv python install 3.12
37
+
38
+ - name: Install docs dependencies
39
+ run: uv sync --group docs
40
+
41
+ - name: Build site (strict)
42
+ run: uv run mkdocs build --strict
43
+
44
+ - name: Upload site as artifact (downloadable preview)
45
+ uses: actions/upload-artifact@v7
46
+ with:
47
+ name: site-preview
48
+ path: site
49
+ retention-days: 14
50
+
51
+ - name: Upload Pages artifact
52
+ if: github.ref == 'refs/heads/main'
53
+ uses: actions/upload-pages-artifact@v3
54
+ with:
55
+ path: site
56
+
57
+ deploy:
58
+ # Only publish from main; PRs just run the strict build above.
59
+ if: github.ref == 'refs/heads/main'
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ environment:
63
+ name: github-pages
64
+ url: ${{ steps.deployment.outputs.page_url }}
65
+ steps:
66
+ - name: Deploy to GitHub Pages
67
+ id: deployment
68
+ uses: actions/deploy-pages@v4
@@ -147,6 +147,8 @@ venv.bak/
147
147
 
148
148
  # mkdocs documentation
149
149
  /site
150
+ # Locally-generated TUI screenshots (regenerated at build time by gen-files)
151
+ docs/assets/tui/
150
152
 
151
153
  # mypy
152
154
  .mypy_cache/
@@ -187,11 +189,10 @@ test/test_data/*.html
187
189
  .local.code-workspace
188
190
  local.ps1
189
191
 
190
- # Generated documentation examples (uploaded to GitHub releases instead)
191
- docs/claude-code-log-transcript.html
192
- docs/cache/
193
192
 
194
193
  # Integration test data - exclude generated cache/HTML, keep JSONL files
195
194
  test/test_data/real_projects/*/cache/
196
195
  test/test_data/real_projects/*/*.html
197
196
  test/test_data/real_projects/index.html
197
+ # SQLite cache the CLI drops into a projects dir when run against test data
198
+ claude-code-log-cache.db
@@ -6,6 +6,54 @@ 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.4.0] - 2026-06-03
10
+
11
+ ### Changed
12
+
13
+ - **Fix sed**
14
+ - **Add MkDocs documentation site with live TUI reference (#197)**
15
+ - **Relax Textual constraint from `==` to `>=` (#196)**
16
+ - **Add `--version` flag to the CLI (#195)**
17
+ - **Fix AskUserQuestion result rendering + highlight chosen options (#180) (#189)**
18
+ - **Derive render_session_id from the SessionTree, not a loop variable (#190)**
19
+ - **Fix collapsible body overlapping preceding content in tool cards (#153) (#187)**
20
+ - **Extract compute_session_data + compute_project_aggregates (C9b) (#188)**
21
+ - **Add C9a characterization tests for session-scan call sites (#186)**
22
+ - **Route converter summary + ai-title extraction through shared helpers (#185)**
23
+ - **status: Wave B fully merged; Wave C kickoff (C8/C9a/C9b stacked, C10 dropped, decisions locked)**
24
+ - **Compute branch preview once from the DAG-line (#184)**
25
+ - **status: #184 fully validated (CI 11/11, CodeRabbit clean) — ready to merge**
26
+ - **docs: keep simplification status note self-contained to project scope**
27
+ - **status: correct #184 state; move GitHub CI/CodeRabbit ops to github guideline**
28
+ - **status: reverse-order stacked-PR lift recipe CONFIRMED on #184**
29
+ - **Factor session-header construction out of _render_messages (#183)**
30
+ - **status: #183/#184 rebased + CodeRabbit forced (#183 clean, #184 2 doc fixes); record @coderabbitai + reverse-order workarounds**
31
+ - **Dedup requestId tokens in pagination cache-miss fallback (#182)**
32
+ - **status: Wave B track complete — opp 7 PR #184 up, all monk-approved; add merge sequence**
33
+ - **status: note stacked-PR CI/CodeRabbit defers to merge-time**
34
+ - **status: opp 1 green (#182, awaiting merge); opp 6 #183 in review; opp 7 in progress**
35
+ - **status: opp 1 -> PR #182 (in review); opp 6 in progress**
36
+ - **Add live-status section to simplification plan**
37
+ - **Add converter/renderer simplification plan**
38
+ - **Move detail-visibility predicate onto MessageContent (#181)**
39
+ - **Sync rendering-architecture.md §5 with the current pipeline (#178)**
40
+ - **Extract inline junction-forward-link block into a named pass (#177)**
41
+ - **Co-locate the away-summary detail rule on AwaySummaryMessage (#176)**
42
+ - **Remove vestigial progress-chain parent repair (#175)**
43
+ - **plugins: dev-docs gaps + public helper API + ToolResult example (Phase 2) (#173)**
44
+ - **Implement unified plugin system from RFC #166 (#169)**
45
+ - **Render Read tool results with pygments via structured payload (closes #170) (#172)**
46
+ - **work/: triage against shipped main (#171)**
47
+ - **Always regenerate projects index so variant-flag toggles refresh links (#168)**
48
+ - **RFC: plugin system (unified message-transformer mechanism) (#166)**
49
+ - **Per-message timestamps in Markdown output (#160) (#165)**
50
+ - **Support non-GitHub forges via static map + `--git-link` fallback (#156) (#164)**
51
+ - **Obsidian-friendly output: --output dir + --expand-paths + --filter-path (#151) (#155)**
52
+ - **Linkify commit SHAs in rendered Markdown + HTML — closes #156 (#161)**
53
+ - **CSS clean-ups (issue #153) (#163)**
54
+ - **Cross-link TaskOutput / TaskUpdate headers back to their spawn (#154) (#158)**
55
+
56
+
9
57
  ## [1.3.0] - 2026-05-14
10
58
 
11
59
  ### Changed
@@ -107,9 +107,16 @@ with pointers to the deep-dive docs:
107
107
  - [dev-docs/teammates.md](dev-docs/teammates.md) - Teammates feature deep-dive
108
108
  - [dev-docs/message-hierarchy.md](dev-docs/message-hierarchy.md) - Fold/unfold state machine
109
109
  - [dev-docs/implementing-a-tool-renderer.md](dev-docs/implementing-a-tool-renderer.md) - How-to: add a new tool
110
+ - [dev-docs/plugins.md](dev-docs/plugins.md) - Plugin system reference + author guide
110
111
 
111
112
  User-facing docs live in [docs/](docs/); plans and TODOs live in [work/](work/).
112
113
 
114
+ A documentation site (MkDocs + Material) is published to GitHub Pages from
115
+ `docs/` + `mkdocs.yml`. The CLI reference is rendered live from Click, and the
116
+ TUI reference (keybindings + screenshots) is auto-generated at build time. See
117
+ the "Documentation Site" section of @CONTRIBUTING.md. Preview with
118
+ `just docs-serve`; strict build with `just docs-build`.
119
+
113
120
  ### Keeping dev-docs/ in sync
114
121
 
115
122
  `dev-docs/` is **as-built reference** — the code is the authoritative
@@ -206,6 +206,47 @@ kill -USR1 $(pgrep -f claude-code-log | head -1)
206
206
 
207
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
208
 
209
+ ## Documentation Site
210
+
211
+ The project publishes a documentation site to GitHub Pages, built with
212
+ [MkDocs](https://www.mkdocs.org/) and the
213
+ [Material](https://squidfunk.github.io/mkdocs-material/) theme. The site
214
+ configuration is `mkdocs.yml`; pages live under `docs/`.
215
+
216
+ ```bash
217
+ # Install docs dependencies
218
+ uv sync --group docs
219
+
220
+ # Live-reload preview at http://127.0.0.1:8000
221
+ just docs-serve
222
+
223
+ # Strict build (fails on broken links/nav — same as CI)
224
+ just docs-build
225
+ ```
226
+
227
+ Key points:
228
+
229
+ - **CLI reference** (`docs/reference/cli.md`) is rendered live from the Click
230
+ command via the `mkdocs-click` plugin — no manual upkeep.
231
+ - **TUI reference** (`reference/tui.md`) is generated at build time by
232
+ `docs/gen_pages.py` (a `mkdocs-gen-files` script): it introspects the Textual
233
+ `BINDINGS` for the keybindings tables (`scripts/generate_tui_docs.py`) and
234
+ captures SVG screenshots of the running TUI
235
+ (`scripts/generate_tui_screenshots.py`). Both scripts are runnable standalone.
236
+ - **Example output** (`example.md` + `examples/transcript.html`) is rendered at
237
+ build time from a bundled sample project
238
+ (`scripts/generate_example_output.py`, also `just example`) — no private data
239
+ or release asset involved. Generation is fault-tolerant so a render hiccup
240
+ can't block the build.
241
+ - **Development** section surfaces `dev-docs/` (symlinked as `docs/development`).
242
+ `CONTRIBUTING.md` and `CHANGELOG.md` are symlinked in as `docs/contributing.md`
243
+ and `docs/changelog.md`. A build hook (`docs/hooks.py`) rewrites links to repo
244
+ source files (e.g. `../claude_code_log/cli.py`) into GitHub URLs so the strict
245
+ build stays green.
246
+ - Deployment is automated by `.github/workflows/docs.yml`: PRs run a strict
247
+ build; pushes to `main` deploy to Pages. The repo's **Settings → Pages →
248
+ Source** must be set to **GitHub Actions** (one-time).
249
+
209
250
  ## Architecture
210
251
 
211
252
  Start with [dev-docs/application_model.md](dev-docs/application_model.md)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-log
3
- Version: 1.3.0
3
+ Version: 1.4.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
@@ -18,7 +18,7 @@ Requires-Dist: mistune>=3.1.4
18
18
  Requires-Dist: packaging>=25.0
19
19
  Requires-Dist: pydantic>=2.12.0
20
20
  Requires-Dist: pygments>=2.19.2
21
- Requires-Dist: textual==6.5.0
21
+ Requires-Dist: textual>=6.5.0
22
22
  Requires-Dist: toml>=0.10.2
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -40,7 +40,7 @@ TUI demo:
40
40
 
41
41
  This tool generates clean, minimalist HTML pages showing user prompts and assistant responses chronologically. It's designed to create a readable log of your Claude Code interactions with support for both individual files and entire project hierarchies.
42
42
 
43
- 📄 **[View Example HTML Output](https://github.com/daaain/claude-code-log/releases/latest/download/claude-code-log-transcript.html)** - Download a real example generated from this project's development (large file, ~100 MB)
43
+ 📄 **[View Example HTML Output](https://daaain.github.io/claude-code-log/example/)** - A real example generated from a sample of this project's development, regenerated on every docs build
44
44
 
45
45
  ## Quickstart
46
46
 
@@ -188,6 +188,21 @@ claude-code-log /path/to/project --detail low --format md --compact
188
188
 
189
189
  `--compact` merges consecutive same-type sections in Markdown so runs of assistant responses share one heading instead of repeating `### 🤖 Assistant:` for each.
190
190
 
191
+ ### Linking Commit SHAs
192
+
193
+ Plain `7c2e6f6`-shaped tokens in transcript prose get turned into clickable commit links when the SHA is reachable from a local remote-tracking branch. **github.com**, **gitlab.com**, and **bitbucket.org** work out of the box. For self-hosted forges (in-house GitLab, Gitea, Forgejo, …), supply a URL template via `--git-link`:
194
+
195
+ ```bash
196
+ # Self-hosted GitLab
197
+ claude-code-log /path/to/transcript --git-link 'https://{host}/{path}/-/commit/{sha}'
198
+
199
+ # Same thing via env var (useful for TUI / repeated invocations)
200
+ export CLAUDE_CODE_LOG_GIT_LINK='https://{host}/{path}/-/commit/{sha}'
201
+ claude-code-log --tui
202
+ ```
203
+
204
+ Placeholders: `{host}`, `{path}`, `{sha}`. The template fires only when the static map doesn't already know the host, so a mix of GitHub repos + self-hosted GitLab gets correct links from both. SHAs not reachable from any local remote-tracking ref render as plain text — local-only work-in-progress commits never produce broken links.
205
+
191
206
  ## Project Hierarchy Output
192
207
 
193
208
  When processing all projects, the tool generates:
@@ -16,7 +16,7 @@ TUI demo:
16
16
 
17
17
  This tool generates clean, minimalist HTML pages showing user prompts and assistant responses chronologically. It's designed to create a readable log of your Claude Code interactions with support for both individual files and entire project hierarchies.
18
18
 
19
- 📄 **[View Example HTML Output](https://github.com/daaain/claude-code-log/releases/latest/download/claude-code-log-transcript.html)** - Download a real example generated from this project's development (large file, ~100 MB)
19
+ 📄 **[View Example HTML Output](https://daaain.github.io/claude-code-log/example/)** - A real example generated from a sample of this project's development, regenerated on every docs build
20
20
 
21
21
  ## Quickstart
22
22
 
@@ -164,6 +164,21 @@ claude-code-log /path/to/project --detail low --format md --compact
164
164
 
165
165
  `--compact` merges consecutive same-type sections in Markdown so runs of assistant responses share one heading instead of repeating `### 🤖 Assistant:` for each.
166
166
 
167
+ ### Linking Commit SHAs
168
+
169
+ Plain `7c2e6f6`-shaped tokens in transcript prose get turned into clickable commit links when the SHA is reachable from a local remote-tracking branch. **github.com**, **gitlab.com**, and **bitbucket.org** work out of the box. For self-hosted forges (in-house GitLab, Gitea, Forgejo, …), supply a URL template via `--git-link`:
170
+
171
+ ```bash
172
+ # Self-hosted GitLab
173
+ claude-code-log /path/to/transcript --git-link 'https://{host}/{path}/-/commit/{sha}'
174
+
175
+ # Same thing via env var (useful for TUI / repeated invocations)
176
+ export CLAUDE_CODE_LOG_GIT_LINK='https://{host}/{path}/-/commit/{sha}'
177
+ claude-code-log --tui
178
+ ```
179
+
180
+ Placeholders: `{host}`, `{path}`, `{sha}`. The template fires only when the static map doesn't already know the host, so a mix of GitHub repos + self-hosted GitLab gets correct links from both. SHAs not reachable from any local remote-tracking ref render as plain text — local-only work-in-progress commits never produce broken links.
181
+
167
182
  ## Project Hierarchy Output
168
183
 
169
184
  When processing all projects, the tool generates:
@@ -468,13 +468,99 @@ def _clear_output_files(
468
468
  click.echo(f"Warning: Failed to clear {ext_upper} files: {e}")
469
469
 
470
470
 
471
+ # Placeholders accepted by ``--git-link`` templates. Mirrors
472
+ # ``resolve_sha`` in claude_code_log.git_remote — keep in sync.
473
+ _GIT_LINK_ALLOWED_PLACEHOLDERS = frozenset({"host", "path", "sha"})
474
+
475
+
476
+ def _validate_git_link_template(template: str) -> None:
477
+ """Validate a ``--git-link`` template eagerly; raise ``click.UsageError`` on issues.
478
+
479
+ Two checks:
480
+
481
+ 1. ``{sha}`` must be present (it's the only mandatory field —
482
+ the resolver's whole job is to substitute the commit SHA).
483
+ 2. All placeholders must be in ``_GIT_LINK_ALLOWED_PLACEHOLDERS``.
484
+ Catches typos like ``{hsot}`` before they reach
485
+ ``template.format()`` (which would raise ``KeyError`` at
486
+ render time). The resolver has a try/except guarding the
487
+ env-var-only path; this validator is the loud-error path for
488
+ CLI users.
489
+
490
+ Uses ``string.Formatter().parse()`` rather than regex so the
491
+ same parser Python uses for ``str.format`` decides what counts
492
+ as a placeholder.
493
+ """
494
+ import string
495
+
496
+ parsed_fields = [
497
+ field
498
+ for _, field, _, _ in string.Formatter().parse(template)
499
+ if field is not None
500
+ ]
501
+ if "" in parsed_fields:
502
+ raise click.UsageError(
503
+ "--git-link template uses an anonymous positional placeholder ({}). "
504
+ "Use a named placeholder ({host}, {path}, or {sha}) instead "
505
+ f"(got: {template!r})."
506
+ )
507
+ fields = set(parsed_fields)
508
+ unknown = fields - _GIT_LINK_ALLOWED_PLACEHOLDERS
509
+ if unknown:
510
+ raise click.UsageError(
511
+ f"--git-link template uses unknown placeholder(s): "
512
+ f"{', '.join('{' + f + '}' for f in sorted(unknown))}. "
513
+ f"Allowed: {{host}}, {{path}}, {{sha}}."
514
+ )
515
+ if "sha" not in fields:
516
+ raise click.UsageError(
517
+ "--git-link template must contain a {sha} placeholder "
518
+ f"(got: {template!r}). Example: "
519
+ "'https://{host}/{path}/-/commit/{sha}'."
520
+ )
521
+
522
+
471
523
  @click.command()
524
+ @click.version_option(version=get_library_version(), prog_name="claude-code-log")
472
525
  @click.argument("input_path", type=click.Path(path_type=Path), required=False)
473
526
  @click.option(
474
527
  "-o",
475
528
  "--output",
476
529
  type=click.Path(path_type=Path),
477
- help="Output file path (default: input file with format extension, or combined_transcripts.{html,md} for directories)",
530
+ help=(
531
+ "Output destination. With a recognised file suffix "
532
+ "(.html/.md/.markdown/.json) treated as a single output file; "
533
+ "otherwise treated as a directory root (and now also honoured "
534
+ "for --all-projects, where outputs land at "
535
+ "<output>/<project>/...). Pair with --expand-paths to project "
536
+ "back to the real on-disk tree."
537
+ ),
538
+ )
539
+ @click.option(
540
+ "--expand-paths",
541
+ is_flag=True,
542
+ help=(
543
+ "When set with --output and --all-projects, expand each "
544
+ "project's flat encoded dir name (e.g. '-home-joe-project-A') "
545
+ "back to its real path under <output>/. Resolves the encoded "
546
+ "name via the cache's recorded `cwd`, falling back to a peek "
547
+ "of the first JSONL when the cache is empty. Useful for "
548
+ "projecting transcripts into Obsidian-style Markdown vaults."
549
+ ),
550
+ )
551
+ @click.option(
552
+ "--filter-path",
553
+ type=str,
554
+ default=None,
555
+ help=(
556
+ "Restrict --all-projects to projects matching a path prefix. "
557
+ "With --expand-paths, the prefix is matched against the "
558
+ "expanded real path AND truncated from the destination "
559
+ "(`/home/joe/project/A` with --filter-path /home/joe lands at "
560
+ "<output>/project/A/). Without --expand-paths, matches the "
561
+ "flat encoded dir name (e.g. '-home-joe' selects projects "
562
+ "starting with '-home-joe-')."
563
+ ),
478
564
  )
479
565
  @click.option(
480
566
  "--open-browser",
@@ -499,7 +585,25 @@ def _clear_output_files(
499
585
  @click.option(
500
586
  "--no-individual-sessions",
501
587
  is_flag=True,
502
- help="Skip generating individual session HTML files (only create combined transcript)",
588
+ help=(
589
+ "Skip generating individual session files (combined transcript only). "
590
+ "Back-compat alias for --combined only."
591
+ ),
592
+ )
593
+ @click.option(
594
+ "--combined",
595
+ "combined",
596
+ type=click.Choice(["yes", "no", "only"], case_sensitive=False),
597
+ default=None,
598
+ help=(
599
+ "Control combined-vs-individual transcript generation: "
600
+ "'yes' = both combined and per-session files (default for --all-projects); "
601
+ "'no' = only per-session files (recommended for Obsidian / vault use — "
602
+ "combined is dead weight); "
603
+ "'only' = only the combined file (= --no-individual-sessions). "
604
+ "When unset, defaults to 'no' under --expand-paths (Obsidian mode), "
605
+ "else 'yes'."
606
+ ),
503
607
  )
504
608
  @click.option(
505
609
  "--no-cache",
@@ -578,6 +682,29 @@ def _clear_output_files(
578
682
  "Markdown-only — a no-op for HTML."
579
683
  ),
580
684
  )
685
+ @click.option(
686
+ "--git-link",
687
+ "git_link",
688
+ default=None,
689
+ envvar="CLAUDE_CODE_LOG_GIT_LINK",
690
+ metavar="TEMPLATE",
691
+ help=(
692
+ "URL template for resolving commit SHAs on forges not in the built-in "
693
+ "map (github.com, gitlab.com, bitbucket.org). Placeholders: {host}, "
694
+ "{path}, {sha}. Example for self-hosted GitLab: "
695
+ "--git-link 'https://{host}/{path}/-/commit/{sha}'. Can also be set "
696
+ "via the CLAUDE_CODE_LOG_GIT_LINK env var."
697
+ ),
698
+ )
699
+ @click.option(
700
+ "--no-timestamps",
701
+ is_flag=True,
702
+ help=(
703
+ "Suppress per-message timestamp lines in Markdown output "
704
+ "(#160). Markdown-only — a warning is emitted (but not an "
705
+ "error) if combined with --format html / --format json."
706
+ ),
707
+ )
581
708
  @click.option(
582
709
  "--debug",
583
710
  is_flag=True,
@@ -587,6 +714,9 @@ def _clear_output_files(
587
714
  def main(
588
715
  input_path: Optional[Path],
589
716
  output: Optional[Path],
717
+ expand_paths: bool,
718
+ filter_path: Optional[str],
719
+ combined: Optional[str],
590
720
  open_browser: bool,
591
721
  from_date: Optional[str],
592
722
  to_date: Optional[str],
@@ -603,6 +733,8 @@ def main(
603
733
  session_id: Optional[str],
604
734
  detail: str,
605
735
  compact: bool,
736
+ git_link: Optional[str],
737
+ no_timestamps: bool,
606
738
  debug: bool,
607
739
  ) -> None:
608
740
  """Convert Claude transcript JSONL files to HTML or Markdown.
@@ -613,9 +745,101 @@ def main(
613
745
  # can be diagnosed with `kill -USR1 <pid>` without root or restart.
614
746
  _install_stack_dump_signal()
615
747
 
748
+ # Custom-forge URL template: validate eagerly with a loud error,
749
+ # then pin to the env var so the resolver (which reads the env at
750
+ # render time) picks it up. Doing this at env-var level keeps the
751
+ # resolver decoupled from Click; the env var is the underlying
752
+ # contract, the CLI flag is a convenience that sets it.
753
+ if git_link is not None:
754
+ _validate_git_link_template(git_link)
755
+ os.environ["CLAUDE_CODE_LOG_GIT_LINK"] = git_link
756
+
616
757
  # Configure logging to show warnings and above
617
758
  logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
618
759
 
760
+ # Resolve --combined default and back-compat with --no-individual-sessions.
761
+ # `--combined` semantics:
762
+ # yes → write combined transcript AND per-session files
763
+ # no → write per-session files only (Obsidian-friendly)
764
+ # only → write combined transcript only (= --no-individual-sessions)
765
+ # Default: yes, except when --expand-paths is set (Obsidian mode → no).
766
+ if combined is None:
767
+ combined = "no" if expand_paths else "yes"
768
+ else:
769
+ combined = combined.lower()
770
+ if no_individual_sessions:
771
+ if combined == "no":
772
+ raise click.BadParameter(
773
+ "--no-individual-sessions conflicts with --combined no "
774
+ "(both attempt to skip per-session files but --no-individual-sessions "
775
+ "implies combined-only). Pick one.",
776
+ param_hint="--no-individual-sessions",
777
+ )
778
+ # `--no-individual-sessions` is a strict alias for `--combined only`;
779
+ # honour it for back-compat (and prefer this over an unset --combined).
780
+ combined = "only"
781
+ # Derived flags actually consumed downstream.
782
+ write_combined = combined in ("yes", "only")
783
+ write_individual = combined in ("yes", "no")
784
+
785
+ # Loud rejection of relative `--filter-path` when paired with
786
+ # `--expand-paths` (#151). Without this, a user typing
787
+ # `--filter-path home/joe` (forgetting the leading `/`) would
788
+ # match against an absolute resolved path via `Path.relative_to`,
789
+ # which raises ValueError for *any* mismatch including
790
+ # "argument is relative" — so the silent failure mode is "every
791
+ # project skipped". Reject up-front instead.
792
+ #
793
+ # `path_looks_absolute` is host-OS-agnostic (accepts POSIX `/`
794
+ # OR Windows `C:\` form), so a Linux-recorded `/home/joe`
795
+ # processed on Windows still passes the guard.
796
+ from .utils import path_looks_absolute as _path_looks_absolute
797
+
798
+ if filter_path and expand_paths and not _path_looks_absolute(filter_path):
799
+ raise click.BadParameter(
800
+ f"--filter-path must be an absolute path when --expand-paths is set; "
801
+ f"got {filter_path!r}",
802
+ param_hint="--filter-path",
803
+ )
804
+
805
+ # Warn early if Obsidian-friendly flags (#151) were passed in a
806
+ # context where they're no-ops. `--all-projects` (explicit or
807
+ # implicit via no input_path) is the only mode that consumes them;
808
+ # `--output` must be a directory (file-suffixed output goes
809
+ # through the single-file path which doesn't honour these flags).
810
+ from .utils import output_path_is_file as _output_path_is_file
811
+
812
+ will_run_all_projects = all_projects or input_path is None
813
+ if (expand_paths or filter_path) and tui:
814
+ click.echo(
815
+ "Warning: --expand-paths / --filter-path are ignored in --tui mode.",
816
+ err=True,
817
+ )
818
+ elif (expand_paths or filter_path) and not will_run_all_projects:
819
+ click.echo(
820
+ "Warning: --expand-paths / --filter-path require --all-projects "
821
+ "(or omitting INPUT_PATH); ignoring.",
822
+ err=True,
823
+ )
824
+ elif (expand_paths or filter_path) and (
825
+ output is None or _output_path_is_file(output)
826
+ ):
827
+ click.echo(
828
+ "Warning: --expand-paths / --filter-path require --output to be a "
829
+ "directory (no recognised file suffix); ignoring.",
830
+ err=True,
831
+ )
832
+
833
+ # `--no-timestamps` is Markdown-only (#160). Warn (not error) when
834
+ # paired with HTML/JSON so the flag is benignly ignored rather than
835
+ # silently misapplied.
836
+ if no_timestamps and output_format not in ("md", "markdown"):
837
+ click.echo(
838
+ f"Warning: --no-timestamps is Markdown-only; ignoring under "
839
+ f"--format {output_format}.",
840
+ err=True,
841
+ )
842
+
619
843
  from .models import DetailLevel
620
844
 
621
845
  detail_level = DetailLevel(detail.lower())
@@ -779,6 +1003,7 @@ def main(
779
1003
  image_export_mode,
780
1004
  detail=detail_level,
781
1005
  compact=compact,
1006
+ no_timestamps=no_timestamps,
782
1007
  )
783
1008
  click.echo(f"Successfully exported session to {output_path}")
784
1009
  if open_browser:
@@ -813,17 +1038,32 @@ def main(
813
1038
  raise FileNotFoundError(f"Projects directory not found: {input_path}")
814
1039
 
815
1040
  click.echo(f"Processing all projects in {input_path}...")
1041
+ # `--output` for `--all-projects` (#151): pass a *directory*
1042
+ # to project per-project outputs into. File-suffixed values
1043
+ # are routed to the single-file path elsewhere; here we
1044
+ # only honour directory-shaped `--output`.
1045
+ from .utils import output_path_is_file
1046
+
1047
+ output_dir_for_projects: Optional[Path] = None
1048
+ if output is not None and not output_path_is_file(output):
1049
+ output_dir_for_projects = output
1050
+
816
1051
  output_path = process_projects_hierarchy(
817
1052
  input_path,
818
1053
  from_date,
819
1054
  to_date,
820
1055
  not no_cache,
821
- not no_individual_sessions,
1056
+ write_individual,
822
1057
  output_format,
823
1058
  image_export_mode,
824
1059
  page_size=page_size,
825
1060
  detail=detail_level,
826
1061
  compact=compact,
1062
+ output_dir=output_dir_for_projects,
1063
+ expand_paths=expand_paths,
1064
+ filter_path=filter_path,
1065
+ write_combined=write_combined,
1066
+ no_timestamps=no_timestamps,
827
1067
  )
828
1068
 
829
1069
  # Count processed projects
@@ -872,7 +1112,7 @@ def main(
872
1112
  output,
873
1113
  from_date,
874
1114
  to_date,
875
- not no_individual_sessions,
1115
+ write_individual,
876
1116
  not no_cache,
877
1117
  image_export_mode=image_export_mode,
878
1118
  page_size=page_size,
@@ -881,12 +1121,14 @@ def main(
881
1121
  # User's `-o` path is a one-off export, not a cached artifact:
882
1122
  # don't occupy a cache slot keyed by an arbitrary destination.
883
1123
  update_cache=output is None,
1124
+ write_combined=write_combined,
1125
+ no_timestamps=no_timestamps,
884
1126
  )
885
1127
  if input_path.is_file():
886
1128
  click.echo(f"Successfully converted {input_path} to {output_path}")
887
1129
  else:
888
1130
  jsonl_count = len(list(input_path.glob("*.jsonl")))
889
- if not no_individual_sessions:
1131
+ if write_individual:
890
1132
  ext = get_file_extension(output_format)
891
1133
  session_files = list(input_path.glob(f"session-*.{ext}"))
892
1134
  click.echo(