claude-stats 0.1.0

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.
@@ -0,0 +1,3 @@
1
+ import { MonitorData } from '@claude-monitor/shared';
2
+ export declare function getAccountId(): string;
3
+ export declare function collectData(): MonitorData;
@@ -0,0 +1,676 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getAccountId = getAccountId;
37
+ exports.collectData = collectData;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const child_process_1 = require("child_process");
43
+ const shared_1 = require("@claude-monitor/shared");
44
+ const usage_limits_1 = require("./usage-limits");
45
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
46
+ const STATS_FILE = path.join(CLAUDE_DIR, 'stats-cache.json');
47
+ const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
48
+ // Generate a unique account ID based on Claude account data
49
+ // This will be the same across machines using the same Claude account
50
+ let cachedAccountId = null;
51
+ function getAccountId() {
52
+ if (cachedAccountId)
53
+ return cachedAccountId;
54
+ try {
55
+ const stats = getStats();
56
+ // Use firstSessionDate as the primary identifier - it's unique per account
57
+ // and won't change over time
58
+ const firstSession = stats.firstSessionDate || '';
59
+ // Also include a hash of the projects directory structure for extra uniqueness
60
+ let projectsHash = '';
61
+ if (fs.existsSync(PROJECTS_DIR)) {
62
+ const projects = fs.readdirSync(PROJECTS_DIR).sort().join(',');
63
+ projectsHash = projects;
64
+ }
65
+ const combined = `${firstSession}:${projectsHash}`;
66
+ cachedAccountId = crypto.createHash('md5').update(combined).digest('hex').slice(0, 12);
67
+ return cachedAccountId;
68
+ }
69
+ catch {
70
+ // Fallback to a random ID if we can't generate one
71
+ cachedAccountId = crypto.randomBytes(6).toString('hex');
72
+ return cachedAccountId;
73
+ }
74
+ }
75
+ function getStats() {
76
+ try {
77
+ if (fs.existsSync(STATS_FILE)) {
78
+ const content = fs.readFileSync(STATS_FILE, 'utf-8');
79
+ return JSON.parse(content);
80
+ }
81
+ }
82
+ catch (e) {
83
+ // Ignore errors
84
+ }
85
+ return {};
86
+ }
87
+ function parseSessionTokens(sessionFile) {
88
+ const result = {
89
+ input_tokens: 0,
90
+ output_tokens: 0,
91
+ cache_read: 0,
92
+ cache_write: 0,
93
+ model: null,
94
+ messages: 0,
95
+ };
96
+ try {
97
+ const content = fs.readFileSync(sessionFile, 'utf-8');
98
+ const lines = content.trim().split('\n');
99
+ for (const line of lines) {
100
+ try {
101
+ const msg = JSON.parse(line);
102
+ if (msg.type === 'assistant') {
103
+ result.messages++;
104
+ const usage = msg.message?.usage || {};
105
+ result.input_tokens += usage.input_tokens || 0;
106
+ result.output_tokens += usage.output_tokens || 0;
107
+ result.cache_read += usage.cache_read_input_tokens || 0;
108
+ result.cache_write += usage.cache_creation_input_tokens || 0;
109
+ if (!result.model) {
110
+ result.model = msg.message?.model || null;
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ continue;
116
+ }
117
+ }
118
+ }
119
+ catch {
120
+ // Ignore errors
121
+ }
122
+ return result;
123
+ }
124
+ function parseSessionActivity(sessionFile) {
125
+ const activity = {
126
+ status: 'idle',
127
+ current_activity: null,
128
+ last_tool: null,
129
+ last_user_message: null,
130
+ subagent_detail: null,
131
+ waiting_reason: null,
132
+ };
133
+ try {
134
+ const stats = fs.statSync(sessionFile);
135
+ const fd = fs.openSync(sessionFile, 'r');
136
+ const bufferSize = Math.min(80000, stats.size);
137
+ const buffer = Buffer.alloc(bufferSize);
138
+ const startPos = Math.max(0, stats.size - bufferSize);
139
+ fs.readSync(fd, buffer, 0, bufferSize, startPos);
140
+ fs.closeSync(fd);
141
+ const content = buffer.toString('utf-8');
142
+ const lines = content.trim().split('\n');
143
+ const lastMessages = [];
144
+ for (const line of lines.slice(-30)) {
145
+ try {
146
+ lastMessages.push(JSON.parse(line));
147
+ }
148
+ catch {
149
+ continue;
150
+ }
151
+ }
152
+ if (lastMessages.length === 0)
153
+ return activity;
154
+ const lastMsg = lastMessages[lastMessages.length - 1];
155
+ const lastType = lastMsg.type;
156
+ // Check if waiting for user input
157
+ if (lastType === 'assistant') {
158
+ const content = lastMsg.message?.content || [];
159
+ let hasToolUse = false;
160
+ let lastText = '';
161
+ if (Array.isArray(content)) {
162
+ for (const block of content) {
163
+ if (block?.type === 'tool_use')
164
+ hasToolUse = true;
165
+ if (block?.type === 'text')
166
+ lastText = block.text || '';
167
+ }
168
+ }
169
+ if (!hasToolUse && lastText) {
170
+ activity.status = 'waiting_input';
171
+ const textLines = lastText.trim().split('\n');
172
+ const lastLine = textLines[textLines.length - 1] || '';
173
+ if (lastText.slice(-300).includes('?')) {
174
+ activity.waiting_reason = 'Asking a question';
175
+ for (let i = textLines.length - 1; i >= 0; i--) {
176
+ if (textLines[i].includes('?')) {
177
+ const q = textLines[i].slice(0, 80);
178
+ activity.current_activity = `❓ ${q}${textLines[i].length > 80 ? '...' : ''}`;
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ else {
184
+ activity.waiting_reason = 'Waiting for your response';
185
+ activity.current_activity = `💬 ${lastLine.slice(0, 60)}${lastLine.length > 60 ? '...' : ''}`;
186
+ }
187
+ }
188
+ }
189
+ // Analyze messages for tool activity
190
+ for (let i = lastMessages.length - 1; i >= 0; i--) {
191
+ const msg = lastMessages[i];
192
+ const msgType = msg.type;
193
+ // Check for user message
194
+ if (msgType === 'user' && !activity.last_user_message) {
195
+ const content = msg.message?.content;
196
+ if (typeof content === 'string') {
197
+ activity.last_user_message = content.slice(0, 100) + (content.length > 100 ? '...' : '');
198
+ }
199
+ else if (Array.isArray(content)) {
200
+ for (const c of content) {
201
+ if (c?.type === 'text') {
202
+ const text = (c.text || '').slice(0, 100);
203
+ activity.last_user_message = text + (text.length >= 100 ? '...' : '');
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ // Check for assistant activity
210
+ if (msgType === 'assistant' && activity.status !== 'waiting_input') {
211
+ const content = msg.message?.content || [];
212
+ if (Array.isArray(content)) {
213
+ for (let j = content.length - 1; j >= 0; j--) {
214
+ const block = content[j];
215
+ if (block?.type === 'tool_use') {
216
+ const toolName = block.name || 'unknown';
217
+ activity.last_tool = toolName;
218
+ activity.status = 'working';
219
+ const inp = block.input || {};
220
+ // Detailed activity based on tool
221
+ if (toolName === 'TodoWrite') {
222
+ const todos = inp.todos || [];
223
+ const inProgress = todos.filter((t) => t.status === 'in_progress');
224
+ if (inProgress.length > 0) {
225
+ activity.current_activity = `📋 ${(inProgress[0].content || 'Working on task').slice(0, 50)}`;
226
+ }
227
+ else {
228
+ activity.current_activity = '📋 Updating task list';
229
+ }
230
+ }
231
+ else if (toolName === 'Read') {
232
+ const file = (inp.file_path || '').split('/').pop();
233
+ activity.current_activity = `📖 Reading ${file}`;
234
+ }
235
+ else if (toolName === 'Write') {
236
+ const file = (inp.file_path || '').split('/').pop();
237
+ activity.current_activity = `✏️ Writing ${file}`;
238
+ }
239
+ else if (toolName === 'Edit') {
240
+ const file = (inp.file_path || '').split('/').pop();
241
+ activity.current_activity = `✏️ Editing ${file}`;
242
+ }
243
+ else if (toolName === 'Bash') {
244
+ const cmd = inp.command || '';
245
+ const desc = inp.description || '';
246
+ if (desc) {
247
+ activity.current_activity = `⚡ ${desc.slice(0, 50)}`;
248
+ }
249
+ else {
250
+ activity.current_activity = `⚡ $ ${cmd.slice(0, 45)}`;
251
+ }
252
+ }
253
+ else if (toolName === 'Task') {
254
+ const desc = inp.description || 'task';
255
+ const agentType = inp.subagent_type || '';
256
+ const prompt = inp.prompt || '';
257
+ if (agentType) {
258
+ activity.current_activity = `🤖 ${agentType} agent: ${desc}`;
259
+ }
260
+ else {
261
+ activity.current_activity = `🤖 Subagent: ${desc}`;
262
+ }
263
+ if (prompt) {
264
+ activity.subagent_detail = prompt.slice(0, 120) + (prompt.length > 120 ? '...' : '');
265
+ }
266
+ }
267
+ else if (toolName === 'Grep') {
268
+ const pattern = inp.pattern || '';
269
+ activity.current_activity = `🔍 Searching: ${pattern.slice(0, 40)}`;
270
+ }
271
+ else if (toolName === 'Glob') {
272
+ const pattern = inp.pattern || '';
273
+ activity.current_activity = `📁 Finding: ${pattern.slice(0, 40)}`;
274
+ }
275
+ else if (toolName === 'AskUserQuestion') {
276
+ const questions = inp.questions || [];
277
+ if (questions.length > 0) {
278
+ const q = (questions[0].question || '').slice(0, 60);
279
+ activity.current_activity = `❓ ${q}`;
280
+ }
281
+ else {
282
+ activity.current_activity = '❓ Asking question';
283
+ }
284
+ activity.status = 'waiting_input';
285
+ activity.waiting_reason = 'Needs your input';
286
+ }
287
+ else if (['WebSearch', 'WebFetch', 'mcp__web-search-prime__webSearchPrime'].includes(toolName)) {
288
+ const query = (inp.query || inp.search_query || '').slice(0, 40);
289
+ activity.current_activity = `🌐 Searching: ${query}`;
290
+ }
291
+ else if (toolName.startsWith('mcp__')) {
292
+ const shortName = toolName.split('__').pop();
293
+ activity.current_activity = `🔌 ${shortName}`;
294
+ }
295
+ else {
296
+ activity.current_activity = `🔧 ${toolName}`;
297
+ }
298
+ break;
299
+ }
300
+ }
301
+ }
302
+ }
303
+ if (activity.current_activity)
304
+ break;
305
+ }
306
+ }
307
+ catch {
308
+ // Ignore errors
309
+ }
310
+ return activity;
311
+ }
312
+ function getCliProcesses() {
313
+ try {
314
+ const result = (0, child_process_1.execSync)('ps aux', { encoding: 'utf-8', timeout: 5000 });
315
+ const processes = [];
316
+ for (const line of result.trim().split('\n').slice(1)) {
317
+ const parts = line.split(/\s+/);
318
+ if (parts.length < 11)
319
+ continue;
320
+ const cmd = parts.slice(10).join(' ');
321
+ if (cmd.startsWith('claude') && !cmd.includes('Claude.app')) {
322
+ const pid = parseInt(parts[1], 10);
323
+ const cpu = parseFloat(parts[2]);
324
+ const mem = parseFloat(parts[3]);
325
+ let cwd = null;
326
+ try {
327
+ const lsof = (0, child_process_1.execSync)(`lsof -p ${pid} -Fn`, { encoding: 'utf-8', timeout: 2000 });
328
+ for (const lline of lsof.split('\n')) {
329
+ if (lline.startsWith('n') && lline.includes('projects') && lline.includes('.jsonl')) {
330
+ const filePath = lline.slice(1);
331
+ const match = filePath.split('/projects/')[1];
332
+ if (match) {
333
+ cwd = match.split('/')[0].replace(/-/g, '/');
334
+ }
335
+ break;
336
+ }
337
+ }
338
+ }
339
+ catch {
340
+ // Ignore
341
+ }
342
+ processes.push({ pid, cpu, mem, cwd });
343
+ }
344
+ }
345
+ return processes;
346
+ }
347
+ catch {
348
+ return [];
349
+ }
350
+ }
351
+ function getOpenFiles() {
352
+ const openFiles = new Set();
353
+ try {
354
+ const result = (0, child_process_1.execSync)('lsof -c claude', { encoding: 'utf-8', timeout: 5000 });
355
+ for (const line of result.split('\n')) {
356
+ if (line.includes('.jsonl') && line.includes('projects')) {
357
+ const parts = line.split(/\s+/);
358
+ for (const part of parts) {
359
+ if (part.includes('.jsonl')) {
360
+ openFiles.add(part);
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ catch {
368
+ // Ignore
369
+ }
370
+ return openFiles;
371
+ }
372
+ function getTodayUsageFromSessions() {
373
+ const today = new Date().toISOString().split('T')[0];
374
+ const todayStart = new Date();
375
+ todayStart.setHours(0, 0, 0, 0);
376
+ const todayStartTs = todayStart.getTime();
377
+ const modelTokens = {};
378
+ let messages = 0;
379
+ let sessions = 0;
380
+ if (!fs.existsSync(PROJECTS_DIR)) {
381
+ return {
382
+ date: today,
383
+ tokens: 0,
384
+ cost: 0,
385
+ messages: 0,
386
+ sessions: 0,
387
+ models: [],
388
+ model_breakdown: {},
389
+ };
390
+ }
391
+ const projectDirs = fs.readdirSync(PROJECTS_DIR);
392
+ for (const projectDir of projectDirs) {
393
+ const projectPath = path.join(PROJECTS_DIR, projectDir);
394
+ if (!fs.statSync(projectPath).isDirectory())
395
+ continue;
396
+ const sessionFiles = fs.readdirSync(projectPath).filter((f) => f.endsWith('.jsonl'));
397
+ for (const sessionFile of sessionFiles) {
398
+ const sessionPath = path.join(projectPath, sessionFile);
399
+ const stat = fs.statSync(sessionPath);
400
+ if (stat.mtimeMs < todayStartTs)
401
+ continue;
402
+ sessions++;
403
+ try {
404
+ const content = fs.readFileSync(sessionPath, 'utf-8');
405
+ for (const line of content.trim().split('\n')) {
406
+ try {
407
+ const msg = JSON.parse(line);
408
+ const ts = msg.timestamp || '';
409
+ if (!ts.startsWith(today))
410
+ continue;
411
+ if (msg.type === 'assistant') {
412
+ messages++;
413
+ const usage = msg.message?.usage || {};
414
+ const model = msg.message?.model || 'unknown';
415
+ if (!modelTokens[model]) {
416
+ modelTokens[model] = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
417
+ }
418
+ modelTokens[model].input += usage.input_tokens || 0;
419
+ modelTokens[model].output += usage.output_tokens || 0;
420
+ modelTokens[model].cache_read += usage.cache_read_input_tokens || 0;
421
+ modelTokens[model].cache_write += usage.cache_creation_input_tokens || 0;
422
+ }
423
+ }
424
+ catch {
425
+ continue;
426
+ }
427
+ }
428
+ }
429
+ catch {
430
+ continue;
431
+ }
432
+ }
433
+ }
434
+ let allTokens = 0;
435
+ let totalCost = 0;
436
+ const modelBreakdown = {};
437
+ for (const [model, tokens] of Object.entries(modelTokens)) {
438
+ const modelTotal = tokens.input + tokens.output + tokens.cache_read + tokens.cache_write;
439
+ allTokens += modelTotal;
440
+ const pricing = shared_1.MODEL_PRICING[model] || shared_1.DEFAULT_PRICING;
441
+ const cost = (tokens.input / 1000000) * pricing.input +
442
+ (tokens.output / 1000000) * pricing.output +
443
+ (tokens.cache_read / 1000000) * pricing.cache_read +
444
+ (tokens.cache_write / 1000000) * pricing.cache_write;
445
+ totalCost += cost;
446
+ modelBreakdown[model] = {
447
+ inputTokens: tokens.input,
448
+ outputTokens: tokens.output,
449
+ cacheReadInputTokens: tokens.cache_read,
450
+ cacheCreationInputTokens: tokens.cache_write,
451
+ cost: Math.round(cost * 10000) / 10000,
452
+ };
453
+ }
454
+ return {
455
+ date: today,
456
+ tokens: allTokens,
457
+ cost: Math.round(totalCost * 10000) / 10000,
458
+ messages,
459
+ sessions,
460
+ models: Object.keys(modelTokens),
461
+ model_breakdown: modelBreakdown,
462
+ };
463
+ }
464
+ function getDailyUsage(stats) {
465
+ const dailyTokens = stats.dailyModelTokens || [];
466
+ const dailyActivity = {};
467
+ for (const d of stats.dailyActivity || []) {
468
+ dailyActivity[d.date] = d;
469
+ }
470
+ const result = [];
471
+ for (const day of dailyTokens.slice(-14)) {
472
+ const date = day.date;
473
+ const tokensByModel = day.tokensByModel || {};
474
+ const totalTokens = Object.values(tokensByModel).reduce((sum, t) => sum + t, 0);
475
+ let dayCost = 0;
476
+ for (const [model, tokens] of Object.entries(tokensByModel)) {
477
+ const pricing = shared_1.MODEL_PRICING[model] || shared_1.DEFAULT_PRICING;
478
+ dayCost += (tokens / 1000000) * pricing.input;
479
+ }
480
+ const activity = dailyActivity[date] || { messageCount: 0, sessionCount: 0 };
481
+ result.push({
482
+ date,
483
+ tokens: totalTokens,
484
+ cost: Math.round(dayCost * 10000) / 10000,
485
+ messages: activity.messageCount,
486
+ sessions: activity.sessionCount,
487
+ models: Object.keys(tokensByModel),
488
+ });
489
+ }
490
+ return result;
491
+ }
492
+ function getSessions() {
493
+ const active = [];
494
+ const recent = [];
495
+ if (!fs.existsSync(PROJECTS_DIR)) {
496
+ return { active, recent };
497
+ }
498
+ const openFiles = getOpenFiles();
499
+ const now = Date.now();
500
+ const projectDirs = fs.readdirSync(PROJECTS_DIR);
501
+ for (const projectDir of projectDirs) {
502
+ const projectPath = path.join(PROJECTS_DIR, projectDir);
503
+ if (!fs.statSync(projectPath).isDirectory())
504
+ continue;
505
+ const sessionFiles = fs.readdirSync(projectPath).filter((f) => f.endsWith('.jsonl'));
506
+ for (const sessionFile of sessionFiles) {
507
+ const sessionPath = path.join(projectPath, sessionFile);
508
+ const stat = fs.statSync(sessionPath);
509
+ const isActive = openFiles.has(sessionPath) || now - stat.mtimeMs < 30000;
510
+ const projectName = projectDir.replace(/-/g, '/');
511
+ const projectShort = projectName.includes('/') ? projectName.split('/').pop() : projectName;
512
+ let activity = {
513
+ status: 'idle',
514
+ current_activity: null,
515
+ last_tool: null,
516
+ last_user_message: null,
517
+ subagent_detail: null,
518
+ waiting_reason: null,
519
+ };
520
+ let tokenData = null;
521
+ let sessionCost = null;
522
+ if (isActive) {
523
+ activity = parseSessionActivity(sessionPath);
524
+ tokenData = parseSessionTokens(sessionPath);
525
+ sessionCost = (0, shared_1.calculateCost)(tokenData.model || 'claude-opus-4-5-20251101', {
526
+ input_tokens: tokenData.input_tokens,
527
+ output_tokens: tokenData.output_tokens,
528
+ cache_read_input_tokens: tokenData.cache_read,
529
+ cache_creation_input_tokens: tokenData.cache_write,
530
+ });
531
+ }
532
+ const session = {
533
+ session_id: path.basename(sessionFile, '.jsonl'),
534
+ project: projectName,
535
+ project_short: projectShort,
536
+ last_modified: new Date(stat.mtimeMs).toLocaleTimeString('en-US', { hour12: false }),
537
+ size_kb: Math.round((stat.size / 1024) * 10) / 10,
538
+ is_active: isActive,
539
+ tokens: tokenData,
540
+ cost: sessionCost,
541
+ model: tokenData?.model || null,
542
+ ...activity,
543
+ };
544
+ if (isActive) {
545
+ active.push(session);
546
+ }
547
+ else if (now - stat.mtimeMs < 86400000) {
548
+ recent.push(session);
549
+ }
550
+ }
551
+ }
552
+ active.sort((a, b) => b.last_modified.localeCompare(a.last_modified));
553
+ recent.sort((a, b) => b.last_modified.localeCompare(a.last_modified));
554
+ return { active, recent: recent.slice(0, 10) };
555
+ }
556
+ function collectData() {
557
+ const stats = getStats();
558
+ const processes = getCliProcesses();
559
+ const { active: activeSessions, recent: recentSessions } = getSessions();
560
+ // Today's usage - compute from session files in real-time
561
+ const todayUsage = getTodayUsageFromSessions();
562
+ // Merge historical model usage with today's
563
+ const modelUsage = { ...stats.modelUsage };
564
+ const todayBreakdown = todayUsage.model_breakdown;
565
+ for (const [model, todayData] of Object.entries(todayBreakdown)) {
566
+ if (modelUsage[model]) {
567
+ modelUsage[model] = {
568
+ inputTokens: (modelUsage[model].inputTokens || 0) + (todayData.inputTokens || 0),
569
+ outputTokens: (modelUsage[model].outputTokens || 0) + (todayData.outputTokens || 0),
570
+ cacheReadInputTokens: (modelUsage[model].cacheReadInputTokens || 0) + (todayData.cacheReadInputTokens || 0),
571
+ cacheCreationInputTokens: (modelUsage[model].cacheCreationInputTokens || 0) + (todayData.cacheCreationInputTokens || 0),
572
+ };
573
+ }
574
+ else {
575
+ modelUsage[model] = {
576
+ inputTokens: todayData.inputTokens,
577
+ outputTokens: todayData.outputTokens,
578
+ cacheReadInputTokens: todayData.cacheReadInputTokens,
579
+ cacheCreationInputTokens: todayData.cacheCreationInputTokens,
580
+ };
581
+ }
582
+ }
583
+ // Calculate costs for each model
584
+ const modelCosts = {};
585
+ let totalCost = 0;
586
+ let totalTokens = 0;
587
+ for (const [model, usage] of Object.entries(modelUsage)) {
588
+ const cost = (0, shared_1.calculateCost)(model, usage);
589
+ modelCosts[model] = cost;
590
+ totalCost += cost;
591
+ totalTokens +=
592
+ (usage.inputTokens || 0) +
593
+ (usage.outputTokens || 0) +
594
+ (usage.cacheReadInputTokens || 0) +
595
+ (usage.cacheCreationInputTokens || 0);
596
+ }
597
+ // Get daily usage and add today
598
+ const dailyUsage = getDailyUsage(stats);
599
+ if (todayUsage.tokens > 0) {
600
+ dailyUsage.push({
601
+ date: todayUsage.date,
602
+ tokens: todayUsage.tokens,
603
+ cost: todayUsage.cost,
604
+ messages: todayUsage.messages,
605
+ sessions: todayUsage.sessions,
606
+ models: todayUsage.models,
607
+ });
608
+ }
609
+ // Active sessions total cost
610
+ const activeSessionsCost = activeSessions.reduce((sum, s) => sum + (s.cost || 0), 0);
611
+ const activeSessionsTokens = activeSessions.reduce((sum, s) => {
612
+ if (!s.tokens)
613
+ return sum;
614
+ return sum + s.tokens.input_tokens + s.tokens.output_tokens + s.tokens.cache_read + s.tokens.cache_write;
615
+ }, 0);
616
+ // Calculate averages and projections
617
+ const totalSessionsCount = (stats.totalSessions || 0) + todayUsage.sessions;
618
+ const totalMessagesCount = (stats.totalMessages || 0) + todayUsage.messages;
619
+ const avgCostPerMessage = totalMessagesCount > 0 ? totalCost / totalMessagesCount : 0;
620
+ const avgCostPerSession = totalSessionsCount > 0 ? totalCost / totalSessionsCount : 0;
621
+ // Days since first session for projection
622
+ let daysActive = dailyUsage.length || 1;
623
+ const firstSessionDate = stats.firstSessionDate;
624
+ if (firstSessionDate) {
625
+ try {
626
+ const firstDt = new Date(firstSessionDate);
627
+ daysActive = Math.max(1, Math.floor((Date.now() - firstDt.getTime()) / 86400000) + 1);
628
+ }
629
+ catch {
630
+ // Use default
631
+ }
632
+ }
633
+ const dailyAvgCost = daysActive > 0 ? totalCost / daysActive : 0;
634
+ // Weekly cost calculation (last 7 days from daily_usage)
635
+ const last7Days = dailyUsage.slice(-7);
636
+ let weeklyCost = last7Days.reduce((sum, d) => sum + (d.cost || 0), 0);
637
+ weeklyCost += todayUsage.cost;
638
+ // Today's hourly rate
639
+ const currentHour = new Date().getHours();
640
+ const hoursElapsed = Math.max(currentHour, 1);
641
+ const todayHourlyRate = hoursElapsed > 0 ? todayUsage.cost / hoursElapsed : 0;
642
+ const hourCounts = stats.hourCounts || {};
643
+ const summary = {
644
+ is_running: activeSessions.length > 0,
645
+ active_count: activeSessions.length,
646
+ waiting_input: activeSessions.filter((s) => s.status === 'waiting_input').length,
647
+ total_sessions: totalSessionsCount,
648
+ total_messages: totalMessagesCount,
649
+ total_cost: Math.round(totalCost * 100) / 100,
650
+ total_tokens: totalTokens,
651
+ active_sessions_cost: Math.round(activeSessionsCost * 10000) / 10000,
652
+ active_sessions_tokens: activeSessionsTokens,
653
+ first_session: firstSessionDate || null,
654
+ days_active: daysActive,
655
+ avg_cost_per_message: Math.round(avgCostPerMessage * 10000) / 10000,
656
+ avg_cost_per_session: Math.round(avgCostPerSession * 100) / 100,
657
+ daily_avg_cost: Math.round(dailyAvgCost * 100) / 100,
658
+ projected_monthly: Math.round(dailyAvgCost * 30 * 100) / 100,
659
+ weekly_cost: Math.round(weeklyCost * 100) / 100,
660
+ today_hourly_rate: Math.round(todayHourlyRate * 10000) / 10000,
661
+ est_weekly: Math.round(dailyAvgCost * 7 * 100) / 100,
662
+ };
663
+ return {
664
+ stats,
665
+ model_usage: modelUsage,
666
+ processes,
667
+ active_sessions: activeSessions,
668
+ recent_sessions: recentSessions,
669
+ model_costs: modelCosts,
670
+ daily_usage: dailyUsage,
671
+ today_usage: todayUsage,
672
+ hour_counts: hourCounts,
673
+ summary,
674
+ usage_limits: (0, usage_limits_1.getUsageLimitsSync)(),
675
+ };
676
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};