claude-code-log 0.3.2__tar.gz → 0.3.3__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 (32) hide show
  1. claude_code_log-0.3.2/.claude/settings.json.bak → claude_code_log-0.3.3/.claude/settings.json +4 -4
  2. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/.gitignore +1 -0
  3. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/CHANGELOG.md +8 -0
  4. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/PKG-INFO +30 -1
  5. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/README.md +29 -0
  6. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/timeline.html +34 -37
  7. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/timeline_styles.css +23 -5
  8. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/transcript.html +6 -2
  9. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/pyproject.toml +1 -1
  10. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/uv.lock +1 -1
  11. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/.claude/settings.local.json +0 -0
  12. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/.github/workflows/ci.yml +0 -0
  13. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/.github/workflows/docs.yml +0 -0
  14. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/CLAUDE.md +0 -0
  15. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/LICENSE +0 -0
  16. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/__init__.py +0 -0
  17. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/cli.py +0 -0
  18. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/converter.py +0 -0
  19. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/models.py +0 -0
  20. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/parser.py +0 -0
  21. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/py.typed +0 -0
  22. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/renderer.py +0 -0
  23. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/filter_styles.css +0 -0
  24. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/global_styles.css +0 -0
  25. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/message_styles.css +0 -0
  26. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/project_card_styles.css +0 -0
  27. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/session_nav.html +0 -0
  28. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/session_nav_styles.css +0 -0
  29. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/components/todo_styles.css +0 -0
  30. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/templates/index.html +0 -0
  31. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/claude_code_log/utils.py +0 -0
  32. {claude_code_log-0.3.2 → claude_code_log-0.3.3}/justfile +0 -0
@@ -17,19 +17,19 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "uv run ruff check --fix"
20
+ "command": "uv run ruff check --fix 2>&1"
21
21
  },
22
22
  {
23
23
  "type": "command",
24
- "command": "uv run ty check"
24
+ "command": "uv run ty check 2>&1"
25
25
  },
26
26
  {
27
27
  "type": "command",
28
- "command": "uv run pyright"
28
+ "command": "uv run pyright 2>&1"
29
29
  },
30
30
  {
31
31
  "type": "command",
32
- "command": "uv run pytest"
32
+ "command": "uv run pytest 2>&1"
33
33
  }
34
34
  ]
35
35
  }
@@ -178,3 +178,4 @@ test_output
178
178
  .DS_Store
179
179
  test/test_data/*.html
180
180
  .claude-trace
181
+ .specstory
@@ -6,6 +6,14 @@ 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
+ ## [0.3.3] - 2025-07-05
10
+
11
+ ### Changed
12
+
13
+ - **Hide groups in the timeline instead of items + bug fixes**
14
+ - **Get tooltip config working + improve rendering and styling**
15
+
16
+
9
17
  ## [0.3.2] - 2025-07-03
10
18
 
11
19
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-log
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -273,6 +273,32 @@ uv run claude-code-log
273
273
 
274
274
  ## TODO
275
275
 
276
+ - handle new(?) message format
277
+
278
+ ```sh
279
+ 9 validation errors for UserTranscriptEntry
280
+ toolUseResult.str
281
+ Input should be a valid string [type=string_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
282
+ toolUseResult.list[TodoItem].0.id
283
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
284
+ toolUseResult.list[TodoItem].0.content
285
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
286
+ toolUseResult.list[TodoItem].0.status
287
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
288
+ toolUseResult.list[TodoItem].0.priority
289
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
290
+ toolUseResult.FileReadResult
291
+ Input should be a valid dictionary or instance of FileReadResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
292
+ toolUseResult.CommandResult
293
+ Input should be a valid dictionary or instance of CommandResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
294
+ toolUseResult.TodoResult
295
+ Input should be a valid dictionary or instance of TodoResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
296
+ toolUseResult.EditResult
297
+ Input should be a valid dictionary or instance of EditResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
298
+ ```
299
+
300
+ - add a bit of padding to the last message time so the timeline doesn't look empty when opening
301
+ - tutorial overlay
276
302
  - integrate `claude-trace` request logs if present?
277
303
  - Shortcut / command to resume a specific conversation by session ID $ claude --resume 550e8400-e29b-41d4-a716-446655440000?
278
304
  - Localised number formatting and timezone adjustment runtime? For this we'd need to make Jinja template variables more granular
@@ -285,3 +311,6 @@ uv run claude-code-log
285
311
  - system blocks like `init` also don't render perfectly, losing new lines
286
312
  - add `ccusage` like daily summary and maybe some textual summary too based on Claude generate session summaries?
287
313
  – import logs from @claude Github Actions
314
+ - stream logs from @claude Github Actions, see [octotail](https://github.com/getbettr/octotail)
315
+ - extend into a VS Code extension that reads the JSONL real-time and displays stats like current context usage and implements a UI to see messages, todos, permissions, config, MCP status, etc
316
+ - feed the filtered user messages to headless claude CLI to distill the user intent from the session
@@ -253,6 +253,32 @@ uv run claude-code-log
253
253
 
254
254
  ## TODO
255
255
 
256
+ - handle new(?) message format
257
+
258
+ ```sh
259
+ 9 validation errors for UserTranscriptEntry
260
+ toolUseResult.str
261
+ Input should be a valid string [type=string_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
262
+ toolUseResult.list[TodoItem].0.id
263
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
264
+ toolUseResult.list[TodoItem].0.content
265
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
266
+ toolUseResult.list[TodoItem].0.status
267
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
268
+ toolUseResult.list[TodoItem].0.priority
269
+ Field required [type=missing, input_value={'type': 'text', 'text': ... it could be acquired.'}, input_type=dict]
270
+ toolUseResult.FileReadResult
271
+ Input should be a valid dictionary or instance of FileReadResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
272
+ toolUseResult.CommandResult
273
+ Input should be a valid dictionary or instance of CommandResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
274
+ toolUseResult.TodoResult
275
+ Input should be a valid dictionary or instance of TodoResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
276
+ toolUseResult.EditResult
277
+ Input should be a valid dictionary or instance of EditResult [type=model_type, input_value=[{'type': 'text', 'text':...it could be acquired.'}], input_type=list]
278
+ ```
279
+
280
+ - add a bit of padding to the last message time so the timeline doesn't look empty when opening
281
+ - tutorial overlay
256
282
  - integrate `claude-trace` request logs if present?
257
283
  - Shortcut / command to resume a specific conversation by session ID $ claude --resume 550e8400-e29b-41d4-a716-446655440000?
258
284
  - Localised number formatting and timezone adjustment runtime? For this we'd need to make Jinja template variables more granular
@@ -265,3 +291,6 @@ uv run claude-code-log
265
291
  - system blocks like `init` also don't render perfectly, losing new lines
266
292
  - add `ccusage` like daily summary and maybe some textual summary too based on Claude generate session summaries?
267
293
  – import logs from @claude Github Actions
294
+ - stream logs from @claude Github Actions, see [octotail](https://github.com/getbettr/octotail)
295
+ - extend into a VS Code extension that reads the JSONL real-time and displays stats like current context usage and implements a UI to see messages, todos, permissions, config, MCP status, etc
296
+ - feed the filtered user messages to headless claude CLI to distill the user intent from the session
@@ -27,9 +27,9 @@
27
27
  'tool_use': { id: 'tool_use', content: '🛠️ Tool Use', style: 'background-color: #fff3e0;' },
28
28
  'tool_result': { id: 'tool_result', content: '🧰 Tool Result', style: 'background-color: #e8f5e8;' },
29
29
  'thinking': { id: 'thinking', content: '💭 Thinking', style: 'background-color: #fce4ec;' },
30
- 'system': { id: 'system', content: '⚙️ System', style: 'background-color: #f5f5f5;' },
30
+ 'system': { id: 'system', content: '⚙️ System', style: 'background-color: #ffeee1;' },
31
31
  'image': { id: 'image', content: '🖼️ Image', style: 'background-color: #e1f5fe;' },
32
- 'sidechain': { id: 'sidechain', content: '🔗 Sub-assistant', style: 'background-color: #e8f5e8;' }
32
+ 'sidechain': { id: 'sidechain', content: '🔗 Sub-assistant', style: 'background-color: #f5f5f5;' }
33
33
  };
34
34
 
35
35
  // Build timeline data from messages
@@ -42,7 +42,7 @@
42
42
  // Clear existing mapping
43
43
  timelineIdToElement.clear();
44
44
 
45
- // Get all messages from the page
45
+ // Get all messages from the page (including filtered ones - we'll hide them with CSS)
46
46
  const messages = document.querySelectorAll('.message:not(.session-header)');
47
47
 
48
48
  messages.forEach((messageEl, index) => {
@@ -114,13 +114,17 @@
114
114
  // Store mapping for click handling
115
115
  timelineIdToElement.set(index, messageEl);
116
116
 
117
- // Strip content to make tooltip formatting more uniform
118
- // TODO: this is very basic and TodoWrites have weird spacing so will need some more work
119
- let title = contentEl.innerHTML.includes("<pre") ? contentEl.innerHTML : `<pre>${contentEl.innerHTML}</pre>`
117
+ // Format tooltip content with proper containment and styling
118
+ let title = contentEl.innerHTML;
119
+ title = title.includes("<pre") ? title : `<pre>${title}</pre>`;
120
+
121
+ // Clean up collapsible details for tooltip display
120
122
  if (title.includes("<details")) {
121
123
  title = title.replace(/(<summary>.*<\/summary>)/gs, '').replace(/<details class="collapsible-details">(.*?)<\/details>/gs, (m, p) => p)
122
124
  }
123
- title = title.replace(/<pre>[\s\r\n]+(.*?)[\s\r\n]+<\/pre>/gs, (m, p) => `<pre>${p}</pre>`)
125
+
126
+ // Clean up excessive whitespace in pre tags
127
+ title = title.replace(/<pre([^>]*)>[\s\r\n]+(.*?)[\s\r\n]+<\/pre>/gs, (m, attrs, content) => `<pre${attrs}>${content}</pre>`)
124
128
 
125
129
  // Adjust content display based on message type
126
130
  let displayContent = content || messageTypeGroups[messageType].content;
@@ -150,11 +154,6 @@
150
154
  content: displayContent,
151
155
  start: timestamp,
152
156
  group: messageType,
153
- tooltip: {
154
- // FIXME: This followMouse doesn't work for some reason and the tooltip box gets cut off for the bottom timeline boxes
155
- followMouse: true,
156
- overflowMethod: 'cap'
157
- },
158
157
  title,
159
158
  className: `timeline-item-${messageType}`
160
159
  };
@@ -166,37 +165,25 @@
166
165
  const timelineEnd = new Date(latestTimeString);
167
166
  const timelineStart = new Date(timelineEnd.getTime() - 60 * 60 * 1000); // 1 hour before latest
168
167
 
169
- return { items: timelineItems, groups: timelineGroups, timelineEnd, timelineStart };
168
+ return { timelineItems, timelineGroups, timelineEnd, timelineStart };
170
169
  }
171
170
 
172
171
  // Filter timeline items based on current message filters
173
172
  function applyFilters() {
174
- if (!timeline || !items) return;
173
+ if (!timeline || !groups) return;
175
174
 
176
- // Get active filter types
177
- const filterToggles = document.querySelectorAll('.filter-toggle');
178
- const activeTypes = [];
179
- let hasActiveFilters = false;
175
+ // Get active filter types from filter toggles
176
+ const activeTypes = Array.from(document.querySelectorAll('.filter-toggle.active'))
177
+ .map(toggle => toggle.dataset.type);
180
178
 
181
- filterToggles.forEach(toggle => {
182
- if (toggle.classList.contains('active')) {
183
- activeTypes.push(toggle.dataset.type);
184
- hasActiveFilters = true;
185
- }
186
- });
179
+ // Update groups visibility based on filter states
180
+ const updatedGroups = groups.map(group => ({
181
+ ...group,
182
+ visible: activeTypes.includes(group.id)
183
+ }));
187
184
 
188
- // If no filters are active, show all items
189
- if (!hasActiveFilters) {
190
- timeline.setItems(items);
191
- return;
192
- }
193
-
194
- // Filter items based on active types
195
- const filteredItems = items.get().filter(item => {
196
- return activeTypes.includes(item.group);
197
- });
198
-
199
- timeline.setItems(new vis.DataSet(filteredItems));
185
+ // Update timeline groups
186
+ timeline.setGroups(updatedGroups);
200
187
  }
201
188
 
202
189
  // Handle timeline item click - scroll to corresponding message
@@ -241,7 +228,9 @@
241
228
  }
242
229
 
243
230
  // Build timeline data
244
- const { items, groups, timelineEnd, timelineStart } = buildTimelineData();
231
+ const { timelineItems, timelineGroups, timelineEnd, timelineStart } = buildTimelineData();
232
+ items = timelineItems
233
+ groups = timelineGroups
245
234
  if (items.length === 0) {
246
235
  console.warn('No timeline items found');
247
236
  return;
@@ -258,6 +247,11 @@
258
247
  end: timelineEnd,
259
248
  orientation: 'top',
260
249
  align: 'left',
250
+ tooltip: {
251
+ // FIXME: This followMouse doesn't work for some reason and the tooltip box gets cut off for the bottom timeline boxes
252
+ followMouse: true,
253
+ overflowMethod: 'cap'
254
+ },
261
255
  margin: {
262
256
  item: 2,
263
257
  axis: 2
@@ -271,6 +265,9 @@
271
265
  // Create timeline
272
266
  timeline = new vis.Timeline(container, new vis.DataSet(items), new vis.DataSet(groups), options);
273
267
 
268
+ // Make timeline available globally for debugging
269
+ window.timeline = timeline;
270
+
274
271
  // Add event listeners
275
272
  timeline.on('select', onTimelineSelect);
276
273
 
@@ -75,8 +75,8 @@
75
75
  }
76
76
 
77
77
  .vis-item.timeline-item-tool_use {
78
- background-color: #fff3e0 !important;
79
- border-color: #ff9800 !important;
78
+ background-color: #fff8e1 !important;
79
+ border-color: #ffc107 !important;
80
80
  }
81
81
 
82
82
  .vis-item.timeline-item-tool_result {
@@ -90,8 +90,8 @@
90
90
  }
91
91
 
92
92
  .vis-item.timeline-item-system {
93
- background-color: #f5f5f5 !important;
94
- border-color: #9e9e9e !important;
93
+ background-color: #ffeee1 !important;
94
+ border-color: #ff8707 !important;
95
95
  }
96
96
 
97
97
  .vis-item.timeline-item-image {
@@ -99,6 +99,16 @@
99
99
  border-color: #00bcd4 !important;
100
100
  }
101
101
 
102
+ .vis-item.timeline-item-sidechain {
103
+ background-color: #f5f5f5 !important;
104
+ border-color: #9e9e9e !important;
105
+ }
106
+
107
+ /* Hide filtered timeline items */
108
+ .vis-item.timeline-filtered-hidden {
109
+ display: none !important;
110
+ }
111
+
102
112
  /* Timeline axis styling */
103
113
  .vis-time-axis {
104
114
  border-top: 1px solid #ddd !important;
@@ -121,13 +131,21 @@
121
131
 
122
132
  .vis-tooltip {
123
133
  max-width: 700px;
124
- padding: 0 !important;
134
+ padding: 1em !important;
135
+ white-space: normal !important;
136
+ font-family: inherit !important;
125
137
  }
126
138
 
127
139
  .vis-tooltip pre {
128
140
  margin: 0;
141
+ padding: 0;
142
+ background-color: transparent;
129
143
  }
130
144
 
131
145
  .vis-tooltip img {
132
146
  max-width: 700px;
147
+ }
148
+
149
+ .vis-tooltip div {
150
+ white-space: normal;
133
151
  }
@@ -196,8 +196,12 @@
196
196
 
197
197
  // Special handling for sidechain messages
198
198
  if (message.classList.contains('sidechain')) {
199
- // For sidechain messages, only show if sidechain filter is active
200
- shouldShow = activeTypes.includes('sidechain');
199
+ // For sidechain messages, show if both sidechain filter is active AND their message type filter is active
200
+ const sidechainActive = activeTypes.includes('sidechain');
201
+ const messageTypeActive = activeTypes.some(type =>
202
+ type !== 'sidechain' && message.classList.contains(type)
203
+ );
204
+ shouldShow = sidechainActive && messageTypeActive;
201
205
  } else {
202
206
  // For non-sidechain messages, show if any of their types are active
203
207
  shouldShow = activeTypes.some(type => message.classList.contains(type));
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-code-log"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "Convert Claude Code transcript JSONL files to HTML"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -89,7 +89,7 @@ wheels = [
89
89
 
90
90
  [[package]]
91
91
  name = "claude-code-log"
92
- version = "0.3.2"
92
+ version = "0.3.3"
93
93
  source = { editable = "." }
94
94
  dependencies = [
95
95
  { name = "anthropic" },
File without changes