claude-code-wrapped 0.1.4 → 0.1.6
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/settings.local.json +9 -0
- package/claude_code_wrapped/__init__.py +1 -1
- package/claude_code_wrapped/main.py +19 -0
- package/claude_code_wrapped/stats.py +112 -9
- package/claude_code_wrapped/ui.py +126 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/uv.lock +1 -1
|
@@ -92,6 +92,25 @@ Examples:
|
|
|
92
92
|
"weekday_distribution": stats.weekday_distribution,
|
|
93
93
|
"estimated_cost_usd": stats.estimated_cost,
|
|
94
94
|
"cost_by_model": stats.cost_by_model,
|
|
95
|
+
# Averages
|
|
96
|
+
"avg_messages_per_day": round(stats.avg_messages_per_day, 1),
|
|
97
|
+
"avg_messages_per_week": round(stats.avg_messages_per_week, 1),
|
|
98
|
+
"avg_messages_per_month": round(stats.avg_messages_per_month, 1),
|
|
99
|
+
"avg_cost_per_day": round(stats.avg_cost_per_day, 2) if stats.avg_cost_per_day else None,
|
|
100
|
+
"avg_cost_per_week": round(stats.avg_cost_per_week, 2) if stats.avg_cost_per_week else None,
|
|
101
|
+
"avg_cost_per_month": round(stats.avg_cost_per_month, 2) if stats.avg_cost_per_month else None,
|
|
102
|
+
# Code activity
|
|
103
|
+
"total_edits": stats.total_edits,
|
|
104
|
+
"total_writes": stats.total_writes,
|
|
105
|
+
"avg_code_changes_per_day": round(stats.avg_edits_per_day, 1),
|
|
106
|
+
"avg_code_changes_per_week": round(stats.avg_edits_per_week, 1),
|
|
107
|
+
# Monthly breakdown
|
|
108
|
+
"monthly_costs": stats.monthly_costs,
|
|
109
|
+
"monthly_tokens": stats.monthly_tokens,
|
|
110
|
+
# Longest conversation
|
|
111
|
+
"longest_conversation_messages": stats.longest_conversation_messages,
|
|
112
|
+
"longest_conversation_tokens": stats.longest_conversation_tokens,
|
|
113
|
+
"longest_conversation_date": stats.longest_conversation_date.isoformat() if stats.longest_conversation_date else None,
|
|
95
114
|
}
|
|
96
115
|
print(json.dumps(output, indent=2))
|
|
97
116
|
else:
|
|
@@ -71,14 +71,39 @@ class WrappedStats:
|
|
|
71
71
|
|
|
72
72
|
# Fun stats
|
|
73
73
|
longest_conversation_tokens: int = 0
|
|
74
|
-
avg_messages_per_day: float = 0.0
|
|
75
74
|
avg_tokens_per_message: float = 0.0
|
|
76
75
|
|
|
76
|
+
# Averages (messages)
|
|
77
|
+
avg_messages_per_day: float = 0.0
|
|
78
|
+
avg_messages_per_week: float = 0.0
|
|
79
|
+
avg_messages_per_month: float = 0.0
|
|
80
|
+
|
|
81
|
+
# Averages (cost)
|
|
82
|
+
avg_cost_per_day: float = 0.0
|
|
83
|
+
avg_cost_per_week: float = 0.0
|
|
84
|
+
avg_cost_per_month: float = 0.0
|
|
85
|
+
|
|
86
|
+
# Code activity (from Edit/Write tools)
|
|
87
|
+
total_edits: int = 0
|
|
88
|
+
total_writes: int = 0
|
|
89
|
+
avg_edits_per_day: float = 0.0
|
|
90
|
+
avg_edits_per_week: float = 0.0
|
|
91
|
+
|
|
77
92
|
# Cost tracking (per model)
|
|
78
93
|
model_token_usage: dict[str, dict[str, int]] = field(default_factory=dict)
|
|
79
94
|
estimated_cost: float | None = None
|
|
80
95
|
cost_by_model: dict[str, float] = field(default_factory=dict)
|
|
81
96
|
|
|
97
|
+
# Monthly breakdown for cost table
|
|
98
|
+
monthly_costs: dict[str, float] = field(default_factory=dict) # "YYYY-MM" -> cost
|
|
99
|
+
monthly_tokens: dict[str, dict[str, int]] = field(default_factory=dict) # "YYYY-MM" -> {input, output, ...}
|
|
100
|
+
|
|
101
|
+
# Longest conversation tracking
|
|
102
|
+
longest_conversation_messages: int = 0
|
|
103
|
+
longest_conversation_tokens: int = 0
|
|
104
|
+
longest_conversation_session: str | None = None
|
|
105
|
+
longest_conversation_date: datetime | None = None
|
|
106
|
+
|
|
82
107
|
@property
|
|
83
108
|
def total_tokens(self) -> int:
|
|
84
109
|
return (
|
|
@@ -163,6 +188,19 @@ def aggregate_stats(messages: list[Message], year: int) -> WrappedStats:
|
|
|
163
188
|
projects = Counter()
|
|
164
189
|
daily = defaultdict(lambda: DailyStats(date=datetime.now()))
|
|
165
190
|
|
|
191
|
+
# Track monthly token usage for cost breakdown
|
|
192
|
+
monthly_tokens: dict[str, dict[str, int]] = defaultdict(
|
|
193
|
+
lambda: {"input": 0, "output": 0, "cache_create": 0, "cache_read": 0}
|
|
194
|
+
)
|
|
195
|
+
monthly_model_tokens: dict[str, dict[str, dict[str, int]]] = defaultdict(
|
|
196
|
+
lambda: defaultdict(lambda: {"input": 0, "output": 0, "cache_create": 0, "cache_read": 0})
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Track per-session message counts for longest conversation
|
|
200
|
+
session_messages: dict[str, int] = Counter()
|
|
201
|
+
session_tokens: dict[str, int] = Counter()
|
|
202
|
+
session_first_time: dict[str, datetime] = {}
|
|
203
|
+
|
|
166
204
|
# Process each message
|
|
167
205
|
for msg in messages:
|
|
168
206
|
stats.total_messages += 1
|
|
@@ -175,6 +213,10 @@ def aggregate_stats(messages: list[Message], year: int) -> WrappedStats:
|
|
|
175
213
|
# Session tracking
|
|
176
214
|
if msg.session_id:
|
|
177
215
|
sessions.add(msg.session_id)
|
|
216
|
+
session_messages[msg.session_id] += 1
|
|
217
|
+
# Track first timestamp for each session
|
|
218
|
+
if msg.session_id not in session_first_time and msg.timestamp:
|
|
219
|
+
session_first_time[msg.session_id] = msg.timestamp
|
|
178
220
|
|
|
179
221
|
# Project tracking
|
|
180
222
|
project_name = extract_project_name(msg.project)
|
|
@@ -218,6 +260,25 @@ def aggregate_stats(messages: list[Message], year: int) -> WrappedStats:
|
|
|
218
260
|
stats.model_token_usage[raw_model]["cache_create"] += msg.usage.cache_creation_tokens
|
|
219
261
|
stats.model_token_usage[raw_model]["cache_read"] += msg.usage.cache_read_tokens
|
|
220
262
|
|
|
263
|
+
# Track monthly token usage for cost breakdown
|
|
264
|
+
if msg.timestamp:
|
|
265
|
+
month_key = msg.timestamp.strftime("%Y-%m")
|
|
266
|
+
monthly_tokens[month_key]["input"] += msg.usage.input_tokens
|
|
267
|
+
monthly_tokens[month_key]["output"] += msg.usage.output_tokens
|
|
268
|
+
monthly_tokens[month_key]["cache_create"] += msg.usage.cache_creation_tokens
|
|
269
|
+
monthly_tokens[month_key]["cache_read"] += msg.usage.cache_read_tokens
|
|
270
|
+
|
|
271
|
+
# Also track per-model per-month for accurate cost calculation
|
|
272
|
+
if raw_model and raw_model != '<synthetic>':
|
|
273
|
+
monthly_model_tokens[month_key][raw_model]["input"] += msg.usage.input_tokens
|
|
274
|
+
monthly_model_tokens[month_key][raw_model]["output"] += msg.usage.output_tokens
|
|
275
|
+
monthly_model_tokens[month_key][raw_model]["cache_create"] += msg.usage.cache_creation_tokens
|
|
276
|
+
monthly_model_tokens[month_key][raw_model]["cache_read"] += msg.usage.cache_read_tokens
|
|
277
|
+
|
|
278
|
+
# Track per-session tokens for longest conversation
|
|
279
|
+
if msg.session_id:
|
|
280
|
+
session_tokens[msg.session_id] += msg.usage.total_tokens
|
|
281
|
+
|
|
221
282
|
# Tool usage
|
|
222
283
|
for tool in msg.tool_calls:
|
|
223
284
|
stats.tool_calls[tool] += 1
|
|
@@ -280,20 +341,62 @@ def aggregate_stats(messages: list[Message], year: int) -> WrappedStats:
|
|
|
280
341
|
# Streaks
|
|
281
342
|
stats.streak_longest, stats.streak_current = calculate_streaks(daily, year)
|
|
282
343
|
|
|
283
|
-
#
|
|
284
|
-
if stats.active_days > 0:
|
|
285
|
-
stats.avg_messages_per_day = stats.total_messages / stats.active_days
|
|
286
|
-
|
|
287
|
-
if stats.total_assistant_messages > 0:
|
|
288
|
-
stats.avg_tokens_per_message = stats.total_tokens / stats.total_assistant_messages
|
|
289
|
-
|
|
290
|
-
# Calculate estimated cost
|
|
344
|
+
# Calculate estimated cost first (needed for averages)
|
|
291
345
|
from .pricing import calculate_total_cost_by_model
|
|
292
346
|
if stats.model_token_usage:
|
|
293
347
|
stats.estimated_cost, stats.cost_by_model = calculate_total_cost_by_model(
|
|
294
348
|
stats.model_token_usage
|
|
295
349
|
)
|
|
296
350
|
|
|
351
|
+
# Calculate monthly costs
|
|
352
|
+
stats.monthly_tokens = dict(monthly_tokens)
|
|
353
|
+
for month_key, model_usage in monthly_model_tokens.items():
|
|
354
|
+
month_cost, _ = calculate_total_cost_by_model(dict(model_usage))
|
|
355
|
+
stats.monthly_costs[month_key] = month_cost
|
|
356
|
+
|
|
357
|
+
# Find longest conversation
|
|
358
|
+
if session_messages:
|
|
359
|
+
longest_session = max(session_messages.items(), key=lambda x: x[1])
|
|
360
|
+
stats.longest_conversation_session = longest_session[0]
|
|
361
|
+
stats.longest_conversation_messages = longest_session[1]
|
|
362
|
+
if longest_session[0] in session_tokens:
|
|
363
|
+
stats.longest_conversation_tokens = session_tokens[longest_session[0]]
|
|
364
|
+
if longest_session[0] in session_first_time:
|
|
365
|
+
stats.longest_conversation_date = session_first_time[longest_session[0]]
|
|
366
|
+
|
|
367
|
+
# Calculate time periods for averages
|
|
368
|
+
today = datetime.now()
|
|
369
|
+
if year == today.year:
|
|
370
|
+
total_days = (today - datetime(year, 1, 1)).days + 1
|
|
371
|
+
else:
|
|
372
|
+
total_days = 366 if year % 4 == 0 else 365
|
|
373
|
+
total_weeks = max(1, total_days / 7)
|
|
374
|
+
total_months = max(1, total_days / 30.44) # Average days per month
|
|
375
|
+
|
|
376
|
+
# Message averages (over total time period, not just active days)
|
|
377
|
+
if total_days > 0:
|
|
378
|
+
stats.avg_messages_per_day = stats.total_messages / total_days
|
|
379
|
+
stats.avg_messages_per_week = stats.total_messages / total_weeks
|
|
380
|
+
stats.avg_messages_per_month = stats.total_messages / total_months
|
|
381
|
+
|
|
382
|
+
# Cost averages
|
|
383
|
+
if stats.estimated_cost is not None and total_days > 0:
|
|
384
|
+
stats.avg_cost_per_day = stats.estimated_cost / total_days
|
|
385
|
+
stats.avg_cost_per_week = stats.estimated_cost / total_weeks
|
|
386
|
+
stats.avg_cost_per_month = stats.estimated_cost / total_months
|
|
387
|
+
|
|
388
|
+
# Token averages
|
|
389
|
+
if stats.total_assistant_messages > 0:
|
|
390
|
+
stats.avg_tokens_per_message = stats.total_tokens / stats.total_assistant_messages
|
|
391
|
+
|
|
392
|
+
# Code activity from Edit/Write tools
|
|
393
|
+
stats.total_edits = stats.tool_calls.get("Edit", 0)
|
|
394
|
+
stats.total_writes = stats.tool_calls.get("Write", 0)
|
|
395
|
+
total_code_changes = stats.total_edits + stats.total_writes
|
|
396
|
+
if total_days > 0:
|
|
397
|
+
stats.avg_edits_per_day = total_code_changes / total_days
|
|
398
|
+
stats.avg_edits_per_week = total_code_changes / total_weeks
|
|
399
|
+
|
|
297
400
|
return stats
|
|
298
401
|
|
|
299
402
|
|
|
@@ -172,14 +172,16 @@ def create_hour_chart(distribution: list[int]) -> Panel:
|
|
|
172
172
|
color = COLORS["orange"]
|
|
173
173
|
elif 12 <= i < 18:
|
|
174
174
|
color = COLORS["blue"]
|
|
175
|
-
elif 18 <= i <
|
|
175
|
+
elif 18 <= i < 24:
|
|
176
176
|
color = COLORS["purple"]
|
|
177
177
|
else:
|
|
178
178
|
color = COLORS["gray"]
|
|
179
179
|
content.append(chars[idx], style=Style(color=color))
|
|
180
180
|
|
|
181
|
+
# Build aligned label (24 chars to match 24 bars)
|
|
182
|
+
# Labels at positions: 0, 6, 12, 18, with end marker
|
|
181
183
|
content.append("\n")
|
|
182
|
-
content.append("0
|
|
184
|
+
content.append("0 6 12 18 24", style=Style(color=COLORS["gray"]))
|
|
183
185
|
|
|
184
186
|
return Panel(
|
|
185
187
|
Align.center(content),
|
|
@@ -355,6 +357,70 @@ def simplify_model_name(model: str) -> str:
|
|
|
355
357
|
return model
|
|
356
358
|
|
|
357
359
|
|
|
360
|
+
def create_monthly_cost_table(stats: WrappedStats) -> Panel:
|
|
361
|
+
"""Create a monthly cost breakdown table like ccusage."""
|
|
362
|
+
from .pricing import format_cost
|
|
363
|
+
|
|
364
|
+
table = Table(
|
|
365
|
+
show_header=True,
|
|
366
|
+
header_style=Style(color=COLORS["white"], bold=True),
|
|
367
|
+
border_style=Style(color=COLORS["dark"]),
|
|
368
|
+
box=None,
|
|
369
|
+
padding=(0, 1),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
table.add_column("Month", style=Style(color=COLORS["gray"]))
|
|
373
|
+
table.add_column("Input", justify="right", style=Style(color=COLORS["blue"]))
|
|
374
|
+
table.add_column("Output", justify="right", style=Style(color=COLORS["orange"]))
|
|
375
|
+
table.add_column("Cache", justify="right", style=Style(color=COLORS["purple"]))
|
|
376
|
+
table.add_column("Cost", justify="right", style=Style(color=COLORS["green"], bold=True))
|
|
377
|
+
|
|
378
|
+
# Sort months chronologically
|
|
379
|
+
sorted_months = sorted(stats.monthly_costs.keys())
|
|
380
|
+
|
|
381
|
+
for month_key in sorted_months:
|
|
382
|
+
cost = stats.monthly_costs.get(month_key, 0)
|
|
383
|
+
tokens = stats.monthly_tokens.get(month_key, {})
|
|
384
|
+
|
|
385
|
+
# Format month name
|
|
386
|
+
try:
|
|
387
|
+
month_date = datetime.strptime(month_key, "%Y-%m")
|
|
388
|
+
month_name = month_date.strftime("%b %Y")
|
|
389
|
+
except ValueError:
|
|
390
|
+
month_name = month_key
|
|
391
|
+
|
|
392
|
+
input_tokens = tokens.get("input", 0)
|
|
393
|
+
output_tokens = tokens.get("output", 0)
|
|
394
|
+
cache_tokens = tokens.get("cache_create", 0) + tokens.get("cache_read", 0)
|
|
395
|
+
|
|
396
|
+
table.add_row(
|
|
397
|
+
month_name,
|
|
398
|
+
format_tokens(input_tokens),
|
|
399
|
+
format_tokens(output_tokens),
|
|
400
|
+
format_tokens(cache_tokens),
|
|
401
|
+
format_cost(cost),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Add total row
|
|
405
|
+
if sorted_months:
|
|
406
|
+
table.add_row("", "", "", "", "") # Separator
|
|
407
|
+
table.add_row(
|
|
408
|
+
"Total",
|
|
409
|
+
format_tokens(stats.total_input_tokens),
|
|
410
|
+
format_tokens(stats.total_output_tokens),
|
|
411
|
+
format_tokens(stats.total_cache_creation_tokens + stats.total_cache_read_tokens),
|
|
412
|
+
format_cost(stats.estimated_cost) if stats.estimated_cost else "N/A",
|
|
413
|
+
style=Style(bold=True),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
return Panel(
|
|
417
|
+
table,
|
|
418
|
+
title="Monthly Cost Breakdown",
|
|
419
|
+
border_style=Style(color=COLORS["green"]),
|
|
420
|
+
padding=(0, 1),
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
358
424
|
def create_credits_roll(stats: WrappedStats) -> list[Text]:
|
|
359
425
|
"""Create end credits content."""
|
|
360
426
|
from .pricing import format_cost
|
|
@@ -411,7 +477,59 @@ def create_credits_roll(stats: WrappedStats) -> list[Text]:
|
|
|
411
477
|
timeline.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
412
478
|
frames.append(timeline)
|
|
413
479
|
|
|
414
|
-
# Frame 3:
|
|
480
|
+
# Frame 3: Averages
|
|
481
|
+
from .pricing import format_cost
|
|
482
|
+
averages = Text()
|
|
483
|
+
averages.append("\n\n\n")
|
|
484
|
+
averages.append(" A V E R A G E S\n\n", style=Style(color=COLORS["blue"], bold=True))
|
|
485
|
+
averages.append(" Messages\n", style=Style(color=COLORS["white"], bold=True))
|
|
486
|
+
averages.append(f" Per day: {stats.avg_messages_per_day:.1f}\n", style=Style(color=COLORS["gray"]))
|
|
487
|
+
averages.append(f" Per week: {stats.avg_messages_per_week:.1f}\n", style=Style(color=COLORS["gray"]))
|
|
488
|
+
averages.append(f" Per month: {stats.avg_messages_per_month:.1f}\n", style=Style(color=COLORS["gray"]))
|
|
489
|
+
if stats.estimated_cost is not None:
|
|
490
|
+
averages.append("\n Cost\n", style=Style(color=COLORS["white"], bold=True))
|
|
491
|
+
averages.append(f" Per day: {format_cost(stats.avg_cost_per_day)}\n", style=Style(color=COLORS["gray"]))
|
|
492
|
+
averages.append(f" Per week: {format_cost(stats.avg_cost_per_week)}\n", style=Style(color=COLORS["gray"]))
|
|
493
|
+
averages.append(f" Per month: {format_cost(stats.avg_cost_per_month)}\n", style=Style(color=COLORS["gray"]))
|
|
494
|
+
averages.append("\n\n")
|
|
495
|
+
averages.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
496
|
+
frames.append(averages)
|
|
497
|
+
|
|
498
|
+
# Frame 4: Code Activity
|
|
499
|
+
code_activity = Text()
|
|
500
|
+
code_activity.append("\n\n\n")
|
|
501
|
+
code_activity.append(" C O D E A C T I V I T Y\n\n", style=Style(color=COLORS["orange"], bold=True))
|
|
502
|
+
total_code_changes = stats.total_edits + stats.total_writes
|
|
503
|
+
code_activity.append(" File Changes\n", style=Style(color=COLORS["white"], bold=True))
|
|
504
|
+
code_activity.append(f" Edits: {stats.total_edits:,}\n", style=Style(color=COLORS["gray"]))
|
|
505
|
+
code_activity.append(f" Writes: {stats.total_writes:,}\n", style=Style(color=COLORS["gray"]))
|
|
506
|
+
code_activity.append(f" Total: {total_code_changes:,}\n", style=Style(color=COLORS["orange"], bold=True))
|
|
507
|
+
code_activity.append("\n Averages\n", style=Style(color=COLORS["white"], bold=True))
|
|
508
|
+
code_activity.append(f" Per day: {stats.avg_edits_per_day:.1f}\n", style=Style(color=COLORS["gray"]))
|
|
509
|
+
code_activity.append(f" Per week: {stats.avg_edits_per_week:.1f}\n", style=Style(color=COLORS["gray"]))
|
|
510
|
+
code_activity.append("\n\n")
|
|
511
|
+
code_activity.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
512
|
+
frames.append(code_activity)
|
|
513
|
+
|
|
514
|
+
# Frame 5: Longest Conversation
|
|
515
|
+
if stats.longest_conversation_messages > 0:
|
|
516
|
+
longest = Text()
|
|
517
|
+
longest.append("\n\n\n")
|
|
518
|
+
longest.append(" L O N G E S T C O N V E R S A T I O N\n\n", style=Style(color=COLORS["purple"], bold=True))
|
|
519
|
+
longest.append(f" Messages ", style=Style(color=COLORS["white"], bold=True))
|
|
520
|
+
longest.append(f"{stats.longest_conversation_messages:,}\n", style=Style(color=COLORS["purple"], bold=True))
|
|
521
|
+
if stats.longest_conversation_tokens > 0:
|
|
522
|
+
longest.append(f" Tokens ", style=Style(color=COLORS["white"], bold=True))
|
|
523
|
+
longest.append(f"{format_tokens(stats.longest_conversation_tokens)}\n", style=Style(color=COLORS["orange"], bold=True))
|
|
524
|
+
if stats.longest_conversation_date:
|
|
525
|
+
longest.append(f" Date ", style=Style(color=COLORS["white"], bold=True))
|
|
526
|
+
longest.append(f"{stats.longest_conversation_date.strftime('%B %d, %Y')}\n", style=Style(color=COLORS["gray"]))
|
|
527
|
+
longest.append("\n That's one epic coding session!\n", style=Style(color=COLORS["gray"]))
|
|
528
|
+
longest.append("\n\n")
|
|
529
|
+
longest.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
530
|
+
frames.append(longest)
|
|
531
|
+
|
|
532
|
+
# Frame 6: Cast (models)
|
|
415
533
|
cast = Text()
|
|
416
534
|
cast.append("\n\n\n")
|
|
417
535
|
cast.append(" S T A R R I N G\n\n", style=Style(color=COLORS["purple"], bold=True))
|
|
@@ -422,7 +540,7 @@ def create_credits_roll(stats: WrappedStats) -> list[Text]:
|
|
|
422
540
|
cast.append(" [ENTER]", style=Style(color=COLORS["dark"]))
|
|
423
541
|
frames.append(cast)
|
|
424
542
|
|
|
425
|
-
# Frame
|
|
543
|
+
# Frame 6: Projects
|
|
426
544
|
if stats.top_projects:
|
|
427
545
|
projects = Text()
|
|
428
546
|
projects.append("\n\n\n")
|
|
@@ -557,6 +675,10 @@ def render_wrapped(stats: WrappedStats, console: Console | None = None, animate:
|
|
|
557
675
|
)
|
|
558
676
|
console.print(lists)
|
|
559
677
|
|
|
678
|
+
# Monthly cost table
|
|
679
|
+
if stats.monthly_costs:
|
|
680
|
+
console.print(create_monthly_cost_table(stats))
|
|
681
|
+
|
|
560
682
|
# Insights
|
|
561
683
|
insights = Text()
|
|
562
684
|
if stats.most_active_day:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED