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/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
- let content;
234
+ const { createReadStream } = await import("fs");
235
+ const { createInterface } = await import("readline");
235
236
  try {
236
- content = fs9.readFileSync(sessionFilePath, "utf-8");
237
- } catch {
238
- return result;
239
- }
240
- const lines = content.trim().split("\n").filter(Boolean);
241
- for (const line of lines) {
242
- try {
243
- const entry = JSON.parse(line);
244
- const usage = entry.usage ?? entry.message?.usage;
245
- if (!usage) continue;
246
- const isAssistant = entry.type === "assistant" || entry.message?.role === "assistant";
247
- if (isAssistant) {
248
- result.inputTokens += usage.input_tokens ?? 0;
249
- result.outputTokens += usage.output_tokens ?? 0;
250
- result.cacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
251
- result.cacheReadTokens += usage.cache_read_input_tokens ?? 0;
252
- result.requestCount++;
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 usage2 = sessionFile2 ? readSessionUsage(sessionFile2) : {
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
- setState((prev) => ({
393
- fileStats: fileStats2,
394
- usage: usage2,
395
- sessionFile: sessionFile2,
396
- lastUpdated: /* @__PURE__ */ new Date(),
397
- tickCount: prev.tickCount + 1
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.0.0";
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);