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