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.
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.claude/skills/tool-renderer/SKILL.md +27 -1
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.github/workflows/ci.yml +3 -3
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CHANGELOG.md +33 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CLAUDE.md +29 -6
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/CONTRIBUTING.md +31 -8
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/PKG-INFO +14 -2
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/README.md +13 -1
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/cache.py +75 -6
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/cli.py +62 -13
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/converter.py +440 -44
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/dag.py +313 -70
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/__init__.py +21 -0
- claude_code_log-1.3.0/claude_code_log/factories/agent_metadata_factory.py +107 -0
- claude_code_log-1.3.0/claude_code_log/factories/attachment_factory.py +126 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/meta_factory.py +2 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/system_factory.py +52 -4
- claude_code_log-1.3.0/claude_code_log/factories/task_notification_factory.py +161 -0
- claude_code_log-1.3.0/claude_code_log/factories/teammate_factory.py +140 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/tool_factory.py +520 -2
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/transcript_factory.py +4 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/user_factory.py +104 -3
- claude_code_log-1.3.0/claude_code_log/html/async_formatter.py +175 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/renderer.py +514 -13
- claude_code_log-1.3.0/claude_code_log/html/system_formatters.py +299 -0
- claude_code_log-1.3.0/claude_code_log/html/teammate_formatter.py +568 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/filter_styles.css +4 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/global_styles.css +87 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/message_styles.css +171 -4
- claude_code_log-1.3.0/claude_code_log/html/templates/components/teammate_styles.css +374 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timeline.html +23 -3
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/index.html +7 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/transcript.html +81 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/tool_formatters.py +235 -1
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/user_formatters.py +42 -5
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/utils.py +164 -6
- claude_code_log-1.3.0/claude_code_log/json/__init__.py +5 -0
- claude_code_log-1.3.0/claude_code_log/json/renderer.py +222 -0
- claude_code_log-1.3.0/claude_code_log/markdown/renderer.py +1875 -0
- claude_code_log-1.3.0/claude_code_log/migrations/005_session_team_name.sql +14 -0
- claude_code_log-1.3.0/claude_code_log/migrations/006_session_ai_title.sql +14 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/models.py +716 -7
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/renderer.py +1387 -96
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/tui.py +28 -16
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/utils.py +18 -16
- claude_code_log-1.3.0/dev-docs/agents.md +191 -0
- claude_code_log-1.3.0/dev-docs/application_model.md +448 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/css-classes.md +5 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/dag.md +119 -6
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/implementing-a-tool-renderer.md +17 -0
- 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
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages.md +80 -5
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/rendering-architecture.md +43 -8
- claude_code_log-1.3.0/dev-docs/teammates.md +978 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/justfile +18 -20
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/pyproject.toml +6 -1
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/uv.lock +1 -1
- claude_code_log-1.3.0/work/async-agents.md +215 -0
- claude_code_log-1.3.0/work/refactor-reindex-with-ghosting.md +164 -0
- {claude_code_log-1.2.0/dev-docs → claude_code_log-1.3.0/work}/rendering-next.md +2 -2
- claude_code_log-1.2.0/.github/workflows/claude.yml +0 -67
- claude_code_log-1.2.0/claude_code_log/html/system_formatters.py +0 -141
- claude_code_log-1.2.0/claude_code_log/markdown/renderer.py +0 -1010
- claude_code_log-1.2.0/dev-docs/restoring-archived-sessions.md +0 -105
- claude_code_log-1.2.0/work/phase-c-agent-transcripts.md +0 -87
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.claude/settings.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.gitignore +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/.vscode/settings.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/LICENSE +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/__init__.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/factories/assistant_factory.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/__init__.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/ansi_colors.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/assistant_formatters.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/renderer_code.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/edit_diff_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/page_nav_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/project_card_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/pygments_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search.html +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_inline.html +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_inline_script.html +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_results_panel.html +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/search_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/session_nav.html +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/session_nav_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timeline_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/timezone_converter.js +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/html/templates/components/todo_styles.css +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/image_export.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/markdown/__init__.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/001_initial_schema.sql +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/002_html_cache.sql +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/003_html_pagination.sql +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/004_html_pagination_variant.sql +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/__init__.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/migrations/runner.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/parser.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/py.typed +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/claude_code_log/renderer_timings.py +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant_sidechain.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/assistant_sidechain.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/thinking.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/assistant/thinking.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/file_history_snapshot.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/file_history_snapshot.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/queue_operation.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/queue_operation.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/summary.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/summary.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/system_info.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/system/system_info.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/AskUserQuestion-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Bash-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/BashOutput-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Edit-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/ExitPlanMode-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Glob-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Grep-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/KillShell-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/LS-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/MultiEdit-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Read-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Task-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/TodoWrite-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebFetch-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/WebSearch-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result_error.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_result_error.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/Write-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_result.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/tools/exit_plan_mode-tool_use.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_input.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_input.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_output.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/bash_output.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/command_output.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/command_output.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/image.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/image.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_command.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_command.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_sidechain.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_sidechain.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_slash_command.json +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/dev-docs/messages/user/user_slash_command.jsonl +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/mise.toml +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/__init__.pyi +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/formatter.pyi +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/formatters/__init__.pyi +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/lexer.pyi +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/lexers/__init__.pyi +0 -0
- {claude_code_log-1.2.0 → claude_code_log-1.3.0}/stubs/pygments/util.pyi +0 -0
- {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 -
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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
|
-
**
|
|
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 -
|
|
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 -
|
|
63
|
+
# or: uv run pytest -m tui
|
|
64
64
|
|
|
65
65
|
# Browser tests
|
|
66
66
|
just test-browser
|
|
67
|
-
# or: uv run pytest -
|
|
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 -
|
|
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
|
-
|
|
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 -
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
364
|
-
"""
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|