ccgauge 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +41 -41
  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/prerender-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/api/blocks/route.js +1 -1
  10. package/.next/standalone/.next/server/app/api/blocks/route.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/api/blocks/route_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/api/export/usage/route.js +1 -1
  13. package/.next/standalone/.next/server/app/api/export/usage/route.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/api/export/usage/route_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/api/pricing/route.js +1 -1
  16. package/.next/standalone/.next/server/app/api/pricing/route_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/api/projects/route.js +1 -1
  18. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/api/scan/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/scan/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/scan/route_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/api/sessions/route.js +1 -1
  24. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/api/usage/route.js +1 -1
  27. package/.next/standalone/.next/server/app/api/usage/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/usage/route_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/models/page.js +2 -2
  30. package/.next/standalone/.next/server/app/models/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/models/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/page.js +2 -2
  33. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/projects/[id]/page.js +2 -2
  36. package/.next/standalone/.next/server/app/projects/[id]/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  39. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/sessions/[id]/page.js +2 -2
  42. package/.next/standalone/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/sessions/page.js +2 -2
  45. package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/settings/page.js +2 -2
  48. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/usage/page.js +3 -2
  51. package/.next/standalone/.next/server/app/usage/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app-paths-manifest.json +9 -9
  54. package/.next/standalone/.next/server/chunks/155.js +1 -0
  55. package/.next/standalone/.next/server/chunks/567.js +28 -0
  56. package/.next/standalone/.next/server/chunks/716.js +1 -1
  57. package/.next/standalone/.next/server/chunks/971.js +1 -0
  58. package/.next/standalone/.next/server/functions-config-manifest.json +3 -3
  59. package/.next/standalone/.next/server/pages/500.html +1 -1
  60. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  61. package/.next/standalone/.next/static/chunks/148-0a1e1b0207b89e3f.js +1 -0
  62. package/.next/standalone/.next/static/chunks/app/layout-2512ccdfb13aeb17.js +1 -0
  63. package/.next/standalone/.next/static/chunks/app/models/page-dcd29049a7b0641c.js +1 -0
  64. package/.next/standalone/.next/static/chunks/app/page-19d3e77d4aa35a63.js +1 -0
  65. package/.next/standalone/.next/static/chunks/app/projects/[id]/page-d6725ed17b04a743.js +1 -0
  66. package/.next/standalone/.next/static/chunks/app/sessions/[id]/page-d6725ed17b04a743.js +1 -0
  67. package/.next/standalone/.next/static/chunks/app/settings/page-cfeb089549c94f88.js +1 -0
  68. package/.next/standalone/.next/static/chunks/app/usage/page-18fd820a3111bd5b.js +1 -0
  69. package/.next/standalone/.next/static/css/406e067663b8b429.css +3 -0
  70. package/.next/standalone/package.json +1 -1
  71. package/.next/standalone/public/claude-logo.webp +0 -0
  72. package/.next/standalone/public/codex-logo.png +0 -0
  73. package/CHANGELOG.md +194 -0
  74. package/README.zh-CN.md +11 -0
  75. package/bin/cli.mjs +16 -4
  76. package/dist/mcp/server.mjs +92 -9
  77. package/dist/report/index.mjs +92 -9
  78. package/package.json +1 -1
  79. package/.next/standalone/.next/server/chunks/426.js +0 -28
  80. package/.next/standalone/.next/server/chunks/520.js +0 -1
  81. package/.next/standalone/.next/server/chunks/775.js +0 -1
  82. package/.next/standalone/.next/static/chunks/148-557ee562aff993b1.js +0 -1
  83. package/.next/standalone/.next/static/chunks/app/layout-6c973d790f015707.js +0 -1
  84. package/.next/standalone/.next/static/chunks/app/models/page-dff43b9050382020.js +0 -1
  85. package/.next/standalone/.next/static/chunks/app/page-6d87d7a8aa752100.js +0 -1
  86. package/.next/standalone/.next/static/chunks/app/projects/[id]/page-3f812f0e20137f2b.js +0 -1
  87. package/.next/standalone/.next/static/chunks/app/sessions/[id]/page-3f812f0e20137f2b.js +0 -1
  88. package/.next/standalone/.next/static/chunks/app/settings/page-d1af886a5c22af9b.js +0 -1
  89. package/.next/standalone/.next/static/chunks/app/usage/page-26297e0641d51da8.js +0 -1
  90. package/.next/standalone/.next/static/css/b07523b7c353538d.css +0 -3
  91. /package/.next/standalone/.next/static/{ZPycmg0NLiIflO5NXMT75 → 4YjiQrRI-CsVEPC1UOUEJ}/_buildManifest.js +0 -0
  92. /package/.next/standalone/.next/static/{ZPycmg0NLiIflO5NXMT75 → 4YjiQrRI-CsVEPC1UOUEJ}/_ssgManifest.js +0 -0
package/bin/cli.mjs CHANGED
@@ -416,19 +416,26 @@ or run the full build with
416
416
  model: opts.model ? String(opts.model) : undefined,
417
417
  project: opts.project ? String(opts.project) : undefined,
418
418
  };
419
+ let payload;
419
420
  try {
420
421
  const mod = await import(pathToFileURL(bundle).href);
421
422
  const out = await mod.runReport(reportOpts);
422
- process.stdout.write(out);
423
- if (!out.endsWith('\n')) process.stdout.write('\n');
423
+ payload = out.endsWith('\n') ? out : out + '\n';
424
424
  } catch (err) {
425
425
  console.error(`[ccgauge] report failed: ${(err && err.message) || err}`);
426
426
  process.exit(1);
427
427
  }
428
428
  // The indexer keeps fs watchers alive, which would block process exit.
429
429
  // For a one-shot report we explicitly exit once stdout is drained.
430
- process.stdout.once?.('drain', () => process.exit(0));
431
- if (process.stdout.writableLength === 0) process.exit(0);
430
+ // Use the write() return value rather than chaining a `drain` listener
431
+ // after the fact: if drain fires between the write and the listener
432
+ // attach, we'd hang forever waiting for an event that already happened.
433
+ const flushed = process.stdout.write(payload);
434
+ if (flushed) {
435
+ process.exit(0);
436
+ } else {
437
+ process.stdout.once('drain', () => process.exit(0));
438
+ }
432
439
  }
433
440
 
434
441
  async function startMcp() {
@@ -469,6 +476,11 @@ async function resolvePort(opts) {
469
476
  if (!Number.isInteger(preferred) || preferred <= 0 || preferred > 65535) {
470
477
  throw new Error(`invalid port: ${opts.port}`);
471
478
  }
479
+ // Try the preferred port first, then up to 19 ports above it (capped at
480
+ // 65535), then 0 (let the OS pick an ephemeral port). For unusually high
481
+ // preferred values (e.g. 65530) the +N candidates are clamped by the
482
+ // filter, leaving just the preferred + ephemeral fallback — that's still
483
+ // correct, just narrower.
472
484
  const candidates = opts.strictPort
473
485
  ? preferred
474
486
  : [preferred, ...Array.from({ length: 19 }, (_, i) => preferred + i + 1).filter((p) => p <= 65535), 0];
@@ -21296,7 +21296,8 @@ function parseAssistant(raw, file) {
21296
21296
  toolNames,
21297
21297
  hasThinking,
21298
21298
  textPreview,
21299
- filePath: file
21299
+ filePath: file,
21300
+ isSidechain: raw.isSidechain === true ? true : void 0
21300
21301
  };
21301
21302
  }
21302
21303
  function parseUser(raw, file) {
@@ -21316,7 +21317,8 @@ function parseUser(raw, file) {
21316
21317
  }
21317
21318
  }
21318
21319
  }
21319
- const isSynthetic = !!textPreview && isSyntheticUserText(textPreview);
21320
+ const isSidechain = raw.isSidechain === true;
21321
+ const isSynthetic = isSidechain || !!textPreview && isSyntheticUserText(textPreview);
21320
21322
  return {
21321
21323
  type: "user",
21322
21324
  source: "claude",
@@ -21327,6 +21329,7 @@ function parseUser(raw, file) {
21327
21329
  cwd: raw.cwd ?? "",
21328
21330
  textPreview,
21329
21331
  isSynthetic,
21332
+ isSidechain: isSidechain ? true : void 0,
21330
21333
  filePath: file
21331
21334
  };
21332
21335
  }
@@ -21537,13 +21540,17 @@ var claudeAdapter = {
21537
21540
  displayName: { en: "Claude", zh: "Claude" },
21538
21541
  shortLabel: "C",
21539
21542
  color: { fg: "#b45309", bg: "#fef3c7" },
21540
- // v2: blank textPreview on synthetic user messages (skill metadata,
21541
- // <system-reminder> blocks) so they don't split a single conversation
21542
- // into multiple "turns" in the usage table.
21543
- // v3: keep textPreview but add an `isSynthetic` flag child rows in the
21544
- // usage table show skill metadata as their per-call "prompt"; only
21545
- // turn-boundary detection skips synthetic users.
21546
- parserVersion: "claude-v3-synthetic-flag",
21543
+ logoSrc: "/claude-logo.webp",
21544
+ // v1 v3 (no v2 ever shipped on npm): user records now carry an
21545
+ // `isSynthetic` flag so skill metadata + <system-reminder> blocks can
21546
+ // still be displayed as the per-call "prompt" on child rows, but are
21547
+ // skipped as turn-boundary anchors so they don't wrongly split a single
21548
+ // conversation into multiple turns.
21549
+ // v4: extend `isSynthetic` to sub-agent first-user records (every record
21550
+ // in a `subagents/agent-*.jsonl` file has `isSidechain: true`); also
21551
+ // propagate `isSidechain` to all records so the indexer's post-link pass
21552
+ // can stitch sub-agent files into the parent session's turn graph.
21553
+ parserVersion: "claude-v4-sidechain-merge",
21547
21554
  capabilities: {
21548
21555
  hasCacheCreation: true,
21549
21556
  hasReasoningTokens: false,
@@ -21981,6 +21988,7 @@ var codexAdapter = {
21981
21988
  displayName: { en: "Codex", zh: "Codex" },
21982
21989
  shortLabel: "X",
21983
21990
  color: { fg: "#047857", bg: "#d1fae5" },
21991
+ logoSrc: "/codex-logo.png",
21984
21992
  // v2: switched from last_token_usage to total_token_usage delta (fixed
21985
21993
  // ~26% over-counting from duplicate/refresh token_count events).
21986
21994
  // v3: split reasoning_tokens out as a display-only breakdown alongside
@@ -22075,6 +22083,74 @@ async function savePersistedIndex(payload, name = DEFAULT_INDEX_NAME) {
22075
22083
  await fs3.rename(tmp, filePath);
22076
22084
  }
22077
22085
 
22086
+ // lib/data-loader/link-sidechain.ts
22087
+ var SUBAGENT_FILE_PATTERN = /\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/subagents\/agent-[^/]+\.jsonl$/i;
22088
+ function extractParentSessionFromSubagentPath(filePath) {
22089
+ const m = SUBAGENT_FILE_PATTERN.exec(filePath);
22090
+ return m ? m[1] : null;
22091
+ }
22092
+ function linkSidechainParents({
22093
+ assistantRecords,
22094
+ userRecords,
22095
+ parentMap
22096
+ }) {
22097
+ const parentAssistantsBySession = /* @__PURE__ */ new Map();
22098
+ for (const a of assistantRecords) {
22099
+ if (a.isSidechain) continue;
22100
+ if (!a.sessionId) continue;
22101
+ let list = parentAssistantsBySession.get(a.sessionId);
22102
+ if (!list) {
22103
+ list = [];
22104
+ parentAssistantsBySession.set(a.sessionId, list);
22105
+ }
22106
+ list.push(a);
22107
+ }
22108
+ for (const list of parentAssistantsBySession.values()) {
22109
+ list.sort((x, y) => x.timestamp < y.timestamp ? -1 : x.timestamp > y.timestamp ? 1 : 0);
22110
+ }
22111
+ const firstSidechainUserByFile = /* @__PURE__ */ new Map();
22112
+ for (const u of userRecords) {
22113
+ if (!u.isSidechain) continue;
22114
+ const existing = firstSidechainUserByFile.get(u.filePath);
22115
+ if (!existing || u.timestamp < existing.timestamp) {
22116
+ firstSidechainUserByFile.set(u.filePath, u);
22117
+ }
22118
+ }
22119
+ const stats = {
22120
+ subagentFiles: 0,
22121
+ relinked: 0,
22122
+ orphans: 0,
22123
+ alreadyLinked: 0
22124
+ };
22125
+ for (const [filePath, firstUser] of firstSidechainUserByFile) {
22126
+ const parentSessionId = extractParentSessionFromSubagentPath(filePath);
22127
+ if (!parentSessionId) continue;
22128
+ stats.subagentFiles += 1;
22129
+ const existingParent = parentMap[firstUser.uuid];
22130
+ if (existingParent !== null && existingParent !== void 0) {
22131
+ stats.alreadyLinked += 1;
22132
+ continue;
22133
+ }
22134
+ const parentAssistants = parentAssistantsBySession.get(parentSessionId);
22135
+ if (!parentAssistants || parentAssistants.length === 0) {
22136
+ stats.orphans += 1;
22137
+ continue;
22138
+ }
22139
+ const t0 = firstUser.timestamp;
22140
+ let anchor;
22141
+ for (let i = parentAssistants.length - 1; i >= 0; i -= 1) {
22142
+ if (parentAssistants[i].timestamp <= t0) {
22143
+ anchor = parentAssistants[i];
22144
+ break;
22145
+ }
22146
+ }
22147
+ if (!anchor) anchor = parentAssistants[0];
22148
+ parentMap[firstUser.uuid] = anchor.uuid;
22149
+ stats.relinked += 1;
22150
+ }
22151
+ return stats;
22152
+ }
22153
+
22078
22154
  // lib/data-loader/indexer.ts
22079
22155
  var RECONCILE_DEBOUNCE_MS = 200;
22080
22156
  var SNAPSHOT_REBUILD_DEBOUNCE_MS = 100;
@@ -22391,6 +22467,11 @@ var FileIndexer = class {
22391
22467
  const dedupedUsers = dedupUserRecords(user).sort(
22392
22468
  (a, b) => a.timestamp.localeCompare(b.timestamp)
22393
22469
  );
22470
+ linkSidechainParents({
22471
+ assistantRecords: dedupedAssistants,
22472
+ userRecords: dedupedUsers,
22473
+ parentMap
22474
+ });
22394
22475
  for (const rec of dedupedAssistants) bySource[rec.source].assistantRecords += 1;
22395
22476
  const stats = {
22396
22477
  filesScanned: this.files.size,
@@ -22802,6 +22883,7 @@ function aggregateByProject(records, opts) {
22802
22883
  let s = map.get(cwd);
22803
22884
  if (!s) {
22804
22885
  s = {
22886
+ source: opts.source,
22805
22887
  cwd,
22806
22888
  projectName: projectNameFromCwd(cwd),
22807
22889
  sessions: 0,
@@ -22849,6 +22931,7 @@ function aggregateBySession(records, userRecords, opts) {
22849
22931
  if (!s) {
22850
22932
  s = {
22851
22933
  sessionId: sid,
22934
+ source: rec.source,
22852
22935
  cwd: rec.cwd,
22853
22936
  projectName: projectNameFromCwd(rec.cwd),
22854
22937
  startTime: rec.timestamp,
@@ -79,7 +79,8 @@ function parseAssistant(raw, file) {
79
79
  toolNames,
80
80
  hasThinking,
81
81
  textPreview,
82
- filePath: file
82
+ filePath: file,
83
+ isSidechain: raw.isSidechain === true ? true : void 0
83
84
  };
84
85
  }
85
86
  function parseUser(raw, file) {
@@ -99,7 +100,8 @@ function parseUser(raw, file) {
99
100
  }
100
101
  }
101
102
  }
102
- const isSynthetic = !!textPreview && isSyntheticUserText(textPreview);
103
+ const isSidechain = raw.isSidechain === true;
104
+ const isSynthetic = isSidechain || !!textPreview && isSyntheticUserText(textPreview);
103
105
  return {
104
106
  type: "user",
105
107
  source: "claude",
@@ -110,6 +112,7 @@ function parseUser(raw, file) {
110
112
  cwd: raw.cwd ?? "",
111
113
  textPreview,
112
114
  isSynthetic,
115
+ isSidechain: isSidechain ? true : void 0,
113
116
  filePath: file
114
117
  };
115
118
  }
@@ -320,13 +323,17 @@ var claudeAdapter = {
320
323
  displayName: { en: "Claude", zh: "Claude" },
321
324
  shortLabel: "C",
322
325
  color: { fg: "#b45309", bg: "#fef3c7" },
323
- // v2: blank textPreview on synthetic user messages (skill metadata,
324
- // <system-reminder> blocks) so they don't split a single conversation
325
- // into multiple "turns" in the usage table.
326
- // v3: keep textPreview but add an `isSynthetic` flag child rows in the
327
- // usage table show skill metadata as their per-call "prompt"; only
328
- // turn-boundary detection skips synthetic users.
329
- parserVersion: "claude-v3-synthetic-flag",
326
+ logoSrc: "/claude-logo.webp",
327
+ // v1 v3 (no v2 ever shipped on npm): user records now carry an
328
+ // `isSynthetic` flag so skill metadata + <system-reminder> blocks can
329
+ // still be displayed as the per-call "prompt" on child rows, but are
330
+ // skipped as turn-boundary anchors so they don't wrongly split a single
331
+ // conversation into multiple turns.
332
+ // v4: extend `isSynthetic` to sub-agent first-user records (every record
333
+ // in a `subagents/agent-*.jsonl` file has `isSidechain: true`); also
334
+ // propagate `isSidechain` to all records so the indexer's post-link pass
335
+ // can stitch sub-agent files into the parent session's turn graph.
336
+ parserVersion: "claude-v4-sidechain-merge",
330
337
  capabilities: {
331
338
  hasCacheCreation: true,
332
339
  hasReasoningTokens: false,
@@ -764,6 +771,7 @@ var codexAdapter = {
764
771
  displayName: { en: "Codex", zh: "Codex" },
765
772
  shortLabel: "X",
766
773
  color: { fg: "#047857", bg: "#d1fae5" },
774
+ logoSrc: "/codex-logo.png",
767
775
  // v2: switched from last_token_usage to total_token_usage delta (fixed
768
776
  // ~26% over-counting from duplicate/refresh token_count events).
769
777
  // v3: split reasoning_tokens out as a display-only breakdown alongside
@@ -870,6 +878,74 @@ async function savePersistedIndex(payload, name = DEFAULT_INDEX_NAME) {
870
878
  await fs2.rename(tmp, filePath);
871
879
  }
872
880
 
881
+ // lib/data-loader/link-sidechain.ts
882
+ var SUBAGENT_FILE_PATTERN = /\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/subagents\/agent-[^/]+\.jsonl$/i;
883
+ function extractParentSessionFromSubagentPath(filePath) {
884
+ const m = SUBAGENT_FILE_PATTERN.exec(filePath);
885
+ return m ? m[1] : null;
886
+ }
887
+ function linkSidechainParents({
888
+ assistantRecords,
889
+ userRecords,
890
+ parentMap
891
+ }) {
892
+ const parentAssistantsBySession = /* @__PURE__ */ new Map();
893
+ for (const a of assistantRecords) {
894
+ if (a.isSidechain) continue;
895
+ if (!a.sessionId) continue;
896
+ let list = parentAssistantsBySession.get(a.sessionId);
897
+ if (!list) {
898
+ list = [];
899
+ parentAssistantsBySession.set(a.sessionId, list);
900
+ }
901
+ list.push(a);
902
+ }
903
+ for (const list of parentAssistantsBySession.values()) {
904
+ list.sort((x, y) => x.timestamp < y.timestamp ? -1 : x.timestamp > y.timestamp ? 1 : 0);
905
+ }
906
+ const firstSidechainUserByFile = /* @__PURE__ */ new Map();
907
+ for (const u of userRecords) {
908
+ if (!u.isSidechain) continue;
909
+ const existing = firstSidechainUserByFile.get(u.filePath);
910
+ if (!existing || u.timestamp < existing.timestamp) {
911
+ firstSidechainUserByFile.set(u.filePath, u);
912
+ }
913
+ }
914
+ const stats = {
915
+ subagentFiles: 0,
916
+ relinked: 0,
917
+ orphans: 0,
918
+ alreadyLinked: 0
919
+ };
920
+ for (const [filePath, firstUser] of firstSidechainUserByFile) {
921
+ const parentSessionId = extractParentSessionFromSubagentPath(filePath);
922
+ if (!parentSessionId) continue;
923
+ stats.subagentFiles += 1;
924
+ const existingParent = parentMap[firstUser.uuid];
925
+ if (existingParent !== null && existingParent !== void 0) {
926
+ stats.alreadyLinked += 1;
927
+ continue;
928
+ }
929
+ const parentAssistants = parentAssistantsBySession.get(parentSessionId);
930
+ if (!parentAssistants || parentAssistants.length === 0) {
931
+ stats.orphans += 1;
932
+ continue;
933
+ }
934
+ const t0 = firstUser.timestamp;
935
+ let anchor;
936
+ for (let i = parentAssistants.length - 1; i >= 0; i -= 1) {
937
+ if (parentAssistants[i].timestamp <= t0) {
938
+ anchor = parentAssistants[i];
939
+ break;
940
+ }
941
+ }
942
+ if (!anchor) anchor = parentAssistants[0];
943
+ parentMap[firstUser.uuid] = anchor.uuid;
944
+ stats.relinked += 1;
945
+ }
946
+ return stats;
947
+ }
948
+
873
949
  // lib/data-loader/indexer.ts
874
950
  var RECONCILE_DEBOUNCE_MS = 200;
875
951
  var SNAPSHOT_REBUILD_DEBOUNCE_MS = 100;
@@ -1186,6 +1262,11 @@ var FileIndexer = class {
1186
1262
  const dedupedUsers = dedupUserRecords(user).sort(
1187
1263
  (a, b) => a.timestamp.localeCompare(b.timestamp)
1188
1264
  );
1265
+ linkSidechainParents({
1266
+ assistantRecords: dedupedAssistants,
1267
+ userRecords: dedupedUsers,
1268
+ parentMap
1269
+ });
1189
1270
  for (const rec of dedupedAssistants) bySource[rec.source].assistantRecords += 1;
1190
1271
  const stats = {
1191
1272
  filesScanned: this.files.size,
@@ -1557,6 +1638,7 @@ function aggregateByProject(records, opts) {
1557
1638
  let s = map.get(cwd);
1558
1639
  if (!s) {
1559
1640
  s = {
1641
+ source: opts.source,
1560
1642
  cwd,
1561
1643
  projectName: projectNameFromCwd(cwd),
1562
1644
  sessions: 0,
@@ -1604,6 +1686,7 @@ function aggregateBySession(records, userRecords, opts) {
1604
1686
  if (!s) {
1605
1687
  s = {
1606
1688
  sessionId: sid,
1689
+ source: rec.source,
1607
1690
  cwd: rec.cwd,
1608
1691
  projectName: projectNameFromCwd(rec.cwd),
1609
1692
  startTime: rec.timestamp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgauge",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Local web dashboard for Claude Code and OpenAI Codex CLI token usage and cost",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,28 +0,0 @@
1
- exports.id=426,exports.ids=[426],exports.modules={11734:(a,b,c)=>{"use strict";c.d(b,{N:()=>h,D:()=>i});var d=c(21124),e=c(38301);let f=(0,e.createContext)({theme:"dark",resolved:"dark",setTheme:()=>{}});function g(a){return"light"===a?"light":"dark"}function h({initialTheme:a,children:b}){let[c,h]=(0,e.useState)(a),[i,j]=(0,e.useState)(()=>"system"===a?"dark":a),k=(0,e.useCallback)(a=>{try{localStorage.setItem("ccgauge.theme",a)}catch{}document.cookie=`ccgauge_theme=${a}; path=/; max-age=31536000; SameSite=Lax`,h(a),j(g(a)),function(a){if("undefined"==typeof document)return;let b=document.documentElement,c=g(a);b.classList.remove("theme-light","theme-dark"),b.classList.add("light"===c?"theme-light":"theme-dark"),b.setAttribute("data-theme",c)}(a)},[]);return(0,d.jsx)(f.Provider,{value:{theme:c,resolved:i,setTheme:k},children:b})}function i(){return(0,e.useContext)(f)}},13132:(a,b,c)=>{Promise.resolve().then(c.bind(c,88267)),Promise.resolve().then(c.bind(c,17290))},15514:(a,b,c)=>{"use strict";c.d(b,{BC:()=>n,P6:()=>p,PJ:()=>m,R8:()=>k,az:()=>i,cn:()=>f,jh:()=>h,l7:()=>j,r6:()=>l});var d=c(43249),e=c(58829);function f(...a){return(0,e.QP)((0,d.$)(a))}function g(a,b){return new Intl.NumberFormat("en-US",{maximumFractionDigits:b?.maxFrac??0}).format(a)}function h(a,b="en"){return Number.isFinite(a)?"zh"===b?a>=1e8?(a/1e8).toFixed(2)+"亿":a>=1e4?(a/1e4).toFixed(1)+"万":g(a):a>=1e9?(a/1e9).toFixed(2)+"B":a>=1e6?(a/1e6).toFixed(2)+"M":a>=1e3?(a/1e3).toFixed(1)+"K":g(a):"0"}function i(a,b){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:b?.minFrac??2,maximumFractionDigits:b?.maxFrac??2}).format(a)}function j(a){return 0===a?"$0":a<.01?new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:4,maximumFractionDigits:6}).format(a):i(a)}function k(a,b=1){return Number.isFinite(a)?`${(100*a).toFixed(b)}%`:"0%"}function l(a){let b="string"==typeof a||"number"==typeof a?new Date(a):a;if(Number.isNaN(b.getTime()))return"";let c=b.getFullYear(),d=String(b.getMonth()+1).padStart(2,"0"),e=String(b.getDate()).padStart(2,"0"),f=String(b.getHours()).padStart(2,"0"),g=String(b.getMinutes()).padStart(2,"0"),h=String(b.getSeconds()).padStart(2,"0");return`${c}-${d}-${e} ${f}:${g}:${h}`}function m(a){if(!a)return"(unknown)";let b=a.replace(/[/\\]+$/,"").split(/[/\\]+/);return b[b.length-1]||a}function n(a,b=8){return a?a.replace(/-/g,"").slice(0,b):""}let o={mini:"Mini",nano:"Nano",pro:"Pro",turbo:"Turbo",preview:"Preview"};function p(a){if(!a)return"(unknown)";let b=a.replace(/^(vertex_ai|bedrock|anthropic|openai)\//,""),c=b.toLowerCase();if(c.startsWith("gpt-")||/^o\d/.test(c))return c.startsWith("gpt-")?"GPT-"+b.slice(4).split("-").map(a=>o[a.toLowerCase()]??a).join(" "):b.toUpperCase();let d=b.replace(/-(\d{8})$/,""),e=(d=d.replace(/^claude-/,"")).split("-");if(e.length>=2){let a=e[0],b=e.slice(1).join(".");return q(a)+" "+b}return q(d.replace(/-/g," "))}function q(a){return a.replace(/\b\w/g,a=>a.toUpperCase())}},15519:(a,b,c)=>{"use strict";c.d(b,{CY:()=>i,s9:()=>j,kj:()=>k});var d=c(21124),e=c(38301),f=c(42378),g=c(78642);let h=(0,e.createContext)({locale:g.Xn,setLocale:()=>{},t:a=>a});function i({initialLocale:a,children:b}){let c=(0,f.useRouter)(),i=(0,e.useCallback)(a=>{try{localStorage.setItem("ccgauge.locale",a)}catch{}document.cookie=`ccgauge_locale=${a}; path=/; max-age=31536000; SameSite=Lax`,c.refresh()},[c]),j=(0,e.useCallback)((b,c)=>(0,g.nA)(a,b,c),[a]),k=(0,e.useMemo)(()=>({locale:a,setLocale:i,t:j}),[a,i,j]);return(0,d.jsx)(h.Provider,{value:k,children:b})}function j(){return(0,e.useContext)(h)}function k(){return(0,e.useContext)(h).t}},16124:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,81170,23)),Promise.resolve().then(c.t.bind(c,23597,23)),Promise.resolve().then(c.t.bind(c,36893,23)),Promise.resolve().then(c.t.bind(c,89748,23)),Promise.resolve().then(c.t.bind(c,6060,23)),Promise.resolve().then(c.t.bind(c,7184,23)),Promise.resolve().then(c.t.bind(c,69576,23)),Promise.resolve().then(c.t.bind(c,73041,23)),Promise.resolve().then(c.t.bind(c,51384,23))},17290:(a,b,c)=>{"use strict";c.d(b,{Providers:()=>g});var d=c(21124),e=c(15519),f=c(11734);function g({locale:a,theme:b,children:c}){return(0,d.jsx)(e.CY,{initialLocale:a,children:(0,d.jsx)(f.N,{initialTheme:b,children:c})})}},25088:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>p,generateMetadata:()=>n,viewport:()=>o});var d=c(75338);c(82704);var e=c(64285),f=c(66664);function g(){let a=`
2
- (function(){
3
- try {
4
- var t = null;
5
- try { t = localStorage.getItem('ccgauge.theme'); } catch (_) {}
6
- if (!t) {
7
- var m = document.cookie.match(/(?:^|; )ccgauge_theme=([^;]+)/);
8
- if (m) t = decodeURIComponent(m[1]);
9
- }
10
- if (t !== 'light' && t !== 'dark' && t !== 'system') t = 'dark';
11
- var resolved = t;
12
- if (t === 'system') {
13
- resolved = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
14
- }
15
- var root = document.documentElement;
16
- root.classList.remove('theme-light','theme-dark');
17
- root.classList.add(resolved === 'light' ? 'theme-light' : 'theme-dark');
18
- root.setAttribute('data-theme', resolved);
19
- } catch (e) {}
20
- try {
21
- var hidden = null;
22
- try { hidden = localStorage.getItem('ccgauge.usage.overview.hidden'); } catch (_) {}
23
- if (hidden === '1') document.documentElement.setAttribute('data-usage-overview', 'hidden');
24
- } catch (e) {}
25
- })();
26
- `;return(0,d.jsx)("script",{dangerouslySetInnerHTML:{__html:a}})}var h=c(91719),i=c(86802);async function j(){try{let a=await (0,i.UL)(),b=a.get("ccgauge_theme")?.value;if("light"===b||"dark"===b||"system"===b)return b}catch{}return"dark"}var k=c(55340),l=c(60704),m=c(46796);async function n(){let a=await (0,h.sG)();return{title:"ccgauge — Claude Code & Codex Dashboard",description:(0,k.nA)(a,"brand.tagline"),icons:{icon:[{url:"/favicon.svg",type:"image/svg+xml"}],shortcut:"/favicon.svg",apple:"/favicon.svg"}}}let o={width:"device-width",initialScale:1};async function p({children:a}){let b=await (0,h.sG)(),c=await j(),i=await (0,l.Fw)(),k=await (0,m.ev)(null),n=(0,l.R0)().map(a=>({id:a.id,shortLabel:a.shortLabel,fg:a.color.fg,bg:a.color.bg,displayEn:a.displayName.en,displayZh:a.displayName.zh}));return(0,d.jsxs)("html",{lang:"zh"===b?"zh-CN":"en",className:"light"===c?"theme-light":"theme-dark",suppressHydrationWarning:!0,children:[(0,d.jsx)("head",{children:(0,d.jsx)(g,{})}),(0,d.jsx)("body",{className:"min-h-screen bg-bg text-text-primary",children:(0,d.jsxs)(f.Providers,{locale:b,theme:c,children:[(0,d.jsx)(e.Nav,{availableProviders:i,initialSource:k,providerInfos:n}),(0,d.jsx)("main",{children:a})]})})]})}},26284:(a,b,c)=>{Promise.resolve().then(c.bind(c,64285)),Promise.resolve().then(c.bind(c,66664))},28607:(a,b,c)=>{"use strict";c.d(b,{ThemeSwitcher:()=>h});var d=c(21124),e=c(11734),f=c(15519);let g={light:"dark",dark:"system",system:"light"};function h(){let{theme:a,resolved:b,setTheme:c}=(0,e.D)(),{t:h}=(0,f.s9)(),j=g[a];return(0,d.jsx)("button",{onClick:()=>c(j),className:"h-7 w-9 inline-flex items-center justify-center rounded-md border border-border bg-bg-surface text-text-secondary hover:text-text-primary hover:bg-bg-surface-hi hover:border-border-hi transition-colors",title:`${h("theme.label")}: ${h(`settings.theme.${a}`)} (${b}) → ${h(`settings.theme.${j}`)}`,"aria-label":h("theme.label"),children:(0,d.jsx)(i,{theme:a})})}function i({theme:a}){return"light"===a?(0,d.jsxs)("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:[(0,d.jsx)("circle",{cx:"12",cy:"12",r:"4"}),(0,d.jsx)("path",{d:"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"})]}):"dark"===a?(0,d.jsx)("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":!0,children:(0,d.jsx)("path",{d:"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"})}):(0,d.jsxs)("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,children:[(0,d.jsx)("circle",{cx:"12",cy:"12",r:"9"}),(0,d.jsx)("path",{d:"M12 3v18"}),(0,d.jsx)("path",{d:"M12 3a9 9 0 0 1 0 18z",fill:"currentColor",stroke:"none"})]})}},41697:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>d});let d=(0,c(97954).registerClientReference)(function(){throw Error("Attempted to call the default export of \"/Users/zuopeng.cheng/personal/workspace/ccgauge/app/error.tsx\" from the server, but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/Users/zuopeng.cheng/personal/workspace/ccgauge/app/error.tsx","default")},46796:(a,b,c)=>{"use strict";c.d(b,{ev:()=>f,rY:()=>g});var d=c(86802),e=c(60704);async function f(a){if(a&&(0,e.rl)(a))return a;let b=await (0,d.UL)(),c=b.get("ccgauge_source")?.value,f=c&&(0,e.rl)(c)?c:e.Gr,g=await (0,e.Fw)();return g.length>0&&!g.includes(f)?g[0]:f}function g(a,b){return a.filter(a=>a.source===b)}},55340:(a,b,c)=>{"use strict";c.d(b,{Xn:()=>d,nA:()=>f});let d="en",e={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","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":"Token usage trend","overview.trend.desc":"Last 30 days \xb7 stacked by token type","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.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.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","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":"Token 用量趋势","overview.trend.desc":"近 30 天 \xb7 按 token 类型堆叠","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.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.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 f(a,b,c){let d=e[a]?.[b];if(void 0===d&&(d=e.en[b]),void 0===d&&(d=b),c)for(let[a,b]of Object.entries(c))d=d.replace(RegExp(`\\{${a}\\}`,"g"),String(b));return d}},64285:(a,b,c)=>{"use strict";c.d(b,{Nav:()=>d});let d=(0,c(97954).registerClientReference)(function(){throw Error("Attempted to call Nav() from the server but Nav is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/Users/zuopeng.cheng/personal/workspace/ccgauge/components/nav.tsx","Nav")},66664:(a,b,c)=>{"use strict";c.d(b,{Providers:()=>d});let d=(0,c(97954).registerClientReference)(function(){throw Error("Attempted to call Providers() from the server but Providers is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/Users/zuopeng.cheng/personal/workspace/ccgauge/components/providers.tsx","Providers")},74268:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,54160,23)),Promise.resolve().then(c.t.bind(c,31603,23)),Promise.resolve().then(c.t.bind(c,68495,23)),Promise.resolve().then(c.t.bind(c,75170,23)),Promise.resolve().then(c.t.bind(c,77526,23)),Promise.resolve().then(c.t.bind(c,78922,23)),Promise.resolve().then(c.t.bind(c,29234,23)),Promise.resolve().then(c.t.bind(c,12263,23)),Promise.resolve().then(c.bind(c,82146))},77731:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>f});var d=c(21124);c(38301);var e=c(15519);function f({error:a,reset:b}){let c=(0,e.kj)();return(0,d.jsx)("div",{className:"max-w-2xl mx-auto px-6 py-12",children:(0,d.jsxs)("div",{className:"card card-pad text-center",children:[(0,d.jsx)("div",{className:"text-base font-semibold text-text-primary",children:c("common.error.title")}),(0,d.jsx)("p",{className:"text-sm text-text-secondary mt-2",children:c("common.error.desc")}),a.message&&(0,d.jsxs)("pre",{className:"mt-4 text-xs text-text-tertiary text-left bg-bg-surface-hi rounded p-3 overflow-auto max-h-48 whitespace-pre-wrap",children:[a.message,a.digest?`
27
-
28
- #${a.digest}`:""]}),(0,d.jsx)("button",{onClick:()=>b(),className:"btn mt-4",children:c("common.error.retry")})]})})}},78288:(a,b,c)=>{Promise.resolve().then(c.bind(c,41697))},78642:(a,b,c)=>{"use strict";c.d(b,{Xn:()=>d,nA:()=>f});let d="en",e={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","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":"Token usage trend","overview.trend.desc":"Last 30 days \xb7 stacked by token type","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.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.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","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":"Token 用量趋势","overview.trend.desc":"近 30 天 \xb7 按 token 类型堆叠","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.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.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 f(a,b,c){let d=e[a]?.[b];if(void 0===d&&(d=e.en[b]),void 0===d&&(d=b),c)for(let[a,b]of Object.entries(c))d=d.replace(RegExp(`\\{${a}\\}`,"g"),String(b));return d}},80489:(a,b,c)=>{"use strict";c.d(b,{Q:()=>j,n:()=>f});var d=c(75338),e=c(95012);function f({label:a,value:b,hint:c,delta:f,deltaTitle:j,progress:k,accent:l="default",className:m}){return(0,d.jsxs)("div",{className:(0,e.cn)("card card-pad relative flex flex-col gap-2 min-h-[132px] overflow-hidden","default"!==l&&"pt-[18px] sm:pt-[22px]",m),children:["default"!==l&&(0,d.jsx)(g,{tone:l}),(0,d.jsx)("div",{className:"label truncate",children:a}),(0,d.jsxs)("div",{className:"flex items-baseline gap-2 flex-wrap min-w-0",children:[(0,d.jsx)("div",{className:"num-hero min-w-0 max-w-full",children:b}),f&&"firstTime"in f&&f.firstTime&&(0,d.jsx)("span",{className:"pill text-[11px] font-medium whitespace-nowrap text-brand bg-brand/10 border border-brand/20",title:j,children:f.label??"NEW"}),f&&"value"in f&&Number.isFinite(f.value)&&(0,d.jsx)(h,{value:f.value,positiveIsGood:f.positiveIsGood,title:j})]}),c&&(0,d.jsx)("div",{className:"text-xs text-text-secondary mt-auto leading-snug",children:c}),k&&(0,d.jsx)("div",{className:"mt-auto pt-2",children:(0,d.jsx)(i,{value:k.value,tone:k.tone})})]})}function g({tone:a}){return(0,d.jsx)("div",{"aria-hidden":!0,className:(0,e.cn)("absolute left-0 right-0 top-0 h-[3px] bg-gradient-to-r",{brand:"from-brand/0 via-brand/70 to-brand/0",success:"from-success/0 via-success/70 to-success/0",warning:"from-warning/0 via-warning/70 to-warning/0"}[a])})}function h({value:a,positiveIsGood:b=!0,title:c}){let f=a>=0;return(0,d.jsxs)("span",{className:(0,e.cn)("pill text-[11px] font-medium whitespace-nowrap border",f===b?"text-success bg-success/10 border-success/20":"text-danger bg-danger/10 border-danger/20"),title:c,children:[f?"↑":"↓"," ",Math.abs(a).toFixed(0),"%"]})}function i({value:a,tone:b="brand"}){let c=Math.max(0,Math.min(1,a));return(0,d.jsx)("div",{className:"h-1.5 w-full bg-bg-surface-hi rounded-full overflow-hidden",children:(0,d.jsx)("div",{className:(0,e.cn)("h-full rounded-full transition-all duration-500 ease-out-soft",{brand:"bg-brand",success:"bg-success",warning:"bg-warning",danger:"bg-danger"}[b]),style:{width:`${100*c}%`}})})}function j(){return(0,d.jsxs)("div",{className:"card card-pad min-h-[132px] animate-pulse",children:[(0,d.jsx)("div",{className:"h-3 w-20 bg-bg-surface-hi rounded mb-3"}),(0,d.jsx)("div",{className:"h-8 w-32 bg-bg-surface-hi rounded mb-2"}),(0,d.jsx)("div",{className:"h-3 w-24 bg-bg-surface-hi rounded mt-auto"})]})}},82704:()=>{},88016:(a,b,c)=>{Promise.resolve().then(c.bind(c,77731))},88267:(a,b,c)=>{"use strict";c.d(b,{Nav:()=>p});var d=c(21124),e=c(3991),f=c.n(e),g=c(42378),h=c(15514),i=c(15519),j=c(95546),k=c(28607);function l({className:a,withBackground:b=!0}){return(0,d.jsxs)("svg",{viewBox:"0 0 64 64",fill:"none","aria-hidden":!0,className:(0,h.cn)("block",a),children:[b&&(0,d.jsx)("rect",{width:"64",height:"64",rx:"14",className:"fill-brand"}),(0,d.jsx)("path",{d:"M14 41 A18 18 0 0 1 50 41",stroke:"currentColor",strokeOpacity:b?.32:.25,strokeWidth:"4.5",strokeLinecap:"round",className:b?"text-white":""}),(0,d.jsx)("path",{d:"M14 41 A18 18 0 0 1 43.2 25.4",stroke:"currentColor",strokeWidth:"4.5",strokeLinecap:"round",className:b?"text-white":"text-brand"}),(0,d.jsx)("line",{x1:"32",y1:"41",x2:"42",y2:"27",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",className:b?"text-white":"text-brand"}),(0,d.jsx)("circle",{cx:"32",cy:"41",r:"3",className:b?"fill-white":"fill-brand"})]})}var m=c(38301);function n({available:a,initial:b,providers:c}){let e=(0,g.useRouter)(),f=(0,g.usePathname)(),h=(0,g.useSearchParams)(),j=(0,i.kj)(),{locale:k}=(0,i.s9)(),[l,n]=(0,m.useTransition)(),o=h.get("source"),[p,q]=(0,m.useState)(o??b);return a.length<2?null:(0,d.jsx)("div",{role:"group","aria-label":j("nav.source"),className:"inline-flex items-center rounded-md border border-border bg-bg-surface p-0.5 gap-0.5",title:j("nav.source"),children:c.filter(b=>a.includes(b.id)).map(a=>{let b=a.id===p,c="zh"===k?a.displayZh:a.displayEn;return(0,d.jsxs)("button",{type:"button",onClick:()=>(a=>{var b;if(a===p)return;q(a),b=a,"undefined"!=typeof document&&(document.cookie=`ccgauge_source=${encodeURIComponent(b)}; path=/; max-age=31536000; SameSite=Lax`);let c=new URLSearchParams(h.toString());c.set("source",a);let d=/^\/sessions\/[^/]+|^\/projects\/[^/]+/.test(f)?`/${f.split("/")[1]}?${c.toString()}`:`${f}?${c.toString()}`;n(()=>{e.push(d)})})(a.id),"aria-pressed":b,disabled:l,className:`px-2.5 h-6 text-xs inline-flex items-center gap-1.5 rounded transition-all ${b?"bg-brand text-white font-semibold shadow-sm ring-1 ring-brand/40":"text-text-tertiary font-medium hover:text-text-primary hover:bg-bg-surface-hi"}`,children:[(0,d.jsx)("span",{className:`inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold leading-none ${b?"ring-1 ring-white/40":""}`,style:{background:a.bg,color:a.fg},children:a.shortLabel}),(0,d.jsx)("span",{children:c})]},a.id)})})}let o=[{href:"/",tk:"nav.overview",exact:!0},{href:"/usage",tk:"nav.usage"},{href:"/sessions",tk:"nav.sessions"},{href:"/projects",tk:"nav.projects"},{href:"/models",tk:"nav.models"},{href:"/settings",tk:"nav.settings"}];function p({availableProviders:a,initialSource:b,providerInfos:c}){let e=(0,g.usePathname)(),m=(0,i.kj)();return(0,d.jsx)("header",{className:"sticky top-0 z-30 border-b border-border bg-bg-base/85 backdrop-blur-md supports-[backdrop-filter]:bg-bg-base/70",children:(0,d.jsxs)("div",{className:"max-w-7xl mx-auto px-3 sm:px-6 h-14 flex items-center gap-2 sm:gap-4",children:[(0,d.jsxs)(f(),{href:"/",className:"flex items-center gap-2 font-semibold tracking-tight whitespace-nowrap shrink-0 text-text-primary hover:opacity-90 transition-opacity","aria-label":"ccgauge home",children:[(0,d.jsx)(l,{className:"w-7 h-7"}),(0,d.jsx)("span",{className:"hidden xs:inline sm:inline",children:"ccgauge"}),(0,d.jsx)("span",{className:"text-xs text-text-tertiary font-normal hidden lg:inline",children:m("brand.tagline")})]}),(0,d.jsx)("nav",{className:"flex-1 min-w-0 flex items-center gap-0.5 overflow-x-auto nav-scroller","aria-label":"Primary",children:o.map(a=>{let b=a.exact?e===a.href:e===a.href||e.startsWith(a.href+"/");return(0,d.jsxs)(f(),{href:a.href,prefetch:!1,"aria-current":b?"page":void 0,className:(0,h.cn)("relative px-2.5 sm:px-3 py-1.5 text-sm rounded-button font-medium whitespace-nowrap shrink-0","transition-colors duration-150",b?"text-text-primary":"text-text-secondary hover:text-text-primary hover:bg-bg-surface-hi/60"),children:[m(a.tk),b&&(0,d.jsx)("span",{"aria-hidden":!0,className:"pointer-events-none absolute left-2 right-2 -bottom-[12px] h-[2px] bg-brand rounded-full"})]},a.href)})}),(0,d.jsxs)("div",{className:"flex items-center gap-2 shrink-0",children:[(0,d.jsx)(n,{available:a,initial:b,providers:c}),(0,d.jsx)("span",{className:"pill bg-bg-surface-hi text-text-tertiary text-[10px] uppercase tracking-wide hidden md:inline-flex",children:m("nav.localBadge")}),(0,d.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,d.jsx)(j.LanguageSwitcher,{}),(0,d.jsx)(k.ThemeSwitcher,{})]})]})]})})}},90391:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>f});var d=c(75338),e=c(80489);function f(){return(0,d.jsxs)("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 py-6 sm:py-8 space-y-6",children:[(0,d.jsxs)("div",{className:"space-y-2",children:[(0,d.jsx)("div",{className:"h-7 w-48 bg-bg-surface-hi rounded animate-pulse"}),(0,d.jsx)("div",{className:"h-4 w-72 bg-bg-surface-hi rounded animate-pulse"})]}),(0,d.jsx)("div",{className:"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",children:Array.from({length:4}).map((a,b)=>(0,d.jsx)(e.Q,{},b))}),(0,d.jsx)("div",{className:"card card-pad min-h-[280px] animate-pulse"})]})}},91719:(a,b,c)=>{"use strict";c.d(b,{sG:()=>f,yO:()=>g});var d=c(86802),e=c(55340);async function f(){try{let a=await (0,d.UL)(),b=a.get("ccgauge_locale")?.value;if("zh"===b||"en"===b)return b}catch{}return e.Xn}async function g(){let a=await f();return(b,c)=>(0,e.nA)(a,b,c)}},95012:(a,b,c)=>{"use strict";c.d(b,{BC:()=>o,PJ:()=>n,R8:()=>j,a3:()=>k,az:()=>i,cn:()=>f,jh:()=>h,om:()=>l,r6:()=>m});var d=c(81171),e=c(11167);function f(...a){return(0,e.QP)((0,d.$)(a))}function g(a,b){return new Intl.NumberFormat("en-US",{maximumFractionDigits:b?.maxFrac??0}).format(a)}function h(a,b="en"){return Number.isFinite(a)?"zh"===b?a>=1e8?(a/1e8).toFixed(2)+"亿":a>=1e4?(a/1e4).toFixed(1)+"万":g(a):a>=1e9?(a/1e9).toFixed(2)+"B":a>=1e6?(a/1e6).toFixed(2)+"M":a>=1e3?(a/1e3).toFixed(1)+"K":g(a):"0"}function i(a,b){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:b?.minFrac??2,maximumFractionDigits:b?.maxFrac??2}).format(a)}function j(a,b=1){return Number.isFinite(a)?`${(100*a).toFixed(b)}%`:"0%"}function k(a){if(a<1e3)return`${a}ms`;let b=Math.floor(a/1e3);if(b<60)return`${b}s`;let c=Math.floor(b/60);if(c<60){let a=b%60;return a?`${c}m ${a}s`:`${c}m`}let d=Math.floor(c/60),e=c%60;return e?`${d}h ${e}m`:`${d}h`}function l(a,b="en"){let c="string"==typeof a||"number"==typeof a?new Date(a):a,d=Date.now()-c.getTime(),e="zh"===b;if(d<0)return e?"刚刚":"just now";let f=Math.floor(d/1e3);if(f<60)return e?"刚刚":`${f}s ago`;let g=Math.floor(f/60);if(g<60)return e?`${g} 分钟前`:`${g}m ago`;let h=Math.floor(g/60);if(h<24)return e?`${h} 小时前`:`${h}h ago`;let i=Math.floor(h/24);if(i<7)return e?`${i} 天前`:`${i}d ago`;let j=Math.floor(i/7);if(j<5)return e?`${j} 周前`:`${j}w ago`;let k=Math.floor(i/30);if(k<12)return e?`${k} 个月前`:`${k}mo ago`;let m=Math.floor(i/365);return e?`${m} 年前`:`${m}y ago`}function m(a){let b="string"==typeof a||"number"==typeof a?new Date(a):a;if(Number.isNaN(b.getTime()))return"";let c=b.getFullYear(),d=String(b.getMonth()+1).padStart(2,"0"),e=String(b.getDate()).padStart(2,"0"),f=String(b.getHours()).padStart(2,"0"),g=String(b.getMinutes()).padStart(2,"0"),h=String(b.getSeconds()).padStart(2,"0");return`${c}-${d}-${e} ${f}:${g}:${h}`}function n(a){if(!a)return"(unknown)";let b=a.replace(/[/\\]+$/,"").split(/[/\\]+/);return b[b.length-1]||a}function o(a,b=8){return a?a.replace(/-/g,"").slice(0,b):""}},95546:(a,b,c)=>{"use strict";c.d(b,{LanguageSwitcher:()=>i});var d=c(21124),e=c(15519);let f={en:"EN",zh:"中"},g={en:"zh",zh:"en"},h={en:"English",zh:"中文"};function i(){let{locale:a,setLocale:b,t:c}=(0,e.s9)(),i=g[a];return(0,d.jsx)("button",{onClick:()=>b(i),className:"h-7 w-9 inline-flex items-center justify-center rounded-md border border-border bg-bg-surface text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-surface-hi hover:border-border-hi transition-colors",title:`${c("lang.label")}: ${h[a]} → ${h[i]}`,"aria-label":c("lang.label"),children:f[a]})}}};