claude-scope 0.8.33 → 0.8.35

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.
Files changed (2) hide show
  1. package/dist/claude-scope.cjs +221 -17
  2. package/package.json +1 -1
@@ -2413,6 +2413,173 @@ var init_widget_types = __esm({
2413
2413
  }
2414
2414
  });
2415
2415
 
2416
+ // src/providers/usage-parser.ts
2417
+ var import_fs, import_readline, UsageParser;
2418
+ var init_usage_parser = __esm({
2419
+ "src/providers/usage-parser.ts"() {
2420
+ "use strict";
2421
+ import_fs = require("fs");
2422
+ import_readline = require("readline");
2423
+ UsageParser = class {
2424
+ /**
2425
+ * Parse the last assistant message with usage data from transcript
2426
+ * Reads file in reverse to find the most recent data quickly
2427
+ *
2428
+ * @param transcriptPath - Path to the JSONL transcript file
2429
+ * @returns ContextUsage or null if not found
2430
+ */
2431
+ async parseLastUsage(transcriptPath) {
2432
+ if (!(0, import_fs.existsSync)(transcriptPath)) {
2433
+ return null;
2434
+ }
2435
+ try {
2436
+ const lines = await this.readAllLines(transcriptPath);
2437
+ let mostRecentUsage = null;
2438
+ let mostRecentTimestamp = null;
2439
+ let hasAnyTimestamp = false;
2440
+ for (const line of lines) {
2441
+ const entry = this.parseLineForUsage(line);
2442
+ if (entry) {
2443
+ const entryTime = this.parseTimestamp(line);
2444
+ if (entryTime) {
2445
+ hasAnyTimestamp = true;
2446
+ if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
2447
+ mostRecentUsage = entry;
2448
+ mostRecentTimestamp = entryTime;
2449
+ }
2450
+ } else if (!hasAnyTimestamp) {
2451
+ mostRecentUsage = entry;
2452
+ }
2453
+ }
2454
+ }
2455
+ return mostRecentUsage;
2456
+ } catch {
2457
+ return null;
2458
+ }
2459
+ }
2460
+ /**
2461
+ * Parse timestamp from a transcript line
2462
+ */
2463
+ parseTimestamp(line) {
2464
+ try {
2465
+ const entry = JSON.parse(line);
2466
+ if (entry.timestamp) {
2467
+ return new Date(entry.timestamp);
2468
+ }
2469
+ } catch {
2470
+ }
2471
+ return null;
2472
+ }
2473
+ /**
2474
+ * Read all lines from transcript file
2475
+ */
2476
+ async readAllLines(transcriptPath) {
2477
+ const lines = [];
2478
+ try {
2479
+ const fileStream = (0, import_fs.createReadStream)(transcriptPath, { encoding: "utf-8" });
2480
+ const rl = (0, import_readline.createInterface)({
2481
+ input: fileStream,
2482
+ crlfDelay: Infinity
2483
+ });
2484
+ for await (const line of rl) {
2485
+ if (line.trim()) {
2486
+ lines.push(line);
2487
+ }
2488
+ }
2489
+ } catch {
2490
+ return [];
2491
+ }
2492
+ return lines;
2493
+ }
2494
+ /**
2495
+ * Parse cumulative cache tokens from all assistant messages in transcript
2496
+ * Sums ALL cache_read and cache_creation tokens across the entire session
2497
+ *
2498
+ * @param transcriptPath - Path to the JSONL transcript file
2499
+ * @returns Object with cumulative cacheRead and cacheCreation, or null if not found
2500
+ */
2501
+ async parseCumulativeCache(transcriptPath) {
2502
+ if (!(0, import_fs.existsSync)(transcriptPath)) {
2503
+ return null;
2504
+ }
2505
+ try {
2506
+ const lines = await this.readAllLines(transcriptPath);
2507
+ let cumulativeCacheRead = 0;
2508
+ let cumulativeCacheCreation = 0;
2509
+ for (const line of lines) {
2510
+ const entry = this.parseLineForCache(line);
2511
+ if (entry) {
2512
+ cumulativeCacheRead += entry.cacheRead;
2513
+ cumulativeCacheCreation += entry.cacheCreation;
2514
+ }
2515
+ }
2516
+ if (cumulativeCacheRead === 0 && cumulativeCacheCreation === 0) {
2517
+ return null;
2518
+ }
2519
+ return {
2520
+ cacheRead: cumulativeCacheRead,
2521
+ cacheCreation: cumulativeCacheCreation
2522
+ };
2523
+ } catch {
2524
+ return null;
2525
+ }
2526
+ }
2527
+ /**
2528
+ * Parse a single transcript line for cache data
2529
+ * Returns null if line is not an assistant message with usage
2530
+ */
2531
+ parseLineForCache(line) {
2532
+ try {
2533
+ const entry = JSON.parse(line);
2534
+ if (entry.type !== "assistant") {
2535
+ return null;
2536
+ }
2537
+ const usage = entry.message?.usage;
2538
+ if (!usage) {
2539
+ return null;
2540
+ }
2541
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
2542
+ const cacheCreation = usage.cache_creation_input_tokens ?? 0;
2543
+ if (cacheRead === 0 && cacheCreation === 0) {
2544
+ return null;
2545
+ }
2546
+ return { cacheRead, cacheCreation };
2547
+ } catch {
2548
+ return null;
2549
+ }
2550
+ }
2551
+ /**
2552
+ * Parse a single transcript line for usage data
2553
+ * Returns null if line is not an assistant message with usage
2554
+ */
2555
+ parseLineForUsage(line) {
2556
+ try {
2557
+ const entry = JSON.parse(line);
2558
+ if (entry.type !== "assistant") {
2559
+ return null;
2560
+ }
2561
+ const usage = entry.message?.usage;
2562
+ if (!usage) {
2563
+ return null;
2564
+ }
2565
+ if (typeof usage.input_tokens !== "number" || typeof usage.output_tokens !== "number") {
2566
+ return null;
2567
+ }
2568
+ return {
2569
+ input_tokens: usage.input_tokens,
2570
+ output_tokens: usage.output_tokens,
2571
+ cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
2572
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
2573
+ // Default to 0 if missing
2574
+ };
2575
+ } catch {
2576
+ return null;
2577
+ }
2578
+ }
2579
+ };
2580
+ }
2581
+ });
2582
+
2416
2583
  // src/storage/cache-manager.ts
2417
2584
  var import_node_fs, import_node_os2, import_node_path2, DEFAULT_CACHE_PATH, DEFAULT_EXPIRY_MS, CacheManager;
2418
2585
  var init_cache_manager = __esm({
@@ -2665,6 +2832,7 @@ var init_cache_metrics_widget = __esm({
2665
2832
  "src/widgets/cache-metrics/cache-metrics-widget.ts"() {
2666
2833
  "use strict";
2667
2834
  init_widget_types();
2835
+ init_usage_parser();
2668
2836
  init_cache_manager();
2669
2837
  init_theme();
2670
2838
  init_stdin_data_widget();
@@ -2684,11 +2852,17 @@ var init_cache_metrics_widget = __esm({
2684
2852
  style = "balanced";
2685
2853
  renderData;
2686
2854
  cacheManager;
2855
+ usageParser;
2687
2856
  lastSessionId;
2857
+ cachedUsage;
2858
+ // Cache parsed usage within render cycle
2859
+ cachedCumulativeCache;
2860
+ // Cumulative cache for session
2688
2861
  constructor(theme) {
2689
2862
  super();
2690
2863
  this.theme = theme ?? DEFAULT_THEME;
2691
2864
  this.cacheManager = new CacheManager();
2865
+ this.usageParser = new UsageParser();
2692
2866
  }
2693
2867
  /**
2694
2868
  * Set display style
@@ -2705,16 +2879,14 @@ var init_cache_metrics_widget = __esm({
2705
2879
  /**
2706
2880
  * Calculate cache metrics from context usage data
2707
2881
  * Returns zero metrics if no usage data is available (widget should always be visible)
2882
+ *
2883
+ * @param usage - Current usage data (for total tokens calculation)
2884
+ * @param cumulativeCache - Cumulative cache data for the session (optional)
2708
2885
  */
2709
- calculateMetrics(data) {
2710
- let usage = data.context_window?.current_usage;
2711
- if (!usage) {
2712
- const cached = this.cacheManager.getCachedUsage(data.session_id);
2713
- if (cached) {
2714
- usage = cached.usage;
2715
- }
2716
- }
2717
- if (!usage) {
2886
+ calculateMetrics(usage, cumulativeCache) {
2887
+ const cacheRead = cumulativeCache?.cacheRead ?? usage?.cache_read_input_tokens ?? 0;
2888
+ const cacheWrite = cumulativeCache?.cacheCreation ?? usage?.cache_creation_input_tokens ?? 0;
2889
+ if (!usage && !cumulativeCache) {
2718
2890
  return {
2719
2891
  cacheRead: 0,
2720
2892
  cacheWrite: 0,
@@ -2723,10 +2895,8 @@ var init_cache_metrics_widget = __esm({
2723
2895
  savings: 0
2724
2896
  };
2725
2897
  }
2726
- const cacheRead = usage.cache_read_input_tokens ?? 0;
2727
- const cacheWrite = usage.cache_creation_input_tokens ?? 0;
2728
- const inputTokens = usage.input_tokens ?? 0;
2729
- const outputTokens = usage.output_tokens ?? 0;
2898
+ const inputTokens = usage?.input_tokens ?? 0;
2899
+ const outputTokens = usage?.output_tokens ?? 0;
2730
2900
  const totalInputTokens = cacheRead + cacheWrite + inputTokens;
2731
2901
  const totalTokens = totalInputTokens + outputTokens;
2732
2902
  const hitRate = totalInputTokens > 0 ? Math.min(100, Math.round(cacheRead / totalInputTokens * 100)) : 0;
@@ -2763,16 +2933,35 @@ var init_cache_metrics_widget = __esm({
2763
2933
  });
2764
2934
  }
2765
2935
  }
2766
- const metrics = this.calculateMetrics(data);
2767
- this.renderData = metrics ?? void 0;
2936
+ const hasRealUsage = usage && ((usage.input_tokens ?? 0) > 0 || (usage.output_tokens ?? 0) > 0 || (usage.cache_read_input_tokens ?? 0) > 0 || (usage.cache_creation_input_tokens ?? 0) > 0);
2937
+ if (!usage || !hasRealUsage) {
2938
+ this.cachedUsage = await this.usageParser.parseLastUsage(data.transcript_path);
2939
+ } else {
2940
+ this.cachedUsage = void 0;
2941
+ }
2942
+ this.cachedCumulativeCache = await this.usageParser.parseCumulativeCache(data.transcript_path);
2768
2943
  }
2769
2944
  /**
2770
2945
  * Render the cache metrics display
2771
2946
  */
2772
- renderWithData(_data, _context) {
2773
- if (!this.renderData) {
2947
+ renderWithData(data, _context) {
2948
+ let usage = data.context_window?.current_usage;
2949
+ const hasRealUsage = usage && ((usage.input_tokens ?? 0) > 0 || (usage.output_tokens ?? 0) > 0 || (usage.cache_read_input_tokens ?? 0) > 0 || (usage.cache_creation_input_tokens ?? 0) > 0);
2950
+ if ((!usage || !hasRealUsage) && this.cachedUsage) {
2951
+ usage = this.cachedUsage;
2952
+ }
2953
+ if (!usage) {
2954
+ const cached = this.cacheManager.getCachedUsage(data.session_id);
2955
+ if (cached) {
2956
+ usage = cached.usage;
2957
+ }
2958
+ }
2959
+ const cumulativeCacheToUse = this.cachedCumulativeCache;
2960
+ const metrics = this.calculateMetrics(usage, cumulativeCacheToUse);
2961
+ if (!metrics) {
2774
2962
  return null;
2775
2963
  }
2964
+ this.renderData = metrics;
2776
2965
  const styleFn = cacheMetricsStyles[this.style] ?? cacheMetricsStyles.balanced;
2777
2966
  if (!styleFn) {
2778
2967
  return null;
@@ -3259,6 +3448,7 @@ var init_context_widget = __esm({
3259
3448
  "src/widgets/context-widget.ts"() {
3260
3449
  "use strict";
3261
3450
  init_widget_types();
3451
+ init_usage_parser();
3262
3452
  init_cache_manager();
3263
3453
  init_theme();
3264
3454
  init_styles4();
@@ -3277,11 +3467,15 @@ var init_context_widget = __esm({
3277
3467
  _lineOverride;
3278
3468
  styleFn = contextStyles.balanced;
3279
3469
  cacheManager;
3470
+ usageParser;
3280
3471
  lastSessionId;
3472
+ cachedUsage;
3473
+ // Cache parsed usage within render cycle
3281
3474
  constructor(colors2) {
3282
3475
  super();
3283
3476
  this.colors = colors2 ?? DEFAULT_THEME;
3284
3477
  this.cacheManager = new CacheManager();
3478
+ this.usageParser = new UsageParser();
3285
3479
  }
3286
3480
  setStyle(style = "balanced") {
3287
3481
  const fn = contextStyles[style];
@@ -3314,10 +3508,20 @@ var init_context_widget = __esm({
3314
3508
  });
3315
3509
  }
3316
3510
  }
3511
+ const hasRealUsage = current_usage && ((current_usage.input_tokens ?? 0) > 0 || (current_usage.output_tokens ?? 0) > 0 || (current_usage.cache_read_input_tokens ?? 0) > 0 || (current_usage.cache_creation_input_tokens ?? 0) > 0);
3512
+ if (!current_usage || !hasRealUsage) {
3513
+ this.cachedUsage = await this.usageParser.parseLastUsage(data.transcript_path);
3514
+ } else {
3515
+ this.cachedUsage = void 0;
3516
+ }
3317
3517
  }
3318
3518
  renderWithData(data, _context) {
3319
3519
  const { current_usage, context_window_size } = data.context_window;
3320
3520
  let usage = current_usage;
3521
+ const hasRealUsage = usage && ((usage.input_tokens ?? 0) > 0 || (usage.output_tokens ?? 0) > 0 || (usage.cache_read_input_tokens ?? 0) > 0 || (usage.cache_creation_input_tokens ?? 0) > 0);
3522
+ if ((!usage || !hasRealUsage) && this.cachedUsage) {
3523
+ usage = this.cachedUsage;
3524
+ }
3321
3525
  if (!usage) {
3322
3526
  const cached = this.cacheManager.getCachedUsage(data.session_id);
3323
3527
  if (cached) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.8.33",
3
+ "version": "0.8.35",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",