ccgauge 1.0.4 → 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 (45) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +33 -33
  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/api/projects/route.js +1 -1
  7. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/api/sessions/route.js +1 -1
  9. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/api/usage/route.js +1 -1
  11. package/.next/standalone/.next/server/app/api/usage/route.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/models/page.js +2 -2
  13. package/.next/standalone/.next/server/app/models/page.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/page.js +2 -2
  15. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/projects/[id]/page.js +2 -2
  17. package/.next/standalone/.next/server/app/projects/[id]/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  19. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/sessions/[id]/page.js +2 -2
  21. package/.next/standalone/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/sessions/page.js +1 -1
  23. package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  25. package/.next/standalone/.next/server/app/usage/page.js +3 -3
  26. package/.next/standalone/.next/server/app/usage/page.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app-paths-manifest.json +9 -9
  28. package/.next/standalone/.next/server/chunks/287.js +1 -0
  29. package/.next/standalone/.next/server/chunks/567.js +2 -2
  30. package/.next/standalone/.next/server/chunks/730.js +1 -0
  31. package/.next/standalone/.next/server/functions-config-manifest.json +2 -2
  32. package/.next/standalone/.next/server/pages/500.html +1 -1
  33. package/.next/standalone/node_modules/next/node_modules/postcss/package.json +0 -0
  34. package/.next/standalone/package.json +1 -1
  35. package/.next/standalone/public/codex-logo.png +0 -0
  36. package/CHANGELOG.md +223 -0
  37. package/README.md +6 -2
  38. package/README.zh-CN.md +6 -2
  39. package/bin/cli.mjs +394 -91
  40. package/dist/mcp/server.mjs +16 -16
  41. package/dist/report/index.mjs +183 -28
  42. package/package.json +22 -24
  43. package/.next/standalone/.next/server/chunks/971.js +0 -1
  44. /package/.next/standalone/.next/static/{ir1LZCnQKkiNUVXLprtzh → kdpS1dOlXPsnKYuNBuMt9}/_buildManifest.js +0 -0
  45. /package/.next/standalone/.next/static/{ir1LZCnQKkiNUVXLprtzh → kdpS1dOlXPsnKYuNBuMt9}/_ssgManifest.js +0 -0
@@ -1528,6 +1528,69 @@ function projectNameFromCwd(cwd) {
1528
1528
  return parts[parts.length - 1] || cwd;
1529
1529
  }
1530
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
+
1531
1594
  // lib/aggregator/index.ts
1532
1595
  var GRANULARITIES = ["hour", "day", "week", "month"];
1533
1596
  function isGranularity(v) {
@@ -1719,6 +1782,12 @@ function aggregateBySession(records, userRecords, opts) {
1719
1782
  source: rec.source,
1720
1783
  cwd: rec.cwd,
1721
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),
1722
1791
  startTime: rec.timestamp,
1723
1792
  endTime: rec.timestamp,
1724
1793
  durationMs: 0,
@@ -1825,6 +1894,66 @@ function rangeToDates(range) {
1825
1894
  return { from };
1826
1895
  }
1827
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
+
1828
1957
  // lib/cli-report/index.ts
1829
1958
  var REPORT_RANGES = ["today", "1d", "7d", "30d", "90d", "all"];
1830
1959
  var DIMS = ["model", "project", "session"];
@@ -1844,7 +1973,7 @@ async function runReport(opts) {
1844
1973
  const filled = normalizeReportOptions(opts);
1845
1974
  const scan = await getCachedScan();
1846
1975
  const sources = filled.source === "all" ? ALL_PROVIDER_IDS : [filled.source];
1847
- const data = computeReportData(scan.records, sources, filled);
1976
+ const data = computeReportData(scan, sources, filled);
1848
1977
  if (filled.json) return JSON.stringify(data, null, 2);
1849
1978
  return renderText(data, filled);
1850
1979
  }
@@ -1879,14 +2008,15 @@ function isDim(v) {
1879
2008
  function invalidOptionMessage(name, value, expected) {
1880
2009
  return `invalid ${name}: ${JSON.stringify(value)}. Expected one of: ${expected.join(", ")}`;
1881
2010
  }
1882
- function computeReportData(allRecords, sources, o) {
2011
+ function computeReportData(scan, sources, o) {
2012
+ const allRecords = scan.records;
1883
2013
  const dates = resolveRange(o);
1884
2014
  const baseOpts = {
1885
2015
  from: dates.from ?? void 0,
1886
- to: dates.until ?? void 0,
1887
- models: o.model ? void 0 : void 0,
1888
- // handled post-filter
1889
- 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.
1890
2020
  };
1891
2021
  const totals = {
1892
2022
  input: 0,
@@ -1897,7 +2027,8 @@ function computeReportData(allRecords, sources, o) {
1897
2027
  total: 0,
1898
2028
  cost: 0,
1899
2029
  saved: 0,
1900
- requests: 0
2030
+ requests: 0,
2031
+ turns: 0
1901
2032
  };
1902
2033
  const trendBuckets = /* @__PURE__ */ new Map();
1903
2034
  for (const source of sources) {
@@ -1913,19 +2044,28 @@ function computeReportData(allRecords, sources, o) {
1913
2044
  totals.saved += t.saved;
1914
2045
  totals.requests += t.requests;
1915
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
+ }
1916
2054
  const buckets = aggregateByTime(sourceRecs, o.gran, opts);
1917
2055
  for (const b of buckets) {
1918
2056
  const ex = trendBuckets.get(b.key);
2057
+ const tnCount = turnsByKey.get(b.key) ?? 0;
1919
2058
  if (ex) {
1920
2059
  ex.cost += b.cost;
1921
2060
  ex.tokens += b.totalTokens;
2061
+ ex.turns += tnCount;
1922
2062
  } else {
1923
- 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 });
1924
2064
  }
1925
2065
  }
1926
2066
  }
1927
2067
  const trend = Array.from(trendBuckets.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
1928
- const breakdown = buildBreakdown(allRecords, sources, baseOpts, o);
2068
+ const breakdown = buildBreakdown(scan, sources, baseOpts, o);
1929
2069
  return {
1930
2070
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1931
2071
  range: o.range,
@@ -1939,7 +2079,8 @@ function computeReportData(allRecords, sources, o) {
1939
2079
  breakdown
1940
2080
  };
1941
2081
  }
1942
- function buildBreakdown(allRecords, sources, base, o) {
2082
+ function buildBreakdown(scan, sources, base, o) {
2083
+ const allRecords = scan.records;
1943
2084
  if (o.by === "model") {
1944
2085
  const rows2 = [];
1945
2086
  for (const source of sources) {
@@ -1947,11 +2088,16 @@ function buildBreakdown(allRecords, sources, base, o) {
1947
2088
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1948
2089
  const models = aggregateByModel(filtered, opts);
1949
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
+ }
1950
2095
  for (const m of models) {
1951
2096
  rows2.push({
1952
2097
  key: `${source}::${m.model}`,
1953
2098
  label: provider.shortenModel(m.model),
1954
2099
  requests: m.requests,
2100
+ turns: turnsByModel.get(m.model) ?? 0,
1955
2101
  tokens: m.totalTokens,
1956
2102
  cost: m.cost,
1957
2103
  share: 0,
@@ -1968,11 +2114,17 @@ function buildBreakdown(allRecords, sources, base, o) {
1968
2114
  const opts = { ...base, source };
1969
2115
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1970
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
+ }
1971
2122
  for (const p of projects) {
1972
2123
  rows2.push({
1973
2124
  key: `${source}::${p.cwd}`,
1974
2125
  label: p.projectName,
1975
2126
  requests: p.requests,
2127
+ turns: turnsByCwd.get(p.cwd) ?? 0,
1976
2128
  tokens: p.totalTokens,
1977
2129
  cost: p.cost,
1978
2130
  share: 0,
@@ -1987,15 +2139,23 @@ function buildBreakdown(allRecords, sources, base, o) {
1987
2139
  const opts = { ...base, source };
1988
2140
  const filtered = allRecords.filter((r) => withinSrcAndFilters(r, opts, o));
1989
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
+ }
1990
2146
  for (const s of sessions) {
1991
2147
  rows.push({
1992
2148
  key: `${source}::${s.sessionId}`,
1993
2149
  label: s.title ?? s.sessionId.slice(0, 8),
1994
2150
  requests: s.requests,
2151
+ turns: turnsBySession.get(s.sessionId) ?? 0,
1995
2152
  tokens: s.totalTokens,
1996
2153
  cost: s.cost,
1997
2154
  share: 0,
1998
- 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
1999
2159
  });
2000
2160
  }
2001
2161
  }
@@ -2097,21 +2257,15 @@ function renderText(d, o) {
2097
2257
  ]
2098
2258
  ];
2099
2259
  if (t.reasoning > 0) {
2100
- tokenRows.push([
2101
- "Reasoning",
2102
- c.dim(formatTokensCompact(t.reasoning)),
2103
- "Requests",
2104
- t.requests.toLocaleString()
2105
- ]);
2106
- tokenRows.push(["Total", c.bold(formatTokensCompact(t.total)), "", ""]);
2107
- } else {
2108
- tokenRows.push([
2109
- "Total",
2110
- c.bold(formatTokensCompact(t.total)),
2111
- "Requests",
2112
- t.requests.toLocaleString()
2113
- ]);
2114
- }
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
+ ]);
2115
2269
  lines.push(renderPairedKv(tokenRows, c));
2116
2270
  lines.push("");
2117
2271
  lines.push(c.brand("\u25B8") + " " + c.bold("Cost"));
@@ -2147,16 +2301,17 @@ function renderText(d, o) {
2147
2301
  lines.push(
2148
2302
  c.brand("\u25B8") + " " + c.bold(`Top ${d.breakdown.length} ${dimLabel}s`) + " " + c.dim("(by cost)")
2149
2303
  );
2150
- const headers = ["#", dimLabel, "Reqs", "Tokens", "Cost", "Share"];
2304
+ const headers = ["#", dimLabel, "Convos", "Reqs", "Tokens", "Cost", "Share"];
2151
2305
  const rows = d.breakdown.map((r, i) => [
2152
2306
  String(i + 1),
2153
2307
  truncate(r.label, 28),
2308
+ r.turns.toLocaleString(),
2154
2309
  r.requests.toLocaleString(),
2155
2310
  formatTokensCompact(r.tokens),
2156
2311
  formatUSD(r.cost),
2157
2312
  formatPct(r.share, 1)
2158
2313
  ]);
2159
- 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]));
2160
2315
  lines.push("");
2161
2316
  }
2162
2317
  return lines.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgauge",
3
- "version": "1.0.4",
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",
@@ -41,7 +41,6 @@
41
41
  "x64",
42
42
  "arm64"
43
43
  ],
44
- "packageManager": "pnpm@10.30.3",
45
44
  "bin": {
46
45
  "ccgauge": "bin/cli.mjs"
47
46
  },
@@ -55,27 +54,6 @@
55
54
  "CHANGELOG.md",
56
55
  "LICENSE"
57
56
  ],
58
- "scripts": {
59
- "dev": "next dev -p 3738",
60
- "build": "next build && node scripts/build-mcp.mjs && node scripts/build-report.mjs && node scripts/postbuild.mjs",
61
- "build:mcp": "node scripts/build-mcp.mjs",
62
- "build:report": "node scripts/build-report.mjs",
63
- "start": "node bin/cli.mjs",
64
- "start:next": "next start -p 3737",
65
- "lint": "eslint .",
66
- "typecheck": "tsc --noEmit",
67
- "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",
68
- "test:mcp": "node scripts/test-mcp-server.mjs",
69
- "clean": "node -e \"for (const p of ['.next','node_modules','tsconfig.tsbuildinfo']) require('node:fs').rmSync(p,{recursive:true,force:true})\"",
70
- "screenshots": "node scripts/screenshots.mjs",
71
- "site:dev": "cd site && astro dev --port 4321",
72
- "site:build": "cd site && astro build",
73
- "site:preview": "cd site && astro preview --port 4322",
74
- "site:check": "cd site && astro check",
75
- "site:gen:placeholders": "node site/scripts/gen-placeholders.mjs",
76
- "site:clean": "node -e \"for (const p of ['site/dist','site/.astro','site/node_modules']) require('node:fs').rmSync(p,{recursive:true,force:true})\"",
77
- "prepack": "pnpm build"
78
- },
79
57
  "dependencies": {
80
58
  "@modelcontextprotocol/sdk": "^1.29.0",
81
59
  "commander": "^13.1.0",
@@ -108,5 +86,25 @@
108
86
  "tailwindcss": "^3.4.17",
109
87
  "typescript": "^5.7.0",
110
88
  "zod": "^3.24.1"
89
+ },
90
+ "scripts": {
91
+ "dev": "next dev -p 3738",
92
+ "build": "next build && node scripts/build-mcp.mjs && node scripts/build-report.mjs && node scripts/postbuild.mjs",
93
+ "build:mcp": "node scripts/build-mcp.mjs",
94
+ "build:report": "node scripts/build-report.mjs",
95
+ "start": "node bin/cli.mjs",
96
+ "start:next": "next start -p 3737",
97
+ "lint": "eslint .",
98
+ "typecheck": "tsc --noEmit",
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",
100
+ "test:mcp": "node scripts/test-mcp-server.mjs",
101
+ "clean": "node -e \"for (const p of ['.next','node_modules','tsconfig.tsbuildinfo']) require('node:fs').rmSync(p,{recursive:true,force:true})\"",
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})\""
111
109
  }
112
- }
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}}}};