claude-code-wrapped 0.1.2 → 0.1.4
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.
- package/claude_code_wrapped/__init__.py +1 -1
- package/claude_code_wrapped/reader.py +19 -3
- package/claude_code_wrapped/ui.py +30 -12
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/uv.lock +1 -1
|
@@ -209,6 +209,7 @@ def read_stats_cache(claude_dir: Path) -> dict | None:
|
|
|
209
209
|
def load_all_messages(claude_dir: Path | None = None, year: int | None = None) -> list[Message]:
|
|
210
210
|
"""Load all messages from all sessions, optionally filtered by year.
|
|
211
211
|
|
|
212
|
+
Reads from both project session files (detailed) and history.jsonl (older data).
|
|
212
213
|
Deduplicates messages by message_id to avoid counting duplicate entries
|
|
213
214
|
that can occur from streaming or retries.
|
|
214
215
|
"""
|
|
@@ -217,21 +218,36 @@ def load_all_messages(claude_dir: Path | None = None, year: int | None = None) -
|
|
|
217
218
|
|
|
218
219
|
all_messages = []
|
|
219
220
|
|
|
220
|
-
# Read from project session files
|
|
221
|
+
# Read from project session files (detailed messages with token counts)
|
|
221
222
|
for project_name, jsonl_path in iter_project_sessions(claude_dir):
|
|
222
223
|
messages = read_session_file(jsonl_path)
|
|
223
224
|
all_messages.extend(messages)
|
|
224
225
|
|
|
226
|
+
# Also read from history.jsonl for older data that may not be in session files
|
|
227
|
+
# This captures user prompts from sessions that have been cleaned up
|
|
228
|
+
history_messages = read_history_file(claude_dir)
|
|
229
|
+
all_messages.extend(history_messages)
|
|
230
|
+
|
|
225
231
|
# Deduplicate by message_id (keep the last occurrence which has final token counts)
|
|
226
232
|
seen_ids: dict[str, Message] = {}
|
|
227
233
|
unique_messages = []
|
|
234
|
+
|
|
235
|
+
# Track seen timestamps+content for history dedup (history has no message_id)
|
|
236
|
+
seen_history: set[tuple[str, str]] = set()
|
|
237
|
+
|
|
228
238
|
for msg in all_messages:
|
|
229
239
|
if msg.message_id:
|
|
230
240
|
# Keep latest version (overwrite previous)
|
|
231
241
|
seen_ids[msg.message_id] = msg
|
|
232
242
|
else:
|
|
233
|
-
# Messages without ID
|
|
234
|
-
|
|
243
|
+
# Messages without ID - deduplicate by timestamp+content hash
|
|
244
|
+
if msg.timestamp:
|
|
245
|
+
key = (msg.timestamp.isoformat(), msg.content[:100] if msg.content else "")
|
|
246
|
+
if key not in seen_history:
|
|
247
|
+
seen_history.add(key)
|
|
248
|
+
unique_messages.append(msg)
|
|
249
|
+
else:
|
|
250
|
+
unique_messages.append(msg)
|
|
235
251
|
|
|
236
252
|
# Add deduplicated messages
|
|
237
253
|
unique_messages.extend(seen_ids.values())
|
|
@@ -95,13 +95,17 @@ def create_big_stat(value: str, label: str, color: str = COLORS["orange"]) -> Te
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def create_contribution_graph(daily_stats: dict, year: int) -> Panel:
|
|
98
|
-
"""Create a GitHub-style contribution graph."""
|
|
98
|
+
"""Create a GitHub-style contribution graph for the full year."""
|
|
99
99
|
if not daily_stats:
|
|
100
100
|
return Panel("No activity data", title="Activity", border_style=COLORS["gray"])
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
start_date = datetime
|
|
104
|
-
|
|
102
|
+
# Always show full year: Jan 1 to Dec 31 (or today if current year)
|
|
103
|
+
start_date = datetime(year, 1, 1)
|
|
104
|
+
today = datetime.now()
|
|
105
|
+
if year == today.year:
|
|
106
|
+
end_date = today
|
|
107
|
+
else:
|
|
108
|
+
end_date = datetime(year, 12, 31)
|
|
105
109
|
|
|
106
110
|
max_count = max(s.message_count for s in daily_stats.values()) if daily_stats else 1
|
|
107
111
|
|
|
@@ -140,9 +144,17 @@ def create_contribution_graph(daily_stats: dict, year: int) -> Panel:
|
|
|
140
144
|
|
|
141
145
|
content = Group(graph, Align.center(legend))
|
|
142
146
|
|
|
147
|
+
# Calculate total days for context
|
|
148
|
+
today = datetime.now()
|
|
149
|
+
if year == today.year:
|
|
150
|
+
total_days = (today - datetime(year, 1, 1)).days + 1
|
|
151
|
+
else:
|
|
152
|
+
total_days = 366 if year % 4 == 0 else 365
|
|
153
|
+
active_count = len([d for d in daily_stats.values() if d.message_count > 0])
|
|
154
|
+
|
|
143
155
|
return Panel(
|
|
144
156
|
Align.center(content),
|
|
145
|
-
title=f"Activity · {
|
|
157
|
+
title=f"Activity · {active_count} of {total_days} days",
|
|
146
158
|
border_style=Style(color=COLORS["green"]),
|
|
147
159
|
padding=(0, 2),
|
|
148
160
|
)
|
|
@@ -372,18 +384,24 @@ def create_credits_roll(stats: WrappedStats) -> list[Text]:
|
|
|
372
384
|
numbers.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
373
385
|
frames.append(numbers)
|
|
374
386
|
|
|
375
|
-
# Frame 2: Timeline
|
|
387
|
+
# Frame 2: Timeline (full year context)
|
|
376
388
|
timeline = Text()
|
|
377
389
|
timeline.append("\n\n\n")
|
|
378
390
|
timeline.append(" T I M E L I N E\n\n", style=Style(color=COLORS["orange"], bold=True))
|
|
391
|
+
timeline.append(" Year ", style=Style(color=COLORS["white"], bold=True))
|
|
392
|
+
timeline.append(f"{stats.year}\n", style=Style(color=COLORS["orange"], bold=True))
|
|
379
393
|
if stats.first_message_date:
|
|
380
|
-
timeline.append("
|
|
381
|
-
timeline.append(f"{stats.first_message_date.strftime('%B %d
|
|
382
|
-
if
|
|
383
|
-
|
|
384
|
-
|
|
394
|
+
timeline.append(" Journey started ", style=Style(color=COLORS["white"], bold=True))
|
|
395
|
+
timeline.append(f"{stats.first_message_date.strftime('%B %d')}\n", style=Style(color=COLORS["gray"]))
|
|
396
|
+
# Calculate total days in year (up to today if current year)
|
|
397
|
+
today = datetime.now()
|
|
398
|
+
if stats.year == today.year:
|
|
399
|
+
total_days = (today - datetime(stats.year, 1, 1)).days + 1
|
|
400
|
+
else:
|
|
401
|
+
total_days = 366 if stats.year % 4 == 0 else 365
|
|
385
402
|
timeline.append(f"\n Active days ", style=Style(color=COLORS["white"], bold=True))
|
|
386
|
-
timeline.append(f"{stats.active_days}
|
|
403
|
+
timeline.append(f"{stats.active_days}", style=Style(color=COLORS["orange"], bold=True))
|
|
404
|
+
timeline.append(f" of {total_days}\n", style=Style(color=COLORS["gray"]))
|
|
387
405
|
if stats.most_active_hour is not None:
|
|
388
406
|
hour_label = "AM" if stats.most_active_hour < 12 else "PM"
|
|
389
407
|
hour_12 = stats.most_active_hour % 12 or 12
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED