claude-stats 0.2.2 → 0.2.3
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 +127 -125
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -507,6 +507,9 @@ async function forceRefreshUsageLimits() {
|
|
|
507
507
|
var CLAUDE_DIR = path2.join(os3.homedir(), ".claude");
|
|
508
508
|
var STATS_FILE = path2.join(CLAUDE_DIR, "stats-cache.json");
|
|
509
509
|
var PROJECTS_DIR = path2.join(CLAUDE_DIR, "projects");
|
|
510
|
+
var allTimeStatsCache = null;
|
|
511
|
+
var allTimeStatsCacheTime = 0;
|
|
512
|
+
var ALL_TIME_CACHE_DURATION = 6e4;
|
|
510
513
|
var cachedAccountId = null;
|
|
511
514
|
function getAccountId() {
|
|
512
515
|
if (cachedAccountId) return cachedAccountId;
|
|
@@ -803,25 +806,28 @@ function getLocalDateString(date) {
|
|
|
803
806
|
const day = String(date.getDate()).padStart(2, "0");
|
|
804
807
|
return `${year}-${month}-${day}`;
|
|
805
808
|
}
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
todayStart.setHours(0, 0, 0, 0);
|
|
811
|
-
const todayStartTs = todayStart.getTime();
|
|
812
|
-
const modelTokens = {};
|
|
813
|
-
let messages = 0;
|
|
814
|
-
let sessions = 0;
|
|
809
|
+
function calculateAllTimeStats() {
|
|
810
|
+
if (allTimeStatsCache && Date.now() - allTimeStatsCacheTime < ALL_TIME_CACHE_DURATION) {
|
|
811
|
+
return allTimeStatsCache;
|
|
812
|
+
}
|
|
815
813
|
const seen = /* @__PURE__ */ new Set();
|
|
814
|
+
const modelTokens = {};
|
|
815
|
+
const dailyTokens = {};
|
|
816
|
+
const dailyMessages = {};
|
|
817
|
+
const dailySessions = {};
|
|
818
|
+
let totalMessages = 0;
|
|
819
|
+
let firstSessionDate = null;
|
|
820
|
+
const allSessionIds = /* @__PURE__ */ new Set();
|
|
816
821
|
if (!fs2.existsSync(PROJECTS_DIR)) {
|
|
817
822
|
return {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
823
|
+
totalCost: 0,
|
|
824
|
+
totalTokens: 0,
|
|
825
|
+
totalMessages: 0,
|
|
826
|
+
totalSessions: 0,
|
|
827
|
+
modelUsage: {},
|
|
828
|
+
modelCosts: {},
|
|
829
|
+
dailyUsage: [],
|
|
830
|
+
firstSessionDate: null
|
|
825
831
|
};
|
|
826
832
|
}
|
|
827
833
|
const projectDirs = fs2.readdirSync(PROJECTS_DIR);
|
|
@@ -831,36 +837,51 @@ function getTodayUsageFromSessions() {
|
|
|
831
837
|
const sessionFiles = fs2.readdirSync(projectPath).filter((f) => f.endsWith(".jsonl"));
|
|
832
838
|
for (const sessionFile of sessionFiles) {
|
|
833
839
|
const sessionPath = path2.join(projectPath, sessionFile);
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
sessions++;
|
|
840
|
+
const sessionId = path2.basename(sessionFile, ".jsonl");
|
|
841
|
+
allSessionIds.add(sessionId);
|
|
837
842
|
try {
|
|
838
843
|
const content = fs2.readFileSync(sessionPath, "utf-8");
|
|
839
844
|
for (const line of content.trim().split("\n")) {
|
|
840
845
|
try {
|
|
841
846
|
const msg = JSON.parse(line);
|
|
847
|
+
if (msg.type !== "assistant") continue;
|
|
848
|
+
const hash = getDedupeHash(msg);
|
|
849
|
+
if (hash) {
|
|
850
|
+
if (seen.has(hash)) continue;
|
|
851
|
+
seen.add(hash);
|
|
852
|
+
}
|
|
842
853
|
const ts = msg.timestamp || "";
|
|
843
854
|
if (!ts) continue;
|
|
844
855
|
const msgDate = new Date(ts);
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
856
|
+
const dateStr = getLocalDateString(msgDate);
|
|
857
|
+
if (!firstSessionDate || dateStr < firstSessionDate) {
|
|
858
|
+
firstSessionDate = dateStr;
|
|
859
|
+
}
|
|
860
|
+
totalMessages++;
|
|
861
|
+
const usage = msg.message?.usage || {};
|
|
862
|
+
const model = msg.message?.model || "unknown";
|
|
863
|
+
if (!modelTokens[model]) {
|
|
864
|
+
modelTokens[model] = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
|
|
865
|
+
}
|
|
866
|
+
modelTokens[model].input += usage.input_tokens || 0;
|
|
867
|
+
modelTokens[model].output += usage.output_tokens || 0;
|
|
868
|
+
modelTokens[model].cache_read += usage.cache_read_input_tokens || 0;
|
|
869
|
+
modelTokens[model].cache_write += usage.cache_creation_input_tokens || 0;
|
|
870
|
+
if (!dailyTokens[dateStr]) {
|
|
871
|
+
dailyTokens[dateStr] = {};
|
|
872
|
+
}
|
|
873
|
+
if (!dailyTokens[dateStr][model]) {
|
|
874
|
+
dailyTokens[dateStr][model] = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
|
|
863
875
|
}
|
|
876
|
+
dailyTokens[dateStr][model].input += usage.input_tokens || 0;
|
|
877
|
+
dailyTokens[dateStr][model].output += usage.output_tokens || 0;
|
|
878
|
+
dailyTokens[dateStr][model].cache_read += usage.cache_read_input_tokens || 0;
|
|
879
|
+
dailyTokens[dateStr][model].cache_write += usage.cache_creation_input_tokens || 0;
|
|
880
|
+
dailyMessages[dateStr] = (dailyMessages[dateStr] || 0) + 1;
|
|
881
|
+
if (!dailySessions[dateStr]) {
|
|
882
|
+
dailySessions[dateStr] = /* @__PURE__ */ new Set();
|
|
883
|
+
}
|
|
884
|
+
dailySessions[dateStr].add(sessionId);
|
|
864
885
|
} catch {
|
|
865
886
|
continue;
|
|
866
887
|
}
|
|
@@ -870,59 +891,57 @@ function getTodayUsageFromSessions() {
|
|
|
870
891
|
}
|
|
871
892
|
}
|
|
872
893
|
}
|
|
873
|
-
|
|
894
|
+
const modelUsage = {};
|
|
895
|
+
const modelCosts = {};
|
|
874
896
|
let totalCost = 0;
|
|
875
|
-
|
|
897
|
+
let totalTokens = 0;
|
|
876
898
|
for (const [model, tokens] of Object.entries(modelTokens)) {
|
|
877
|
-
|
|
878
|
-
allTokens += modelTotal;
|
|
879
|
-
const pricing = import_shared.MODEL_PRICING[model] || import_shared.DEFAULT_PRICING;
|
|
880
|
-
const cost = tokens.input / 1e6 * pricing.input + tokens.output / 1e6 * pricing.output + tokens.cache_read / 1e6 * pricing.cache_read + tokens.cache_write / 1e6 * pricing.cache_write;
|
|
881
|
-
totalCost += cost;
|
|
882
|
-
modelBreakdown[model] = {
|
|
899
|
+
modelUsage[model] = {
|
|
883
900
|
inputTokens: tokens.input,
|
|
884
901
|
outputTokens: tokens.output,
|
|
885
902
|
cacheReadInputTokens: tokens.cache_read,
|
|
886
|
-
cacheCreationInputTokens: tokens.cache_write
|
|
887
|
-
cost: Math.round(cost * 1e4) / 1e4
|
|
903
|
+
cacheCreationInputTokens: tokens.cache_write
|
|
888
904
|
};
|
|
905
|
+
const cost = (0, import_shared.calculateCost)(model, modelUsage[model]);
|
|
906
|
+
modelCosts[model] = cost;
|
|
907
|
+
totalCost += cost;
|
|
908
|
+
totalTokens += tokens.input + tokens.output + tokens.cache_read + tokens.cache_write;
|
|
889
909
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
sessions,
|
|
896
|
-
models: Object.keys(modelTokens),
|
|
897
|
-
model_breakdown: modelBreakdown
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
function getDailyUsage(stats) {
|
|
901
|
-
const dailyTokens = stats.dailyModelTokens || [];
|
|
902
|
-
const dailyActivity = {};
|
|
903
|
-
for (const d of stats.dailyActivity || []) {
|
|
904
|
-
dailyActivity[d.date] = d;
|
|
905
|
-
}
|
|
906
|
-
const result = [];
|
|
907
|
-
for (const day of dailyTokens.slice(-14)) {
|
|
908
|
-
const date = day.date;
|
|
909
|
-
const tokensByModel = day.tokensByModel || {};
|
|
910
|
-
const totalTokens = Object.values(tokensByModel).reduce((sum, t) => sum + t, 0);
|
|
910
|
+
const dailyUsage = [];
|
|
911
|
+
const dates = Object.keys(dailyTokens).sort();
|
|
912
|
+
for (const date of dates) {
|
|
913
|
+
const dayModels = dailyTokens[date];
|
|
914
|
+
let dayTokens = 0;
|
|
911
915
|
let dayCost = 0;
|
|
912
|
-
|
|
916
|
+
const models = [];
|
|
917
|
+
for (const [model, tokens] of Object.entries(dayModels)) {
|
|
918
|
+
models.push(model);
|
|
919
|
+
const modelTotal = tokens.input + tokens.output + tokens.cache_read + tokens.cache_write;
|
|
920
|
+
dayTokens += modelTotal;
|
|
913
921
|
const pricing = import_shared.MODEL_PRICING[model] || import_shared.DEFAULT_PRICING;
|
|
914
|
-
dayCost += tokens / 1e6 * pricing.input;
|
|
922
|
+
dayCost += tokens.input / 1e6 * pricing.input + tokens.output / 1e6 * pricing.output + tokens.cache_read / 1e6 * pricing.cache_read + tokens.cache_write / 1e6 * pricing.cache_write;
|
|
915
923
|
}
|
|
916
|
-
|
|
917
|
-
result.push({
|
|
924
|
+
dailyUsage.push({
|
|
918
925
|
date,
|
|
919
|
-
tokens:
|
|
926
|
+
tokens: dayTokens,
|
|
920
927
|
cost: Math.round(dayCost * 1e4) / 1e4,
|
|
921
|
-
messages:
|
|
922
|
-
sessions:
|
|
923
|
-
models
|
|
928
|
+
messages: dailyMessages[date] || 0,
|
|
929
|
+
sessions: dailySessions[date]?.size || 0,
|
|
930
|
+
models
|
|
924
931
|
});
|
|
925
932
|
}
|
|
933
|
+
const result = {
|
|
934
|
+
totalCost: Math.round(totalCost * 100) / 100,
|
|
935
|
+
totalTokens,
|
|
936
|
+
totalMessages,
|
|
937
|
+
totalSessions: allSessionIds.size,
|
|
938
|
+
modelUsage,
|
|
939
|
+
modelCosts,
|
|
940
|
+
dailyUsage,
|
|
941
|
+
firstSessionDate
|
|
942
|
+
};
|
|
943
|
+
allTimeStatsCache = result;
|
|
944
|
+
allTimeStatsCacheTime = Date.now();
|
|
926
945
|
return result;
|
|
927
946
|
}
|
|
928
947
|
function getSessions() {
|
|
@@ -991,57 +1010,41 @@ function collectData() {
|
|
|
991
1010
|
const stats = getStats();
|
|
992
1011
|
const processes = getCliProcesses();
|
|
993
1012
|
const { active: activeSessions, recent: recentSessions } = getSessions();
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
let totalCost = 0;
|
|
1016
|
-
let totalTokens = 0;
|
|
1017
|
-
for (const [model, usage] of Object.entries(modelUsage)) {
|
|
1018
|
-
const cost = (0, import_shared.calculateCost)(model, usage);
|
|
1019
|
-
modelCosts[model] = cost;
|
|
1020
|
-
totalCost += cost;
|
|
1021
|
-
totalTokens += (usage.inputTokens || 0) + (usage.outputTokens || 0) + (usage.cacheReadInputTokens || 0) + (usage.cacheCreationInputTokens || 0);
|
|
1022
|
-
}
|
|
1023
|
-
const dailyUsage = getDailyUsage(stats);
|
|
1024
|
-
if (todayUsage.tokens > 0) {
|
|
1025
|
-
dailyUsage.push({
|
|
1026
|
-
date: todayUsage.date,
|
|
1027
|
-
tokens: todayUsage.tokens,
|
|
1028
|
-
cost: todayUsage.cost,
|
|
1029
|
-
messages: todayUsage.messages,
|
|
1030
|
-
sessions: todayUsage.sessions,
|
|
1031
|
-
models: todayUsage.models
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1013
|
+
const allTimeStats = calculateAllTimeStats();
|
|
1014
|
+
const today = getLocalDateString(/* @__PURE__ */ new Date());
|
|
1015
|
+
const todayFromAllTime = allTimeStats.dailyUsage.find((d) => d.date === today);
|
|
1016
|
+
const todayUsage = todayFromAllTime ? {
|
|
1017
|
+
date: today,
|
|
1018
|
+
tokens: todayFromAllTime.tokens,
|
|
1019
|
+
cost: todayFromAllTime.cost,
|
|
1020
|
+
messages: todayFromAllTime.messages,
|
|
1021
|
+
sessions: todayFromAllTime.sessions,
|
|
1022
|
+
models: todayFromAllTime.models,
|
|
1023
|
+
model_breakdown: {}
|
|
1024
|
+
// Not used in summary display
|
|
1025
|
+
} : {
|
|
1026
|
+
date: today,
|
|
1027
|
+
tokens: 0,
|
|
1028
|
+
cost: 0,
|
|
1029
|
+
messages: 0,
|
|
1030
|
+
sessions: 0,
|
|
1031
|
+
models: [],
|
|
1032
|
+
model_breakdown: {}
|
|
1033
|
+
};
|
|
1034
1034
|
const activeSessionsCost = activeSessions.reduce((sum, s) => sum + (s.cost || 0), 0);
|
|
1035
1035
|
const activeSessionsTokens = activeSessions.reduce((sum, s) => {
|
|
1036
1036
|
if (!s.tokens) return sum;
|
|
1037
1037
|
return sum + s.tokens.input_tokens + s.tokens.output_tokens + s.tokens.cache_read + s.tokens.cache_write;
|
|
1038
1038
|
}, 0);
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1039
|
+
const totalCost = allTimeStats.totalCost;
|
|
1040
|
+
const totalTokens = allTimeStats.totalTokens;
|
|
1041
|
+
const totalMessagesCount = allTimeStats.totalMessages;
|
|
1042
|
+
const totalSessionsCount = allTimeStats.totalSessions;
|
|
1041
1043
|
const avgCostPerMessage = totalMessagesCount > 0 ? totalCost / totalMessagesCount : 0;
|
|
1042
1044
|
const avgCostPerSession = totalSessionsCount > 0 ? totalCost / totalSessionsCount : 0;
|
|
1045
|
+
const dailyUsage = allTimeStats.dailyUsage;
|
|
1043
1046
|
let daysActive = dailyUsage.length || 1;
|
|
1044
|
-
const firstSessionDate =
|
|
1047
|
+
const firstSessionDate = allTimeStats.firstSessionDate;
|
|
1045
1048
|
if (firstSessionDate) {
|
|
1046
1049
|
try {
|
|
1047
1050
|
const firstDt = new Date(firstSessionDate);
|
|
@@ -1051,11 +1054,10 @@ function collectData() {
|
|
|
1051
1054
|
}
|
|
1052
1055
|
const dailyAvgCost = daysActive > 0 ? totalCost / daysActive : 0;
|
|
1053
1056
|
const last7Days = dailyUsage.slice(-7);
|
|
1054
|
-
|
|
1055
|
-
weeklyCost += todayUsage.cost;
|
|
1057
|
+
const weeklyCost = last7Days.reduce((sum, d) => sum + (d.cost || 0), 0);
|
|
1056
1058
|
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
1057
1059
|
const hoursElapsed = Math.max(currentHour, 1);
|
|
1058
|
-
const todayHourlyRate =
|
|
1060
|
+
const todayHourlyRate = todayUsage.cost > 0 ? todayUsage.cost / hoursElapsed : 0;
|
|
1059
1061
|
const hourCounts = stats.hourCounts || {};
|
|
1060
1062
|
const summary = {
|
|
1061
1063
|
is_running: activeSessions.length > 0,
|
|
@@ -1079,11 +1081,11 @@ function collectData() {
|
|
|
1079
1081
|
};
|
|
1080
1082
|
return {
|
|
1081
1083
|
stats,
|
|
1082
|
-
model_usage: modelUsage,
|
|
1084
|
+
model_usage: allTimeStats.modelUsage,
|
|
1083
1085
|
processes,
|
|
1084
1086
|
active_sessions: activeSessions,
|
|
1085
1087
|
recent_sessions: recentSessions,
|
|
1086
|
-
model_costs: modelCosts,
|
|
1088
|
+
model_costs: allTimeStats.modelCosts,
|
|
1087
1089
|
daily_usage: dailyUsage,
|
|
1088
1090
|
today_usage: todayUsage,
|
|
1089
1091
|
hour_counts: hourCounts,
|
|
@@ -1242,7 +1244,7 @@ function setupDaemonCleanup() {
|
|
|
1242
1244
|
// src/index.ts
|
|
1243
1245
|
var DEFAULT_SERVER = "https://cm.cban.top";
|
|
1244
1246
|
var program = new import_commander.Command();
|
|
1245
|
-
program.name("claude-stats").description("Monitor and stream Claude Code usage statistics").version("0.2.
|
|
1247
|
+
program.name("claude-stats").description("Monitor and stream Claude Code usage statistics").version("0.2.3");
|
|
1246
1248
|
function promptForInput(question, hidden = false) {
|
|
1247
1249
|
return new Promise((resolve) => {
|
|
1248
1250
|
const rl = readline.createInterface({
|