claudectx 1.1.1 → 1.1.2
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/README.md +27 -19
- package/dist/index.js +64 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -222,7 +222,7 @@ function findSessionFile(sessionId) {
|
|
|
222
222
|
}
|
|
223
223
|
return files[0]?.filePath ?? null;
|
|
224
224
|
}
|
|
225
|
-
function readSessionUsage(sessionFilePath) {
|
|
225
|
+
async function readSessionUsage(sessionFilePath) {
|
|
226
226
|
const result = {
|
|
227
227
|
inputTokens: 0,
|
|
228
228
|
outputTokens: 0,
|
|
@@ -231,28 +231,32 @@ function readSessionUsage(sessionFilePath) {
|
|
|
231
231
|
requestCount: 0
|
|
232
232
|
};
|
|
233
233
|
if (!fs9.existsSync(sessionFilePath)) return result;
|
|
234
|
-
|
|
234
|
+
const { createReadStream } = await import("fs");
|
|
235
|
+
const { createInterface } = await import("readline");
|
|
235
236
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
237
|
+
const rl = createInterface({
|
|
238
|
+
input: createReadStream(sessionFilePath, { encoding: "utf-8" }),
|
|
239
|
+
crlfDelay: Infinity
|
|
240
|
+
});
|
|
241
|
+
for await (const line of rl) {
|
|
242
|
+
if (!line.trim()) continue;
|
|
243
|
+
try {
|
|
244
|
+
const entry = JSON.parse(line);
|
|
245
|
+
const usage = entry.usage ?? entry.message?.usage;
|
|
246
|
+
if (!usage) continue;
|
|
247
|
+
const isAssistant = entry.type === "assistant" || entry.message?.role === "assistant";
|
|
248
|
+
if (isAssistant) {
|
|
249
|
+
result.inputTokens += usage.input_tokens ?? 0;
|
|
250
|
+
result.outputTokens += usage.output_tokens ?? 0;
|
|
251
|
+
result.cacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
|
|
252
|
+
result.cacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
253
|
+
result.requestCount++;
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
253
256
|
}
|
|
254
|
-
} catch {
|
|
255
257
|
}
|
|
258
|
+
} catch {
|
|
259
|
+
return result;
|
|
256
260
|
}
|
|
257
261
|
return result;
|
|
258
262
|
}
|
|
@@ -382,20 +386,23 @@ function Dashboard({
|
|
|
382
386
|
const events = readAllEvents();
|
|
383
387
|
const fileStats2 = aggregateStats(events);
|
|
384
388
|
const sessionFile2 = sessionId ? findSessionFile(sessionId) : findSessionFile();
|
|
385
|
-
const
|
|
389
|
+
const usagePromise = sessionFile2 ? readSessionUsage(sessionFile2) : Promise.resolve({
|
|
386
390
|
inputTokens: 0,
|
|
387
391
|
outputTokens: 0,
|
|
388
392
|
cacheCreationTokens: 0,
|
|
389
393
|
cacheReadTokens: 0,
|
|
390
394
|
requestCount: 0
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
395
|
+
});
|
|
396
|
+
usagePromise.then((usage2) => {
|
|
397
|
+
setState((prev) => ({
|
|
398
|
+
fileStats: fileStats2,
|
|
399
|
+
usage: usage2,
|
|
400
|
+
sessionFile: sessionFile2,
|
|
401
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
402
|
+
tickCount: prev.tickCount + 1
|
|
403
|
+
}));
|
|
404
|
+
}).catch(() => {
|
|
405
|
+
});
|
|
399
406
|
}, [sessionId]);
|
|
400
407
|
useEffect(() => {
|
|
401
408
|
refresh();
|
|
@@ -2749,9 +2756,9 @@ init_models();
|
|
|
2749
2756
|
function isoDate(d) {
|
|
2750
2757
|
return d.toISOString().slice(0, 10);
|
|
2751
2758
|
}
|
|
2752
|
-
function calcCost2(inputTokens, outputTokens, model) {
|
|
2759
|
+
function calcCost2(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
|
|
2753
2760
|
const p = MODEL_PRICING[model];
|
|
2754
|
-
return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion;
|
|
2761
|
+
return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion + cacheCreationTokens / 1e6 * p.cacheWritePerMillion + cacheReadTokens / 1e6 * p.cacheReadPerMillion;
|
|
2755
2762
|
}
|
|
2756
2763
|
async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
2757
2764
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2770,6 +2777,7 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2770
2777
|
inputTokens: 0,
|
|
2771
2778
|
outputTokens: 0,
|
|
2772
2779
|
cacheReadTokens: 0,
|
|
2780
|
+
cacheCreationTokens: 0,
|
|
2773
2781
|
requests: 0,
|
|
2774
2782
|
costUsd: 0
|
|
2775
2783
|
});
|
|
@@ -2778,20 +2786,23 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2778
2786
|
let totalInput = 0;
|
|
2779
2787
|
let totalOutput = 0;
|
|
2780
2788
|
let totalCacheRead = 0;
|
|
2789
|
+
let totalCacheCreation = 0;
|
|
2781
2790
|
for (const sf of sessionFiles) {
|
|
2782
2791
|
const dateStr = isoDate(new Date(sf.mtimeMs));
|
|
2783
2792
|
const bucket = bucketMap.get(dateStr);
|
|
2784
2793
|
if (!bucket) continue;
|
|
2785
|
-
const usage = readSessionUsage(sf.filePath);
|
|
2794
|
+
const usage = await readSessionUsage(sf.filePath);
|
|
2786
2795
|
bucket.sessions++;
|
|
2787
2796
|
bucket.inputTokens += usage.inputTokens;
|
|
2788
2797
|
bucket.outputTokens += usage.outputTokens;
|
|
2789
2798
|
bucket.cacheReadTokens += usage.cacheReadTokens;
|
|
2799
|
+
bucket.cacheCreationTokens += usage.cacheCreationTokens;
|
|
2790
2800
|
bucket.requests += usage.requestCount;
|
|
2791
|
-
bucket.costUsd += calcCost2(usage.inputTokens, usage.outputTokens, model);
|
|
2801
|
+
bucket.costUsd += calcCost2(usage.inputTokens, usage.outputTokens, usage.cacheCreationTokens, usage.cacheReadTokens, model);
|
|
2792
2802
|
totalInput += usage.inputTokens;
|
|
2793
2803
|
totalOutput += usage.outputTokens;
|
|
2794
2804
|
totalCacheRead += usage.cacheReadTokens;
|
|
2805
|
+
totalCacheCreation += usage.cacheCreationTokens;
|
|
2795
2806
|
totalRequests += usage.requestCount;
|
|
2796
2807
|
}
|
|
2797
2808
|
const fileEvents = readAllEvents().filter(
|
|
@@ -2802,10 +2813,12 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2802
2813
|
filePath: s.filePath,
|
|
2803
2814
|
readCount: s.readCount
|
|
2804
2815
|
}));
|
|
2805
|
-
const totalCost = calcCost2(totalInput, totalOutput, model);
|
|
2816
|
+
const totalCost = calcCost2(totalInput, totalOutput, totalCacheCreation, totalCacheRead, model);
|
|
2806
2817
|
const cacheHitRate = totalInput > 0 ? Math.round(totalCacheRead / totalInput * 100) : 0;
|
|
2807
2818
|
const byDay = [...bucketMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
2808
2819
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
2820
|
+
const dailyAvgCostUsd = days > 0 ? totalCost / days : 0;
|
|
2821
|
+
const projectedMonthlyUsd = dailyAvgCostUsd * 30;
|
|
2809
2822
|
return {
|
|
2810
2823
|
periodDays: days,
|
|
2811
2824
|
startDate: isoDate(cutoff),
|
|
@@ -2815,10 +2828,13 @@ async function aggregateUsage(days, model = "claude-sonnet-4-6") {
|
|
|
2815
2828
|
totalInputTokens: totalInput,
|
|
2816
2829
|
totalOutputTokens: totalOutput,
|
|
2817
2830
|
totalCacheReadTokens: totalCacheRead,
|
|
2831
|
+
totalCacheCreationTokens: totalCacheCreation,
|
|
2818
2832
|
cacheHitRate,
|
|
2819
2833
|
totalCostUsd: totalCost,
|
|
2820
2834
|
avgCostPerSession: uniqueSessions > 0 ? totalCost / uniqueSessions : 0,
|
|
2821
2835
|
avgTokensPerRequest: totalRequests > 0 ? Math.round(totalInput / totalRequests) : 0,
|
|
2836
|
+
dailyAvgCostUsd,
|
|
2837
|
+
projectedMonthlyUsd,
|
|
2822
2838
|
byDay,
|
|
2823
2839
|
topFiles,
|
|
2824
2840
|
model,
|
|
@@ -2861,9 +2877,12 @@ function formatText(data) {
|
|
|
2861
2877
|
lines.push(` Input tokens: ${fmtNum2(data.totalInputTokens)}`);
|
|
2862
2878
|
lines.push(` Output tokens: ${fmtNum2(data.totalOutputTokens)}`);
|
|
2863
2879
|
lines.push(` Cache reads: ${fmtNum2(data.totalCacheReadTokens)} (${data.cacheHitRate}% hit rate)`);
|
|
2880
|
+
lines.push(` Cache writes: ${fmtNum2(data.totalCacheCreationTokens)}`);
|
|
2864
2881
|
lines.push(` Total cost (est.): ${fmtCost2(data.totalCostUsd)}`);
|
|
2865
2882
|
lines.push(` Avg cost/session: ${fmtCost2(data.avgCostPerSession)}`);
|
|
2866
2883
|
lines.push(` Avg tokens/request: ${fmtNum2(data.avgTokensPerRequest)}`);
|
|
2884
|
+
lines.push(` Daily avg cost: ${fmtCost2(data.dailyAvgCostUsd)}`);
|
|
2885
|
+
lines.push(` Projected (30-day): ${fmtCost2(data.projectedMonthlyUsd)}`);
|
|
2867
2886
|
lines.push(` Model: ${data.model}`);
|
|
2868
2887
|
lines.push("");
|
|
2869
2888
|
const activeDays = data.byDay.filter((d) => d.sessions > 0);
|
|
@@ -2932,9 +2951,12 @@ function formatMarkdown(data) {
|
|
|
2932
2951
|
lines.push(`| Input tokens | ${fmtNum2(data.totalInputTokens)} |`);
|
|
2933
2952
|
lines.push(`| Output tokens | ${fmtNum2(data.totalOutputTokens)} |`);
|
|
2934
2953
|
lines.push(`| Cache hit rate | ${data.cacheHitRate}% |`);
|
|
2954
|
+
lines.push(`| Cache writes | ${fmtNum2(data.totalCacheCreationTokens)} tokens |`);
|
|
2935
2955
|
lines.push(`| Total cost (est.) | ${fmtCost2(data.totalCostUsd)} |`);
|
|
2936
2956
|
lines.push(`| Avg cost/session | ${fmtCost2(data.avgCostPerSession)} |`);
|
|
2937
2957
|
lines.push(`| Avg tokens/request | ${fmtNum2(data.avgTokensPerRequest)} |`);
|
|
2958
|
+
lines.push(`| Daily avg cost | ${fmtCost2(data.dailyAvgCostUsd)} |`);
|
|
2959
|
+
lines.push(`| Projected (30-day) | ${fmtCost2(data.projectedMonthlyUsd)} |`);
|
|
2938
2960
|
lines.push(`| Model | \`${data.model}\` |`);
|
|
2939
2961
|
lines.push("");
|
|
2940
2962
|
const activeDays = data.byDay.filter((d) => d.sessions > 0);
|
|
@@ -4082,9 +4104,9 @@ function getDeveloperIdentity() {
|
|
|
4082
4104
|
}
|
|
4083
4105
|
return os4.hostname();
|
|
4084
4106
|
}
|
|
4085
|
-
function calcCost3(inputTokens, outputTokens, model) {
|
|
4107
|
+
function calcCost3(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, model) {
|
|
4086
4108
|
const p = MODEL_PRICING[model];
|
|
4087
|
-
return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion;
|
|
4109
|
+
return inputTokens / 1e6 * p.inputPerMillion + outputTokens / 1e6 * p.outputPerMillion + cacheCreationTokens / 1e6 * p.cacheWritePerMillion + cacheReadTokens / 1e6 * p.cacheReadPerMillion;
|
|
4088
4110
|
}
|
|
4089
4111
|
function isoDate2(d) {
|
|
4090
4112
|
return d.toISOString().slice(0, 10);
|
|
@@ -4115,6 +4137,7 @@ async function buildTeamExport(days, model, anonymize) {
|
|
|
4115
4137
|
inputTokens: 0,
|
|
4116
4138
|
outputTokens: 0,
|
|
4117
4139
|
cacheReadTokens: 0,
|
|
4140
|
+
cacheCreationTokens: 0,
|
|
4118
4141
|
requests: 0,
|
|
4119
4142
|
costUsd: 0
|
|
4120
4143
|
});
|
|
@@ -4126,14 +4149,15 @@ async function buildTeamExport(days, model, anonymize) {
|
|
|
4126
4149
|
for (const sf of sessionFiles) {
|
|
4127
4150
|
const dateStr = isoDate2(new Date(sf.mtimeMs));
|
|
4128
4151
|
const bucket = bucketMap.get(dateStr);
|
|
4129
|
-
const usage = readSessionUsage(sf.filePath);
|
|
4152
|
+
const usage = await readSessionUsage(sf.filePath);
|
|
4130
4153
|
if (bucket) {
|
|
4131
4154
|
bucket.sessions++;
|
|
4132
4155
|
bucket.inputTokens += usage.inputTokens;
|
|
4133
4156
|
bucket.outputTokens += usage.outputTokens;
|
|
4134
4157
|
bucket.cacheReadTokens += usage.cacheReadTokens;
|
|
4158
|
+
bucket.cacheCreationTokens += usage.cacheCreationTokens;
|
|
4135
4159
|
bucket.requests += usage.requestCount;
|
|
4136
|
-
bucket.costUsd += calcCost3(usage.inputTokens, usage.outputTokens, model);
|
|
4160
|
+
bucket.costUsd += calcCost3(usage.inputTokens, usage.outputTokens, usage.cacheCreationTokens, usage.cacheReadTokens, model);
|
|
4137
4161
|
}
|
|
4138
4162
|
totalInput += usage.inputTokens;
|
|
4139
4163
|
totalOutput += usage.outputTokens;
|
|
@@ -4144,7 +4168,7 @@ async function buildTeamExport(days, model, anonymize) {
|
|
|
4144
4168
|
(e) => new Date(e.timestamp).getTime() >= cutoffMs
|
|
4145
4169
|
);
|
|
4146
4170
|
const topWasteFiles = aggregateStats(fileEvents).slice(0, 10).map((s) => ({ filePath: s.filePath, readCount: s.readCount }));
|
|
4147
|
-
const totalCostUsd = calcCost3(totalInput, totalOutput, model);
|
|
4171
|
+
const totalCostUsd = calcCost3(totalInput, totalOutput, 0, totalCacheRead, model);
|
|
4148
4172
|
const cacheHitRate = totalInput > 0 ? Math.round(totalCacheRead / totalInput * 100) : 0;
|
|
4149
4173
|
const uniqueSessions = new Set(sessionFiles.map((f) => f.sessionId)).size;
|
|
4150
4174
|
const developer = {
|
|
@@ -4360,7 +4384,7 @@ async function teamsCommand(subcommand, options) {
|
|
|
4360
4384
|
}
|
|
4361
4385
|
|
|
4362
4386
|
// src/index.ts
|
|
4363
|
-
var VERSION = "1.
|
|
4387
|
+
var VERSION = "1.1.2";
|
|
4364
4388
|
var DESCRIPTION = "Reduce Claude Code token usage by up to 80%. Context analyzer, auto-optimizer, live dashboard, and smart MCP tools.";
|
|
4365
4389
|
var program = new Command();
|
|
4366
4390
|
program.name("claudectx").description(DESCRIPTION).version(VERSION);
|