claude-scope 0.6.11 → 0.6.14

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 +198 -50
  2. package/package.json +12 -2
@@ -102,7 +102,7 @@ var Renderer = class {
102
102
  if (!lineMap.has(line)) {
103
103
  lineMap.set(line, []);
104
104
  }
105
- lineMap.get(line).push(widget);
105
+ lineMap.get(line)?.push(widget);
106
106
  }
107
107
  const lines = [];
108
108
  const sortedLines = Array.from(lineMap.entries()).sort((a, b) => a[0] - b[0]);
@@ -392,8 +392,8 @@ var StdinProvider = class {
392
392
  };
393
393
 
394
394
  // src/providers/transcript-provider.ts
395
- var import_fs = require("fs");
396
- var import_readline = require("readline");
395
+ var import_node_fs = require("node:fs");
396
+ var import_node_readline = require("node:readline");
397
397
  var TranscriptProvider = class {
398
398
  MAX_TOOLS = 20;
399
399
  /**
@@ -402,13 +402,13 @@ var TranscriptProvider = class {
402
402
  * @returns Array of tool entries, limited to last 20
403
403
  */
404
404
  async parseTools(transcriptPath) {
405
- if (!(0, import_fs.existsSync)(transcriptPath)) {
405
+ if (!(0, import_node_fs.existsSync)(transcriptPath)) {
406
406
  return [];
407
407
  }
408
408
  const toolMap = /* @__PURE__ */ new Map();
409
409
  try {
410
- const fileStream = (0, import_fs.createReadStream)(transcriptPath, { encoding: "utf-8" });
411
- const rl = (0, import_readline.createInterface)({
410
+ const fileStream = (0, import_node_fs.createReadStream)(transcriptPath, { encoding: "utf-8" });
411
+ const rl = (0, import_node_readline.createInterface)({
412
412
  input: fileStream,
413
413
  crlfDelay: Infinity
414
414
  });
@@ -487,7 +487,7 @@ var TranscriptProvider = class {
487
487
  */
488
488
  truncateCommand(cmd) {
489
489
  if (cmd.length <= 30) return cmd;
490
- return cmd.slice(0, 30) + "...";
490
+ return `${cmd.slice(0, 30)}...`;
491
491
  }
492
492
  };
493
493
 
@@ -1793,7 +1793,7 @@ var ActiveToolsWidget = class extends StdinDataWidget {
1793
1793
  * @param context - Render context
1794
1794
  * @returns Rendered string or null if no tools
1795
1795
  */
1796
- renderWithData(data, context) {
1796
+ renderWithData(_data, _context) {
1797
1797
  if (!this.renderData || this.tools.length === 0) {
1798
1798
  return null;
1799
1799
  }
@@ -1823,6 +1823,113 @@ function createWidgetMetadata(name, description, version = "1.0.0", author = "cl
1823
1823
  };
1824
1824
  }
1825
1825
 
1826
+ // src/storage/cache-manager.ts
1827
+ var import_node_fs2 = require("node:fs");
1828
+ var import_node_os = require("node:os");
1829
+ var import_node_path = require("node:path");
1830
+ var DEFAULT_CACHE_PATH = `${(0, import_node_os.homedir)()}/.config/claude-scope/cache.json`;
1831
+ var DEFAULT_EXPIRY_MS = 5 * 60 * 1e3;
1832
+ var CacheManager = class {
1833
+ cachePath;
1834
+ expiryMs;
1835
+ constructor(options) {
1836
+ this.cachePath = options?.cachePath ?? DEFAULT_CACHE_PATH;
1837
+ this.expiryMs = options?.expiryMs ?? DEFAULT_EXPIRY_MS;
1838
+ this.ensureCacheDir();
1839
+ }
1840
+ /**
1841
+ * Get cached usage data for a session
1842
+ * @param sessionId - Session identifier
1843
+ * @returns Cached usage if valid and not expired, null otherwise
1844
+ */
1845
+ getCachedUsage(sessionId) {
1846
+ const cache = this.loadCache();
1847
+ const cached = cache.sessions[sessionId];
1848
+ if (!cached) {
1849
+ return null;
1850
+ }
1851
+ const age = Date.now() - cached.timestamp;
1852
+ if (age > this.expiryMs) {
1853
+ delete cache.sessions[sessionId];
1854
+ this.saveCache(cache);
1855
+ return null;
1856
+ }
1857
+ return cached;
1858
+ }
1859
+ /**
1860
+ * Store usage data for a session
1861
+ * @param sessionId - Session identifier
1862
+ * @param usage - Context usage data to cache
1863
+ */
1864
+ setCachedUsage(sessionId, usage) {
1865
+ const cache = this.loadCache();
1866
+ cache.sessions[sessionId] = {
1867
+ timestamp: Date.now(),
1868
+ usage
1869
+ };
1870
+ this.saveCache(cache);
1871
+ }
1872
+ /**
1873
+ * Clear all cached data (useful for testing)
1874
+ */
1875
+ clearCache() {
1876
+ const emptyCache = {
1877
+ sessions: {},
1878
+ version: 1
1879
+ };
1880
+ this.saveCache(emptyCache);
1881
+ }
1882
+ /**
1883
+ * Clean up expired sessions
1884
+ */
1885
+ cleanupExpired() {
1886
+ const cache = this.loadCache();
1887
+ const now = Date.now();
1888
+ for (const [sessionId, cached] of Object.entries(cache.sessions)) {
1889
+ const age = now - cached.timestamp;
1890
+ if (age > this.expiryMs) {
1891
+ delete cache.sessions[sessionId];
1892
+ }
1893
+ }
1894
+ this.saveCache(cache);
1895
+ }
1896
+ /**
1897
+ * Load cache from file
1898
+ */
1899
+ loadCache() {
1900
+ if (!(0, import_node_fs2.existsSync)(this.cachePath)) {
1901
+ return { sessions: {}, version: 1 };
1902
+ }
1903
+ try {
1904
+ const content = (0, import_node_fs2.readFileSync)(this.cachePath, "utf-8");
1905
+ return JSON.parse(content);
1906
+ } catch {
1907
+ return { sessions: {}, version: 1 };
1908
+ }
1909
+ }
1910
+ /**
1911
+ * Save cache to file
1912
+ */
1913
+ saveCache(cache) {
1914
+ try {
1915
+ (0, import_node_fs2.writeFileSync)(this.cachePath, JSON.stringify(cache, null, 2), "utf-8");
1916
+ } catch {
1917
+ }
1918
+ }
1919
+ /**
1920
+ * Ensure cache directory exists
1921
+ */
1922
+ ensureCacheDir() {
1923
+ try {
1924
+ const dir = (0, import_node_path.dirname)(this.cachePath);
1925
+ if (!(0, import_node_fs2.existsSync)(dir)) {
1926
+ (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
1927
+ }
1928
+ } catch {
1929
+ }
1930
+ }
1931
+ };
1932
+
1826
1933
  // src/ui/utils/formatters.ts
1827
1934
  function formatDuration(ms) {
1828
1935
  if (ms <= 0) return "0s";
@@ -1966,9 +2073,11 @@ var CacheMetricsWidget = class extends StdinDataWidget {
1966
2073
  theme;
1967
2074
  style = "balanced";
1968
2075
  renderData;
2076
+ cacheManager;
1969
2077
  constructor(theme) {
1970
2078
  super();
1971
2079
  this.theme = theme ?? DEFAULT_THEME;
2080
+ this.cacheManager = new CacheManager();
1972
2081
  }
1973
2082
  /**
1974
2083
  * Set display style
@@ -1978,10 +2087,16 @@ var CacheMetricsWidget = class extends StdinDataWidget {
1978
2087
  }
1979
2088
  /**
1980
2089
  * Calculate cache metrics from context usage data
1981
- * Returns null if no usage data is available
2090
+ * Returns null if no usage data is available (current or cached)
1982
2091
  */
1983
2092
  calculateMetrics(data) {
1984
- const usage = data.context_window?.current_usage;
2093
+ let usage = data.context_window?.current_usage;
2094
+ if (!usage) {
2095
+ const cached = this.cacheManager.getCachedUsage(data.session_id);
2096
+ if (cached) {
2097
+ usage = cached.usage;
2098
+ }
2099
+ }
1985
2100
  if (!usage) {
1986
2101
  return null;
1987
2102
  }
@@ -2004,9 +2119,19 @@ var CacheMetricsWidget = class extends StdinDataWidget {
2004
2119
  }
2005
2120
  /**
2006
2121
  * Update widget with new data and calculate metrics
2122
+ * Stores valid usage data in cache for future use
2007
2123
  */
2008
2124
  async update(data) {
2009
2125
  await super.update(data);
2126
+ const usage = data.context_window?.current_usage;
2127
+ if (usage) {
2128
+ this.cacheManager.setCachedUsage(data.session_id, {
2129
+ input_tokens: usage.input_tokens,
2130
+ output_tokens: usage.output_tokens,
2131
+ cache_creation_input_tokens: usage.cache_creation_input_tokens,
2132
+ cache_read_input_tokens: usage.cache_read_input_tokens
2133
+ });
2134
+ }
2010
2135
  const metrics = this.calculateMetrics(data);
2011
2136
  this.renderData = metrics ?? void 0;
2012
2137
  }
@@ -2035,9 +2160,9 @@ var CacheMetricsWidget = class extends StdinDataWidget {
2035
2160
  var DEFAULT_WIDGET_STYLE = "balanced";
2036
2161
 
2037
2162
  // src/providers/config-provider.ts
2038
- var fs = __toESM(require("fs/promises"), 1);
2039
- var os = __toESM(require("os"), 1);
2040
- var path = __toESM(require("path"), 1);
2163
+ var fs = __toESM(require("node:fs/promises"), 1);
2164
+ var os = __toESM(require("node:os"), 1);
2165
+ var path = __toESM(require("node:path"), 1);
2041
2166
  var ConfigProvider = class {
2042
2167
  cachedCounts;
2043
2168
  lastScan = 0;
@@ -2288,7 +2413,7 @@ var ConfigCountWidget = class {
2288
2413
  const { claudeMdCount, rulesCount, mcpCount, hooksCount } = this.configs;
2289
2414
  return claudeMdCount > 0 || rulesCount > 0 || mcpCount > 0 || hooksCount > 0;
2290
2415
  }
2291
- async render(context) {
2416
+ async render(_context) {
2292
2417
  if (!this.configs) {
2293
2418
  return null;
2294
2419
  }
@@ -2347,7 +2472,7 @@ var contextStyles = {
2347
2472
  const bar = progressBar(data.percent, 10);
2348
2473
  const output = `\u{1F9E0} [${bar}] ${data.percent}%`;
2349
2474
  if (!colors) return output;
2350
- return `\u{1F9E0} ` + colorize(`[${bar}] ${data.percent}%`, getContextColor(data.percent, colors));
2475
+ return `\u{1F9E0} ${colorize(`[${bar}] ${data.percent}%`, getContextColor(data.percent, colors))}`;
2351
2476
  },
2352
2477
  verbose: (data, colors) => {
2353
2478
  const usedFormatted = data.used.toLocaleString();
@@ -2390,9 +2515,11 @@ var ContextWidget = class extends StdinDataWidget {
2390
2515
  );
2391
2516
  colors;
2392
2517
  styleFn = contextStyles.balanced;
2518
+ cacheManager;
2393
2519
  constructor(colors) {
2394
2520
  super();
2395
2521
  this.colors = colors ?? DEFAULT_THEME;
2522
+ this.cacheManager = new CacheManager();
2396
2523
  }
2397
2524
  setStyle(style = "balanced") {
2398
2525
  const fn = contextStyles[style];
@@ -2400,10 +2527,32 @@ var ContextWidget = class extends StdinDataWidget {
2400
2527
  this.styleFn = fn;
2401
2528
  }
2402
2529
  }
2530
+ /**
2531
+ * Update widget with new data, storing valid values in cache
2532
+ */
2533
+ async update(data) {
2534
+ await super.update(data);
2535
+ const { current_usage } = data.context_window;
2536
+ if (current_usage) {
2537
+ this.cacheManager.setCachedUsage(data.session_id, {
2538
+ input_tokens: current_usage.input_tokens,
2539
+ output_tokens: current_usage.output_tokens,
2540
+ cache_creation_input_tokens: current_usage.cache_creation_input_tokens,
2541
+ cache_read_input_tokens: current_usage.cache_read_input_tokens
2542
+ });
2543
+ }
2544
+ }
2403
2545
  renderWithData(data, _context) {
2404
2546
  const { current_usage, context_window_size } = data.context_window;
2405
- if (!current_usage) return null;
2406
- const used = current_usage.input_tokens + current_usage.cache_creation_input_tokens + current_usage.cache_read_input_tokens + current_usage.output_tokens;
2547
+ let usage = current_usage;
2548
+ if (!usage) {
2549
+ const cached = this.cacheManager.getCachedUsage(data.session_id);
2550
+ if (cached) {
2551
+ usage = cached.usage;
2552
+ }
2553
+ }
2554
+ if (!usage) return null;
2555
+ const used = usage.input_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens + usage.output_tokens;
2407
2556
  const percent = Math.round(used / context_window_size * 100);
2408
2557
  const renderData = {
2409
2558
  used,
@@ -2412,19 +2561,21 @@ var ContextWidget = class extends StdinDataWidget {
2412
2561
  };
2413
2562
  return this.styleFn(renderData, this.colors.context);
2414
2563
  }
2564
+ isEnabled() {
2565
+ return true;
2566
+ }
2415
2567
  };
2416
2568
 
2417
2569
  // src/widgets/cost/styles.ts
2570
+ function balancedStyle(data, colors) {
2571
+ const formatted = formatCostUSD(data.costUsd);
2572
+ if (!colors) return formatted;
2573
+ const amountStr = data.costUsd.toFixed(2);
2574
+ return colorize("$", colors.currency) + colorize(amountStr, colors.amount);
2575
+ }
2418
2576
  var costStyles = {
2419
- balanced: (data, colors) => {
2420
- const formatted = formatCostUSD(data.costUsd);
2421
- if (!colors) return formatted;
2422
- const amountStr = data.costUsd.toFixed(2);
2423
- return colorize("$", colors.currency) + colorize(amountStr, colors.amount);
2424
- },
2425
- compact: (data, colors) => {
2426
- return costStyles.balanced(data, colors);
2427
- },
2577
+ balanced: balancedStyle,
2578
+ compact: balancedStyle,
2428
2579
  playful: (data, colors) => {
2429
2580
  const formatted = formatCostUSD(data.costUsd);
2430
2581
  if (!colors) return `\u{1F4B0} ${formatted}`;
@@ -2516,7 +2667,7 @@ var durationStyles = {
2516
2667
  const colored = colorize(`${hours}`, colors.value) + colorize("h", colors.unit) + colorize(` ${minutes}`, colors.value) + colorize("m", colors.unit);
2517
2668
  return `\u231B ${colored}`;
2518
2669
  }
2519
- return `\u231B ` + colorize(`${minutes}`, colors.value) + colorize("m", colors.unit);
2670
+ return `\u231B ${colorize(`${minutes}`, colors.value)}${colorize("m", colors.unit)}`;
2520
2671
  },
2521
2672
  technical: (data, colors) => {
2522
2673
  const value = `${Math.floor(data.durationMs)}ms`;
@@ -2747,7 +2898,7 @@ var GitTagWidget = class {
2747
2898
  async initialize(context) {
2748
2899
  this.enabled = context.config?.enabled !== false;
2749
2900
  }
2750
- async render(context) {
2901
+ async render(_context) {
2751
2902
  if (!this.enabled || !this.git || !this.cwd) {
2752
2903
  return null;
2753
2904
  }
@@ -2896,7 +3047,7 @@ var GitWidget = class {
2896
3047
  async initialize(context) {
2897
3048
  this.enabled = context.config?.enabled !== false;
2898
3049
  }
2899
- async render(context) {
3050
+ async render(_context) {
2900
3051
  if (!this.enabled || !this.git || !this.cwd) {
2901
3052
  return null;
2902
3053
  }
@@ -3286,7 +3437,7 @@ function getStraightIndices(cards, highCard) {
3286
3437
  cardIndicesByRank.set(value, []);
3287
3438
  uniqueValues.add(value);
3288
3439
  }
3289
- cardIndicesByRank.get(value).push(i);
3440
+ cardIndicesByRank.get(value)?.push(i);
3290
3441
  }
3291
3442
  const sortedValues = Array.from(uniqueValues).sort((a, b) => b - a);
3292
3443
  if (sortedValues.includes(14)) {
@@ -3318,7 +3469,7 @@ function getStraightFlushHighCard(cards, suit) {
3318
3469
  return getStraightHighCard(suitCards);
3319
3470
  }
3320
3471
  function getStraightFlushIndices(cards, highCard, suit) {
3321
- const suitCards = cards.filter((c) => c.suit === suit);
3472
+ const _suitCards = cards.filter((c) => c.suit === suit);
3322
3473
  const suitCardIndices = [];
3323
3474
  const indexMap = /* @__PURE__ */ new Map();
3324
3475
  for (let i = 0; i < cards.length; i++) {
@@ -3596,23 +3747,20 @@ function getHandAbbreviation(handResult) {
3596
3747
  const abbreviation = HAND_ABBREVIATIONS[handResult.name] ?? "\u2014";
3597
3748
  return `${abbreviation} (${handResult.name})`;
3598
3749
  }
3750
+ function balancedStyle2(data, colors) {
3751
+ const { holeCards, boardCards, handResult } = data;
3752
+ const participatingSet = new Set(handResult?.participatingIndices || []);
3753
+ const handStr = holeCards.map((hc, idx) => formatCardByParticipation(hc, participatingSet.has(idx))).join("");
3754
+ const boardStr = boardCards.map((bc, idx) => formatCardByParticipation(bc, participatingSet.has(idx + 2))).join("");
3755
+ const labelColor = colors?.participating ?? lightGray;
3756
+ const handLabel = colorize2("Hand:", labelColor);
3757
+ const boardLabel = colorize2("Board:", labelColor);
3758
+ return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors)}`;
3759
+ }
3599
3760
  var pokerStyles = {
3600
- balanced: (data, colors) => {
3601
- const { holeCards, boardCards, handResult } = data;
3602
- const participatingSet = new Set(handResult?.participatingIndices || []);
3603
- const handStr = holeCards.map((hc, idx) => formatCardByParticipation(hc, participatingSet.has(idx))).join("");
3604
- const boardStr = boardCards.map((bc, idx) => formatCardByParticipation(bc, participatingSet.has(idx + 2))).join("");
3605
- const labelColor = colors?.participating ?? lightGray;
3606
- const handLabel = colorize2("Hand:", labelColor);
3607
- const boardLabel = colorize2("Board:", labelColor);
3608
- return `${handLabel} ${handStr}| ${boardLabel} ${boardStr}\u2192 ${formatHandResult(handResult, colors)}`;
3609
- },
3610
- compact: (data, colors) => {
3611
- return pokerStyles.balanced(data, colors);
3612
- },
3613
- playful: (data, colors) => {
3614
- return pokerStyles.balanced(data, colors);
3615
- },
3761
+ balanced: balancedStyle2,
3762
+ compact: balancedStyle2,
3763
+ playful: balancedStyle2,
3616
3764
  "compact-verbose": (data, colors) => {
3617
3765
  const { holeCards, boardCards, handResult } = data;
3618
3766
  const participatingSet = new Set(handResult?.participatingIndices || []);
@@ -3703,7 +3851,7 @@ var PokerWidget = class extends StdinDataWidget {
3703
3851
  * Format card with appropriate color (red for ♥♦, gray for ♠♣)
3704
3852
  */
3705
3853
  formatCardColor(card) {
3706
- const color = isRedSuit(card.suit) ? "red" : "gray";
3854
+ const _color = isRedSuit(card.suit) ? "red" : "gray";
3707
3855
  return formatCard(card);
3708
3856
  }
3709
3857
  renderWithData(_data, _context) {
@@ -3774,7 +3922,7 @@ async function main() {
3774
3922
  await registry.register(new EmptyLineWidget());
3775
3923
  const renderer = new Renderer({
3776
3924
  separator: " \u2502 ",
3777
- onError: (error, widget) => {
3925
+ onError: (_error, _widget) => {
3778
3926
  },
3779
3927
  showErrors: false
3780
3928
  });
@@ -3786,7 +3934,7 @@ async function main() {
3786
3934
  timestamp: Date.now()
3787
3935
  });
3788
3936
  return lines.join("\n");
3789
- } catch (error) {
3937
+ } catch (_error) {
3790
3938
  const fallback = await tryGitFallback();
3791
3939
  return fallback;
3792
3940
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scope",
3
- "version": "0.6.11",
3
+ "version": "0.6.14",
4
4
  "description": "Claude Code plugin for session status and analytics",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,7 +25,10 @@
25
25
  "test:coverage": "c8 --check-coverage --lines=80 --functions=75 --statements=80 --branches=65 --reporter=text --reporter=html --exclude='tests/**' --exclude='**/*.test.ts' tsx --test tests/e2e/stdin-flow.test.ts tests/integration/cli-flow.integration.test.ts tests/integration/five-widgets.integration.test.ts tests/unit/cli.test.ts tests/unit/types.test.ts tests/unit/core/*.test.ts tests/unit/data/*.test.ts tests/unit/utils/*.test.ts tests/unit/widgets/*.test.ts tests/unit/ui/theme/*.test.ts",
26
26
  "test:snapshot": "SNAPSHOT_UPDATE=true tsx --test tests/e2e/stdin-flow.test.ts tests/integration/cli-flow.integration.test.ts tests/integration/five-widgets.integration.test.ts tests/unit/cli.test.ts tests/unit/types.test.ts tests/unit/core/*.test.ts tests/unit/data/*.test.ts tests/unit/utils/*.test.ts tests/unit/widgets/*.test.ts",
27
27
  "test:snapshot:verify": "tsx --test tests/e2e/stdin-flow.test.ts tests/integration/cli-flow.integration.test.ts tests/integration/five-widgets.integration.test.ts tests/unit/cli.test.ts tests/unit/types.test.ts tests/unit/core/*.test.ts tests/unit/data/*.test.ts tests/unit/utils/*.test.ts tests/unit/widgets/*.test.ts",
28
- "dev": "tsx src/index.ts"
28
+ "dev": "tsx src/index.ts",
29
+ "prepare": "husky",
30
+ "lint": "biome check .",
31
+ "lint:fix": "biome check --write ."
29
32
  },
30
33
  "devDependencies": {
31
34
  "@biomejs/biome": "^2.3.11",
@@ -33,11 +36,18 @@
33
36
  "c8": "^10.1.3",
34
37
  "chai": "^6.2.2",
35
38
  "esbuild": "^0.24.2",
39
+ "husky": "^9.1.7",
40
+ "lint-staged": "^16.2.7",
36
41
  "rimraf": "^6.1.2",
37
42
  "tsx": "^4.19.2",
38
43
  "typescript": "^5.7.2"
39
44
  },
40
45
  "engines": {
41
46
  "node": ">=18.0.0"
47
+ },
48
+ "lint-staged": {
49
+ "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
50
+ "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
51
+ ]
42
52
  }
43
53
  }