@sleekdesign/ccw25 0.0.1

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/dist/cli.js ADDED
@@ -0,0 +1,2037 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from 'citty';
3
+ import { existsSync, readdirSync, statSync, createReadStream } from 'fs';
4
+ import { createInterface } from 'readline';
5
+ import { homedir } from 'os';
6
+ import { join, dirname, basename } from 'path';
7
+
8
+ function validateEntry(obj) {
9
+ if (typeof obj !== "object" || obj === null) {
10
+ return false;
11
+ }
12
+ const entry = obj;
13
+ if (typeof entry.type !== "string") {
14
+ return false;
15
+ }
16
+ switch (entry.type) {
17
+ case "user":
18
+ case "assistant":
19
+ case "summary":
20
+ case "file-history-snapshot":
21
+ return true;
22
+ default:
23
+ return true;
24
+ }
25
+ }
26
+ function parseLine(line, lineNumber) {
27
+ const trimmed = line.trim();
28
+ if (trimmed === "") {
29
+ return {
30
+ success: false,
31
+ error: "Empty line",
32
+ line: lineNumber
33
+ };
34
+ }
35
+ try {
36
+ const parsed = JSON.parse(trimmed);
37
+ if (!validateEntry(parsed)) {
38
+ return {
39
+ success: false,
40
+ error: "Invalid entry structure",
41
+ line: lineNumber
42
+ };
43
+ }
44
+ return {
45
+ success: true,
46
+ entry: parsed
47
+ };
48
+ } catch (e) {
49
+ return {
50
+ success: false,
51
+ error: e instanceof Error ? e.message : "Parse error",
52
+ line: lineNumber
53
+ };
54
+ }
55
+ }
56
+ async function* parseJSONLFile(filePath) {
57
+ const stats = {
58
+ totalLines: 0,
59
+ successfulLines: 0,
60
+ failedLines: 0,
61
+ errors: []
62
+ };
63
+ const fileStream = createReadStream(filePath, { encoding: "utf-8" });
64
+ const rl = createInterface({
65
+ input: fileStream,
66
+ crlfDelay: Number.POSITIVE_INFINITY
67
+ });
68
+ let lineNumber = 0;
69
+ for await (const line of rl) {
70
+ lineNumber += 1;
71
+ stats.totalLines += 1;
72
+ const result = parseLine(line, lineNumber);
73
+ if (result.success) {
74
+ stats.successfulLines += 1;
75
+ yield result.entry;
76
+ } else {
77
+ stats.failedLines += 1;
78
+ if (result.error !== "Empty line") {
79
+ stats.errors.push({
80
+ line: result.line,
81
+ error: result.error
82
+ });
83
+ }
84
+ }
85
+ }
86
+ return stats;
87
+ }
88
+ async function parseJSONLFileAll(filePath) {
89
+ const entries = [];
90
+ let finalStats = {
91
+ totalLines: 0,
92
+ successfulLines: 0,
93
+ failedLines: 0,
94
+ errors: []
95
+ };
96
+ const generator = parseJSONLFile(filePath);
97
+ while (true) {
98
+ const result = await generator.next();
99
+ if (result.done) {
100
+ finalStats = result.value;
101
+ break;
102
+ }
103
+ entries.push(result.value);
104
+ }
105
+ return { entries, stats: finalStats };
106
+ }
107
+ async function parseStatsCache(filePath) {
108
+ try {
109
+ const { readFile } = await import('fs/promises');
110
+ const content = await readFile(filePath, "utf-8");
111
+ return JSON.parse(content);
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ var DEFAULT_CLAUDE_DIR = ".claude";
117
+ var PROJECTS_DIR = "projects";
118
+ var HISTORY_FILE = "history.jsonl";
119
+ var STATS_CACHE_FILE = "stats-cache.json";
120
+ function detectClaudeDir() {
121
+ const envDir = process.env.CLAUDE_CONFIG_DIR;
122
+ if (envDir && existsSync(envDir)) {
123
+ return envDir;
124
+ }
125
+ const home = homedir();
126
+ return join(home, DEFAULT_CLAUDE_DIR);
127
+ }
128
+ function getClaudePaths(baseDir) {
129
+ const resolvedBase = baseDir ?? detectClaudeDir();
130
+ const paths = {
131
+ baseDir: resolvedBase,
132
+ projectsDir: join(resolvedBase, PROJECTS_DIR),
133
+ historyFile: join(resolvedBase, HISTORY_FILE),
134
+ statsCacheFile: join(resolvedBase, STATS_CACHE_FILE),
135
+ exists: existsSync(resolvedBase)
136
+ };
137
+ return paths;
138
+ }
139
+ function parseProjectName(dirName) {
140
+ const cleaned = dirName.startsWith("-") ? dirName.slice(1) : dirName;
141
+ const parts = cleaned.split("-").filter(Boolean);
142
+ if (parts.length === 0) {
143
+ return "Unknown Project";
144
+ }
145
+ const lastParts = parts.slice(-2);
146
+ const commonParents = ["projects", "repos", "code", "dev", "src", "work"];
147
+ const firstPart = lastParts[0];
148
+ const secondPart = lastParts[1];
149
+ if (lastParts.length === 2 && firstPart && secondPart && commonParents.includes(firstPart.toLowerCase())) {
150
+ return secondPart;
151
+ }
152
+ return lastParts.join("/");
153
+ }
154
+ function discoverProjects(projectsDir) {
155
+ if (!existsSync(projectsDir)) {
156
+ return [];
157
+ }
158
+ const projects = [];
159
+ try {
160
+ const entries = readdirSync(projectsDir, { withFileTypes: true });
161
+ for (const entry of entries) {
162
+ if (!entry.isDirectory()) {
163
+ continue;
164
+ }
165
+ const projectPath = join(projectsDir, entry.name);
166
+ const sessionFiles = discoverSessionFiles(projectPath);
167
+ if (sessionFiles.length > 0) {
168
+ projects.push({
169
+ path: projectPath,
170
+ displayName: parseProjectName(entry.name),
171
+ dirName: entry.name,
172
+ sessionFiles
173
+ });
174
+ }
175
+ }
176
+ } catch {
177
+ }
178
+ return projects;
179
+ }
180
+ function discoverSessionFiles(projectPath) {
181
+ const files = [];
182
+ try {
183
+ const entries = readdirSync(projectPath, { withFileTypes: true });
184
+ for (const entry of entries) {
185
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
186
+ files.push(join(projectPath, entry.name));
187
+ }
188
+ }
189
+ } catch {
190
+ }
191
+ return files;
192
+ }
193
+ function extractSessionId(filePath) {
194
+ const fileName = basename(filePath, ".jsonl");
195
+ return fileName;
196
+ }
197
+ function extractProjectPath(filePath) {
198
+ const projectDir = dirname(filePath);
199
+ return basename(projectDir);
200
+ }
201
+ function getFileModTime(filePath) {
202
+ try {
203
+ const stats = statSync(filePath);
204
+ return stats.mtime;
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+ function filterFilesByDate(files, from, to) {
210
+ return files.filter((file) => {
211
+ const modTime = getFileModTime(file);
212
+ if (!modTime) {
213
+ return false;
214
+ }
215
+ return modTime >= from && modTime <= to;
216
+ });
217
+ }
218
+
219
+ // src/loader/iterator.ts
220
+ function enumerateSessions(options = {}) {
221
+ const paths = getClaudePaths(options.baseDir);
222
+ if (!paths.exists) {
223
+ return [];
224
+ }
225
+ const projects = discoverProjects(paths.projectsDir);
226
+ const sessions = [];
227
+ for (const project of projects) {
228
+ if (options.projectFilter) {
229
+ const matchesFilter = options.projectFilter.some(
230
+ (filter) => project.displayName.toLowerCase().includes(filter.toLowerCase()) || project.dirName.toLowerCase().includes(filter.toLowerCase())
231
+ );
232
+ if (!matchesFilter) {
233
+ continue;
234
+ }
235
+ }
236
+ let files = project.sessionFiles;
237
+ if (options.dateRange) {
238
+ files = filterFilesByDate(
239
+ files,
240
+ options.dateRange.from,
241
+ options.dateRange.to
242
+ );
243
+ }
244
+ for (const filePath of files) {
245
+ sessions.push({
246
+ sessionId: extractSessionId(filePath),
247
+ projectPath: extractProjectPath(filePath),
248
+ projectDisplayName: project.displayName,
249
+ filePath
250
+ });
251
+ }
252
+ }
253
+ return sessions;
254
+ }
255
+ async function loadSession(metadata) {
256
+ const { entries } = await parseJSONLFileAll(metadata.filePath);
257
+ return {
258
+ sessionId: metadata.sessionId,
259
+ projectPath: metadata.projectPath,
260
+ filePath: metadata.filePath,
261
+ entries
262
+ };
263
+ }
264
+ async function* iterateSessions(options = {}) {
265
+ const sessionList = enumerateSessions(options);
266
+ for (const metadata of sessionList) {
267
+ try {
268
+ const session = await loadSession(metadata);
269
+ if (session.entries.length > 0) {
270
+ yield session;
271
+ }
272
+ } catch {
273
+ }
274
+ }
275
+ }
276
+ async function collectAllEntries(options = {}) {
277
+ const entries = [];
278
+ let sessionCount = 0;
279
+ for await (const session of iterateSessions(options)) {
280
+ sessionCount += 1;
281
+ for (const entry of session.entries) {
282
+ entries.push({
283
+ ...entry,
284
+ sessionId: session.sessionId,
285
+ projectPath: session.projectPath
286
+ });
287
+ }
288
+ }
289
+ return { entries, sessionCount };
290
+ }
291
+
292
+ // src/loader/index.ts
293
+ async function loadClaudeData(config) {
294
+ const paths = getClaudePaths(config.baseDir);
295
+ let statsCache = null;
296
+ if (config.useStatsCache) {
297
+ const cache = await parseStatsCache(paths.statsCacheFile);
298
+ if (cache && typeof cache.version === "number") {
299
+ statsCache = cache;
300
+ }
301
+ }
302
+ const { entries } = await collectAllEntries({
303
+ baseDir: config.baseDir,
304
+ dateRange: config.dateRange,
305
+ projectFilter: config.projectFilter
306
+ });
307
+ const sessionMetadataList = enumerateSessions({
308
+ baseDir: config.baseDir,
309
+ dateRange: config.dateRange,
310
+ projectFilter: config.projectFilter
311
+ });
312
+ const sessionMap = /* @__PURE__ */ new Map();
313
+ for (const entry of entries) {
314
+ const existing = sessionMap.get(entry.sessionId);
315
+ if (existing) {
316
+ existing.entries.push(entry);
317
+ } else {
318
+ const meta = sessionMetadataList.find(
319
+ (m) => m.sessionId === entry.sessionId
320
+ );
321
+ sessionMap.set(entry.sessionId, {
322
+ sessionId: entry.sessionId,
323
+ projectPath: entry.projectPath,
324
+ filePath: meta?.filePath ?? "",
325
+ entries: [entry]
326
+ });
327
+ }
328
+ }
329
+ const sessions = Array.from(sessionMap.values());
330
+ return {
331
+ sessions,
332
+ entries,
333
+ statsCache,
334
+ config
335
+ };
336
+ }
337
+ function resolveConfig(config = {}) {
338
+ const now = /* @__PURE__ */ new Date();
339
+ const year = config.year ?? now.getFullYear();
340
+ const defaultFrom = new Date(year, 0, 1);
341
+ const defaultTo = new Date(year, 11, 31, 23, 59, 59, 999);
342
+ return {
343
+ baseDir: config.baseDir ?? getClaudePaths().baseDir,
344
+ year,
345
+ dateRange: config.dateRange ?? {
346
+ from: defaultFrom,
347
+ to: defaultTo
348
+ },
349
+ projectFilter: config.projectFilter ?? null,
350
+ useStatsCache: config.useStatsCache ?? true
351
+ };
352
+ }
353
+
354
+ // src/pricing/models.ts
355
+ var MODEL_PRICING = {
356
+ // Claude 4.5 family
357
+ "claude-opus-4-5-20251101": {
358
+ input: 15,
359
+ output: 75,
360
+ cacheWrite: 18.75,
361
+ cacheRead: 1.5
362
+ },
363
+ "claude-sonnet-4-5-20250929": {
364
+ input: 3,
365
+ output: 15,
366
+ cacheWrite: 3.75,
367
+ cacheRead: 0.3
368
+ },
369
+ "claude-haiku-4-5-20251015": {
370
+ input: 1,
371
+ output: 5,
372
+ cacheWrite: 1.25,
373
+ cacheRead: 0.1
374
+ },
375
+ // Claude 4.1 family
376
+ "claude-opus-4-1-20250805": {
377
+ input: 15,
378
+ output: 75,
379
+ cacheWrite: 18.75,
380
+ cacheRead: 1.5
381
+ },
382
+ // Claude 4 family
383
+ "claude-opus-4-20250514": {
384
+ input: 15,
385
+ output: 75,
386
+ cacheWrite: 18.75,
387
+ cacheRead: 1.5
388
+ },
389
+ "claude-sonnet-4-20250514": {
390
+ input: 3,
391
+ output: 15,
392
+ cacheWrite: 3.75,
393
+ cacheRead: 0.3
394
+ },
395
+ // Claude 3.7 Sonnet (hybrid reasoning)
396
+ "claude-3-7-sonnet-20250219": {
397
+ input: 3,
398
+ output: 15,
399
+ cacheWrite: 3.75,
400
+ cacheRead: 0.3
401
+ },
402
+ // Claude 3.5 family
403
+ "claude-3-5-sonnet-20241022": {
404
+ input: 3,
405
+ output: 15,
406
+ cacheWrite: 3.75,
407
+ cacheRead: 0.3
408
+ },
409
+ "claude-3-5-sonnet-20240620": {
410
+ input: 3,
411
+ output: 15,
412
+ cacheWrite: 3.75,
413
+ cacheRead: 0.3
414
+ },
415
+ "claude-3-5-haiku-20241022": {
416
+ input: 0.8,
417
+ output: 4,
418
+ cacheWrite: 1,
419
+ cacheRead: 0.08
420
+ },
421
+ // Claude 3 family
422
+ "claude-3-opus-20240229": {
423
+ input: 15,
424
+ output: 75,
425
+ cacheWrite: 18.75,
426
+ cacheRead: 1.5
427
+ },
428
+ "claude-3-sonnet-20240229": {
429
+ input: 3,
430
+ output: 15,
431
+ cacheWrite: 3.75,
432
+ cacheRead: 0.3
433
+ },
434
+ "claude-3-haiku-20240307": {
435
+ input: 0.25,
436
+ output: 1.25,
437
+ cacheWrite: 0.3,
438
+ cacheRead: 0.03
439
+ }
440
+ };
441
+ var DEFAULT_PRICING = {
442
+ input: 3,
443
+ output: 15,
444
+ cacheWrite: 3.75,
445
+ cacheRead: 0.3
446
+ };
447
+ var PRICING_PATTERNS = [
448
+ // Opus
449
+ {
450
+ test: (s) => s.includes("opus") && (s.includes("4-5") || s.includes("4.5")),
451
+ key: "claude-opus-4-5-20251101"
452
+ },
453
+ {
454
+ test: (s) => s.includes("opus") && (s.includes("4-1") || s.includes("4.1")),
455
+ key: "claude-opus-4-1-20250805"
456
+ },
457
+ {
458
+ test: (s) => s.includes("opus") && s.includes("4"),
459
+ key: "claude-opus-4-20250514"
460
+ },
461
+ { test: (s) => s.includes("opus"), key: "claude-3-opus-20240229" },
462
+ // Haiku
463
+ {
464
+ test: (s) => s.includes("haiku") && (s.includes("4-5") || s.includes("4.5")),
465
+ key: "claude-haiku-4-5-20251015"
466
+ },
467
+ {
468
+ test: (s) => s.includes("haiku") && (s.includes("3-5") || s.includes("3.5")),
469
+ key: "claude-3-5-haiku-20241022"
470
+ },
471
+ { test: (s) => s.includes("haiku"), key: "claude-3-haiku-20240307" },
472
+ // Sonnet
473
+ {
474
+ test: (s) => s.includes("sonnet") && (s.includes("4-5") || s.includes("4.5")),
475
+ key: "claude-sonnet-4-5-20250929"
476
+ },
477
+ {
478
+ test: (s) => s.includes("sonnet") && s.includes("4"),
479
+ key: "claude-sonnet-4-20250514"
480
+ },
481
+ {
482
+ test: (s) => s.includes("3-7") || s.includes("3.7"),
483
+ key: "claude-3-7-sonnet-20250219"
484
+ },
485
+ { test: (s) => s.includes("sonnet"), key: "claude-3-5-sonnet-20241022" }
486
+ ];
487
+ var DISPLAY_PATTERNS = [
488
+ // Opus
489
+ {
490
+ test: (s) => s.includes("opus") && (s.includes("4-5") || s.includes("4.5")),
491
+ name: "Claude Opus 4.5"
492
+ },
493
+ {
494
+ test: (s) => s.includes("opus") && (s.includes("4-1") || s.includes("4.1")),
495
+ name: "Claude Opus 4.1"
496
+ },
497
+ {
498
+ test: (s) => s.includes("opus") && s.includes("4"),
499
+ name: "Claude Opus 4"
500
+ },
501
+ { test: (s) => s.includes("opus"), name: "Claude Opus 3" },
502
+ // Sonnet
503
+ {
504
+ test: (s) => s.includes("sonnet") && (s.includes("4-5") || s.includes("4.5")),
505
+ name: "Claude Sonnet 4.5"
506
+ },
507
+ {
508
+ test: (s) => s.includes("sonnet") && s.includes("4"),
509
+ name: "Claude Sonnet 4"
510
+ },
511
+ {
512
+ test: (s) => s.includes("3-7") || s.includes("3.7"),
513
+ name: "Claude Sonnet 3.7"
514
+ },
515
+ {
516
+ test: (s) => s.includes("3-5") && s.includes("sonnet"),
517
+ name: "Claude Sonnet 3.5"
518
+ },
519
+ { test: (s) => s.includes("sonnet"), name: "Claude Sonnet" },
520
+ // Haiku
521
+ {
522
+ test: (s) => s.includes("haiku") && (s.includes("4-5") || s.includes("4.5")),
523
+ name: "Claude Haiku 4.5"
524
+ },
525
+ {
526
+ test: (s) => s.includes("3-5") && s.includes("haiku"),
527
+ name: "Claude Haiku 3.5"
528
+ },
529
+ { test: (s) => s.includes("haiku"), name: "Claude Haiku" }
530
+ ];
531
+ function getModelPricing(model) {
532
+ const exactMatch = MODEL_PRICING[model];
533
+ if (exactMatch) {
534
+ return exactMatch;
535
+ }
536
+ const lower = model.toLowerCase();
537
+ for (const pattern of PRICING_PATTERNS) {
538
+ if (pattern.test(lower)) {
539
+ return MODEL_PRICING[pattern.key] ?? DEFAULT_PRICING;
540
+ }
541
+ }
542
+ return DEFAULT_PRICING;
543
+ }
544
+ function getModelDisplayName(model) {
545
+ const lower = model.toLowerCase();
546
+ for (const pattern of DISPLAY_PATTERNS) {
547
+ if (pattern.test(lower)) {
548
+ return pattern.name;
549
+ }
550
+ }
551
+ return model;
552
+ }
553
+
554
+ // src/pricing/index.ts
555
+ function calculateCost(model, usage) {
556
+ const pricing = getModelPricing(model);
557
+ const inputCost = usage.input_tokens * pricing.input / 1e6;
558
+ const outputCost = usage.output_tokens * pricing.output / 1e6;
559
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * pricing.cacheWrite / 1e6;
560
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * pricing.cacheRead / 1e6;
561
+ return {
562
+ input: inputCost,
563
+ output: outputCost,
564
+ cacheCreation: cacheCreationCost,
565
+ cacheRead: cacheReadCost,
566
+ total: inputCost + outputCost + cacheCreationCost + cacheReadCost
567
+ };
568
+ }
569
+ function aggregateCosts(costs) {
570
+ return costs.reduce(
571
+ (acc, cost) => ({
572
+ input: acc.input + cost.input,
573
+ output: acc.output + cost.output,
574
+ cacheCreation: acc.cacheCreation + cost.cacheCreation,
575
+ cacheRead: acc.cacheRead + cost.cacheRead,
576
+ total: acc.total + cost.total
577
+ }),
578
+ { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, total: 0 }
579
+ );
580
+ }
581
+ function estimateCacheSavings(model, cacheReadTokens) {
582
+ const pricing = getModelPricing(model);
583
+ const withoutCache = cacheReadTokens * pricing.input / 1e6;
584
+ const withCache = cacheReadTokens * pricing.cacheRead / 1e6;
585
+ return withoutCache - withCache;
586
+ }
587
+
588
+ // src/types/jsonl.ts
589
+ function isUserMessage(entry) {
590
+ return entry.type === "user";
591
+ }
592
+ function isAssistantMessage(entry) {
593
+ return entry.type === "assistant";
594
+ }
595
+ function isSummaryEntry(entry) {
596
+ return entry.type === "summary";
597
+ }
598
+ function isToolUseContent(content) {
599
+ return content.type === "tool_use";
600
+ }
601
+ function isTextContent(content) {
602
+ return content.type === "text";
603
+ }
604
+ function isThinkingContent(content) {
605
+ return content.type === "thinking";
606
+ }
607
+
608
+ // src/utils/aggregators.ts
609
+ function getTopN(counts, n) {
610
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).slice(0, n).map(([key, count]) => ({ key, count }));
611
+ }
612
+ function findMax(counts) {
613
+ let maxKey = null;
614
+ let maxCount = 0;
615
+ for (const [key, count] of counts) {
616
+ if (count > maxCount) {
617
+ maxKey = key;
618
+ maxCount = count;
619
+ }
620
+ }
621
+ if (maxKey === null) {
622
+ return null;
623
+ }
624
+ return { key: maxKey, count: maxCount };
625
+ }
626
+ function calculatePercentage(part, total) {
627
+ if (total === 0) {
628
+ return 0;
629
+ }
630
+ return part / total * 100;
631
+ }
632
+ function initHourlyDistribution() {
633
+ const map = /* @__PURE__ */ new Map();
634
+ for (let i = 0; i < 24; i++) {
635
+ map.set(i, 0);
636
+ }
637
+ return map;
638
+ }
639
+ function initWeeklyDistribution() {
640
+ const map = /* @__PURE__ */ new Map();
641
+ for (let i = 0; i < 7; i++) {
642
+ map.set(i, 0);
643
+ }
644
+ return map;
645
+ }
646
+ function initMonthlyDistribution() {
647
+ const map = /* @__PURE__ */ new Map();
648
+ for (let i = 0; i < 12; i++) {
649
+ map.set(i, 0);
650
+ }
651
+ return map;
652
+ }
653
+ function incrementMap(map, key) {
654
+ map.set(key, (map.get(key) ?? 0) + 1);
655
+ }
656
+ function extractWords(text) {
657
+ const stopWords = /* @__PURE__ */ new Set([
658
+ "a",
659
+ "an",
660
+ "the",
661
+ "and",
662
+ "or",
663
+ "but",
664
+ "in",
665
+ "on",
666
+ "at",
667
+ "to",
668
+ "for",
669
+ "of",
670
+ "with",
671
+ "by",
672
+ "from",
673
+ "as",
674
+ "is",
675
+ "was",
676
+ "are",
677
+ "were",
678
+ "been",
679
+ "be",
680
+ "have",
681
+ "has",
682
+ "had",
683
+ "do",
684
+ "does",
685
+ "did",
686
+ "will",
687
+ "would",
688
+ "could",
689
+ "should",
690
+ "may",
691
+ "might",
692
+ "must",
693
+ "shall",
694
+ "can",
695
+ "need",
696
+ "this",
697
+ "that",
698
+ "these",
699
+ "those",
700
+ "i",
701
+ "you",
702
+ "he",
703
+ "she",
704
+ "it",
705
+ "we",
706
+ "they",
707
+ "what",
708
+ "which",
709
+ "who",
710
+ "whom",
711
+ "whose",
712
+ "where",
713
+ "when",
714
+ "why",
715
+ "how",
716
+ "all",
717
+ "each",
718
+ "every",
719
+ "both",
720
+ "few",
721
+ "more",
722
+ "most",
723
+ "other",
724
+ "some",
725
+ "such",
726
+ "no",
727
+ "nor",
728
+ "not",
729
+ "only",
730
+ "own",
731
+ "same",
732
+ "so",
733
+ "than",
734
+ "too",
735
+ "very",
736
+ "just",
737
+ "also",
738
+ "now",
739
+ "here"
740
+ ]);
741
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((word) => word.length > 3 && !stopWords.has(word));
742
+ }
743
+ function buildWordCloud(texts, limit = 50) {
744
+ const wordCounts = /* @__PURE__ */ new Map();
745
+ for (const text of texts) {
746
+ const words = extractWords(text);
747
+ for (const word of words) {
748
+ incrementMap(wordCounts, word);
749
+ }
750
+ }
751
+ return getTopN(wordCounts, limit).map(({ key, count }) => ({
752
+ word: key,
753
+ count
754
+ }));
755
+ }
756
+
757
+ // src/metrics/collaboration.ts
758
+ function createCollaborationAccumulator() {
759
+ return {
760
+ totalInputTokens: 0,
761
+ totalCacheReadTokens: 0,
762
+ totalCacheCreationTokens: 0,
763
+ messagesWithThinking: 0,
764
+ totalAssistantMessages: 0,
765
+ questionsAsked: 0,
766
+ plansCreated: 0,
767
+ todoItemsCreated: 0,
768
+ standardRequests: 0,
769
+ priorityRequests: 0,
770
+ cacheSavings: 0
771
+ };
772
+ }
773
+ function processCollaborationEntry(acc, entry) {
774
+ if (!isAssistantMessage(entry)) {
775
+ return;
776
+ }
777
+ acc.totalAssistantMessages += 1;
778
+ const usage = entry.message.usage;
779
+ const model = entry.message.model;
780
+ acc.totalInputTokens += usage.input_tokens;
781
+ acc.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
782
+ acc.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
783
+ if (usage.service_tier === "priority") {
784
+ acc.priorityRequests += 1;
785
+ } else {
786
+ acc.standardRequests += 1;
787
+ }
788
+ const cacheReadTokens = usage.cache_read_input_tokens ?? 0;
789
+ if (cacheReadTokens > 0) {
790
+ acc.cacheSavings += estimateCacheSavings(model, cacheReadTokens);
791
+ }
792
+ const hasThinking = entry.message.content.some(isThinkingContent);
793
+ if (hasThinking) {
794
+ acc.messagesWithThinking += 1;
795
+ }
796
+ for (const content of entry.message.content) {
797
+ if (!isToolUseContent(content)) {
798
+ continue;
799
+ }
800
+ switch (content.name) {
801
+ case "AskUserQuestion":
802
+ acc.questionsAsked += 1;
803
+ break;
804
+ case "ExitPlanMode":
805
+ acc.plansCreated += 1;
806
+ break;
807
+ case "TodoWrite":
808
+ acc.todoItemsCreated += 1;
809
+ break;
810
+ }
811
+ }
812
+ }
813
+ function finalizeCollaborationMetrics(acc) {
814
+ const totalEffectiveInput = acc.totalInputTokens + acc.totalCacheReadTokens;
815
+ const cacheHitRate = totalEffectiveInput > 0 ? calculatePercentage(acc.totalCacheReadTokens, totalEffectiveInput) : 0;
816
+ const cacheCreationRate = totalEffectiveInput > 0 ? calculatePercentage(acc.totalCacheCreationTokens, totalEffectiveInput) : 0;
817
+ const thinkingPercentage = acc.totalAssistantMessages > 0 ? calculatePercentage(
818
+ acc.messagesWithThinking,
819
+ acc.totalAssistantMessages
820
+ ) : 0;
821
+ const totalRequests = acc.standardRequests + acc.priorityRequests;
822
+ const priorityPercentage = totalRequests > 0 ? calculatePercentage(acc.priorityRequests, totalRequests) : 0;
823
+ return {
824
+ cacheEfficiency: {
825
+ cacheHitRate,
826
+ cacheCreationRate,
827
+ estimatedSavings: acc.cacheSavings
828
+ },
829
+ extendedThinking: {
830
+ messagesWithThinking: acc.messagesWithThinking,
831
+ percentageOfTotal: thinkingPercentage
832
+ },
833
+ interactivity: {
834
+ questionsAsked: acc.questionsAsked,
835
+ plansCreated: acc.plansCreated,
836
+ todoItemsCreated: acc.todoItemsCreated
837
+ },
838
+ serviceTier: {
839
+ standardRequests: acc.standardRequests,
840
+ priorityRequests: acc.priorityRequests,
841
+ priorityPercentage
842
+ }
843
+ };
844
+ }
845
+ function calculateCollaborationMetrics(entries) {
846
+ const acc = createCollaborationAccumulator();
847
+ for (const entry of entries) {
848
+ processCollaborationEntry(acc, entry);
849
+ }
850
+ return finalizeCollaborationMetrics(acc);
851
+ }
852
+
853
+ // src/utils/dates.ts
854
+ var DAY_NAMES = [
855
+ "Sunday",
856
+ "Monday",
857
+ "Tuesday",
858
+ "Wednesday",
859
+ "Thursday",
860
+ "Friday",
861
+ "Saturday"
862
+ ];
863
+ var MONTH_NAMES = [
864
+ "January",
865
+ "February",
866
+ "March",
867
+ "April",
868
+ "May",
869
+ "June",
870
+ "July",
871
+ "August",
872
+ "September",
873
+ "October",
874
+ "November",
875
+ "December"
876
+ ];
877
+ function getDayName(day) {
878
+ return DAY_NAMES[day] ?? "Unknown";
879
+ }
880
+ function getMonthName(month) {
881
+ return MONTH_NAMES[month] ?? "Unknown";
882
+ }
883
+ function parseTimestamp(timestamp) {
884
+ try {
885
+ const date = new Date(timestamp);
886
+ if (Number.isNaN(date.getTime())) {
887
+ return null;
888
+ }
889
+ return date;
890
+ } catch {
891
+ return null;
892
+ }
893
+ }
894
+ function toDateString(date) {
895
+ const parts = date.toISOString().split("T");
896
+ return parts[0] ?? "";
897
+ }
898
+ function getHour(date) {
899
+ return date.getHours();
900
+ }
901
+ function getDayOfWeek(date) {
902
+ return date.getDay();
903
+ }
904
+ function getMonth(date) {
905
+ return date.getMonth();
906
+ }
907
+ function calculateStreak(sortedDates) {
908
+ if (sortedDates.length === 0) {
909
+ return { longestStreak: 0, currentStreak: 0 };
910
+ }
911
+ const uniqueDates = [...new Set(sortedDates)].sort();
912
+ let longestStreak = 1;
913
+ let currentStreak = 1;
914
+ let tempStreak = 1;
915
+ const today = toDateString(/* @__PURE__ */ new Date());
916
+ const yesterday = toDateString(new Date(Date.now() - 24 * 60 * 60 * 1e3));
917
+ for (let i = 1; i < uniqueDates.length; i++) {
918
+ const prevDateStr = uniqueDates[i - 1];
919
+ const currDateStr = uniqueDates[i];
920
+ if (!(prevDateStr && currDateStr)) {
921
+ continue;
922
+ }
923
+ const prevDate = new Date(prevDateStr);
924
+ const currDate = new Date(currDateStr);
925
+ const diffDays = Math.round(
926
+ (currDate.getTime() - prevDate.getTime()) / (24 * 60 * 60 * 1e3)
927
+ );
928
+ if (diffDays === 1) {
929
+ tempStreak += 1;
930
+ longestStreak = Math.max(longestStreak, tempStreak);
931
+ } else {
932
+ tempStreak = 1;
933
+ }
934
+ }
935
+ const lastDate = uniqueDates.at(-1) ?? "";
936
+ if (lastDate === today || lastDate === yesterday) {
937
+ currentStreak = 1;
938
+ for (let i = uniqueDates.length - 2; i >= 0; i--) {
939
+ const currDateStr = uniqueDates[i + 1];
940
+ const prevDateStr = uniqueDates[i];
941
+ if (!(currDateStr && prevDateStr)) {
942
+ break;
943
+ }
944
+ const currDate = new Date(currDateStr);
945
+ const prevDate = new Date(prevDateStr);
946
+ const diffDays = Math.round(
947
+ (currDate.getTime() - prevDate.getTime()) / (24 * 60 * 60 * 1e3)
948
+ );
949
+ if (diffDays === 1) {
950
+ currentStreak += 1;
951
+ } else {
952
+ break;
953
+ }
954
+ }
955
+ } else {
956
+ currentStreak = 0;
957
+ }
958
+ return { longestStreak, currentStreak };
959
+ }
960
+ function countDaysInRange(from, to) {
961
+ const diffTime = Math.abs(to.getTime() - from.getTime());
962
+ return Math.ceil(diffTime / (24 * 60 * 60 * 1e3)) + 1;
963
+ }
964
+
965
+ // src/metrics/core.ts
966
+ function createCoreAccumulator() {
967
+ return {
968
+ tokens: { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 },
969
+ costs: [],
970
+ sessionMessages: /* @__PURE__ */ new Map(),
971
+ sessionProjects: /* @__PURE__ */ new Map(),
972
+ sessionDates: /* @__PURE__ */ new Map(),
973
+ userMessageCount: 0,
974
+ assistantMessageCount: 0,
975
+ activeDates: /* @__PURE__ */ new Set(),
976
+ longestSession: null
977
+ };
978
+ }
979
+ function processCoreEntry(acc, entry) {
980
+ const sessionCount = acc.sessionMessages.get(entry.sessionId) ?? 0;
981
+ acc.sessionMessages.set(entry.sessionId, sessionCount + 1);
982
+ acc.sessionProjects.set(entry.sessionId, entry.projectPath);
983
+ const date = parseTimestamp(entry.timestamp);
984
+ if (date) {
985
+ acc.activeDates.add(toDateString(date));
986
+ if (!acc.sessionDates.has(entry.sessionId)) {
987
+ acc.sessionDates.set(entry.sessionId, date);
988
+ }
989
+ }
990
+ if (isUserMessage(entry)) {
991
+ acc.userMessageCount += 1;
992
+ }
993
+ if (isAssistantMessage(entry)) {
994
+ acc.assistantMessageCount += 1;
995
+ const usage = entry.message.usage;
996
+ const model = entry.message.model;
997
+ acc.tokens.input += usage.input_tokens;
998
+ acc.tokens.output += usage.output_tokens;
999
+ acc.tokens.cacheCreation += usage.cache_creation_input_tokens ?? 0;
1000
+ acc.tokens.cacheRead += usage.cache_read_input_tokens ?? 0;
1001
+ const cost = calculateCost(model, usage);
1002
+ acc.costs.push(cost);
1003
+ }
1004
+ }
1005
+ function finalizeCoreMetrics(acc, dateRange) {
1006
+ const totalCost = aggregateCosts(acc.costs);
1007
+ let longestSession = null;
1008
+ let maxMessages = 0;
1009
+ for (const [sessionId, messageCount] of acc.sessionMessages) {
1010
+ if (messageCount > maxMessages) {
1011
+ maxMessages = messageCount;
1012
+ longestSession = {
1013
+ id: sessionId,
1014
+ messageCount,
1015
+ project: acc.sessionProjects.get(sessionId) ?? "Unknown",
1016
+ date: acc.sessionDates.get(sessionId) ?? /* @__PURE__ */ new Date()
1017
+ };
1018
+ }
1019
+ }
1020
+ const sortedDates = Array.from(acc.activeDates).sort();
1021
+ const { longestStreak, currentStreak } = calculateStreak(sortedDates);
1022
+ const firstDateStr = sortedDates[0];
1023
+ const lastDateStr = sortedDates.at(-1);
1024
+ const firstActiveDay = firstDateStr ? new Date(firstDateStr) : null;
1025
+ const lastActiveDay = lastDateStr ? new Date(lastDateStr) : null;
1026
+ const totalMessages = acc.userMessageCount + acc.assistantMessageCount;
1027
+ const sessionCount = acc.sessionMessages.size;
1028
+ return {
1029
+ totalTokens: {
1030
+ input: acc.tokens.input,
1031
+ output: acc.tokens.output,
1032
+ cacheCreation: acc.tokens.cacheCreation,
1033
+ cacheRead: acc.tokens.cacheRead,
1034
+ total: acc.tokens.input + acc.tokens.output + acc.tokens.cacheCreation + acc.tokens.cacheRead
1035
+ },
1036
+ estimatedCost: {
1037
+ total: totalCost.total,
1038
+ byCategory: {
1039
+ input: totalCost.input,
1040
+ output: totalCost.output,
1041
+ cacheCreation: totalCost.cacheCreation,
1042
+ cacheRead: totalCost.cacheRead
1043
+ }
1044
+ },
1045
+ sessions: {
1046
+ total: sessionCount,
1047
+ averageMessages: sessionCount > 0 ? totalMessages / sessionCount : 0,
1048
+ longestSession
1049
+ },
1050
+ messages: {
1051
+ total: totalMessages,
1052
+ userMessages: acc.userMessageCount,
1053
+ assistantMessages: acc.assistantMessageCount,
1054
+ averagePerSession: sessionCount > 0 ? totalMessages / sessionCount : 0
1055
+ },
1056
+ activity: {
1057
+ daysActive: acc.activeDates.size,
1058
+ totalDaysInPeriod: countDaysInRange(dateRange.from, dateRange.to),
1059
+ longestStreak,
1060
+ currentStreak,
1061
+ firstActiveDay,
1062
+ lastActiveDay
1063
+ }
1064
+ };
1065
+ }
1066
+ function calculateCoreMetrics(entries, dateRange) {
1067
+ const acc = createCoreAccumulator();
1068
+ for (const entry of entries) {
1069
+ processCoreEntry(acc, entry);
1070
+ }
1071
+ return finalizeCoreMetrics(acc, dateRange);
1072
+ }
1073
+
1074
+ // src/metrics/fun.ts
1075
+ function createFunAccumulator() {
1076
+ return {
1077
+ totalCharacters: 0,
1078
+ summaries: [],
1079
+ longestResponse: null,
1080
+ dailyActivity: /* @__PURE__ */ new Map(),
1081
+ versions: /* @__PURE__ */ new Map(),
1082
+ milestones: [],
1083
+ sessionCount: 0,
1084
+ messageCount: 0,
1085
+ tokenCount: 0
1086
+ };
1087
+ }
1088
+ function processFunEntry(acc, entry) {
1089
+ const date = parseTimestamp(entry.timestamp);
1090
+ const dateStr = date ? toDateString(date) : null;
1091
+ if (dateStr) {
1092
+ const daily = acc.dailyActivity.get(dateStr) ?? {
1093
+ messageCount: 0,
1094
+ tokensUsed: 0
1095
+ };
1096
+ daily.messageCount += 1;
1097
+ acc.dailyActivity.set(dateStr, daily);
1098
+ }
1099
+ if (isSummaryEntry(entry) && entry.summary) {
1100
+ acc.summaries.push(entry.summary);
1101
+ }
1102
+ if (isUserMessage(entry) && entry.version) {
1103
+ const existing = acc.versions.get(entry.version);
1104
+ if (existing) {
1105
+ existing.count += 1;
1106
+ } else if (date) {
1107
+ acc.versions.set(entry.version, { count: 1, firstSeen: date });
1108
+ }
1109
+ }
1110
+ if (isAssistantMessage(entry)) {
1111
+ acc.messageCount += 1;
1112
+ const usage = entry.message.usage;
1113
+ const tokens = usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
1114
+ acc.tokenCount += tokens;
1115
+ if (dateStr) {
1116
+ const daily = acc.dailyActivity.get(dateStr);
1117
+ if (daily) {
1118
+ daily.tokensUsed += tokens;
1119
+ }
1120
+ }
1121
+ let responseChars = 0;
1122
+ for (const content of entry.message.content) {
1123
+ if (isTextContent(content)) {
1124
+ responseChars += content.text.length;
1125
+ acc.totalCharacters += content.text.length;
1126
+ }
1127
+ }
1128
+ if (date && responseChars > 0 && (!acc.longestResponse || responseChars > acc.longestResponse.characterCount)) {
1129
+ acc.longestResponse = {
1130
+ characterCount: responseChars,
1131
+ date,
1132
+ sessionId: entry.sessionId
1133
+ };
1134
+ }
1135
+ }
1136
+ }
1137
+ function checkMilestones(acc, sortedDates) {
1138
+ const milestones = [];
1139
+ const firstDate = sortedDates[0];
1140
+ if (firstDate) {
1141
+ milestones.push({
1142
+ type: "first_session",
1143
+ date: new Date(firstDate),
1144
+ description: "You started your Claude Code journey"
1145
+ });
1146
+ }
1147
+ let cumulativeMessages = 0;
1148
+ let cumulativeTokens = 0;
1149
+ const milestonesHit = /* @__PURE__ */ new Set();
1150
+ for (const dateStr of sortedDates) {
1151
+ const daily = acc.dailyActivity.get(dateStr);
1152
+ if (!daily) {
1153
+ continue;
1154
+ }
1155
+ cumulativeMessages += daily.messageCount;
1156
+ cumulativeTokens += daily.tokensUsed;
1157
+ const date = new Date(dateStr);
1158
+ if (cumulativeMessages >= 100 && !milestonesHit.has("100_messages")) {
1159
+ milestonesHit.add("100_messages");
1160
+ milestones.push({
1161
+ type: "100_messages",
1162
+ date,
1163
+ description: "You exchanged 100 messages with Claude"
1164
+ });
1165
+ }
1166
+ if (cumulativeMessages >= 1e3 && !milestonesHit.has("1000_messages")) {
1167
+ milestonesHit.add("1000_messages");
1168
+ milestones.push({
1169
+ type: "1000_messages",
1170
+ date,
1171
+ description: "You reached 1,000 messages!"
1172
+ });
1173
+ }
1174
+ if (cumulativeMessages >= 1e4 && !milestonesHit.has("10000_messages")) {
1175
+ milestonesHit.add("10000_messages");
1176
+ milestones.push({
1177
+ type: "10000_messages",
1178
+ date,
1179
+ description: "A true power user: 10,000 messages!"
1180
+ });
1181
+ }
1182
+ if (cumulativeTokens >= 1e6 && !milestonesHit.has("1m_tokens")) {
1183
+ milestonesHit.add("1m_tokens");
1184
+ milestones.push({
1185
+ type: "1m_tokens",
1186
+ date,
1187
+ description: "You processed 1 million tokens"
1188
+ });
1189
+ }
1190
+ if (cumulativeTokens >= 1e7 && !milestonesHit.has("10m_tokens")) {
1191
+ milestonesHit.add("10m_tokens");
1192
+ milestones.push({
1193
+ type: "10m_tokens",
1194
+ date,
1195
+ description: "10 million tokens processed!"
1196
+ });
1197
+ }
1198
+ }
1199
+ return milestones;
1200
+ }
1201
+ function finalizeFunMetrics(acc) {
1202
+ const wordsGenerated = Math.round(acc.totalCharacters / 5);
1203
+ const equivalentPages = Math.round(wordsGenerated / 250);
1204
+ const summaryWordCloud = buildWordCloud(acc.summaries, 50);
1205
+ let mostActiveDay = null;
1206
+ let maxMessages = 0;
1207
+ for (const [dateStr, data] of acc.dailyActivity) {
1208
+ if (data.messageCount > maxMessages) {
1209
+ maxMessages = data.messageCount;
1210
+ mostActiveDay = {
1211
+ date: new Date(dateStr),
1212
+ messageCount: data.messageCount,
1213
+ tokensUsed: data.tokensUsed
1214
+ };
1215
+ }
1216
+ }
1217
+ const versions = Array.from(acc.versions.entries()).map(([version, data]) => ({
1218
+ version,
1219
+ messageCount: data.count,
1220
+ firstSeen: data.firstSeen
1221
+ })).sort((a, b) => b.messageCount - a.messageCount);
1222
+ const sortedDates = Array.from(acc.dailyActivity.keys()).sort();
1223
+ const milestones = checkMilestones(acc, sortedDates);
1224
+ return {
1225
+ charactersGenerated: acc.totalCharacters,
1226
+ wordsGenerated,
1227
+ equivalentPages,
1228
+ summaryWordCloud,
1229
+ longestResponse: acc.longestResponse,
1230
+ mostActiveDay,
1231
+ milestones,
1232
+ versions
1233
+ };
1234
+ }
1235
+ function calculateFunMetrics(entries) {
1236
+ const acc = createFunAccumulator();
1237
+ for (const entry of entries) {
1238
+ processFunEntry(acc, entry);
1239
+ }
1240
+ return finalizeFunMetrics(acc);
1241
+ }
1242
+
1243
+ // src/metrics/models.ts
1244
+ function createModelAccumulator() {
1245
+ return {
1246
+ models: /* @__PURE__ */ new Map()
1247
+ };
1248
+ }
1249
+ function processModelEntry(acc, entry) {
1250
+ if (!isAssistantMessage(entry)) {
1251
+ return;
1252
+ }
1253
+ const model = entry.message.model;
1254
+ const usage = entry.message.usage;
1255
+ const existing = acc.models.get(model) ?? {
1256
+ messageCount: 0,
1257
+ tokenCount: 0,
1258
+ cost: 0
1259
+ };
1260
+ const cost = calculateCost(model, usage);
1261
+ const totalTokens = usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
1262
+ acc.models.set(model, {
1263
+ messageCount: existing.messageCount + 1,
1264
+ tokenCount: existing.tokenCount + totalTokens,
1265
+ cost: existing.cost + cost.total
1266
+ });
1267
+ }
1268
+ function finalizeModelMetrics(acc) {
1269
+ const totalMessages = Array.from(acc.models.values()).reduce(
1270
+ (sum, m) => sum + m.messageCount,
1271
+ 0
1272
+ );
1273
+ const totalCost = Array.from(acc.models.values()).reduce(
1274
+ (sum, m) => sum + m.cost,
1275
+ 0
1276
+ );
1277
+ const modelsUsed = Array.from(acc.models.entries()).map(([model, data]) => ({
1278
+ model,
1279
+ displayName: getModelDisplayName(model),
1280
+ messageCount: data.messageCount,
1281
+ tokenCount: data.tokenCount,
1282
+ cost: data.cost,
1283
+ percentage: calculatePercentage(data.messageCount, totalMessages)
1284
+ })).sort((a, b) => b.messageCount - a.messageCount);
1285
+ const topModel = modelsUsed[0];
1286
+ const favoriteModel = topModel ? {
1287
+ model: topModel.model,
1288
+ displayName: topModel.displayName,
1289
+ messageCount: topModel.messageCount,
1290
+ percentage: topModel.percentage
1291
+ } : null;
1292
+ const costByModel = Array.from(acc.models.entries()).map(([model, data]) => ({
1293
+ model,
1294
+ displayName: getModelDisplayName(model),
1295
+ cost: data.cost,
1296
+ percentage: calculatePercentage(data.cost, totalCost)
1297
+ })).sort((a, b) => b.cost - a.cost);
1298
+ return {
1299
+ modelsUsed,
1300
+ favoriteModel,
1301
+ costByModel
1302
+ };
1303
+ }
1304
+ function calculateModelMetrics(entries) {
1305
+ const acc = createModelAccumulator();
1306
+ for (const entry of entries) {
1307
+ processModelEntry(acc, entry);
1308
+ }
1309
+ return finalizeModelMetrics(acc);
1310
+ }
1311
+
1312
+ // src/metrics/patterns.ts
1313
+ function createPatternAccumulator() {
1314
+ return {
1315
+ hourly: initHourlyDistribution(),
1316
+ weekly: initWeeklyDistribution(),
1317
+ monthly: initMonthlyDistribution()
1318
+ };
1319
+ }
1320
+ function processPatternEntry(acc, entry) {
1321
+ const date = parseTimestamp(entry.timestamp);
1322
+ if (!date) {
1323
+ return;
1324
+ }
1325
+ incrementMap(acc.hourly, getHour(date));
1326
+ incrementMap(acc.weekly, getDayOfWeek(date));
1327
+ incrementMap(acc.monthly, getMonth(date));
1328
+ }
1329
+ function finalizePatternMetrics(acc) {
1330
+ const peakHourData = findMax(acc.hourly);
1331
+ const peakDayData = findMax(acc.weekly);
1332
+ const peakMonthData = findMax(acc.monthly);
1333
+ const hourlyDistribution = Array.from(acc.hourly.entries()).map(([hour, count]) => ({ hour, count })).sort((a, b) => a.hour - b.hour);
1334
+ const weeklyDistribution = Array.from(acc.weekly.entries()).map(([day, count]) => ({ day, dayName: getDayName(day), count })).sort((a, b) => a.day - b.day);
1335
+ const monthlyDistribution = Array.from(acc.monthly.entries()).map(([month, count]) => ({ month, monthName: getMonthName(month), count })).sort((a, b) => a.month - b.month);
1336
+ return {
1337
+ peakHour: {
1338
+ hour: typeof peakHourData?.key === "number" ? peakHourData.key : 0,
1339
+ messageCount: peakHourData?.count ?? 0
1340
+ },
1341
+ peakDayOfWeek: {
1342
+ day: typeof peakDayData?.key === "number" ? peakDayData.key : 0,
1343
+ dayName: getDayName(
1344
+ typeof peakDayData?.key === "number" ? peakDayData.key : 0
1345
+ ),
1346
+ messageCount: peakDayData?.count ?? 0
1347
+ },
1348
+ busiestMonth: {
1349
+ month: typeof peakMonthData?.key === "number" ? peakMonthData.key : 0,
1350
+ monthName: getMonthName(
1351
+ typeof peakMonthData?.key === "number" ? peakMonthData.key : 0
1352
+ ),
1353
+ messageCount: peakMonthData?.count ?? 0
1354
+ },
1355
+ hourlyDistribution,
1356
+ weeklyDistribution,
1357
+ monthlyDistribution
1358
+ };
1359
+ }
1360
+ function calculatePatternMetrics(entries) {
1361
+ const acc = createPatternAccumulator();
1362
+ for (const entry of entries) {
1363
+ processPatternEntry(acc, entry);
1364
+ }
1365
+ return finalizePatternMetrics(acc);
1366
+ }
1367
+
1368
+ // src/metrics/projects.ts
1369
+ function createProjectAccumulator() {
1370
+ return {
1371
+ projects: /* @__PURE__ */ new Map(),
1372
+ branches: /* @__PURE__ */ new Map()
1373
+ };
1374
+ }
1375
+ function processProjectEntry(acc, entry) {
1376
+ const projectPath = entry.projectPath;
1377
+ const projectData = acc.projects.get(projectPath) ?? {
1378
+ sessionIds: /* @__PURE__ */ new Set(),
1379
+ messageCount: 0,
1380
+ tokenCount: 0,
1381
+ lastActive: null
1382
+ };
1383
+ projectData.sessionIds.add(entry.sessionId);
1384
+ projectData.messageCount += 1;
1385
+ const date = parseTimestamp(entry.timestamp);
1386
+ if (date && (!projectData.lastActive || date > projectData.lastActive)) {
1387
+ projectData.lastActive = date;
1388
+ }
1389
+ if (isAssistantMessage(entry)) {
1390
+ const usage = entry.message.usage;
1391
+ projectData.tokenCount += usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
1392
+ }
1393
+ acc.projects.set(projectPath, projectData);
1394
+ if (isUserMessage(entry) || isAssistantMessage(entry)) {
1395
+ const gitBranch = "gitBranch" in entry ? entry.gitBranch : void 0;
1396
+ if (gitBranch) {
1397
+ const branchKey = `${projectPath}:${gitBranch}`;
1398
+ const branchData = acc.branches.get(branchKey) ?? {
1399
+ projectPath,
1400
+ messageCount: 0
1401
+ };
1402
+ branchData.messageCount += 1;
1403
+ acc.branches.set(branchKey, branchData);
1404
+ }
1405
+ }
1406
+ }
1407
+ function finalizeProjectMetrics(acc) {
1408
+ const totalMessages = Array.from(acc.projects.values()).reduce(
1409
+ (sum, p) => sum + p.messageCount,
1410
+ 0
1411
+ );
1412
+ const projects = Array.from(acc.projects.entries()).map(([path, data]) => ({
1413
+ path,
1414
+ displayName: parseProjectName(path),
1415
+ sessionCount: data.sessionIds.size,
1416
+ messageCount: data.messageCount,
1417
+ tokenCount: data.tokenCount,
1418
+ lastActive: data.lastActive ?? /* @__PURE__ */ new Date()
1419
+ })).sort((a, b) => b.messageCount - a.messageCount);
1420
+ const firstProject = projects[0];
1421
+ const topProject = firstProject ? {
1422
+ path: firstProject.path,
1423
+ displayName: firstProject.displayName,
1424
+ sessionCount: firstProject.sessionCount,
1425
+ messageCount: firstProject.messageCount,
1426
+ percentageOfTotal: calculatePercentage(
1427
+ firstProject.messageCount,
1428
+ totalMessages
1429
+ )
1430
+ } : null;
1431
+ const branches = Array.from(acc.branches.entries()).map(([key, data]) => {
1432
+ const branchName = key.split(":").slice(1).join(":");
1433
+ return {
1434
+ name: branchName,
1435
+ projectPath: data.projectPath,
1436
+ messageCount: data.messageCount
1437
+ };
1438
+ }).sort((a, b) => b.messageCount - a.messageCount);
1439
+ return {
1440
+ projectsWorkedOn: acc.projects.size,
1441
+ projects,
1442
+ topProject,
1443
+ gitBranches: {
1444
+ total: acc.branches.size,
1445
+ branches: branches.slice(0, 20)
1446
+ // Top 20 branches
1447
+ }
1448
+ };
1449
+ }
1450
+ function calculateProjectMetrics(entries) {
1451
+ const acc = createProjectAccumulator();
1452
+ for (const entry of entries) {
1453
+ processProjectEntry(acc, entry);
1454
+ }
1455
+ return finalizeProjectMetrics(acc);
1456
+ }
1457
+
1458
+ // src/metrics/tools.ts
1459
+ var READING_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep"]);
1460
+ var WRITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit"]);
1461
+ var EXECUTING_TOOLS = /* @__PURE__ */ new Set(["Bash", "KillShell", "TaskOutput"]);
1462
+ var RESEARCHING_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "WebSearch"]);
1463
+ var PLANNING_TOOLS = /* @__PURE__ */ new Set([
1464
+ "Task",
1465
+ "TodoWrite",
1466
+ "TodoRead",
1467
+ "ExitPlanMode",
1468
+ "EnterPlanMode",
1469
+ "AskUserQuestion"
1470
+ ]);
1471
+ function createToolAccumulator() {
1472
+ return {
1473
+ tools: /* @__PURE__ */ new Map(),
1474
+ mcpTools: /* @__PURE__ */ new Map(),
1475
+ subagentTypes: /* @__PURE__ */ new Map(),
1476
+ taskToolCalls: 0
1477
+ };
1478
+ }
1479
+ function isMcpTool(name) {
1480
+ return name.startsWith("mcp__") || name.includes("::");
1481
+ }
1482
+ function parseMcpTool(name) {
1483
+ if (name.startsWith("mcp__")) {
1484
+ const parts = name.split("__");
1485
+ return {
1486
+ server: parts[1] ?? "unknown",
1487
+ tool: parts.slice(2).join("__")
1488
+ };
1489
+ }
1490
+ if (name.includes("::")) {
1491
+ const [server, ...toolParts] = name.split("::");
1492
+ return {
1493
+ server: server ?? "unknown",
1494
+ tool: toolParts.join("::")
1495
+ };
1496
+ }
1497
+ return { server: "unknown", tool: name };
1498
+ }
1499
+ function processToolEntry(acc, entry) {
1500
+ if (!isAssistantMessage(entry)) {
1501
+ return;
1502
+ }
1503
+ for (const content of entry.message.content) {
1504
+ if (!isToolUseContent(content)) {
1505
+ continue;
1506
+ }
1507
+ const toolName = content.name;
1508
+ acc.tools.set(toolName, (acc.tools.get(toolName) ?? 0) + 1);
1509
+ if (isMcpTool(toolName)) {
1510
+ acc.mcpTools.set(toolName, (acc.mcpTools.get(toolName) ?? 0) + 1);
1511
+ }
1512
+ if (toolName === "Task") {
1513
+ acc.taskToolCalls += 1;
1514
+ const input = content.input;
1515
+ const subagentType = typeof input.subagent_type === "string" ? input.subagent_type : "unknown";
1516
+ acc.subagentTypes.set(
1517
+ subagentType,
1518
+ (acc.subagentTypes.get(subagentType) ?? 0) + 1
1519
+ );
1520
+ }
1521
+ }
1522
+ }
1523
+ function determineDeveloperStyle(breakdown) {
1524
+ const total = breakdown.reading + breakdown.writing + breakdown.executing + breakdown.researching + breakdown.planning;
1525
+ if (total === 0) {
1526
+ return { style: "balanced", description: "Just getting started" };
1527
+ }
1528
+ const percentages = {
1529
+ reading: breakdown.reading / total * 100,
1530
+ writing: breakdown.writing / total * 100,
1531
+ executing: breakdown.executing / total * 100,
1532
+ researching: breakdown.researching / total * 100,
1533
+ planning: breakdown.planning / total * 100
1534
+ };
1535
+ if (percentages.reading > 40) {
1536
+ return {
1537
+ style: "reader",
1538
+ description: "You prefer understanding code before changing it"
1539
+ };
1540
+ }
1541
+ if (percentages.writing > 40) {
1542
+ return {
1543
+ style: "writer",
1544
+ description: "You dive straight into writing and editing code"
1545
+ };
1546
+ }
1547
+ if (percentages.executing > 40) {
1548
+ return {
1549
+ style: "executor",
1550
+ description: "You love running commands and scripts"
1551
+ };
1552
+ }
1553
+ if (percentages.researching > 30) {
1554
+ return {
1555
+ style: "researcher",
1556
+ description: "You gather information before acting"
1557
+ };
1558
+ }
1559
+ if (percentages.planning > 30) {
1560
+ return {
1561
+ style: "planner",
1562
+ description: "You carefully plan and organize your work"
1563
+ };
1564
+ }
1565
+ return {
1566
+ style: "balanced",
1567
+ description: "You have a well-rounded approach to coding"
1568
+ };
1569
+ }
1570
+ function finalizeToolMetrics(acc) {
1571
+ const totalCalls = Array.from(acc.tools.values()).reduce(
1572
+ (sum, count) => sum + count,
1573
+ 0
1574
+ );
1575
+ const toolDistribution = Array.from(acc.tools.entries()).map(([tool, count]) => ({
1576
+ tool,
1577
+ count,
1578
+ percentage: calculatePercentage(count, totalCalls)
1579
+ })).sort((a, b) => b.count - a.count);
1580
+ const topTools = toolDistribution.slice(0, 10).map((item, index) => ({
1581
+ tool: item.tool,
1582
+ count: item.count,
1583
+ rank: index + 1
1584
+ }));
1585
+ const subagentTypes = Array.from(acc.subagentTypes.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count);
1586
+ const mcpTools = Array.from(acc.mcpTools.entries()).map(([name, count]) => {
1587
+ const { server, tool } = parseMcpTool(name);
1588
+ return { server, tool, count };
1589
+ }).sort((a, b) => b.count - a.count);
1590
+ const styleBreakdown = {
1591
+ reading: 0,
1592
+ writing: 0,
1593
+ executing: 0,
1594
+ researching: 0,
1595
+ planning: 0
1596
+ };
1597
+ for (const [tool, count] of acc.tools) {
1598
+ if (READING_TOOLS.has(tool)) {
1599
+ styleBreakdown.reading += count;
1600
+ } else if (WRITING_TOOLS.has(tool)) {
1601
+ styleBreakdown.writing += count;
1602
+ } else if (EXECUTING_TOOLS.has(tool)) {
1603
+ styleBreakdown.executing += count;
1604
+ } else if (RESEARCHING_TOOLS.has(tool)) {
1605
+ styleBreakdown.researching += count;
1606
+ } else if (PLANNING_TOOLS.has(tool)) {
1607
+ styleBreakdown.planning += count;
1608
+ }
1609
+ }
1610
+ const { style, description } = determineDeveloperStyle(styleBreakdown);
1611
+ return {
1612
+ toolCallsTotal: totalCalls,
1613
+ toolDistribution,
1614
+ topTools,
1615
+ subagentUsage: {
1616
+ taskToolCalls: acc.taskToolCalls,
1617
+ subagentTypes
1618
+ },
1619
+ mcpTools,
1620
+ developerProfile: {
1621
+ primaryStyle: style,
1622
+ styleBreakdown,
1623
+ description
1624
+ }
1625
+ };
1626
+ }
1627
+ function calculateToolMetrics(entries) {
1628
+ const acc = createToolAccumulator();
1629
+ for (const entry of entries) {
1630
+ processToolEntry(acc, entry);
1631
+ }
1632
+ return finalizeToolMetrics(acc);
1633
+ }
1634
+
1635
+ // src/metrics/index.ts
1636
+ function calculateAllMetrics(entries, dateRange) {
1637
+ const core = calculateCoreMetrics(entries, dateRange);
1638
+ const patterns = calculatePatternMetrics(entries);
1639
+ const models = calculateModelMetrics(entries);
1640
+ const tools = calculateToolMetrics(entries);
1641
+ const projects = calculateProjectMetrics(entries);
1642
+ const collaboration = calculateCollaborationMetrics(entries);
1643
+ const fun = calculateFunMetrics(entries);
1644
+ return {
1645
+ generatedAt: /* @__PURE__ */ new Date(),
1646
+ period: {
1647
+ from: dateRange.from,
1648
+ to: dateRange.to,
1649
+ year: dateRange.from.getFullYear()
1650
+ },
1651
+ core,
1652
+ patterns,
1653
+ models,
1654
+ tools,
1655
+ projects,
1656
+ collaboration,
1657
+ fun
1658
+ };
1659
+ }
1660
+
1661
+ // src/index.ts
1662
+ async function generateWrappedSummary(config = {}) {
1663
+ const startTime = Date.now();
1664
+ const warnings = [];
1665
+ const errors = [];
1666
+ const resolvedConfig = resolveConfig(config);
1667
+ let loadedData;
1668
+ try {
1669
+ loadedData = await loadClaudeData(resolvedConfig);
1670
+ } catch (e) {
1671
+ errors.push({
1672
+ code: "LOAD_ERROR",
1673
+ message: e instanceof Error ? e.message : "Failed to load Claude data",
1674
+ fatal: true
1675
+ });
1676
+ return {
1677
+ summary: createEmptySummary(resolvedConfig),
1678
+ warnings,
1679
+ errors,
1680
+ stats: {
1681
+ filesProcessed: 0,
1682
+ filesSkipped: 0,
1683
+ entriesProcessed: 0,
1684
+ entriesSkipped: 0,
1685
+ processingTimeMs: Date.now() - startTime
1686
+ }
1687
+ };
1688
+ }
1689
+ if (loadedData.entries.length === 0) {
1690
+ warnings.push({
1691
+ code: "NO_DATA",
1692
+ message: "No Claude Code data found for the specified period",
1693
+ context: {
1694
+ baseDir: resolvedConfig.baseDir,
1695
+ dateRange: resolvedConfig.dateRange
1696
+ }
1697
+ });
1698
+ }
1699
+ const summary = calculateAllMetrics(
1700
+ loadedData.entries,
1701
+ resolvedConfig.dateRange
1702
+ );
1703
+ return {
1704
+ summary,
1705
+ warnings,
1706
+ errors,
1707
+ stats: {
1708
+ filesProcessed: loadedData.sessions.length,
1709
+ filesSkipped: 0,
1710
+ entriesProcessed: loadedData.entries.length,
1711
+ entriesSkipped: 0,
1712
+ processingTimeMs: Date.now() - startTime
1713
+ }
1714
+ };
1715
+ }
1716
+ function createEmptySummary(config) {
1717
+ const emptyDate = /* @__PURE__ */ new Date();
1718
+ return {
1719
+ generatedAt: emptyDate,
1720
+ period: {
1721
+ from: config.dateRange.from,
1722
+ to: config.dateRange.to,
1723
+ year: config.year
1724
+ },
1725
+ core: {
1726
+ totalTokens: {
1727
+ input: 0,
1728
+ output: 0,
1729
+ cacheCreation: 0,
1730
+ cacheRead: 0,
1731
+ total: 0
1732
+ },
1733
+ estimatedCost: {
1734
+ total: 0,
1735
+ byCategory: { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 }
1736
+ },
1737
+ sessions: { total: 0, averageMessages: 0, longestSession: null },
1738
+ messages: {
1739
+ total: 0,
1740
+ userMessages: 0,
1741
+ assistantMessages: 0,
1742
+ averagePerSession: 0
1743
+ },
1744
+ activity: {
1745
+ daysActive: 0,
1746
+ totalDaysInPeriod: 0,
1747
+ longestStreak: 0,
1748
+ currentStreak: 0,
1749
+ firstActiveDay: null,
1750
+ lastActiveDay: null
1751
+ }
1752
+ },
1753
+ patterns: {
1754
+ peakHour: { hour: 0, messageCount: 0 },
1755
+ peakDayOfWeek: { day: 0, dayName: "Sunday", messageCount: 0 },
1756
+ busiestMonth: { month: 0, monthName: "January", messageCount: 0 },
1757
+ hourlyDistribution: [],
1758
+ weeklyDistribution: [],
1759
+ monthlyDistribution: []
1760
+ },
1761
+ models: {
1762
+ modelsUsed: [],
1763
+ favoriteModel: null,
1764
+ costByModel: []
1765
+ },
1766
+ tools: {
1767
+ toolCallsTotal: 0,
1768
+ toolDistribution: [],
1769
+ topTools: [],
1770
+ subagentUsage: { taskToolCalls: 0, subagentTypes: [] },
1771
+ mcpTools: [],
1772
+ developerProfile: {
1773
+ primaryStyle: "balanced",
1774
+ styleBreakdown: {
1775
+ reading: 0,
1776
+ writing: 0,
1777
+ executing: 0,
1778
+ researching: 0,
1779
+ planning: 0
1780
+ },
1781
+ description: "No data yet"
1782
+ }
1783
+ },
1784
+ projects: {
1785
+ projectsWorkedOn: 0,
1786
+ projects: [],
1787
+ topProject: null,
1788
+ gitBranches: { total: 0, branches: [] }
1789
+ },
1790
+ collaboration: {
1791
+ cacheEfficiency: {
1792
+ cacheHitRate: 0,
1793
+ cacheCreationRate: 0,
1794
+ estimatedSavings: 0
1795
+ },
1796
+ extendedThinking: { messagesWithThinking: 0, percentageOfTotal: 0 },
1797
+ interactivity: {
1798
+ questionsAsked: 0,
1799
+ plansCreated: 0,
1800
+ todoItemsCreated: 0
1801
+ },
1802
+ serviceTier: {
1803
+ standardRequests: 0,
1804
+ priorityRequests: 0,
1805
+ priorityPercentage: 0
1806
+ }
1807
+ },
1808
+ fun: {
1809
+ charactersGenerated: 0,
1810
+ wordsGenerated: 0,
1811
+ equivalentPages: 0,
1812
+ summaryWordCloud: [],
1813
+ longestResponse: null,
1814
+ mostActiveDay: null,
1815
+ milestones: [],
1816
+ versions: []
1817
+ }
1818
+ };
1819
+ }
1820
+
1821
+ // src/url/params.ts
1822
+ var PARAMS = {
1823
+ // Core stats
1824
+ sessions: "s",
1825
+ tokens: "t",
1826
+ cost: "c",
1827
+ daysActive: "d",
1828
+ longestStreak: "ls",
1829
+ currentStreak: "cs",
1830
+ // Patterns
1831
+ peakHour: "ph",
1832
+ peakDay: "pd",
1833
+ // Models
1834
+ favoriteModel: "fm",
1835
+ modelBreakdown: "mb",
1836
+ // Tools
1837
+ topTools: "tt",
1838
+ developerStyle: "st",
1839
+ // Projects
1840
+ topProject: "tp",
1841
+ projectCount: "pc",
1842
+ // Fun
1843
+ wordsGenerated: "wg",
1844
+ firstActiveDay: "fad"};
1845
+ var MODEL_ABBREV = {
1846
+ // Claude 4.5 family
1847
+ "claude-opus-4-5-20251101": "o45",
1848
+ "claude-sonnet-4-5-20250929": "s45",
1849
+ "claude-haiku-4-5-20251015": "h45",
1850
+ // Claude 4.1 family
1851
+ "claude-opus-4-1-20250805": "o41",
1852
+ // Claude 4 family
1853
+ "claude-opus-4-20250514": "o4",
1854
+ "claude-sonnet-4-20250514": "s4",
1855
+ // Claude 3.7 family (hybrid reasoning)
1856
+ "claude-3-7-sonnet-20250219": "s37",
1857
+ "claude-3-7-sonnet-latest": "s37",
1858
+ // Claude 3.5 family
1859
+ "claude-3-5-sonnet-20241022": "s35",
1860
+ "claude-3-5-sonnet-20240620": "s35",
1861
+ "claude-3-5-haiku-20241022": "h35",
1862
+ // Claude 3 family
1863
+ "claude-3-opus-20240229": "o3",
1864
+ "claude-3-sonnet-20240229": "s3",
1865
+ "claude-3-haiku-20240307": "h3"
1866
+ };
1867
+ Object.fromEntries(
1868
+ Object.entries(MODEL_ABBREV).map(([k, v]) => [v, k])
1869
+ );
1870
+ var STYLE_ABBREV = {
1871
+ reader: "r",
1872
+ writer: "w",
1873
+ executor: "e",
1874
+ researcher: "rs",
1875
+ planner: "p",
1876
+ balanced: "b"
1877
+ };
1878
+ Object.fromEntries(
1879
+ Object.entries(STYLE_ABBREV).map(([k, v]) => [v, k])
1880
+ );
1881
+ var BASE_URL = "https://sleek.design/claude-code-wrapped-2025";
1882
+
1883
+ // src/url/encode.ts
1884
+ function formatCompact(num) {
1885
+ if (num >= 1e9) {
1886
+ return `${(num / 1e9).toFixed(1)}B`;
1887
+ }
1888
+ if (num >= 1e6) {
1889
+ return `${(num / 1e6).toFixed(1)}M`;
1890
+ }
1891
+ if (num >= 1e3) {
1892
+ return `${(num / 1e3).toFixed(1)}K`;
1893
+ }
1894
+ return num.toString();
1895
+ }
1896
+ function formatCost(cost) {
1897
+ return cost.toFixed(2).replace(/\.?0+$/, "");
1898
+ }
1899
+ function formatDate(date) {
1900
+ if (!date) {
1901
+ return "";
1902
+ }
1903
+ const year = date.getFullYear();
1904
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1905
+ const day = String(date.getDate()).padStart(2, "0");
1906
+ return `${year}${month}${day}`;
1907
+ }
1908
+ function abbreviateModel(model) {
1909
+ return MODEL_ABBREV[model] ?? model.slice(0, 10);
1910
+ }
1911
+ function abbreviateStyle(style) {
1912
+ return STYLE_ABBREV[style] ?? style.slice(0, 2);
1913
+ }
1914
+ function encodeModelBreakdown(models) {
1915
+ return models.slice(0, 3).map((m) => `${abbreviateModel(m.model)}:${Math.round(m.percentage)}`).join(",");
1916
+ }
1917
+ function encodeTopTools(tools) {
1918
+ return tools.slice(0, 5).map((t) => t.tool).join(",");
1919
+ }
1920
+ function encodeToParams(summary) {
1921
+ const params = new URLSearchParams();
1922
+ params.set(PARAMS.sessions, summary.core.sessions.total.toString());
1923
+ params.set(PARAMS.tokens, formatCompact(summary.core.totalTokens.total));
1924
+ params.set(PARAMS.cost, formatCost(summary.core.estimatedCost.total));
1925
+ params.set(PARAMS.daysActive, summary.core.activity.daysActive.toString());
1926
+ params.set(
1927
+ PARAMS.longestStreak,
1928
+ summary.core.activity.longestStreak.toString()
1929
+ );
1930
+ params.set(
1931
+ PARAMS.currentStreak,
1932
+ summary.core.activity.currentStreak.toString()
1933
+ );
1934
+ params.set(PARAMS.peakHour, summary.patterns.peakHour.hour.toString());
1935
+ params.set(PARAMS.peakDay, summary.patterns.peakDayOfWeek.day.toString());
1936
+ if (summary.models.favoriteModel) {
1937
+ params.set(
1938
+ PARAMS.favoriteModel,
1939
+ abbreviateModel(summary.models.favoriteModel.model)
1940
+ );
1941
+ }
1942
+ if (summary.models.modelsUsed.length > 0) {
1943
+ params.set(
1944
+ PARAMS.modelBreakdown,
1945
+ encodeModelBreakdown(summary.models.modelsUsed)
1946
+ );
1947
+ }
1948
+ if (summary.tools.topTools.length > 0) {
1949
+ params.set(PARAMS.topTools, encodeTopTools(summary.tools.topTools));
1950
+ }
1951
+ params.set(
1952
+ PARAMS.developerStyle,
1953
+ abbreviateStyle(summary.tools.developerProfile.primaryStyle)
1954
+ );
1955
+ if (summary.projects.topProject) {
1956
+ params.set(PARAMS.topProject, summary.projects.topProject.displayName);
1957
+ }
1958
+ params.set(PARAMS.projectCount, summary.projects.projectsWorkedOn.toString());
1959
+ params.set(PARAMS.wordsGenerated, formatCompact(summary.fun.wordsGenerated));
1960
+ if (summary.core.activity.firstActiveDay) {
1961
+ params.set(
1962
+ PARAMS.firstActiveDay,
1963
+ formatDate(summary.core.activity.firstActiveDay)
1964
+ );
1965
+ }
1966
+ return params;
1967
+ }
1968
+ function encodeToUrl(summary) {
1969
+ const params = encodeToParams(summary);
1970
+ return `${BASE_URL}?${params.toString()}`;
1971
+ }
1972
+
1973
+ // src/cli.ts
1974
+ var main = defineCommand({
1975
+ meta: {
1976
+ name: "@sleekdesign/ccw25",
1977
+ version: "0.1.0",
1978
+ description: "Generate your Claude Code Wrapped 2025 summary"
1979
+ },
1980
+ args: {
1981
+ year: {
1982
+ type: "string",
1983
+ description: "Year to generate wrapped for (default: 2025)",
1984
+ default: "2025"
1985
+ },
1986
+ json: {
1987
+ type: "boolean",
1988
+ description: "Output raw JSON instead of URL",
1989
+ default: false
1990
+ }
1991
+ },
1992
+ async run({ args }) {
1993
+ const year = Number.parseInt(args.year, 10);
1994
+ console.log("\n\u{1F381} Generating your Claude Code Wrapped 2025...\n");
1995
+ const result = await generateWrappedSummary({ year });
1996
+ if (result.errors.some((e) => e.fatal)) {
1997
+ console.error("\u274C Failed to generate wrapped summary:");
1998
+ for (const error of result.errors) {
1999
+ console.error(` ${error.message}`);
2000
+ }
2001
+ process.exit(1);
2002
+ }
2003
+ if (result.warnings.length > 0) {
2004
+ for (const warning of result.warnings) {
2005
+ console.warn(`\u26A0\uFE0F ${warning.message}`);
2006
+ }
2007
+ console.log("");
2008
+ }
2009
+ if (args.json) {
2010
+ console.log(JSON.stringify(result.summary, null, 2));
2011
+ return;
2012
+ }
2013
+ const { summary } = result;
2014
+ console.log("\u{1F4CA} Your 2025 Stats:\n");
2015
+ console.log(
2016
+ ` Sessions: ${summary.core.sessions.total.toLocaleString()}`
2017
+ );
2018
+ console.log(
2019
+ ` Tokens: ${summary.core.totalTokens.total.toLocaleString()}`
2020
+ );
2021
+ console.log(
2022
+ ` Cost: $${summary.core.estimatedCost.total.toFixed(2)}`
2023
+ );
2024
+ console.log(` Days Active: ${summary.core.activity.daysActive}`);
2025
+ console.log(` Streak: ${summary.core.activity.longestStreak} days`);
2026
+ console.log(
2027
+ ` Style: ${summary.tools.developerProfile.primaryStyle.toUpperCase()}`
2028
+ );
2029
+ console.log("");
2030
+ const url = encodeToUrl(summary);
2031
+ console.log("\u2728 Your wrapped is ready!\n");
2032
+ console.log(`\u{1F517} ${url}
2033
+ `);
2034
+ console.log("Share it on social media! \u{1F680}\n");
2035
+ }
2036
+ });
2037
+ runMain(main);