ccgauge 1.0.3 → 1.0.5

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 (116) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +42 -42
  3. package/.next/standalone/.next/app-path-routes-manifest.json +9 -9
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  6. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/api/blocks/route_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/api/export/usage/route_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/api/pricing/route_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/api/projects/route.js +1 -1
  11. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/api/scan/route_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/api/sessions/route.js +1 -1
  15. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/api/usage/route.js +1 -1
  18. package/.next/standalone/.next/server/app/api/usage/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/usage/route_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/models/page.js +2 -2
  21. package/.next/standalone/.next/server/app/models/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/models/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/page.js +2 -2
  24. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/projects/[id]/page.js +2 -2
  27. package/.next/standalone/.next/server/app/projects/[id]/page.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  30. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/sessions/[id]/page.js +2 -2
  33. package/.next/standalone/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/sessions/page.js +1 -1
  36. package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  39. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/usage/page.js +3 -3
  41. package/.next/standalone/.next/server/app/usage/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app-paths-manifest.json +9 -9
  44. package/.next/standalone/.next/server/chunks/287.js +1 -0
  45. package/.next/standalone/.next/server/chunks/517.js +1 -1
  46. package/.next/standalone/.next/server/chunks/567.js +2 -2
  47. package/.next/standalone/.next/server/chunks/730.js +1 -0
  48. package/.next/standalone/.next/server/chunks/98.js +1 -1
  49. package/.next/standalone/.next/server/functions-config-manifest.json +2 -2
  50. package/.next/standalone/.next/server/pages/500.html +1 -1
  51. package/.next/standalone/.next/static/chunks/148-6c2eaf5508bfe739.js +1 -0
  52. package/.next/standalone/.next/static/chunks/930-ca5c6f8b5cb6ac3d.js +1 -0
  53. package/.next/standalone/.next/static/chunks/app/layout-4f3538437c5e8366.js +1 -0
  54. package/.next/standalone/.next/static/chunks/app/page-3cda7f70ecf5017a.js +1 -0
  55. package/.next/standalone/.next/static/chunks/app/settings/page-1ba7c4a4c0fae2f8.js +1 -0
  56. package/.next/standalone/.next/static/css/{406e067663b8b429.css → fbd2c395e5bf32cb.css} +1 -1
  57. package/.next/standalone/node_modules/next/node_modules/@img/sharp-darwin-arm64/LICENSE +191 -0
  58. package/.next/standalone/node_modules/next/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  59. package/.next/standalone/node_modules/next/node_modules/@img/sharp-darwin-arm64/package.json +40 -0
  60. package/.next/standalone/node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64/lib/index.js +1 -0
  61. package/.next/standalone/node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib +0 -0
  62. package/.next/standalone/node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64/package.json +36 -0
  63. package/.next/standalone/node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64/versions.json +30 -0
  64. package/.next/standalone/node_modules/next/node_modules/sharp/lib/channel.js +177 -0
  65. package/.next/standalone/node_modules/next/node_modules/sharp/lib/colour.js +195 -0
  66. package/.next/standalone/node_modules/next/node_modules/sharp/lib/composite.js +212 -0
  67. package/.next/standalone/node_modules/next/node_modules/sharp/lib/constructor.js +499 -0
  68. package/.next/standalone/node_modules/next/node_modules/sharp/lib/index.js +16 -0
  69. package/.next/standalone/node_modules/next/node_modules/sharp/lib/input.js +809 -0
  70. package/.next/standalone/node_modules/next/node_modules/sharp/lib/is.js +143 -0
  71. package/.next/standalone/node_modules/next/node_modules/sharp/lib/libvips.js +207 -0
  72. package/.next/standalone/node_modules/next/node_modules/sharp/lib/operation.js +1016 -0
  73. package/.next/standalone/node_modules/next/node_modules/sharp/lib/output.js +1666 -0
  74. package/.next/standalone/node_modules/next/node_modules/sharp/lib/resize.js +595 -0
  75. package/.next/standalone/node_modules/next/node_modules/sharp/lib/sharp.js +121 -0
  76. package/.next/standalone/node_modules/next/node_modules/sharp/lib/utility.js +291 -0
  77. package/.next/standalone/node_modules/next/node_modules/sharp/package.json +202 -0
  78. package/.next/standalone/node_modules/semver/classes/comparator.js +143 -0
  79. package/.next/standalone/node_modules/semver/classes/range.js +557 -0
  80. package/.next/standalone/node_modules/semver/classes/semver.js +333 -0
  81. package/.next/standalone/node_modules/semver/functions/cmp.js +54 -0
  82. package/.next/standalone/node_modules/semver/functions/coerce.js +62 -0
  83. package/.next/standalone/node_modules/semver/functions/compare.js +7 -0
  84. package/.next/standalone/node_modules/semver/functions/eq.js +5 -0
  85. package/.next/standalone/node_modules/semver/functions/gt.js +5 -0
  86. package/.next/standalone/node_modules/semver/functions/gte.js +5 -0
  87. package/.next/standalone/node_modules/semver/functions/lt.js +5 -0
  88. package/.next/standalone/node_modules/semver/functions/lte.js +5 -0
  89. package/.next/standalone/node_modules/semver/functions/neq.js +5 -0
  90. package/.next/standalone/node_modules/semver/functions/parse.js +18 -0
  91. package/.next/standalone/node_modules/semver/functions/satisfies.js +12 -0
  92. package/.next/standalone/node_modules/semver/internal/constants.js +37 -0
  93. package/.next/standalone/node_modules/semver/internal/debug.js +11 -0
  94. package/.next/standalone/node_modules/semver/internal/identifiers.js +29 -0
  95. package/.next/standalone/node_modules/semver/internal/lrucache.js +42 -0
  96. package/.next/standalone/node_modules/semver/internal/parse-options.js +17 -0
  97. package/.next/standalone/node_modules/semver/internal/re.js +223 -0
  98. package/.next/standalone/node_modules/semver/package.json +78 -0
  99. package/.next/standalone/package.json +13 -2
  100. package/.next/standalone/public/codex-logo.png +0 -0
  101. package/.next/standalone/public/favicon.svg +19 -5
  102. package/CHANGELOG.md +287 -0
  103. package/README.md +8 -2
  104. package/README.zh-CN.md +11 -4
  105. package/bin/cli.mjs +404 -92
  106. package/dist/mcp/server.mjs +16 -16
  107. package/dist/report/index.mjs +196 -30
  108. package/package.json +14 -3
  109. package/.next/standalone/.next/server/chunks/971.js +0 -1
  110. package/.next/standalone/.next/static/chunks/148-0a1e1b0207b89e3f.js +0 -1
  111. package/.next/standalone/.next/static/chunks/930-3035d0b294080d0b.js +0 -1
  112. package/.next/standalone/.next/static/chunks/app/layout-2512ccdfb13aeb17.js +0 -1
  113. package/.next/standalone/.next/static/chunks/app/page-19d3e77d4aa35a63.js +0 -1
  114. package/.next/standalone/.next/static/chunks/app/settings/page-cfeb089549c94f88.js +0 -1
  115. /package/.next/standalone/.next/static/{alqi5oQtTQUdpxp2x0yAt → kdpS1dOlXPsnKYuNBuMt9}/_buildManifest.js +0 -0
  116. /package/.next/standalone/.next/static/{alqi5oQtTQUdpxp2x0yAt → kdpS1dOlXPsnKYuNBuMt9}/_ssgManifest.js +0 -0
@@ -950,8 +950,19 @@ import { homedir } from "node:os";
950
950
  function sanitizeForUser(s) {
951
951
  const home = homedir();
952
952
  if (!home) return s;
953
- const escaped = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
954
- return s.replace(new RegExp(escaped, "g"), "~");
953
+ const variants = /* @__PURE__ */ new Set([home]);
954
+ if (process.platform === "win32") {
955
+ variants.add(home.replace(/\\/g, "/"));
956
+ variants.add(home.replace(/\\/g, "\\\\"));
957
+ variants.add("\\\\?\\" + home);
958
+ variants.add("\\\\?\\" + home.replace(/\\/g, "/"));
959
+ }
960
+ let out = s;
961
+ for (const v of variants) {
962
+ const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
963
+ out = out.replace(new RegExp(escaped, "g"), "~");
964
+ }
965
+ return out;
955
966
  }
956
967
 
957
968
  // lib/data-loader/indexer.ts
@@ -1517,6 +1528,69 @@ function projectNameFromCwd(cwd) {
1517
1528
  return parts[parts.length - 1] || cwd;
1518
1529
  }
1519
1530
 
1531
+ // lib/project-label.ts
1532
+ import { readFileSync, statSync } from "node:fs";
1533
+ import { basename } from "node:path";
1534
+ var cache = /* @__PURE__ */ new Map();
1535
+ var GITDIR_PATTERN = /^(.+?)[/\\]\.git[/\\]worktrees[/\\]([^/\\]+)[/\\]?$/;
1536
+ var CWD_WORKTREE_PATTERN = /^(.+?)[/\\](?:\.git|\.claude)[/\\]worktrees[/\\]([^/\\]+)(?:[/\\].*)?$/;
1537
+ function resolveRaw(cwd) {
1538
+ const fallbackName = projectNameFromCwd(cwd);
1539
+ if (!cwd) {
1540
+ return { label: fallbackName, isWorktree: false, mainName: fallbackName, worktreeName: "", canonicalCwd: cwd };
1541
+ }
1542
+ const pathMatch = CWD_WORKTREE_PATTERN.exec(cwd);
1543
+ if (pathMatch) {
1544
+ const mainRepoPath = pathMatch[1];
1545
+ const worktreeName = pathMatch[2];
1546
+ const mainName = basename(mainRepoPath) || mainRepoPath;
1547
+ return {
1548
+ label: `${mainName} (${worktreeName})`,
1549
+ isWorktree: true,
1550
+ mainName,
1551
+ worktreeName,
1552
+ canonicalCwd: mainRepoPath
1553
+ };
1554
+ }
1555
+ try {
1556
+ const gitPath = `${cwd}/.git`;
1557
+ const s = statSync(gitPath);
1558
+ if (!s.isFile()) {
1559
+ return { label: fallbackName, isWorktree: false, mainName: fallbackName, worktreeName: "", canonicalCwd: cwd };
1560
+ }
1561
+ const text = readFileSync(gitPath, "utf8").trim();
1562
+ const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
1563
+ const m = /^gitdir:\s*(.+)$/.exec(firstLine);
1564
+ if (!m) {
1565
+ return { label: fallbackName, isWorktree: false, mainName: fallbackName, worktreeName: "", canonicalCwd: cwd };
1566
+ }
1567
+ const gitdir = m[1].trim();
1568
+ const wt = GITDIR_PATTERN.exec(gitdir);
1569
+ if (!wt) {
1570
+ return { label: fallbackName, isWorktree: false, mainName: fallbackName, worktreeName: "", canonicalCwd: cwd };
1571
+ }
1572
+ const mainRepoPath = wt[1];
1573
+ const worktreeName = wt[2];
1574
+ const mainName = basename(mainRepoPath) || mainRepoPath;
1575
+ return {
1576
+ label: `${mainName} (${worktreeName})`,
1577
+ isWorktree: true,
1578
+ mainName,
1579
+ worktreeName,
1580
+ canonicalCwd: mainRepoPath
1581
+ };
1582
+ } catch {
1583
+ return { label: fallbackName, isWorktree: false, mainName: fallbackName, worktreeName: "", canonicalCwd: cwd };
1584
+ }
1585
+ }
1586
+ function resolveProjectLabel(cwd) {
1587
+ const cached = cache.get(cwd);
1588
+ if (cached) return cached.label;
1589
+ const r = resolveRaw(cwd);
1590
+ cache.set(cwd, r);
1591
+ return r.label;
1592
+ }
1593
+
1520
1594
  // lib/aggregator/index.ts
1521
1595
  var GRANULARITIES = ["hour", "day", "week", "month"];
1522
1596
  function isGranularity(v) {
@@ -1708,6 +1782,12 @@ function aggregateBySession(records, userRecords, opts) {
1708
1782
  source: rec.source,
1709
1783
  cwd: rec.cwd,
1710
1784
  projectName: projectNameFromCwd(rec.cwd),
1785
+ // Worktree-aware label so the sessions table shows the same
1786
+ // identifier the usage table does for the same record (e.g.
1787
+ // `ai-self-web (playwright)` instead of just `playwright`).
1788
+ // `resolveProjectLabel` caches per-cwd, so this is one fs.stat
1789
+ // per unique cwd across the whole aggregation.
1790
+ projectLabel: resolveProjectLabel(rec.cwd),
1711
1791
  startTime: rec.timestamp,
1712
1792
  endTime: rec.timestamp,
1713
1793
  durationMs: 0,
@@ -1814,6 +1894,66 @@ function rangeToDates(range) {
1814
1894
  return { from };
1815
1895
  }
1816
1896
 
1897
+ // lib/turns.ts
1898
+ var MAX_PARENT_WALK = 5e3;
1899
+ function buildTurnIndex(assistants, users, parentMap) {
1900
+ const userTextMap = /* @__PURE__ */ new Map();
1901
+ for (const u of users) {
1902
+ if (u.isSynthetic) continue;
1903
+ if (u.textPreview && u.textPreview.trim()) userTextMap.set(u.uuid, u.textPreview);
1904
+ }
1905
+ const result = /* @__PURE__ */ new Map();
1906
+ const memo = /* @__PURE__ */ new Map();
1907
+ function resolve(startUuid) {
1908
+ const path5 = [];
1909
+ let cur = startUuid;
1910
+ let answer = null;
1911
+ let steps = 0;
1912
+ const seen = /* @__PURE__ */ new Set();
1913
+ while (cur && steps++ < MAX_PARENT_WALK) {
1914
+ if (seen.has(cur)) break;
1915
+ seen.add(cur);
1916
+ const m = memo.get(cur);
1917
+ if (m) {
1918
+ answer = m;
1919
+ break;
1920
+ }
1921
+ path5.push(cur);
1922
+ if (userTextMap.has(cur)) {
1923
+ answer = cur;
1924
+ break;
1925
+ }
1926
+ cur = parentMap[cur] ?? null;
1927
+ }
1928
+ if (!answer) answer = startUuid;
1929
+ for (const id of path5) memo.set(id, answer);
1930
+ return answer;
1931
+ }
1932
+ for (const a of assistants) {
1933
+ result.set(a.uuid, resolve(a.uuid));
1934
+ }
1935
+ return result;
1936
+ }
1937
+ function summarizeTurns(records, users, parentMap) {
1938
+ const turnIndex = buildTurnIndex(records, users, parentMap);
1939
+ const out = /* @__PURE__ */ new Map();
1940
+ for (const r of records) {
1941
+ const turnId = turnIndex.get(r.uuid) ?? r.uuid;
1942
+ const existing = out.get(turnId);
1943
+ if (!existing || r.timestamp < existing.firstTimestamp) {
1944
+ out.set(turnId, {
1945
+ turnId,
1946
+ firstTimestamp: r.timestamp,
1947
+ firstModel: r.model,
1948
+ cwd: r.cwd,
1949
+ sessionId: r.sessionId,
1950
+ source: r.source
1951
+ });
1952
+ }
1953
+ }
1954
+ return out;
1955
+ }
1956
+
1817
1957
  // lib/cli-report/index.ts
1818
1958
  var REPORT_RANGES = ["today", "1d", "7d", "30d", "90d", "all"];
1819
1959
  var DIMS = ["model", "project", "session"];
@@ -1833,7 +1973,7 @@ async function runReport(opts) {
1833
1973
  const filled = normalizeReportOptions(opts);
1834
1974
  const scan = await getCachedScan();
1835
1975
  const sources = filled.source === "all" ? ALL_PROVIDER_IDS : [filled.source];
1836
- const data = computeReportData(scan.records, sources, filled);
1976
+ const data = computeReportData(scan, sources, filled);
1837
1977
  if (filled.json) return JSON.stringify(data, null, 2);
1838
1978
  return renderText(data, filled);
1839
1979
  }
@@ -1868,14 +2008,15 @@ function isDim(v) {
1868
2008
  function invalidOptionMessage(name, value, expected) {
1869
2009
  return `invalid ${name}: ${JSON.stringify(value)}. Expected one of: ${expected.join(", ")}`;
1870
2010
  }
1871
- function computeReportData(allRecords, sources, o) {
2011
+ function computeReportData(scan, sources, o) {
2012
+ const allRecords = scan.records;
1872
2013
  const dates = resolveRange(o);
1873
2014
  const baseOpts = {
1874
2015
  from: dates.from ?? void 0,
1875
- to: dates.until ?? void 0,
1876
- models: o.model ? void 0 : void 0,
1877
- // handled post-filter
1878
- projects: o.project ? void 0 : void 0
2016
+ to: dates.until ?? void 0
2017
+ // `o.model` / `o.project` are substring patterns, not exact matches —
2018
+ // they're applied per-record by `withinSrcAndFilters` below, so we
2019
+ // deliberately leave the aggregator's exact-match filters unset.
1879
2020
  };
1880
2021
  const totals = {
1881
2022
  input: 0,
@@ -1886,7 +2027,8 @@ function computeReportData(allRecords, sources, o) {
1886
2027
  total: 0,
1887
2028
  cost: 0,
1888
2029
  saved: 0,
1889
- requests: 0
2030
+ requests: 0,
2031
+ turns: 0
1890
2032
  };
1891
2033
  const trendBuckets = /* @__PURE__ */ new Map();
1892
2034
  for (const source of sources) {
@@ -1902,19 +2044,28 @@ function computeReportData(allRecords, sources, o) {
1902
2044
  totals.saved += t.saved;
1903
2045
  totals.requests += t.requests;
1904
2046
  for (const r of sourceRecs) totals.reasoning += r.usage.reasoning_tokens ?? 0;
2047
+ const turnSummaries = summarizeTurns(sourceRecs, scan.userRecords, scan.parentMap);
2048
+ totals.turns += turnSummaries.size;
2049
+ const turnsByKey = /* @__PURE__ */ new Map();
2050
+ for (const tn of turnSummaries.values()) {
2051
+ const { key } = bucketKey(tn.firstTimestamp, o.gran);
2052
+ turnsByKey.set(key, (turnsByKey.get(key) ?? 0) + 1);
2053
+ }
1905
2054
  const buckets = aggregateByTime(sourceRecs, o.gran, opts);
1906
2055
  for (const b of buckets) {
1907
2056
  const ex = trendBuckets.get(b.key);
2057
+ const tnCount = turnsByKey.get(b.key) ?? 0;
1908
2058
  if (ex) {
1909
2059
  ex.cost += b.cost;
1910
2060
  ex.tokens += b.totalTokens;
2061
+ ex.turns += tnCount;
1911
2062
  } else {
1912
- trendBuckets.set(b.key, { label: b.label, cost: b.cost, tokens: b.totalTokens });
2063
+ trendBuckets.set(b.key, { label: b.label, cost: b.cost, tokens: b.totalTokens, turns: tnCount });
1913
2064
  }
1914
2065
  }
1915
2066
  }
1916
2067
  const trend = Array.from(trendBuckets.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
1917
- const breakdown = buildBreakdown(allRecords, sources, baseOpts, o);
2068
+ const breakdown = buildBreakdown(scan, sources, baseOpts, o);
1918
2069
  return {
1919
2070
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1920
2071
  range: o.range,
@@ -1928,7 +2079,8 @@ function computeReportData(allRecords, sources, o) {
1928
2079
  breakdown
1929
2080
  };
1930
2081
  }
1931
- function buildBreakdown(allRecords, sources, base, o) {
2082
+ function buildBreakdown(scan, sources, base, o) {
2083
+ const allRecords = scan.records;
1932
2084
  if (o.by === "model") {
1933
2085
  const rows2 = [];
1934
2086
  for (const source of sources) {
@@ -1936,11 +2088,16 @@ function buildBreakdown(allRecords, sources, base, o) {
1936
2088
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1937
2089
  const models = aggregateByModel(filtered, opts);
1938
2090
  const provider = getProvider(source);
2091
+ const turnsByModel = /* @__PURE__ */ new Map();
2092
+ for (const tn of summarizeTurns(filtered, scan.userRecords, scan.parentMap).values()) {
2093
+ turnsByModel.set(tn.firstModel, (turnsByModel.get(tn.firstModel) ?? 0) + 1);
2094
+ }
1939
2095
  for (const m of models) {
1940
2096
  rows2.push({
1941
2097
  key: `${source}::${m.model}`,
1942
2098
  label: provider.shortenModel(m.model),
1943
2099
  requests: m.requests,
2100
+ turns: turnsByModel.get(m.model) ?? 0,
1944
2101
  tokens: m.totalTokens,
1945
2102
  cost: m.cost,
1946
2103
  share: 0,
@@ -1957,11 +2114,17 @@ function buildBreakdown(allRecords, sources, base, o) {
1957
2114
  const opts = { ...base, source };
1958
2115
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1959
2116
  const projects = aggregateByProject(filtered, opts);
2117
+ const turnsByCwd = /* @__PURE__ */ new Map();
2118
+ for (const tn of summarizeTurns(filtered, scan.userRecords, scan.parentMap).values()) {
2119
+ const key = tn.cwd || "(unknown)";
2120
+ turnsByCwd.set(key, (turnsByCwd.get(key) ?? 0) + 1);
2121
+ }
1960
2122
  for (const p of projects) {
1961
2123
  rows2.push({
1962
2124
  key: `${source}::${p.cwd}`,
1963
2125
  label: p.projectName,
1964
2126
  requests: p.requests,
2127
+ turns: turnsByCwd.get(p.cwd) ?? 0,
1965
2128
  tokens: p.totalTokens,
1966
2129
  cost: p.cost,
1967
2130
  share: 0,
@@ -1976,15 +2139,23 @@ function buildBreakdown(allRecords, sources, base, o) {
1976
2139
  const opts = { ...base, source };
1977
2140
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1978
2141
  const sessions = aggregateBySession(filtered, [], opts);
2142
+ const turnsBySession = /* @__PURE__ */ new Map();
2143
+ for (const tn of summarizeTurns(filtered, scan.userRecords, scan.parentMap).values()) {
2144
+ turnsBySession.set(tn.sessionId, (turnsBySession.get(tn.sessionId) ?? 0) + 1);
2145
+ }
1979
2146
  for (const s of sessions) {
1980
2147
  rows.push({
1981
2148
  key: `${source}::${s.sessionId}`,
1982
2149
  label: s.title ?? s.sessionId.slice(0, 8),
1983
2150
  requests: s.requests,
2151
+ turns: turnsBySession.get(s.sessionId) ?? 0,
1984
2152
  tokens: s.totalTokens,
1985
2153
  cost: s.cost,
1986
2154
  share: 0,
1987
- sub: s.projectName
2155
+ // Worktree-aware so the breakdown reads
2156
+ // `ai-self-web (playwright)` instead of just `playwright`,
2157
+ // matching the dashboard's usage / sessions tables.
2158
+ sub: s.projectLabel
1988
2159
  });
1989
2160
  }
1990
2161
  }
@@ -2086,21 +2257,15 @@ function renderText(d, o) {
2086
2257
  ]
2087
2258
  ];
2088
2259
  if (t.reasoning > 0) {
2089
- tokenRows.push([
2090
- "Reasoning",
2091
- c.dim(formatTokensCompact(t.reasoning)),
2092
- "Requests",
2093
- t.requests.toLocaleString()
2094
- ]);
2095
- tokenRows.push(["Total", c.bold(formatTokensCompact(t.total)), "", ""]);
2096
- } else {
2097
- tokenRows.push([
2098
- "Total",
2099
- c.bold(formatTokensCompact(t.total)),
2100
- "Requests",
2101
- t.requests.toLocaleString()
2102
- ]);
2103
- }
2260
+ tokenRows.push(["Reasoning", c.dim(formatTokensCompact(t.reasoning)), "", ""]);
2261
+ }
2262
+ tokenRows.push(["Total", c.bold(formatTokensCompact(t.total)), "", ""]);
2263
+ tokenRows.push([
2264
+ "Convos",
2265
+ c.bold(t.turns.toLocaleString()),
2266
+ "Requests",
2267
+ t.requests.toLocaleString()
2268
+ ]);
2104
2269
  lines.push(renderPairedKv(tokenRows, c));
2105
2270
  lines.push("");
2106
2271
  lines.push(c.brand("\u25B8") + " " + c.bold("Cost"));
@@ -2136,16 +2301,17 @@ function renderText(d, o) {
2136
2301
  lines.push(
2137
2302
  c.brand("\u25B8") + " " + c.bold(`Top ${d.breakdown.length} ${dimLabel}s`) + " " + c.dim("(by cost)")
2138
2303
  );
2139
- const headers = ["#", dimLabel, "Reqs", "Tokens", "Cost", "Share"];
2304
+ const headers = ["#", dimLabel, "Convos", "Reqs", "Tokens", "Cost", "Share"];
2140
2305
  const rows = d.breakdown.map((r, i) => [
2141
2306
  String(i + 1),
2142
2307
  truncate(r.label, 28),
2308
+ r.turns.toLocaleString(),
2143
2309
  r.requests.toLocaleString(),
2144
2310
  formatTokensCompact(r.tokens),
2145
2311
  formatUSD(r.cost),
2146
2312
  formatPct(r.share, 1)
2147
2313
  ]);
2148
- lines.push(renderTable(headers, rows, c, [false, false, true, true, true, true]));
2314
+ lines.push(renderTable(headers, rows, c, [false, false, true, true, true, true, true]));
2149
2315
  lines.push("");
2150
2316
  }
2151
2317
  return lines.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgauge",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Local web dashboard for Claude Code and OpenAI Codex CLI token usage and cost",
5
5
  "keywords": [
6
6
  "claude",
@@ -61,10 +61,15 @@
61
61
  "open": "^10.1.0"
62
62
  },
63
63
  "devDependencies": {
64
+ "@astrojs/check": "^0.9.4",
65
+ "@astrojs/tailwind": "^5.1.4",
64
66
  "@eslint/eslintrc": "^3.3.5",
67
+ "@fontsource-variable/geist": "^5.1.1",
68
+ "@fontsource-variable/geist-mono": "^5.1.1",
65
69
  "@types/node": "^22.10.0",
66
70
  "@types/react": "^19.0.0",
67
71
  "@types/react-dom": "^19.0.0",
72
+ "astro": "^4.16.18",
68
73
  "autoprefixer": "^10.4.20",
69
74
  "clsx": "^2.1.1",
70
75
  "date-fns": "^4.1.0",
@@ -91,9 +96,15 @@
91
96
  "start:next": "next start -p 3737",
92
97
  "lint": "eslint .",
93
98
  "typecheck": "tsc --noEmit",
94
- "test": "node --experimental-strip-types --no-warnings scripts/test-codex-parser.mjs",
99
+ "test": "node --experimental-strip-types --no-warnings scripts/test-codex-parser.mjs && node --experimental-strip-types --no-warnings scripts/test-turns.mjs && node --experimental-strip-types --no-warnings scripts/test-source-merge.mjs && node --experimental-strip-types --no-warnings scripts/test-cost-from-usage.mjs && node --experimental-strip-types --no-warnings scripts/test-range.mjs && node scripts/check-parser-versions.mjs && node scripts/check-readme-images.mjs",
95
100
  "test:mcp": "node scripts/test-mcp-server.mjs",
96
101
  "clean": "node -e \"for (const p of ['.next','node_modules','tsconfig.tsbuildinfo']) require('node:fs').rmSync(p,{recursive:true,force:true})\"",
97
- "screenshots": "node scripts/screenshots.mjs"
102
+ "screenshots": "node scripts/screenshots.mjs",
103
+ "site:dev": "cd site && astro dev --port 4321",
104
+ "site:build": "cd site && astro build",
105
+ "site:preview": "cd site && astro preview --port 4322",
106
+ "site:check": "cd site && astro check",
107
+ "site:gen:placeholders": "node site/scripts/gen-placeholders.mjs",
108
+ "site:clean": "node -e \"for (const p of ['site/dist','site/.astro','site/node_modules']) require('node:fs').rmSync(p,{recursive:true,force:true})\""
98
109
  }
99
110
  }
@@ -1 +0,0 @@
1
- "use strict";exports.id=971,exports.ids=[971],exports.modules={24919:(a,b,c)=>{c.d(b,{TokenStackChart:()=>o});var d=c(21124),e=c(6077),f=c(72400),g=c(57495),h=c(59296),i=c(16803),j=c(11767),k=c(18311),l=c(15514),m=c(15519);let n={input:"rgb(var(--chart-input))",output:"rgb(var(--chart-output))",cacheRead:"rgb(var(--chart-cache-read))",cacheCreation:"rgb(var(--chart-cache-create))"};function o({data:a,height:b="h-72"}){let c=(0,m.kj)(),{locale:o}=(0,m.s9)();return a.length?(0,d.jsxs)("div",{className:`${b} w-full`,children:[(0,d.jsx)(e.u,{width:"100%",height:"100%",children:(0,d.jsxs)(f.E,{data:a,margin:{top:12,right:8,bottom:4,left:8},barCategoryGap:"22%",children:[(0,d.jsx)(g.d,{stroke:"rgb(var(--chart-grid))",strokeOpacity:.6,strokeDasharray:"3 3",vertical:!1}),(0,d.jsx)(h.W,{dataKey:"label",tick:{fill:"rgb(var(--chart-axis))",fontSize:11},tickLine:!1,axisLine:!1,interval:"preserveStartEnd",minTickGap:32,tickMargin:8}),(0,d.jsx)(i.h,{tickFormatter:a=>(0,l.jh)(Number(a),o),tick:{fill:"rgb(var(--chart-axis))",fontSize:11},tickLine:!1,axisLine:!1,width:56,tickMargin:4}),(0,d.jsx)(j.m,{content:(0,d.jsx)(q,{}),cursor:{fill:"rgb(var(--text-primary) / 0.05)",radius:4}}),(0,d.jsx)(k.y,{dataKey:"input",stackId:"a",fill:n.input,isAnimationActive:!1}),(0,d.jsx)(k.y,{dataKey:"cacheCreation",stackId:"a",fill:n.cacheCreation,isAnimationActive:!1}),(0,d.jsx)(k.y,{dataKey:"cacheRead",stackId:"a",fill:n.cacheRead,isAnimationActive:!1}),(0,d.jsx)(k.y,{dataKey:"output",stackId:"a",fill:n.output,radius:[4,4,0,0],isAnimationActive:!1})]})}),(0,d.jsxs)("div",{className:"flex items-center flex-wrap justify-center gap-4 text-xs text-text-secondary mt-2",children:[(0,d.jsx)(p,{color:n.input,label:c("chart.legend.input")}),(0,d.jsx)(p,{color:n.cacheCreation,label:c("chart.legend.cacheWrite")}),(0,d.jsx)(p,{color:n.cacheRead,label:c("chart.legend.cacheRead")}),(0,d.jsx)(p,{color:n.output,label:c("chart.legend.output")})]})]}):(0,d.jsx)("div",{className:`${b} flex items-center justify-center text-text-tertiary text-sm`,children:c("chart.empty")})}function p({color:a,label:b}){return(0,d.jsxs)("span",{className:"inline-flex items-center gap-1.5",children:[(0,d.jsx)("span",{className:"w-2.5 h-2.5 rounded-sm",style:{background:a}}),(0,d.jsx)("span",{children:b})]})}function q(a){let b=(0,m.kj)(),{locale:c}=(0,m.s9)();if(!a.active||!a.payload||!a.payload.length)return null;let e=a.payload[0].payload,f=e.input+e.output+e.cacheRead+e.cacheCreation;return(0,d.jsxs)("div",{className:"card-elevated border border-border-hi rounded-card p-3 text-xs min-w-[200px]",children:[(0,d.jsx)("div",{className:"font-medium text-text-primary mb-2",children:a.label}),(0,d.jsxs)("div",{className:"space-y-1",children:[(0,d.jsx)(r,{color:n.input,label:b("chart.legend.input"),value:e.input,locale:c}),(0,d.jsx)(r,{color:n.cacheCreation,label:b("chart.legend.cacheWrite"),value:e.cacheCreation,locale:c}),(0,d.jsx)(r,{color:n.cacheRead,label:b("chart.legend.cacheRead"),value:e.cacheRead,locale:c}),(0,d.jsx)(r,{color:n.output,label:b("chart.legend.output"),value:e.output,locale:c})]}),(0,d.jsxs)("div",{className:"mt-2 pt-2 border-t border-border flex items-center justify-between",children:[(0,d.jsx)("span",{className:"text-text-secondary",children:b("chart.tooltip.total")}),(0,d.jsx)("span",{className:"num-mono text-text-primary",children:(0,l.jh)(f,c)})]}),(0,d.jsxs)("div",{className:"flex items-center justify-between mt-1",children:[(0,d.jsx)("span",{className:"text-text-secondary",children:b("chart.tooltip.cost")}),(0,d.jsx)("span",{className:"num-mono text-brand",children:(0,l.az)(e.cost)})]}),(0,d.jsxs)("div",{className:"flex items-center justify-between mt-1",children:[(0,d.jsx)("span",{className:"text-text-secondary",children:b("chart.tooltip.requests")}),(0,d.jsx)("span",{className:"num-mono text-text-primary",children:e.requests})]})]})}function r({color:a,label:b,value:c,locale:e}){return(0,d.jsxs)("div",{className:"flex items-center justify-between gap-3",children:[(0,d.jsxs)("span",{className:"inline-flex items-center gap-1.5 text-text-secondary",children:[(0,d.jsx)("span",{className:"w-2 h-2 rounded-sm",style:{background:a}}),b]}),(0,d.jsx)("span",{className:"num-mono text-text-primary",children:(0,l.jh)(c,e)})]})}},37583:(a,b,c)=>{c.d(b,{pp:()=>h,qK:()=>g,wn:()=>f});var d=c(75338),e=c(95012);function f({title:a,desc:b,right:c,children:f,className:g,inlineDesc:h,fillBody:i}){return(0,d.jsxs)("section",{className:(0,e.cn)("card overflow-hidden",i&&"flex flex-col",g),children:[(a||c)&&(0,d.jsxs)("header",{className:(0,e.cn)("section-header","flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 sm:gap-4","px-5 sm:px-6 pt-4 sm:pt-5 pb-3 border-b border-border"),children:[(0,d.jsxs)("div",{className:(0,e.cn)("min-w-0",h&&"sm:flex sm:items-baseline sm:gap-2.5"),children:[a&&(0,d.jsx)("h2",{className:"text-[15px] font-semibold text-text-primary tracking-tight leading-tight",children:a}),b&&(0,d.jsx)("p",{className:(0,e.cn)("text-xs text-text-secondary leading-relaxed",h?"mt-1 sm:mt-0":"mt-1.5"),children:b})]}),c&&(0,d.jsx)("div",{className:"flex items-center gap-2 flex-wrap",children:c})]}),(0,d.jsx)("div",{className:(0,e.cn)("p-5 sm:p-6",i&&"flex-1 flex flex-col"),children:f})]})}function g({title:a,desc:b,right:c,children:e}){return(0,d.jsxs)("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 py-6 sm:py-8 space-y-5 sm:space-y-6",children:[(0,d.jsxs)("div",{className:"flex flex-col sm:flex-row sm:items-end sm:justify-between gap-3 sm:gap-4",children:[(0,d.jsxs)("div",{className:"min-w-0",children:[(0,d.jsx)("h1",{className:"text-2xl sm:text-[1.75rem] font-semibold tracking-tight leading-tight truncate",children:a}),b&&(0,d.jsx)("p",{className:"text-sm text-text-secondary mt-1.5 leading-relaxed",children:b})]}),c&&(0,d.jsx)("div",{className:"flex items-center gap-2 flex-wrap",children:c})]}),e]})}function h({title:a,desc:b,icon:c,action:e}){return(0,d.jsxs)("div",{className:"card text-center py-14 sm:py-16 px-6 flex flex-col items-center gap-3",children:[(0,d.jsx)("div",{className:"w-12 h-12 rounded-full bg-bg-surface-hi border border-border flex items-center justify-center text-text-tertiary",children:c??(0,d.jsx)(i,{})}),(0,d.jsx)("div",{className:"text-base font-medium text-text-primary",children:a}),b&&(0,d.jsx)("div",{className:"text-sm text-text-tertiary max-w-md leading-relaxed",children:b}),e&&(0,d.jsx)("div",{className:"mt-2",children:e})]})}function i(){return(0,d.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.75",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[(0,d.jsx)("circle",{cx:"12",cy:"12",r:"9"}),(0,d.jsx)("path",{d:"M9 9h.01M15 9h.01M9 15c1 1 2 1.5 3 1.5s2-.5 3-1.5"})]})}},50160:(a,b,c)=>{c.d(b,{SI:()=>e});var d=c(60704);function e(a){let b=(0,d.sO)(a.source),{pricing:c}=b.resolvePricing(a.model);return b.costFromUsage(a.usage,c)}},67803:(a,b,c)=>{c.d(b,{$M:()=>p,OJ:()=>n,a3:()=>i,a6:()=>l,a9:()=>m,bo:()=>h,pb:()=>o});var d=c(50160),e=c(60704),f=c(95012);let g=["hour","day","week","month"];function h(a){return"string"==typeof a&&g.includes(a)}function i(a,b){let c=new Date(a),d=c.getFullYear(),e=String(c.getMonth()+1).padStart(2,"0"),f=String(c.getDate()).padStart(2,"0"),g=String(c.getHours()).padStart(2,"0");if("hour"===b)return{key:`${d}-${e}-${f}T${g}`,label:`${e}/${f} ${g}:00`};if("day"===b)return{key:`${d}-${e}-${f}`,label:`${e}/${f}`};if("week"===b){let a=new Date(c),b=a.getDay()||7;a.setDate(a.getDate()-b+1);let d=String(a.getMonth()+1).padStart(2,"0"),e=String(a.getDate()).padStart(2,"0");return{key:`${a.getFullYear()}-W${d}${e}`,label:`Wk ${d}/${e}`}}return{key:`${d}-${e}`,label:`${d}-${e}`}}function j(a){return{source:a.source,fromIso:a.from?.toISOString(),toIso:a.to?.toISOString(),models:a.models&&a.models.length?new Set(a.models):void 0,projects:a.projects&&a.projects.length?new Set(a.projects):void 0}}function k(a,b){return a.source===b.source&&(!b.fromIso||!(a.timestamp<b.fromIso))&&(!b.toIso||!(a.timestamp>b.toIso))&&(!b.models||!!b.models.has(a.model))&&(!b.projects||!!b.projects.has(a.cwd))}function l(a,b,c){let e=new Map,f=j(c);for(let c of a){if(!k(c,f))continue;let{key:a,label:g}=i(c.timestamp,b),h=e.get(a);h||(h={key:a,label:g,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalTokens:0,cost:0,saved:0,requests:0,models:{}},e.set(a,h)),function(a,b){let c=(0,d.SI)(b);a.inputTokens+=b.usage.input_tokens,a.outputTokens+=b.usage.output_tokens,a.cacheReadTokens+=b.usage.cache_read_input_tokens,a.cacheCreationTokens+=b.usage.cache_creation_input_tokens,a.totalTokens=a.inputTokens+a.outputTokens+a.cacheReadTokens+a.cacheCreationTokens,a.cost+=c.total,a.saved+=c.saved,a.requests+=1;let e=a.models[b.model]??{tokens:0,cost:0,requests:0};e.tokens+=b.usage.input_tokens+b.usage.output_tokens+b.usage.cache_read_input_tokens+b.usage.cache_creation_input_tokens,e.cost+=c.total,e.requests+=1,a.models[b.model]=e}(h,c)}return Array.from(e.values()).sort((a,b)=>a.key.localeCompare(b.key))}function m(a,b){let c=new Map,f=j(b);for(let b of a){if(!k(b,f))continue;let a=c.get(b.model);if(!a){let{pricing:d,matchType:f}=(0,e.sO)(b.source).resolvePricing(b.model);a={model:b.model,source:b.source,requests:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalTokens:0,cost:0,saved:0,pricing:d,pricingResolved:"exact"===f||"date-stripped"===f||"prefix-stripped"===f},c.set(b.model,a)}let g=(0,d.SI)(b);a.requests+=1,a.inputTokens+=b.usage.input_tokens,a.outputTokens+=b.usage.output_tokens,a.cacheReadTokens+=b.usage.cache_read_input_tokens,a.cacheCreationTokens+=b.usage.cache_creation_input_tokens,a.totalTokens=a.inputTokens+a.outputTokens+a.cacheReadTokens+a.cacheCreationTokens,a.cost+=g.total,a.saved+=g.saved}return Array.from(c.values()).sort((a,b)=>b.cost-a.cost)}function n(a,b){let c=new Map,e=new Map,g=j(b);for(let h of a){if(!k(h,g))continue;let a=h.cwd||"(unknown)",i=c.get(a);i||(i={source:b.source,cwd:a,projectName:(0,f.PJ)(a),sessions:0,requests:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalTokens:0,cost:0,saved:0,firstActivity:h.timestamp,lastActivity:h.timestamp,models:[]},c.set(a,i),e.set(a,new Set)),e.get(a).add(h.sessionId);let j=(0,d.SI)(h);i.requests+=1,i.inputTokens+=h.usage.input_tokens,i.outputTokens+=h.usage.output_tokens,i.cacheReadTokens+=h.usage.cache_read_input_tokens,i.cacheCreationTokens+=h.usage.cache_creation_input_tokens,i.totalTokens=i.inputTokens+i.outputTokens+i.cacheReadTokens+i.cacheCreationTokens,i.cost+=j.total,i.saved+=j.saved,h.timestamp<i.firstActivity&&(i.firstActivity=h.timestamp),h.timestamp>i.lastActivity&&(i.lastActivity=h.timestamp),i.models.includes(h.model)||i.models.push(h.model)}for(let[a,b]of e)c.get(a).sessions=b.size;return Array.from(c.values()).sort((a,b)=>b.cost-a.cost)}function o(a,b,c){let e=new Map,g=j(c);for(let b of a){if(!k(b,g))continue;let a=b.sessionId||b.uuid,c=e.get(a);c||(c={sessionId:a,source:b.source,cwd:b.cwd,projectName:(0,f.PJ)(b.cwd),startTime:b.timestamp,endTime:b.timestamp,durationMs:0,requests:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalTokens:0,cost:0,saved:0,models:[],modelBreakdown:{}},e.set(a,c));let h=(0,d.SI)(b);c.requests+=1,c.inputTokens+=b.usage.input_tokens,c.outputTokens+=b.usage.output_tokens,c.cacheReadTokens+=b.usage.cache_read_input_tokens,c.cacheCreationTokens+=b.usage.cache_creation_input_tokens,c.totalTokens=c.inputTokens+c.outputTokens+c.cacheReadTokens+c.cacheCreationTokens,c.cost+=h.total,c.saved+=h.saved,b.timestamp<c.startTime&&(c.startTime=b.timestamp),b.timestamp>c.endTime&&(c.endTime=b.timestamp),c.models.includes(b.model)||c.models.push(b.model);let i=c.modelBreakdown[b.model]??{tokens:0,cost:0,requests:0};i.tokens+=b.usage.input_tokens+b.usage.output_tokens+b.usage.cache_read_input_tokens+b.usage.cache_creation_input_tokens,i.cost+=h.total,i.requests+=1,c.modelBreakdown[b.model]=i}let h=new Map;for(let a of b){let b=h.get(a.sessionId);(!b||a.timestamp<b.timestamp)&&h.set(a.sessionId,a)}for(let a of e.values()){a.durationMs=Math.max(0,new Date(a.endTime).getTime()-new Date(a.startTime).getTime());let b=h.get(a.sessionId);b&&b.textPreview&&(a.firstUserMessage=b.textPreview,a.title=b.textPreview.slice(0,80))}return Array.from(e.values()).sort((a,b)=>b.endTime.localeCompare(a.endTime))}function p(a,b){let c=0,e=0,f=0,g=0,h=0,i=0,l=0,m=j(b);for(let b of a){if(!k(b,m))continue;let a=(0,d.SI)(b);c+=b.usage.input_tokens,e+=b.usage.output_tokens,f+=b.usage.cache_read_input_tokens,g+=b.usage.cache_creation_input_tokens,h+=a.total,i+=a.saved,l+=1}return{inputTokens:c,outputTokens:e,cacheReadTokens:f,cacheCreationTokens:g,totalTokens:c+e+f+g,cost:h,saved:i,requests:l}}}};
@@ -1 +0,0 @@
1
- "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[148],{51148:(e,t,s)=>{s.d(t,{CY:()=>d,s9:()=>u,kj:()=>m});var o=s(95155),i=s(12115),a=s(20063),n=s(79808);let c="ccgauge_locale",r=(0,i.createContext)({locale:n.Xn,setLocale:()=>{},t:e=>e}),l="ccgauge.locale";function d(e){let{initialLocale:t,children:s}=e,d=(0,a.useRouter)();(0,i.useEffect)(()=>{try{let e=localStorage.getItem(l);e&&e!==t&&("en"===e||"zh"===e)&&(document.cookie="".concat(c,"=").concat(e,"; path=/; max-age=").concat(31536e3,"; SameSite=Lax"),d.refresh())}catch(e){}},[]);let u=(0,i.useCallback)(e=>{try{localStorage.setItem(l,e)}catch(e){}document.cookie="".concat(c,"=").concat(e,"; path=/; max-age=").concat(31536e3,"; SameSite=Lax"),d.refresh()},[d]),m=(0,i.useCallback)((e,s)=>(0,n.nA)(t,e,s),[t]),g=(0,i.useMemo)(()=>({locale:t,setLocale:u,t:m}),[t,u,m]);return(0,o.jsx)(r.Provider,{value:g,children:s})}function u(){return(0,i.useContext)(r)}function m(){return(0,i.useContext)(r).t}},79808:(e,t,s)=>{s.d(t,{Xn:()=>o,nA:()=>a});let o="en",i={en:{"brand.tagline":"usage dashboard for AI coding CLIs","nav.overview":"Overview","nav.usage":"Usage","nav.sessions":"Sessions","nav.projects":"Projects","nav.models":"Models","nav.settings":"Settings","nav.localBadge":"local","nav.source":"Data source","source.claude":"Claude","source.codex":"Codex","source.all":"All","cost.footnote.codex":"Cost shown is the OpenAI API equivalent (subscription plans pay differently).","common.requests":"requests","common.tokens":"tokens","common.cost":"Cost","common.savedViaCache":"Saved {amount} via cache","common.savedTodayViaCache":"Saved {amount} today","common.live":"live","common.allModels":"All","common.allProjects":"All","common.unknown":"(unknown)","common.session":"Session","common.sessions":"sessions","common.projects":"projects","common.req":"req","common.day":"day","common.days":"days","common.activity":"activity","common.fallbackPrice":"fallback price","common.thinking":"thinking","common.lastActivity":"last activity","common.empty.title":"No usage data yet","common.empty.desc":"Open Claude Code, send a message, then refresh.","common.refresh":"Refresh","common.search":"Search…","common.searchPlaceholder":"Search model, project, session, tool…","common.exportCsv":"Export CSV","common.rows":"{count} rows","common.prev":"‹ Prev","common.next":"Next ›","common.first":"\xab","common.last":"\xbb","common.pageOf":"Page {page} of {total}","common.allSessions":"← All sessions","common.allProjectsLink":"← All projects","common.noMatchingRows":"No matching rows","range.label":"Date range","range.today":"Today","range.7d":"7d","range.30d":"30d","range.90d":"90d","range.all":"All","gran.label":"Granularity","gran.hour":"Hour","gran.day":"Day","gran.week":"Week","gran.month":"Month","overview.title":"Overview","overview.subtitle":"{count} requests across {files} files \xb7 scan in {ms}ms","overview.subtitle.empty":"Scanned {dirs} directory(ies). Open Claude Code, send a message, then refresh.","overview.empty.title":"No usage data yet","overview.kpi.tokensToday":"Tokens today","overview.kpi.costToday":"Cost today","overview.kpi.thisMonth":"This month","overview.kpi.cacheHit":"Cache hit rate","overview.kpi.topModel":"Top model","overview.kpi.activeSessions":"Sessions today","overview.kpi.activeSessions.hint":"{count} project(s)","overview.kpi.thisMonth.hint":"{tokens} tokens \xb7 {req} req","overview.kpi.tokensToday.hint":"{count} requests","overview.kpi.topModel.hint":"{pct} of cost this month","overview.kpi.cacheHit.hint":"Saved {amount} today","overview.delta.title":"vs yesterday","overview.delta.firstTime":"NEW","common.loading":"Loading…","common.error.title":"Something went wrong","common.error.desc":"Failed to load this page. Try again.","common.error.retry":"Retry","overview.trend.title":"Usage trend","overview.trend.desc":"Last 30 days \xb7 {metric}","overview.trend.desc.tokens":"stacked by token type","overview.trend.desc.conversations":"conversations per day","overview.trend.metric.tokens":"Tokens","overview.trend.metric.conversations":"Conversations","overview.trend.activeDays":"{n} day with activity","overview.trend.activeDays.plural":"{n} days with activity","overview.costByModel.title":"Cost by model","overview.costByModel.desc":"This month, sorted by spend","activity.title":"Activity","activity.subtitle":"Lifetime stats and when you usually code","activity.sessions":"Sessions","activity.messages":"Messages","activity.totalTokens":"Total tokens","activity.activeDays":"Active days","activity.currentStreak":"Current streak","activity.longestStreak":"Longest streak","activity.streakCombinedLabel":"Streak (cur / max)","activity.streakCombinedValue":"{current} / {longest} d","activity.peakHour":"Peak hour","activity.favoriteModel":"Favorite model","activity.streakValue":"{n}d","activity.dow.0":"Sun","activity.dow.1":"Mon","activity.dow.2":"Tue","activity.dow.3":"Wed","activity.dow.4":"Thu","activity.dow.5":"Fri","activity.dow.6":"Sat","activity.heatmap.tooltip":"{dow} \xb7 {hour} \xb7 {count} messages","activity.heatmap.messages":"Messages","activity.heatmap.tokens":"Tokens","activity.heatmap.shareLabel":"Share","activity.heatmap.intensityLabel":"Of peak","activity.heatmap.empty":"No activity","activity.comparison":"You've used ~{multiplier}\xd7 more tokens than {ref}.","activity.ref.haiku":"a haiku","activity.ref.tweet":"a tweet","activity.ref.littlePrince":"The Little Prince","activity.ref.gatsby":"The Great Gatsby","activity.ref.hobbit":"The Hobbit","activity.ref.lotrTrilogy":"the Lord of the Rings trilogy","activity.ref.warAndPeace":"War and Peace","activity.ref.harryPotterAll":"all 7 Harry Potter books","activity.ref.encyclopediaBritannica":"the Encyclopedia Britannica","activity.ref.wikipediaEn":"all of English Wikipedia","block.title":"Active 5h block","block.remaining":"remaining","block.elapsed":"Time elapsed {pct}%","block.tokensSuffix":"tokens","block.spentSoFar":"Spent so far","block.burnPerMin":"Burn / min","block.projectedTotal":"Projected total","block.requests":"Requests","block.empty":"No active block","block.emptyDesc":"Send a message in Claude Code to start one.","block.disclaimer":"Wall-clock progress of the 5h window — not your plan quota.","chart.legend.input":"Input","chart.legend.output":"Output","chart.legend.cacheRead":"Cache read","chart.legend.cacheWrite":"Cache write","chart.tooltip.total":"Total","chart.tooltip.cost":"Cost","chart.tooltip.requests":"Requests","chart.tooltip.conversations":"Conversations","chart.empty":"No data in this range","chart.empty.short":"No data","usage.title":"Usage","usage.subtitle":"{count} turns in selected range","usage.col.calls":"Calls","usage.col.userMessage":"Prompt","usage.overview.label":"Overview","usage.overview.show":"Show overview (KPIs + trend)","usage.overview.hide":"Hide overview (KPIs + trend)","usage.turn.expand":"Expand","usage.turn.collapse":"Collapse","usage.turn.callsCount":"{count} calls","usage.turn.noPrompt":"(no user text)","usage.requests.desc":"One row per turn (user message + tool calls); click ▸ to expand","usage.kpi.totalTokens":"Total tokens","usage.kpi.totalCost":"Total cost","usage.kpi.cacheSaved":"Cache saved","usage.kpi.cacheHit":"Cache hit","usage.trend":"Trend","usage.trend.gran":"Granularity: {gran}","usage.requests.title":"Requests","usage.col.time":"Time","usage.col.duration":"Duration","usage.col.model":"Model","usage.col.project":"Project","usage.col.session":"Session","usage.col.input":"Input","usage.col.output":"Output","usage.col.cacheRead":"Cache R","usage.col.cacheWrite":"Cache W","usage.col.cost":"Cost","usage.col.tools":"Tools","usage.col.total":"Total","usage.columns.button":"Columns","usage.columns.title":"Visible columns","usage.columns.reset":"Reset","usage.breakdown.title":"Token breakdown","usage.breakdown.headerTokens":"Tokens","usage.breakdown.headerCost":"Cost","usage.breakdown.total":"Total","usage.breakdown.reasoning":"reasoning","usage.breakdown.reasoningNote":"incl. above","filter.modelLabel":"Model","filter.projectLabel":"Project","filter.modelAll":"Model: All","filter.projectAll":"Project: All","filter.modelSingle":"Model: {value}","filter.projectSingle":"Project: {value}","filter.modelMulti":"Model: {count}","filter.projectMulti":"Project: {count}","filter.clearAll":"Clear all","filter.noOptions":"No options","sessions.title":"Sessions","sessions.subtitle":"{count} sessions \xb7 sorted by most recent activity","sessions.col.session":"Session","sessions.col.project":"Project","sessions.col.models":"Model(s)","sessions.col.requests":"Requests","sessions.col.tokens":"Tokens","sessions.col.cost":"Cost","sessions.col.duration":"Duration","sessions.col.lastActivity":"Last activity","sessions.untitled":"Session {hash}","sessions.empty":"No sessions yet","session.kpi.requests":"Requests","session.kpi.totalTokens":"Total tokens","session.kpi.cost":"Cost","session.kpi.duration":"Duration","session.timeline.title":"Message timeline","session.timeline.desc":"In order; newest at the bottom","session.perMessage.title":"Per-message tokens","session.modelsInSession":"Models in this session","session.modelLine":"{requests} req \xb7 {tokens} tokens","session.token.in":"in","session.token.out":"out","session.token.cacheR":"cache r","session.token.cacheW":"cache w","projects.title":"Projects","projects.subtitle":"{count} projects \xb7 sorted by spend","projects.empty":"No projects yet","projects.stat.sessions":"Sessions","projects.stat.requests":"Requests","projects.stat.tokens":"Tokens","project.activity":"Activity (last 30 days)","project.sessions.title":"Sessions ({count})","models.title":"Models","models.subtitle":"{count} model(s) used in total","models.empty":"No model usage yet","models.share.cost":"Cost share","models.share.tokens":"Tokens share","models.share.cacheHit":"Cache hit","models.field.requests":"Requests","models.field.savedByCache":"Saved by cache","models.field.input1M":"Input / 1M","models.field.output1M":"Output / 1M","models.field.cacheRead1M":"Cache read / 1M","models.field.pctOfTotal":"{pct} of total spend \xb7 {tokens} tokens","models.eachTrend":"Combined trend (last 30 days)","settings.title":"Settings","settings.subtitle":"Data sources, pricing, and behavior","settings.dataSources.title":"Data sources","settings.dataSources.desc":"ccgauge scans these locations for JSONL files","settings.dataSources.active":"active","settings.dataSources.notPresent":"not present","settings.dataSources.envHint":"Override with {env1} or {env2} environment variables (the dashboard appends {appendix}).","settings.rescan":"Rescan now","settings.rescanning":"Rescanning…","settings.scanStats.title":"Scan stats","settings.scanStats.files":"Files scanned","settings.scanStats.records":"Records parsed","settings.scanStats.assistant":"Assistant records (deduped)","settings.scanStats.duration":"Scan duration","settings.pricing.title":"Pricing table","settings.pricing.desc":"USD per 1M tokens \xb7 built-in snapshot, fuzzy match for date-suffixed model names","settings.pricing.col.model":"Model","settings.pricing.col.input":"Input","settings.pricing.col.output":"Output","settings.pricing.col.write5m":"Cache write 5m","settings.pricing.col.write1h":"Cache write 1h","settings.pricing.col.read":"Cache read","settings.preferences.title":"Preferences","settings.preferences.language":"Language","settings.preferences.theme":"Theme","settings.theme.light":"Light","settings.theme.dark":"Dark","settings.theme.system":"System","settings.about.title":"About","settings.about.subtitle":"Version {version} \xb7 MIT licensed","settings.about.line1":"Fully local: data never leaves your machine; no telemetry, no network calls.","settings.about.line2":"Read-only: ccgauge only reads JSONL files, never writes back to ~/.claude.","settings.about.line3":'Cache: scan results are memoized for 5s; click "Rescan" to force a fresh read.',"settings.about.line4":"Stop with Ctrl+C in the terminal that started ccgauge.","settings.indexer.desc":"Background indexer keeps the cache fresh via file watchers.","settings.indexer.lastIndexedAt":"Last indexed","settings.indexer.indexDuration":"Last index time","settings.indexer.watchers":"Active watchers","settings.indexer.loadedFromDisk":"Loaded from disk","settings.indexer.status":"Status","settings.indexer.indexing":"indexing…","settings.indexer.idle":"idle","settings.indexer.recentErrors":"Recent indexer errors","common.yes":"yes","common.no":"no","lang.label":"Language","lang.en":"English","lang.zh":"中文","theme.label":"Theme"},zh:{"brand.tagline":"AI 编程 CLI 的本地用量看板","nav.overview":"概览","nav.usage":"用量","nav.sessions":"会话","nav.projects":"项目","nav.models":"模型","nav.settings":"设置","nav.localBadge":"本地","nav.source":"数据源","source.claude":"Claude","source.codex":"Codex","source.all":"全部","cost.footnote.codex":"按 OpenAI API 单价折算估值(订阅计划实际计费不同)","common.requests":"次请求","common.tokens":"tokens","common.cost":"花费","common.savedViaCache":"通过缓存节省 {amount}","common.savedTodayViaCache":"今日缓存节省 {amount}","common.live":"进行中","common.allModels":"全部","common.allProjects":"全部","common.unknown":"(未知)","common.session":"会话","common.sessions":"个会话","common.projects":"个项目","common.req":"请求","common.day":"天","common.days":"天","common.activity":"活跃","common.fallbackPrice":"使用兜底单价","common.thinking":"思考","common.lastActivity":"最近活跃","common.empty.title":"暂无用量数据","common.empty.desc":"打开 Claude Code 发送一条消息后刷新本页。","common.refresh":"刷新","common.search":"搜索…","common.searchPlaceholder":"搜索模型 / 项目 / 会话 / 工具…","common.exportCsv":"导出 CSV","common.rows":"{count} 行","common.prev":"‹ 上一页","common.next":"下一页 ›","common.first":"\xab","common.last":"\xbb","common.pageOf":"第 {page} / {total} 页","common.allSessions":"← 返回会话列表","common.allProjectsLink":"← 返回项目列表","common.noMatchingRows":"没有匹配的记录","range.label":"时间范围","range.today":"今天","range.7d":"7 天","range.30d":"30 天","range.90d":"90 天","range.all":"全部","gran.label":"粒度","gran.hour":"小时","gran.day":"天","gran.week":"周","gran.month":"月","overview.title":"概览","overview.subtitle":"{count} 次请求,覆盖 {files} 个文件 \xb7 扫描耗时 {ms}ms","overview.subtitle.empty":"已扫描 {dirs} 个目录。打开 Claude Code 发一条消息后刷新本页。","overview.empty.title":"暂无用量数据","overview.kpi.tokensToday":"今日 tokens","overview.kpi.costToday":"今日花费","overview.kpi.thisMonth":"本月累计","overview.kpi.cacheHit":"缓存命中率","overview.kpi.topModel":"主力模型","overview.kpi.activeSessions":"今日会话","overview.kpi.activeSessions.hint":"涉及 {count} 个项目","overview.kpi.thisMonth.hint":"{tokens} tokens \xb7 {req} 次请求","overview.kpi.tokensToday.hint":"{count} 次请求","overview.kpi.topModel.hint":"本月成本占比 {pct}","overview.kpi.cacheHit.hint":"今日缓存节省 {amount}","overview.delta.title":"相比昨日","overview.delta.firstTime":"首次","common.loading":"加载中…","common.error.title":"出错了","common.error.desc":"加载失败,请重试。","common.error.retry":"重试","overview.trend.title":"用量趋势","overview.trend.desc":"近 30 天 \xb7 {metric}","overview.trend.desc.tokens":"按 token 类型堆叠","overview.trend.desc.conversations":"每日对话次数","overview.trend.metric.tokens":"Token","overview.trend.metric.conversations":"对话次数","overview.trend.activeDays":"{n} 天有数据","overview.trend.activeDays.plural":"{n} 天有数据","overview.costByModel.title":"按模型成本分布","overview.costByModel.desc":"本月,按花费排序","activity.title":"活动统计","activity.subtitle":"总览数据 + 每天什么时段最忙","activity.sessions":"会话","activity.messages":"消息","activity.totalTokens":"总 tokens","activity.activeDays":"活跃天数","activity.currentStreak":"当前连续","activity.longestStreak":"最长连续","activity.streakCombinedLabel":"当前 / 最长连续","activity.streakCombinedValue":"{current} / {longest} 天","activity.peakHour":"高峰小时","activity.favoriteModel":"最常用模型","activity.streakValue":"{n} 天","activity.dow.0":"周日","activity.dow.1":"周一","activity.dow.2":"周二","activity.dow.3":"周三","activity.dow.4":"周四","activity.dow.5":"周五","activity.dow.6":"周六","activity.heatmap.tooltip":"周{dow} \xb7 {hour} \xb7 {count} 条消息","activity.heatmap.messages":"消息数","activity.heatmap.tokens":"Tokens","activity.heatmap.shareLabel":"占总数","activity.heatmap.intensityLabel":"占峰值","activity.heatmap.empty":"无活动","activity.comparison":"你用掉的 token 大约是 {ref} 的 {multiplier}\xd7。","activity.ref.haiku":"一首俳句","activity.ref.tweet":"一条推文","activity.ref.littlePrince":"《小王子》","activity.ref.gatsby":"《了不起的盖茨比》","activity.ref.hobbit":"《霍比特人》","activity.ref.lotrTrilogy":"《魔戒》三部曲","activity.ref.warAndPeace":"《战争与和平》","activity.ref.harryPotterAll":"《哈利\xb7波特》全 7 册","activity.ref.encyclopediaBritannica":"《大英百科全书》","activity.ref.wikipediaEn":"英文维基百科全部","block.title":"当前 5h block","block.remaining":"剩余","block.elapsed":"时间进度 {pct}%","block.tokensSuffix":"tokens","block.spentSoFar":"已花费","block.burnPerMin":"每分钟消耗","block.projectedTotal":"预计总花费","block.requests":"请求数","block.empty":"当前无活跃 block","block.emptyDesc":"在 Claude Code 中发送消息会启动一个新 block。","block.disclaimer":"仅是 5h 窗口的钟表进度,不是你的套餐配额。","chart.legend.input":"输入","chart.legend.output":"输出","chart.legend.cacheRead":"缓存读取","chart.legend.cacheWrite":"缓存写入","chart.tooltip.total":"合计","chart.tooltip.cost":"花费","chart.tooltip.requests":"请求数","chart.tooltip.conversations":"对话次数","chart.empty":"区间内无数据","chart.empty.short":"暂无数据","usage.title":"用量明细","usage.subtitle":"当前筛选范围内 {count} 轮对话","usage.kpi.totalTokens":"总 tokens","usage.kpi.totalCost":"总花费","usage.kpi.cacheSaved":"缓存节省","usage.kpi.cacheHit":"缓存命中","usage.trend":"趋势","usage.trend.gran":"粒度:{gran}","usage.requests.title":"对话轮次","usage.requests.desc":"每轮对话一行(用户消息 + 工具调用),点击 ▸ 展开明细","usage.col.calls":"调用","usage.col.userMessage":"提示","usage.overview.label":"概览","usage.overview.show":"显示概览(KPI + 趋势)","usage.overview.hide":"隐藏概览(KPI + 趋势)","usage.turn.expand":"展开","usage.turn.collapse":"收起","usage.turn.callsCount":"{count} 次调用","usage.turn.noPrompt":"(无用户文本)","usage.col.time":"时间","usage.col.duration":"耗时","usage.col.model":"模型","usage.col.project":"项目","usage.col.session":"会话","usage.col.input":"输入","usage.col.output":"输出","usage.col.cacheRead":"缓存读","usage.col.cacheWrite":"缓存写","usage.col.cost":"花费","usage.col.tools":"工具","usage.col.total":"总量","usage.columns.button":"列","usage.columns.title":"显示列","usage.columns.reset":"重置","usage.breakdown.title":"总量明细","usage.breakdown.headerTokens":"Token","usage.breakdown.headerCost":"花费","usage.breakdown.total":"合计","usage.breakdown.reasoning":"其中推理","usage.breakdown.reasoningNote":"已含在 output","filter.modelLabel":"模型","filter.projectLabel":"项目","filter.modelAll":"模型:全部","filter.projectAll":"项目:全部","filter.modelSingle":"模型:{value}","filter.projectSingle":"项目:{value}","filter.modelMulti":"模型:{count} 个","filter.projectMulti":"项目:{count} 个","filter.clearAll":"清除筛选","filter.noOptions":"没有可选项","sessions.title":"会话","sessions.subtitle":"共 {count} 个会话 \xb7 按最近活跃排序","sessions.col.session":"会话","sessions.col.project":"项目","sessions.col.models":"模型","sessions.col.requests":"请求数","sessions.col.tokens":"Tokens","sessions.col.cost":"花费","sessions.col.duration":"时长","sessions.col.lastActivity":"最近活跃","sessions.untitled":"会话 {hash}","sessions.empty":"暂无会话","session.kpi.requests":"请求数","session.kpi.totalTokens":"总 tokens","session.kpi.cost":"花费","session.kpi.duration":"时长","session.timeline.title":"消息时间线","session.timeline.desc":"按时间正序,最新的在最下方","session.perMessage.title":"逐条消息 tokens","session.modelsInSession":"本会话使用的模型","session.modelLine":"{requests} 次 \xb7 {tokens} tokens","session.token.in":"输入","session.token.out":"输出","session.token.cacheR":"缓存读","session.token.cacheW":"缓存写","projects.title":"项目","projects.subtitle":"共 {count} 个项目 \xb7 按花费排序","projects.empty":"暂无项目","projects.stat.sessions":"会话","projects.stat.requests":"请求","projects.stat.tokens":"Tokens","project.activity":"活跃情况(近 30 天)","project.sessions.title":"会话({count})","models.title":"模型","models.subtitle":"共使用过 {count} 个模型","models.empty":"暂无模型用量","models.share.cost":"成本占比","models.share.tokens":"Tokens 占比","models.share.cacheHit":"缓存命中","models.field.requests":"请求数","models.field.savedByCache":"缓存节省","models.field.input1M":"输入 / 1M","models.field.output1M":"输出 / 1M","models.field.cacheRead1M":"缓存读 / 1M","models.field.pctOfTotal":"占总花费 {pct} \xb7 {tokens} tokens","models.eachTrend":"整体趋势(近 30 天)","settings.title":"设置","settings.subtitle":"数据源、价格表与行为偏好","settings.dataSources.title":"数据源","settings.dataSources.desc":"ccgauge 会扫描以下路径的 JSONL 文件","settings.dataSources.active":"已启用","settings.dataSources.notPresent":"不存在","settings.dataSources.envHint":"可以通过环境变量 {env1} 或 {env2} 自定义路径(看板会自动追加 {appendix})。","settings.rescan":"立即重新扫描","settings.rescanning":"扫描中…","settings.scanStats.title":"扫描统计","settings.scanStats.files":"扫描文件数","settings.scanStats.records":"解析记录数","settings.scanStats.assistant":"去重后 assistant 记录","settings.scanStats.duration":"扫描耗时","settings.pricing.title":"价格表","settings.pricing.desc":"美元 / 1M tokens \xb7 内置快照,对带日期后缀的模型名做 fuzzy 匹配","settings.pricing.col.model":"模型","settings.pricing.col.input":"输入","settings.pricing.col.output":"输出","settings.pricing.col.write5m":"缓存写入 5 分钟","settings.pricing.col.write1h":"缓存写入 1 小时","settings.pricing.col.read":"缓存读取","settings.preferences.title":"偏好设置","settings.preferences.language":"语言","settings.preferences.theme":"主题","settings.theme.light":"亮色","settings.theme.dark":"暗色","settings.theme.system":"跟随系统","settings.about.title":"关于","settings.about.subtitle":"版本 {version} \xb7 MIT 协议","settings.about.line1":"完全本地:数据从不离开你的机器,没有任何遥测、没有任何网络调用。","settings.about.line2":"只读:ccgauge 只读取 JSONL,绝不会写回 ~/.claude。","settings.about.line3":'缓存:扫描结果会缓存 5 秒;点击"重新扫描"可强制刷新。',"settings.about.line4":"在启动 ccgauge 的终端按 Ctrl+C 即可停止。","settings.indexer.desc":"后台 indexer 通过文件监听增量维护缓存。","settings.indexer.lastIndexedAt":"最近索引时间","settings.indexer.indexDuration":"最近索引耗时","settings.indexer.watchers":"活跃 watcher 数","settings.indexer.loadedFromDisk":"从磁盘恢复","settings.indexer.status":"状态","settings.indexer.indexing":"索引中…","settings.indexer.idle":"空闲","settings.indexer.recentErrors":"最近的索引错误","common.yes":"是","common.no":"否","lang.label":"语言","lang.en":"English","lang.zh":"中文","theme.label":"主题"}};function a(e,t,s){var o;let a=null==(o=i[e])?void 0:o[t];if(void 0===a&&(a=i.en[t]),void 0===a&&(a=t),s)for(let[e,t]of Object.entries(s))a=a.replace(RegExp("\\{".concat(e,"\\}"),"g"),String(t));return a}}}]);