tokenleak 1.0.1 → 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 (2) hide show
  1. package/package.json +1 -1
  2. package/tokenleak +446 -451
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenleak",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Visualise your AI coding-assistant token usage across providers — heatmaps, dashboards, and shareable cards.",
5
5
  "type": "module",
6
6
  "bin": {
package/tokenleak CHANGED
@@ -756,7 +756,7 @@ function computePreviousPeriod(current) {
756
756
  };
757
757
  }
758
758
  // packages/core/dist/index.js
759
- var VERSION = "1.0.1";
759
+ var VERSION = "1.0.2";
760
760
 
761
761
  // packages/registry/dist/models/normalizer.js
762
762
  var DATE_SUFFIX_PATTERN = /-\d{8}$/;
@@ -1792,66 +1792,10 @@ class JsonRenderer {
1792
1792
  return JSON.stringify(output, null, 2);
1793
1793
  }
1794
1794
  }
1795
- // packages/renderers/dist/svg/theme.js
1796
- var DARK_THEME = {
1797
- background: "#0d1117",
1798
- foreground: "#e6edf3",
1799
- muted: "#7d8590",
1800
- border: "#30363d",
1801
- cardBackground: "#161b22",
1802
- heatmap: ["#161b22", "#1e3a5f", "#2563eb", "#3b82f6", "#1d4ed8"],
1803
- accent: "#58a6ff",
1804
- accentSecondary: "#bc8cff",
1805
- barFill: "#3b82f6",
1806
- barBackground: "#21262d"
1807
- };
1808
- var LIGHT_THEME = {
1809
- background: "#ffffff",
1810
- foreground: "#1a1a2e",
1811
- muted: "#8b8fa3",
1812
- border: "#e5e7eb",
1813
- cardBackground: "#f8f9fc",
1814
- heatmap: ["#ebedf0", "#c6d4f7", "#8da4ef", "#5b6abf", "#2f3778"],
1815
- accent: "#3b5bdb",
1816
- accentSecondary: "#7048e8",
1817
- barFill: "#5b6abf",
1818
- barBackground: "#ebedf0"
1819
- };
1820
- function getTheme(mode) {
1821
- return mode === "dark" ? DARK_THEME : LIGHT_THEME;
1822
- }
1823
-
1824
- // packages/renderers/dist/svg/layout.js
1825
- var PADDING = 40;
1826
- var CELL_SIZE = 16;
1827
- var CELL_GAP = 4;
1828
- var MONTH_LABEL_HEIGHT = 24;
1829
- var DAY_LABEL_WIDTH = 44;
1830
- var HEATMAP_ROWS = 7;
1831
- var SECTION_GAP = 32;
1832
- var FONT_SIZE_TITLE = 28;
1833
- var FONT_SIZE_SUBTITLE = 14;
1834
- var FONT_SIZE_SMALL = 11;
1835
- var FONT_SIZE_STAT_VALUE = 32;
1836
- var FONT_SIZE_STAT_LABEL = 11;
1837
- var FONT_FAMILY = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif";
1838
-
1839
1795
  // packages/renderers/dist/svg/utils.js
1840
1796
  function escapeXml(str) {
1841
1797
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1842
1798
  }
1843
- function rect(x, y, w, h, fill, rx) {
1844
- const rxAttr = rx !== undefined ? ` rx="${rx}"` : "";
1845
- return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${escapeXml(fill)}"${rxAttr}/>`;
1846
- }
1847
- function text(x, y, content, attrs) {
1848
- const attrStr = attrs ? Object.entries(attrs).map(([k, v]) => ` ${k}="${typeof v === "string" ? escapeXml(v) : v}"`).join("") : "";
1849
- return `<text x="${x}" y="${y}"${attrStr}>${escapeXml(content)}</text>`;
1850
- }
1851
- function group(children, transform) {
1852
- const transformAttr = transform ? ` transform="${escapeXml(transform)}"` : "";
1853
- return `<g${transformAttr}>${children.join("")}</g>`;
1854
- }
1855
1799
  function formatNumber(n) {
1856
1800
  if (n >= 1e6) {
1857
1801
  const millions = Number((n / 1e6).toFixed(1));
@@ -1879,275 +1823,35 @@ function formatCost(cost) {
1879
1823
  return `$${cost.toFixed(4)}`;
1880
1824
  }
1881
1825
 
1882
- // packages/renderers/dist/svg/heatmap.js
1883
- var DAY_LABELS2 = ["Mon", "", "Wed", "", "Fri", "", "Sun"];
1884
- var MONTH_NAMES = [
1885
- "Jan",
1886
- "Feb",
1887
- "Mar",
1888
- "Apr",
1889
- "May",
1890
- "Jun",
1891
- "Jul",
1892
- "Aug",
1893
- "Sep",
1894
- "Oct",
1895
- "Nov",
1896
- "Dec"
1897
- ];
1898
- function getLevel(tokens, quantiles) {
1899
- if (tokens <= 0)
1900
- return 0;
1901
- if (tokens <= quantiles[0])
1902
- return 1;
1903
- if (tokens <= quantiles[1])
1904
- return 2;
1905
- if (tokens <= quantiles[2])
1906
- return 3;
1907
- return 4;
1908
- }
1909
- function computeQuantiles(values) {
1910
- const nonZero = values.filter((v) => v > 0).sort((a, b) => a - b);
1911
- if (nonZero.length === 0)
1912
- return [0, 0, 0];
1913
- const q = (p) => {
1914
- const idx = Math.floor(p * (nonZero.length - 1));
1915
- return nonZero[idx] ?? 0;
1916
- };
1917
- return [q(0.25), q(0.5), q(0.75)];
1918
- }
1919
- function renderHeatmap(daily, theme, options = {}) {
1920
- const tokenMap = new Map;
1921
- for (const d of daily) {
1922
- const existing = tokenMap.get(d.date) ?? 0;
1923
- tokenMap.set(d.date, existing + d.totalTokens);
1924
- }
1925
- const dates = daily.map((d) => d.date).sort();
1926
- const endStr = options.endDate ?? dates[dates.length - 1] ?? new Date().toISOString().slice(0, 10);
1927
- const startStr = options.startDate ?? dates[0] ?? endStr;
1928
- const end = new Date(endStr + "T00:00:00Z");
1929
- const start = new Date(startStr + "T00:00:00Z");
1930
- const startDay = start.getUTCDay();
1931
- start.setUTCDate(start.getUTCDate() - startDay);
1932
- const cells = [];
1933
- const allTokens = Array.from(tokenMap.values());
1934
- const quantiles = computeQuantiles(allTokens);
1935
- const current = new Date(start);
1936
- let col = 0;
1937
- const monthLabels = [];
1938
- let lastMonth = -1;
1939
- const cellRadius = 3;
1940
- while (current <= end) {
1941
- const row = current.getUTCDay();
1942
- const dateStr = current.toISOString().slice(0, 10);
1943
- const tokens = tokenMap.get(dateStr) ?? 0;
1944
- const level = getLevel(tokens, quantiles);
1945
- const x = DAY_LABEL_WIDTH + col * (CELL_SIZE + CELL_GAP);
1946
- const y = MONTH_LABEL_HEIGHT + row * (CELL_SIZE + CELL_GAP);
1947
- const title = `${dateStr}: ${tokens.toLocaleString()} tokens`;
1948
- cells.push(`<rect x="${x}" y="${y}" width="${CELL_SIZE}" height="${CELL_SIZE}" fill="${escapeXml(theme.heatmap[level])}" rx="${cellRadius}"><title>${escapeXml(title)}</title></rect>`);
1949
- const month = current.getUTCMonth();
1950
- if (month !== lastMonth && row === 0) {
1951
- lastMonth = month;
1952
- monthLabels.push(text(x, MONTH_LABEL_HEIGHT - 8, MONTH_NAMES[month] ?? "", {
1953
- fill: theme.muted,
1954
- "font-size": FONT_SIZE_SMALL,
1955
- "font-family": FONT_FAMILY
1956
- }));
1957
- }
1958
- if (row === 6) {
1959
- col++;
1960
- }
1961
- current.setUTCDate(current.getUTCDate() + 1);
1962
- }
1963
- const dayLabels = DAY_LABELS2.map((label, i) => {
1964
- if (!label)
1965
- return "";
1966
- const y = MONTH_LABEL_HEIGHT + i * (CELL_SIZE + CELL_GAP) + CELL_SIZE - 2;
1967
- return text(0, y, label, {
1968
- fill: theme.muted,
1969
- "font-size": FONT_SIZE_SMALL,
1970
- "font-family": FONT_FAMILY
1971
- });
1972
- });
1973
- const totalCols = col + 1;
1974
- const gridWidth = DAY_LABEL_WIDTH + totalCols * CELL_SIZE + Math.max(0, totalCols - 1) * CELL_GAP;
1975
- const height = MONTH_LABEL_HEIGHT + HEATMAP_ROWS * CELL_SIZE + (HEATMAP_ROWS - 1) * CELL_GAP;
1976
- const legendY = height + 16;
1977
- const legendItems = [];
1978
- const legendStartX = 0;
1979
- legendItems.push(text(legendStartX, legendY + CELL_SIZE - 2, "LESS", {
1980
- fill: theme.muted,
1981
- "font-size": 9,
1982
- "font-family": FONT_FAMILY,
1983
- "font-weight": "600",
1984
- "letter-spacing": "0.5"
1985
- }));
1986
- const legendBoxStart = legendStartX + 40;
1987
- for (let i = 0;i < 5; i++) {
1988
- legendItems.push(`<rect x="${legendBoxStart + i * (CELL_SIZE + 3)}" y="${legendY}" width="${CELL_SIZE}" height="${CELL_SIZE}" fill="${escapeXml(theme.heatmap[i])}" rx="${cellRadius}"/>`);
1989
- }
1990
- legendItems.push(text(legendBoxStart + 5 * (CELL_SIZE + 3) + 4, legendY + CELL_SIZE - 2, "MORE", {
1991
- fill: theme.muted,
1992
- "font-size": 9,
1993
- "font-family": FONT_FAMILY,
1994
- "font-weight": "600",
1995
- "letter-spacing": "0.5"
1996
- }));
1997
- const totalHeight = legendY + CELL_SIZE + 8;
1998
- const legendRightX = legendBoxStart + 5 * (CELL_SIZE + 3) + 4 + 40;
1999
- const width = Math.max(gridWidth, legendRightX);
2000
- const svg = group([...monthLabels, ...dayLabels, ...cells, ...legendItems]);
2001
- return { svg, width, height: totalHeight };
2002
- }
2003
-
2004
- // packages/renderers/dist/svg/svg-renderer.js
2005
- var MIN_SVG_WIDTH = 1000;
2006
- var MAX_STAT_VALUE_CHARS = 28;
2007
- function truncateText(value, maxChars) {
2008
- if (value.length <= maxChars)
2009
- return value;
2010
- return value.slice(0, maxChars - 1) + "\u2026";
2011
- }
2012
- function renderHeaderStat(x, y, label, value, theme, align = "end") {
2013
- const anchor = align === "end" ? "end" : "start";
2014
- return group([
2015
- text(x, y, label, {
2016
- fill: theme.muted,
2017
- "font-size": FONT_SIZE_STAT_LABEL,
2018
- "font-family": FONT_FAMILY,
2019
- "font-weight": "700",
2020
- "text-anchor": anchor,
2021
- "letter-spacing": "1"
2022
- }),
2023
- text(x, y + 34, value, {
2024
- fill: theme.foreground,
2025
- "font-size": FONT_SIZE_STAT_VALUE,
2026
- "font-family": FONT_FAMILY,
2027
- "font-weight": "700",
2028
- "text-anchor": anchor
2029
- })
2030
- ]);
2031
- }
2032
- function renderBottomStat(x, y, label, value, theme) {
2033
- return group([
2034
- text(x, y, label, {
2035
- fill: theme.muted,
2036
- "font-size": FONT_SIZE_STAT_LABEL,
2037
- "font-family": FONT_FAMILY,
2038
- "font-weight": "700",
2039
- "letter-spacing": "0.8"
2040
- }),
2041
- text(x, y + 32, value, {
2042
- fill: theme.foreground,
2043
- "font-size": 18,
2044
- "font-family": FONT_FAMILY,
2045
- "font-weight": "700"
2046
- })
2047
- ]);
2048
- }
2049
-
2050
- class SvgRenderer {
2051
- format = "svg";
2052
- async render(output, options) {
2053
- const theme = getTheme(options.theme);
2054
- const sections = [];
2055
- const sectionWidths = [];
2056
- let y = PADDING;
2057
- const providerName = output.providers.length > 0 ? output.providers.map((p) => p.displayName).join(" + ") : "Tokenleak";
2058
- sections.push(text(PADDING, y + FONT_SIZE_TITLE, providerName, {
2059
- fill: theme.foreground,
2060
- "font-size": FONT_SIZE_TITLE,
2061
- "font-family": FONT_FAMILY,
2062
- "font-weight": "700"
2063
- }));
2064
- sections.push(text(PADDING, y + FONT_SIZE_TITLE + 22, `${output.dateRange.since} \u2014 ${output.dateRange.until}`, {
2065
- fill: theme.muted,
2066
- "font-size": FONT_SIZE_SUBTITLE,
2067
- "font-family": FONT_FAMILY
2068
- }));
2069
- const allDaily = output.providers.flatMap((p) => p.daily);
2070
- const heatmap = renderHeatmap(allDaily, theme, {
2071
- startDate: output.dateRange.since,
2072
- endDate: output.dateRange.until
2073
- });
2074
- sectionWidths.push(heatmap.width);
2075
- const contentWidth = Math.max(heatmap.width, MIN_SVG_WIDTH - PADDING * 2);
2076
- const stats = output.aggregated;
2077
- const headerStatSpacing = 180;
2078
- const headerStatsX = PADDING + contentWidth;
2079
- sections.push(renderHeaderStat(headerStatsX, y, "INPUT TOKENS", formatNumber(stats.totalInputTokens), theme));
2080
- sections.push(renderHeaderStat(headerStatsX - headerStatSpacing, y, "OUTPUT TOKENS", formatNumber(stats.totalOutputTokens), theme));
2081
- sections.push(renderHeaderStat(headerStatsX - headerStatSpacing * 2, y, "TOTAL TOKENS", formatNumber(stats.totalTokens), theme));
2082
- y += FONT_SIZE_TITLE + 22 + SECTION_GAP;
2083
- if (allDaily.length > 0) {
2084
- sections.push(group([heatmap.svg], `translate(${PADDING}, ${y})`));
2085
- y += heatmap.height + SECTION_GAP + 8;
2086
- }
2087
- sections.push(`<line x1="${PADDING}" y1="${y}" x2="${PADDING + contentWidth}" y2="${y}" stroke="${escapeXml(theme.border)}" stroke-width="1"/>`);
2088
- y += SECTION_GAP;
2089
- const col1Width = contentWidth * 0.35;
2090
- const col2Width = contentWidth * 0.35;
2091
- const col3Width = contentWidth * 0.15;
2092
- const col4Width = contentWidth * 0.15;
2093
- const topModel = stats.topModels.length > 0 ? stats.topModels[0] : null;
2094
- const topModelLabel = topModel ? truncateText(`${topModel.model} (${formatNumber(topModel.tokens)})`, MAX_STAT_VALUE_CHARS) : "N/A";
2095
- sections.push(renderBottomStat(PADDING, y, "MOST USED MODEL", topModelLabel, theme));
2096
- const recent30Label = stats.rolling30dTopModel ? truncateText(`${stats.rolling30dTopModel} (${formatNumber(stats.rolling30dTokens)})`, MAX_STAT_VALUE_CHARS) : formatNumber(stats.rolling30dTokens);
2097
- sections.push(renderBottomStat(PADDING + col1Width, y, "RECENT USE (LAST 30 DAYS)", recent30Label, theme));
2098
- sections.push(renderBottomStat(PADDING + col1Width + col2Width, y, "LONGEST STREAK", `${stats.longestStreak} days`, theme));
2099
- sections.push(renderBottomStat(PADDING + col1Width + col2Width + col3Width, y, "CURRENT STREAK", `${stats.currentStreak} days`, theme));
2100
- y += 56 + SECTION_GAP;
2101
- const evenCardWidth = contentWidth / 4;
2102
- sections.push(`<line x1="${PADDING}" y1="${y - SECTION_GAP / 2}" x2="${PADDING + contentWidth}" y2="${y - SECTION_GAP / 2}" stroke="${escapeXml(theme.border)}" stroke-width="1"/>`);
2103
- sections.push(renderBottomStat(PADDING, y, "TOTAL COST", stats.totalCost >= 100 ? `$${stats.totalCost.toFixed(0)}` : `$${stats.totalCost.toFixed(2)}`, theme));
2104
- sections.push(renderBottomStat(PADDING + evenCardWidth, y, "CACHE HIT RATE", `${(stats.cacheHitRate * 100).toFixed(1)}%`, theme));
2105
- sections.push(renderBottomStat(PADDING + evenCardWidth * 2, y, "ACTIVE DAYS", `${stats.activeDays} / ${stats.totalDays}`, theme));
2106
- sections.push(renderBottomStat(PADDING + evenCardWidth * 3, y, "AVG DAILY TOKENS", formatNumber(stats.averageDailyTokens), theme));
2107
- y += 56 + PADDING;
2108
- const totalHeight = y;
2109
- const svgWidth = Math.max(contentWidth + PADDING * 2, MIN_SVG_WIDTH);
2110
- sections.push(text(svgWidth - PADDING, totalHeight - 16, "tokenleak", {
2111
- fill: theme.muted,
2112
- "font-size": 10,
2113
- "font-family": FONT_FAMILY,
2114
- "text-anchor": "end",
2115
- opacity: "0.4"
2116
- }));
2117
- const svgContent = [
2118
- `<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${totalHeight}" viewBox="0 0 ${svgWidth} ${totalHeight}">`,
2119
- `<defs><style>@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&amp;display=swap');</style></defs>`,
2120
- rect(0, 0, svgWidth, totalHeight, theme.background, 12),
2121
- ...sections,
2122
- "</svg>"
2123
- ].join(`
2124
- `);
2125
- return svgContent;
2126
- }
2127
- }
2128
- // packages/renderers/dist/png/terminal-card.js
1826
+ // packages/renderers/dist/card/layout.js
2129
1827
  var CARD_PADDING = 48;
2130
1828
  var TITLEBAR_HEIGHT = 48;
2131
1829
  var DOT_RADIUS = 6;
2132
1830
  var DOT_GAP = 8;
2133
- var CELL_SIZE2 = 16;
2134
- var CELL_GAP2 = 4;
1831
+ var CELL_SIZE = 16;
1832
+ var CELL_GAP = 4;
2135
1833
  var STAT_GRID_COLS = 3;
2136
1834
  var MODEL_BAR_HEIGHT = 8;
2137
- var DAY_LABEL_WIDTH2 = 44;
2138
- var MONTH_LABEL_HEIGHT2 = 24;
1835
+ var DAY_LABEL_WIDTH = 44;
1836
+ var MONTH_LABEL_HEIGHT = 24;
2139
1837
  var PROVIDER_SECTION_GAP = 36;
2140
- var FONT_FAMILY2 = "'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', monospace";
1838
+ var MIN_CONTENT_WIDTH = 700;
1839
+ var MODEL_NAME_WIDTH = 220;
1840
+ var MODEL_BAR_GAP = 36;
1841
+ var MODEL_PERCENT_WIDTH = 40;
1842
+
1843
+ // packages/renderers/dist/png/terminal-card.js
1844
+ var FONT_FAMILY = "'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', monospace";
2141
1845
  function getCardTheme(mode) {
2142
1846
  if (mode === "dark") {
2143
1847
  return {
2144
- bg: "#0c0c0c",
1848
+ bg: "#09090b",
2145
1849
  fg: "#ffffff",
2146
1850
  muted: "#52525b",
2147
1851
  border: "rgba(255,255,255,0.06)",
2148
1852
  accent: "#10b981",
2149
- heatmapEmpty: "#1a1a1a",
2150
- barTrack: "#1c1c1c",
1853
+ heatmapEmpty: "#141418",
1854
+ barTrack: "#18181b",
2151
1855
  titlebarBorder: "rgba(255,255,255,0.06)"
2152
1856
  };
2153
1857
  }
@@ -2189,7 +1893,7 @@ function rgbToHex(r, g, b) {
2189
1893
  const toHex = (n) => n.toString(16).padStart(2, "0");
2190
1894
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
2191
1895
  }
2192
- function computeQuantiles2(values) {
1896
+ function computeQuantiles(values) {
2193
1897
  const nonZero = values.filter((v) => v > 0).sort((a, b) => a - b);
2194
1898
  if (nonZero.length === 0)
2195
1899
  return [0, 0, 0];
@@ -2199,7 +1903,7 @@ function computeQuantiles2(values) {
2199
1903
  };
2200
1904
  return [q(0.25), q(0.5), q(0.75)];
2201
1905
  }
2202
- function getLevel2(tokens, quantiles) {
1906
+ function getLevel(tokens, quantiles) {
2203
1907
  if (tokens <= 0)
2204
1908
  return 0;
2205
1909
  if (tokens <= quantiles[0])
@@ -2210,7 +1914,7 @@ function getLevel2(tokens, quantiles) {
2210
1914
  return 3;
2211
1915
  return 4;
2212
1916
  }
2213
- var MONTH_NAMES2 = [
1917
+ var MONTH_NAMES = [
2214
1918
  "Jan",
2215
1919
  "Feb",
2216
1920
  "Mar",
@@ -2265,7 +1969,7 @@ function renderProviderHeatmap(daily, since, until, heatmapColors, emptyColor) {
2265
1969
  const start = new Date(startStr + "T00:00:00Z");
2266
1970
  start.setUTCDate(start.getUTCDate() - start.getUTCDay());
2267
1971
  const allTokens = Array.from(tokenMap.values());
2268
- const quantiles = computeQuantiles2(allTokens);
1972
+ const quantiles = computeQuantiles(allTokens);
2269
1973
  const cells = [];
2270
1974
  const monthLabels = [];
2271
1975
  let lastMonth = -1;
@@ -2275,33 +1979,36 @@ function renderProviderHeatmap(daily, since, until, heatmapColors, emptyColor) {
2275
1979
  const row = current.getUTCDay();
2276
1980
  const dateStr = current.toISOString().slice(0, 10);
2277
1981
  const tokens = tokenMap.get(dateStr) ?? 0;
2278
- const level = getLevel2(tokens, quantiles);
2279
- const x = DAY_LABEL_WIDTH2 + col * (CELL_SIZE2 + CELL_GAP2);
2280
- const y = MONTH_LABEL_HEIGHT2 + row * (CELL_SIZE2 + CELL_GAP2);
1982
+ const level = getLevel(tokens, quantiles);
1983
+ const x = DAY_LABEL_WIDTH + col * (CELL_SIZE + CELL_GAP);
1984
+ const y = MONTH_LABEL_HEIGHT + row * (CELL_SIZE + CELL_GAP);
2281
1985
  const fill = level === 0 ? emptyColor : heatmapColors[level];
2282
1986
  const title = `${dateStr}: ${tokens.toLocaleString()} tokens`;
2283
- cells.push(`<rect x="${x}" y="${y}" width="${CELL_SIZE2}" height="${CELL_SIZE2}" fill="${escapeXml(fill)}" rx="3"><title>${escapeXml(title)}</title></rect>`);
1987
+ cells.push(`<rect x="${x}" y="${y}" width="${CELL_SIZE}" height="${CELL_SIZE}" fill="${escapeXml(fill)}" rx="3"><title>${escapeXml(title)}</title></rect>`);
2284
1988
  const month = current.getUTCMonth();
2285
1989
  if (month !== lastMonth && row === 0) {
2286
1990
  lastMonth = month;
2287
- monthLabels.push(`<text x="${x}" y="${MONTH_LABEL_HEIGHT2 - 8}" fill="__MUTED__" font-size="11" font-family="${escapeXml(FONT_FAMILY2)}">${escapeXml(MONTH_NAMES2[month] ?? "")}</text>`);
1991
+ monthLabels.push(`<text x="${x}" y="${MONTH_LABEL_HEIGHT - 8}" fill="__MUTED__" font-size="11" font-family="${escapeXml(FONT_FAMILY)}">${escapeXml(MONTH_NAMES[month] ?? "")}</text>`);
2288
1992
  }
2289
1993
  if (row === 6)
2290
1994
  col++;
2291
1995
  current.setUTCDate(current.getUTCDate() + 1);
2292
1996
  }
2293
1997
  const dayLabels = [
1998
+ { label: "Sun", row: 0 },
2294
1999
  { label: "Mon", row: 1 },
2000
+ { label: "Tue", row: 2 },
2295
2001
  { label: "Wed", row: 3 },
2002
+ { label: "Thu", row: 4 },
2296
2003
  { label: "Fri", row: 5 },
2297
- { label: "Sun", row: 0 }
2004
+ { label: "Sat", row: 6 }
2298
2005
  ].map((d) => {
2299
- const y = MONTH_LABEL_HEIGHT2 + d.row * (CELL_SIZE2 + CELL_GAP2) + CELL_SIZE2 - 2;
2300
- return `<text x="0" y="${y}" fill="__MUTED__" font-size="11" font-family="${escapeXml(FONT_FAMILY2)}">${escapeXml(d.label)}</text>`;
2006
+ const y = MONTH_LABEL_HEIGHT + d.row * (CELL_SIZE + CELL_GAP) + CELL_SIZE - 2;
2007
+ return `<text x="0" y="${y}" fill="__MUTED__" font-size="11" font-family="${escapeXml(FONT_FAMILY)}">${escapeXml(d.label)}</text>`;
2301
2008
  }).join("");
2302
2009
  const totalCols = col + 1;
2303
- const gridWidth = DAY_LABEL_WIDTH2 + totalCols * (CELL_SIZE2 + CELL_GAP2);
2304
- const height = MONTH_LABEL_HEIGHT2 + 7 * (CELL_SIZE2 + CELL_GAP2);
2010
+ const gridWidth = DAY_LABEL_WIDTH + totalCols * (CELL_SIZE + CELL_GAP);
2011
+ const height = MONTH_LABEL_HEIGHT + 7 * (CELL_SIZE + CELL_GAP);
2305
2012
  const svg = [dayLabels, ...monthLabels, ...cells].join(`
2306
2013
  `);
2307
2014
  return { svg, gridWidth, height };
@@ -2313,6 +2020,7 @@ function renderTerminalCardSvg(output, options) {
2313
2020
  const stats = output.aggregated;
2314
2021
  const { since, until } = output.dateRange;
2315
2022
  const providers = output.providers;
2023
+ const cardAccent = providers.length === 1 ? providers[0]?.colors.primary ?? theme.accent : theme.accent;
2316
2024
  const providerHeatmaps = providers.map((p) => {
2317
2025
  const heatmapColors = buildHeatmapScale(p.colors, isDark);
2318
2026
  return {
@@ -2322,7 +2030,7 @@ function renderTerminalCardSvg(output, options) {
2322
2030
  };
2323
2031
  });
2324
2032
  const maxHeatmapWidth = providerHeatmaps.reduce((max, ph) => Math.max(max, ph.heatmap.gridWidth), 0);
2325
- const minContentWidth = Math.max(maxHeatmapWidth, 700);
2033
+ const minContentWidth = Math.max(maxHeatmapWidth, MIN_CONTENT_WIDTH);
2326
2034
  const cardWidth = minContentWidth + pad * 2;
2327
2035
  const contentWidth = cardWidth - pad * 2;
2328
2036
  let y = 0;
@@ -2340,23 +2048,21 @@ function renderTerminalCardSvg(output, options) {
2340
2048
  for (const dot of dots) {
2341
2049
  sections.push(`<circle cx="${dot.cx}" cy="${dotY}" r="${DOT_RADIUS}" fill="${escapeXml(dot.color)}"/>`);
2342
2050
  }
2343
- const titleX = dots[2].cx + DOT_RADIUS + 20;
2344
- sections.push(`<text x="${titleX}" y="${dotY + 5}" fill="${escapeXml(theme.muted)}" font-size="13" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="500">${escapeXml("tokenleak")}</text>`);
2345
2051
  sections.push(`<line x1="0" y1="${TITLEBAR_HEIGHT}" x2="${cardWidth}" y2="${TITLEBAR_HEIGHT}" stroke="${escapeXml(theme.titlebarBorder)}" stroke-width="1"/>`);
2346
2052
  y = TITLEBAR_HEIGHT + pad * 0.6;
2347
- sections.push(`<text x="${pad}" y="${y + 16}" font-size="15" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="500">` + `<tspan fill="${escapeXml(theme.accent)}">$</tspan>` + `<tspan fill="${escapeXml(theme.fg)}"> tokenleak</tspan>` + `<tspan fill="${escapeXml(theme.accent)}">_</tspan>` + `</text>`);
2053
+ sections.push(`<text x="${pad}" y="${y + 16}" font-size="15" font-family="${escapeXml(FONT_FAMILY)}" font-weight="500">` + `<tspan fill="${escapeXml(cardAccent)}">$</tspan>` + `<tspan fill="${escapeXml(theme.fg)}"> tokenleak</tspan>` + `<tspan fill="${escapeXml(cardAccent)}">_</tspan>` + `</text>`);
2348
2054
  y += 40;
2349
2055
  const dateRangeText = formatDateRange(since, until);
2350
- sections.push(`<text x="${pad}" y="${y + 14}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="600" letter-spacing="2">${escapeXml(dateRangeText)}</text>`);
2056
+ sections.push(`<text x="${pad}" y="${y + 14}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY)}" font-weight="600" letter-spacing="2">${escapeXml(dateRangeText)}</text>`);
2351
2057
  y += 40;
2352
2058
  for (let pi = 0;pi < providerHeatmaps.length; pi++) {
2353
2059
  const { provider, heatmap, heatmapColors } = providerHeatmaps[pi];
2354
2060
  const provDotRadius = 5;
2355
2061
  const provColor = provider.colors.primary;
2356
2062
  sections.push(`<circle cx="${pad + provDotRadius}" cy="${y + 8}" r="${provDotRadius}" fill="${escapeXml(provColor)}"/>`);
2357
- sections.push(`<text x="${pad + provDotRadius * 2 + 10}" y="${y + 13}" fill="${escapeXml(theme.fg)}" font-size="14" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="600">${escapeXml(provider.displayName)}</text>`);
2063
+ sections.push(`<text x="${pad + provDotRadius * 2 + 10}" y="${y + 13}" fill="${escapeXml(theme.fg)}" font-size="14" font-family="${escapeXml(FONT_FAMILY)}" font-weight="600">${escapeXml(provider.displayName)}</text>`);
2358
2064
  const summaryText = `${formatNumber(provider.totalTokens)} tokens \xB7 ${formatCost(provider.totalCost)}`;
2359
- sections.push(`<text x="${cardWidth - pad}" y="${y + 13}" fill="${escapeXml(theme.muted)}" font-size="11" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="500" text-anchor="end">${escapeXml(summaryText)}</text>`);
2065
+ sections.push(`<text x="${cardWidth - pad}" y="${y + 13}" fill="${escapeXml(theme.muted)}" font-size="11" font-family="${escapeXml(FONT_FAMILY)}" font-weight="500" text-anchor="end">${escapeXml(summaryText)}</text>`);
2360
2066
  y += 28;
2361
2067
  const heatmapSvg = heatmap.svg.replace(/__MUTED__/g, escapeXml(theme.muted));
2362
2068
  sections.push(`<g transform="translate(${pad}, ${y})">`);
@@ -2372,13 +2078,13 @@ function renderTerminalCardSvg(output, options) {
2372
2078
  }
2373
2079
  }
2374
2080
  if (providers.length === 0) {
2375
- sections.push(`<text x="${pad}" y="${y + 14}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="500">${escapeXml("No provider data")}</text>`);
2081
+ sections.push(`<text x="${pad}" y="${y + 14}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY)}" font-weight="500">${escapeXml("No provider data")}</text>`);
2376
2082
  y += 32;
2377
2083
  }
2378
2084
  sections.push(`<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="${escapeXml(theme.border)}" stroke-width="1"/>`);
2379
2085
  y += 28;
2380
2086
  if (providers.length > 1) {
2381
- sections.push(`<text x="${pad}" y="${y}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="600" letter-spacing="2">${escapeXml("OVERALL")}</text>`);
2087
+ sections.push(`<text x="${pad}" y="${y}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY)}" font-weight="600" letter-spacing="2">${escapeXml("OVERALL")}</text>`);
2382
2088
  y += 24;
2383
2089
  }
2384
2090
  const statColWidth = contentWidth / STAT_GRID_COLS;
@@ -2396,9 +2102,9 @@ function renderTerminalCardSvg(output, options) {
2396
2102
  for (let i = 0;i < row.length; i++) {
2397
2103
  const stat = row[i];
2398
2104
  const x = pad + i * statColWidth;
2399
- sections.push(`<text x="${x}" y="${startY}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="600" letter-spacing="1.5">${escapeXml(stat.label)}</text>`);
2400
- const valueColor = stat.accent ? theme.accent : theme.fg;
2401
- sections.push(`<text x="${x}" y="${startY + 28}" fill="${escapeXml(valueColor)}" font-size="22" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="700">${escapeXml(stat.value)}</text>`);
2105
+ sections.push(`<text x="${x}" y="${startY}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY)}" font-weight="600" letter-spacing="1.5">${escapeXml(stat.label)}</text>`);
2106
+ const valueColor = stat.accent ? cardAccent : theme.fg;
2107
+ sections.push(`<text x="${x}" y="${startY + 28}" fill="${escapeXml(valueColor)}" font-size="22" font-family="${escapeXml(FONT_FAMILY)}" font-weight="700">${escapeXml(stat.value)}</text>`);
2402
2108
  }
2403
2109
  }
2404
2110
  renderStatRow(statsRow1, y);
@@ -2408,21 +2114,22 @@ function renderTerminalCardSvg(output, options) {
2408
2114
  y += 8;
2409
2115
  sections.push(`<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="${escapeXml(theme.border)}" stroke-width="1"/>`);
2410
2116
  y += 28;
2411
- sections.push(`<text x="${pad}" y="${y}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="600" letter-spacing="2">${escapeXml("TOP MODELS")}</text>`);
2117
+ sections.push(`<text x="${pad}" y="${y}" fill="${escapeXml(theme.muted)}" font-size="10" font-family="${escapeXml(FONT_FAMILY)}" font-weight="600" letter-spacing="2">${escapeXml("TOP MODELS")}</text>`);
2412
2118
  y += 24;
2413
2119
  const topModels2 = stats.topModels.slice(0, 3);
2414
- const modelNameWidth = 200;
2415
- const percentWidth = 60;
2416
- const barMaxWidth = contentWidth - modelNameWidth - percentWidth - 20;
2417
- for (const model of topModels2) {
2120
+ const modelNameWidth = MODEL_NAME_WIDTH;
2121
+ const barGap = MODEL_BAR_GAP;
2122
+ const percentX = cardWidth - pad;
2123
+ const barX = pad + modelNameWidth;
2124
+ const barMaxWidth = Math.max(48, percentX - barX - barGap);
2125
+ for (const [index, model] of topModels2.entries()) {
2418
2126
  const barWidth = Math.max(4, model.percentage / 100 * barMaxWidth);
2419
- sections.push(`<text x="${pad}" y="${y + MODEL_BAR_HEIGHT + 4}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="400">${escapeXml(model.model)}</text>`);
2420
- const barX = pad + modelNameWidth;
2127
+ sections.push(`<text x="${pad}" y="${y + MODEL_BAR_HEIGHT - 1}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY)}" font-weight="400">${escapeXml(model.model)}</text>`);
2421
2128
  sections.push(`<rect x="${barX}" y="${y}" width="${barMaxWidth}" height="${MODEL_BAR_HEIGHT}" rx="4" fill="${escapeXml(theme.barTrack)}"/>`);
2422
- const gradId = `grad-${model.model.replace(/[^a-zA-Z0-9]/g, "")}`;
2423
- sections.push(`<defs><linearGradient id="${escapeXml(gradId)}" x1="0%" y1="0%" x2="100%" y2="0%">` + `<stop offset="0%" stop-color="${escapeXml(theme.accent)}44"/>` + `<stop offset="100%" stop-color="${escapeXml(theme.accent)}"/>` + `</linearGradient></defs>`);
2129
+ const gradId = `grad-${index}-${model.model.replace(/[^a-zA-Z0-9]/g, "")}`;
2130
+ sections.push(`<defs><linearGradient id="${escapeXml(gradId)}" x1="0%" y1="0%" x2="100%" y2="0%">` + `<stop offset="0%" stop-color="${escapeXml(cardAccent)}44"/>` + `<stop offset="100%" stop-color="${escapeXml(cardAccent)}"/>` + `</linearGradient></defs>`);
2424
2131
  sections.push(`<rect x="${barX}" y="${y}" width="${barWidth}" height="${MODEL_BAR_HEIGHT}" rx="4" fill="url(#${escapeXml(gradId)})"/>`);
2425
- sections.push(`<text x="${barX + barMaxWidth + 12}" y="${y + MODEL_BAR_HEIGHT + 4}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY2)}" font-weight="500" text-anchor="end">${escapeXml(`${model.percentage.toFixed(0)}%`)}</text>`);
2132
+ sections.push(`<text x="${percentX}" y="${y + MODEL_BAR_HEIGHT - 1}" fill="${escapeXml(theme.muted)}" font-size="12" font-family="${escapeXml(FONT_FAMILY)}" font-weight="500" text-anchor="end">${escapeXml(`${model.percentage.toFixed(0)}%`)}</text>`);
2426
2133
  y += 32;
2427
2134
  }
2428
2135
  y += pad * 0.5;
@@ -2430,16 +2137,26 @@ function renderTerminalCardSvg(output, options) {
2430
2137
  const svg = sections.join(`
2431
2138
  `).replace("__CARD_HEIGHT__", String(cardHeight));
2432
2139
  return [
2433
- `<svg xmlns="http://www.w3.org/2000/svg" width="${cardWidth}" height="${cardHeight}" viewBox="0 0 ${cardWidth} ${cardHeight}" shape-rendering="geometricPrecision" text-rendering="geometricPrecision">`,
2140
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${cardWidth}" height="${cardHeight}" viewBox="0 0 ${cardWidth} ${cardHeight}" shape-rendering="geometricPrecision" text-rendering="optimizeLegibility" color-rendering="optimizeQuality">`,
2434
2141
  svg,
2435
2142
  "</svg>"
2436
2143
  ].join(`
2437
2144
  `);
2438
2145
  }
2439
2146
 
2147
+ // packages/renderers/dist/svg/svg-renderer.js
2148
+ class SvgRenderer {
2149
+ format = "svg";
2150
+ async render(output, options) {
2151
+ return renderTerminalCardSvg(output, {
2152
+ ...options,
2153
+ format: "svg"
2154
+ });
2155
+ }
2156
+ }
2440
2157
  // packages/renderers/dist/png/png-renderer.js
2441
2158
  import sharp from "sharp";
2442
- var PNG_DENSITY = 288;
2159
+ var PNG_DENSITY = 432;
2443
2160
 
2444
2161
  class PngRenderer {
2445
2162
  format = "png";
@@ -2448,7 +2165,9 @@ class PngRenderer {
2448
2165
  const pngBuffer = await sharp(Buffer.from(svgString), {
2449
2166
  density: PNG_DENSITY
2450
2167
  }).png({
2451
- compressionLevel: 9
2168
+ adaptiveFiltering: true,
2169
+ compressionLevel: 9,
2170
+ force: true
2452
2171
  }).toBuffer();
2453
2172
  return pngBuffer;
2454
2173
  }
@@ -2475,20 +2194,8 @@ var HEATMAP_BLOCKS = {
2475
2194
  DARK: "\u2593",
2476
2195
  MEDIUM: "\u2592",
2477
2196
  LIGHT: "\u2591",
2478
- EMPTY: " "
2197
+ EMPTY: "\xB7"
2479
2198
  };
2480
- function intensityBlock(value, max) {
2481
- if (max <= 0 || value <= 0)
2482
- return HEATMAP_BLOCKS.EMPTY;
2483
- const ratio = value / max;
2484
- if (ratio >= 0.75)
2485
- return HEATMAP_BLOCKS.FULL;
2486
- if (ratio >= 0.5)
2487
- return HEATMAP_BLOCKS.DARK;
2488
- if (ratio >= 0.25)
2489
- return HEATMAP_BLOCKS.MEDIUM;
2490
- return HEATMAP_BLOCKS.LIGHT;
2491
- }
2492
2199
  function intensityColor(value, max) {
2493
2200
  if (max <= 0 || value <= 0)
2494
2201
  return "dim";
@@ -2503,9 +2210,10 @@ function intensityColor(value, max) {
2503
2210
  }
2504
2211
 
2505
2212
  // packages/renderers/dist/terminal/heatmap.js
2506
- var DAY_LABELS3 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2213
+ var DAY_LABELS2 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
2507
2214
  var MONTH_LABELS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2508
- var DAY_LABEL_WIDTH3 = 4;
2215
+ var DAY_LABEL_WIDTH2 = 4;
2216
+ var WEEK_COLUMN_WIDTH = 2;
2509
2217
  var LEGEND_TEXT = "Less";
2510
2218
  var LEGEND_TEXT_MORE = "More";
2511
2219
  function buildUsageMap(daily) {
@@ -2515,83 +2223,117 @@ function buildUsageMap(daily) {
2515
2223
  }
2516
2224
  return map;
2517
2225
  }
2518
- function renderTerminalHeatmap(daily, options) {
2519
- if (daily.length === 0) {
2520
- return " No usage data available.";
2226
+ function computeQuantiles2(values) {
2227
+ const nonZero = values.filter((value) => value > 0).sort((a, b) => a - b);
2228
+ if (nonZero.length === 0)
2229
+ return [0, 0, 0];
2230
+ const quantile = (ratio) => {
2231
+ const index = Math.floor(ratio * (nonZero.length - 1));
2232
+ return nonZero[index] ?? 0;
2233
+ };
2234
+ return [quantile(0.25), quantile(0.5), quantile(0.75)];
2235
+ }
2236
+ function getHeatmapBlock(tokens, quantiles) {
2237
+ if (tokens <= 0)
2238
+ return HEATMAP_BLOCKS.EMPTY;
2239
+ if (tokens <= quantiles[0])
2240
+ return HEATMAP_BLOCKS.LIGHT;
2241
+ if (tokens <= quantiles[1])
2242
+ return HEATMAP_BLOCKS.MEDIUM;
2243
+ if (tokens <= quantiles[2])
2244
+ return HEATMAP_BLOCKS.DARK;
2245
+ return HEATMAP_BLOCKS.FULL;
2246
+ }
2247
+ function formatDate(date) {
2248
+ const year = date.getUTCFullYear();
2249
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
2250
+ const day = String(date.getUTCDate()).padStart(2, "0");
2251
+ return `${year}-${month}-${day}`;
2252
+ }
2253
+ function buildMonthHeader(weeks) {
2254
+ const header = Array.from({ length: weeks.length * WEEK_COLUMN_WIDTH }, () => " ");
2255
+ let lastMonth = -1;
2256
+ let nextFreeIndex = 0;
2257
+ for (let weekIndex = 0;weekIndex < weeks.length; weekIndex++) {
2258
+ const firstDay = weeks[weekIndex]?.[0];
2259
+ if (!firstDay)
2260
+ continue;
2261
+ const month = firstDay.getUTCMonth();
2262
+ if (month === lastMonth)
2263
+ continue;
2264
+ lastMonth = month;
2265
+ const desiredStart = weekIndex * WEEK_COLUMN_WIDTH;
2266
+ const startIndex = Math.max(desiredStart, nextFreeIndex);
2267
+ const remaining = header.length - startIndex;
2268
+ if (remaining <= 0)
2269
+ continue;
2270
+ const fullLabel = MONTH_LABELS[month] ?? "";
2271
+ const label = remaining >= fullLabel.length ? fullLabel : remaining >= 2 ? fullLabel.slice(0, 2) : fullLabel.slice(0, 1);
2272
+ for (let offset = 0;offset < label.length; offset++) {
2273
+ header[startIndex + offset] = label[offset] ?? " ";
2274
+ }
2275
+ nextFreeIndex = startIndex + label.length + 1;
2521
2276
  }
2522
- const usageMap = buildUsageMap(daily);
2523
- const maxTokens = Math.max(...usageMap.values(), 0);
2524
- const dates = daily.map((d) => d.date).sort();
2525
- const startDate = new Date(dates[0]);
2526
- const endDate = new Date(dates[dates.length - 1]);
2277
+ return `${" ".repeat(DAY_LABEL_WIDTH2)}${header.join("")}`;
2278
+ }
2279
+ function buildWeeks(startDate, endDate) {
2527
2280
  const alignedStart = new Date(startDate);
2528
- alignedStart.setDate(alignedStart.getDate() - alignedStart.getDay());
2281
+ alignedStart.setUTCDate(alignedStart.getUTCDate() - alignedStart.getUTCDay());
2529
2282
  const weeks = [];
2530
- const current = new Date(alignedStart);
2531
- while (current <= endDate) {
2283
+ const cursor = new Date(alignedStart);
2284
+ while (cursor <= endDate) {
2532
2285
  const week = [];
2533
- for (let d = 0;d < 7; d++) {
2534
- week.push(new Date(current));
2535
- current.setDate(current.getDate() + 1);
2286
+ for (let day = 0;day < 7; day++) {
2287
+ week.push(new Date(cursor));
2288
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
2536
2289
  }
2537
2290
  weeks.push(week);
2538
2291
  }
2539
- const availableWidth = options.width - DAY_LABEL_WIDTH3;
2540
- const maxWeeks = Math.min(weeks.length, availableWidth);
2541
- const displayWeeks = weeks.slice(Math.max(0, weeks.length - maxWeeks));
2542
- const lines = [];
2543
- let monthHeader = " ".repeat(DAY_LABEL_WIDTH3);
2544
- let lastMonth = -1;
2545
- for (const week of displayWeeks) {
2546
- const month = week[0].getMonth();
2547
- if (month !== lastMonth) {
2548
- monthHeader += MONTH_LABELS[month];
2549
- lastMonth = month;
2550
- const labelLen = MONTH_LABELS[month].length;
2551
- } else {
2552
- monthHeader += " ";
2553
- }
2554
- }
2555
- if (monthHeader.length > options.width) {
2556
- monthHeader = monthHeader.slice(0, options.width);
2292
+ return weeks;
2293
+ }
2294
+ function renderTerminalHeatmap(daily, options) {
2295
+ if (daily.length === 0) {
2296
+ return " No usage data available.";
2557
2297
  }
2558
- lines.push(monthHeader);
2559
- for (let dayIdx = 0;dayIdx < 7; dayIdx++) {
2560
- const label = dayIdx % 2 === 1 ? DAY_LABELS3[dayIdx] : " ";
2561
- let line = label + " ";
2562
- line = line.slice(0, DAY_LABEL_WIDTH3);
2298
+ const usageMap = buildUsageMap(daily);
2299
+ const quantiles = computeQuantiles2(Array.from(usageMap.values()));
2300
+ const maxTokens = Math.max(...usageMap.values(), 0);
2301
+ const dates = daily.map((entry) => entry.date).sort();
2302
+ const startDate = new Date(`${dates[0]}T00:00:00Z`);
2303
+ const endDate = new Date(`${dates[dates.length - 1]}T00:00:00Z`);
2304
+ const weeks = buildWeeks(startDate, endDate);
2305
+ const availableColumns = Math.max(WEEK_COLUMN_WIDTH, options.width - DAY_LABEL_WIDTH2);
2306
+ const maxWeeks = Math.max(1, Math.floor(availableColumns / WEEK_COLUMN_WIDTH));
2307
+ const displayWeeks = weeks.slice(Math.max(0, weeks.length - maxWeeks));
2308
+ const lines = [buildMonthHeader(displayWeeks)];
2309
+ for (let dayIndex = 0;dayIndex < 7; dayIndex++) {
2310
+ const label = DAY_LABELS2[dayIndex] ?? " ";
2311
+ let line = `${label} `.slice(0, DAY_LABEL_WIDTH2);
2563
2312
  for (const week of displayWeeks) {
2564
- const date = week[dayIdx];
2565
- if (!date || date > endDate || date < startDate) {
2566
- line += " ";
2313
+ const date = week[dayIndex];
2314
+ if (!date || date < startDate || date > endDate) {
2315
+ line += `${HEATMAP_BLOCKS.EMPTY} `;
2567
2316
  continue;
2568
2317
  }
2569
- const dateStr = formatDate(date);
2570
- const tokens = usageMap.get(dateStr) ?? 0;
2571
- const block = intensityBlock(tokens, maxTokens);
2318
+ const dateString = formatDate(date);
2319
+ const tokens = usageMap.get(dateString) ?? 0;
2320
+ const block = getHeatmapBlock(tokens, quantiles);
2572
2321
  const color = intensityColor(tokens, maxTokens);
2573
- line += colorize(block, color, options.noColor);
2322
+ line += `${colorize(block, color, options.noColor)} `;
2574
2323
  }
2575
- lines.push(line);
2324
+ lines.push(line.trimEnd());
2576
2325
  }
2577
- const legendBlocks = [
2326
+ const legend = `${" ".repeat(DAY_LABEL_WIDTH2)}${LEGEND_TEXT} ${[
2578
2327
  HEATMAP_BLOCKS.EMPTY,
2579
2328
  HEATMAP_BLOCKS.LIGHT,
2580
2329
  HEATMAP_BLOCKS.MEDIUM,
2581
2330
  HEATMAP_BLOCKS.DARK,
2582
2331
  HEATMAP_BLOCKS.FULL
2583
- ];
2584
- const legend = `${" ".repeat(DAY_LABEL_WIDTH3)}${LEGEND_TEXT} ${legendBlocks.join("")} ${LEGEND_TEXT_MORE}`;
2332
+ ].join("")} ${LEGEND_TEXT_MORE}`;
2585
2333
  lines.push(legend);
2586
2334
  return lines.join(`
2587
2335
  `);
2588
2336
  }
2589
- function formatDate(date) {
2590
- const y = date.getFullYear();
2591
- const m = String(date.getMonth() + 1).padStart(2, "0");
2592
- const d = String(date.getDate()).padStart(2, "0");
2593
- return `${y}-${m}-${d}`;
2594
- }
2595
2337
 
2596
2338
  // packages/renderers/dist/terminal/dashboard.js
2597
2339
  var BOX_H = "\u2500";
@@ -2686,11 +2428,18 @@ function renderDayOfWeek(stats, width, noColor2) {
2686
2428
  }
2687
2429
  function renderTopModels(stats, width, noColor2) {
2688
2430
  const lines = [];
2431
+ const nameWidth = Math.min(28, Math.max(16, Math.floor(width * 0.35)));
2432
+ const pctWidth = 4;
2433
+ const barGap = 2;
2434
+ const barWidth = Math.max(8, width - nameWidth - pctWidth - 6);
2689
2435
  for (const model of stats.topModels.slice(0, 5)) {
2690
2436
  const pct = formatSharePercent(model.percentage);
2691
- const tokens = formatTokens(model.tokens);
2692
- const line = ` ${colorize(model.model, "yellow", noColor2)} ${tokens} ${pct}`;
2693
- lines.push(line.length > width ? line.slice(0, width) : line);
2437
+ const normalizedName = model.model.length > nameWidth ? `${model.model.slice(0, nameWidth - 1)}\u2026` : model.model;
2438
+ const fillLength = Math.max(1, Math.round(model.percentage / 100 * barWidth));
2439
+ const fill = colorize(BAR_CHAR.repeat(fillLength), "green", noColor2);
2440
+ const track = "\u2591".repeat(Math.max(0, barWidth - fillLength));
2441
+ const line = ` ${colorize(normalizedName.padEnd(nameWidth), "yellow", noColor2)}${" ".repeat(barGap)}${fill}${track}${" ".repeat(barGap)}${pct.padStart(pctWidth)}`;
2442
+ lines.push(line);
2694
2443
  }
2695
2444
  return lines.join(`
2696
2445
  `);
@@ -2757,7 +2506,8 @@ function renderDashboard(output, options) {
2757
2506
  }
2758
2507
  for (let i = 0;i < output.providers.length; i++) {
2759
2508
  const provider = output.providers[i];
2760
- sections.push(renderProviderSection(provider, output.aggregated, width, noColor2, options.showInsights));
2509
+ const providerStats = aggregate(provider.daily, output.dateRange.until);
2510
+ sections.push(renderProviderSection(provider, providerStats, width, noColor2, options.showInsights));
2761
2511
  if (i < output.providers.length - 1) {
2762
2512
  sections.push("");
2763
2513
  sections.push(divider(width));
@@ -2802,7 +2552,7 @@ class TerminalRenderer {
2802
2552
  }
2803
2553
  }
2804
2554
  // packages/renderers/dist/live/template.js
2805
- var MONTH_NAMES3 = [
2555
+ var MONTH_NAMES2 = [
2806
2556
  "Jan",
2807
2557
  "Feb",
2808
2558
  "Mar",
@@ -2858,7 +2608,7 @@ function computeQuantiles3(values) {
2858
2608
  };
2859
2609
  return [q(0.25), q(0.5), q(0.75)];
2860
2610
  }
2861
- function getLevel3(tokens, quantiles) {
2611
+ function getLevel2(tokens, quantiles) {
2862
2612
  if (tokens <= 0)
2863
2613
  return 0;
2864
2614
  if (tokens <= quantiles[0])
@@ -2918,12 +2668,12 @@ function buildHeatmapCells(daily, since, until) {
2918
2668
  const row = current.getUTCDay();
2919
2669
  const dateStr = current.toISOString().slice(0, 10);
2920
2670
  const tokens = tokenMap.get(dateStr) ?? 0;
2921
- const level = getLevel3(tokens, quantiles);
2671
+ const level = getLevel2(tokens, quantiles);
2922
2672
  cells.push({ date: dateStr, tokens, level, row, col });
2923
2673
  const month = current.getUTCMonth();
2924
2674
  if (month !== lastMonth && row === 0) {
2925
2675
  lastMonth = month;
2926
- months.push({ label: MONTH_NAMES3[month] ?? "", col });
2676
+ months.push({ label: MONTH_NAMES2[month] ?? "", col });
2927
2677
  }
2928
2678
  if (row === 6)
2929
2679
  col++;
@@ -2934,21 +2684,17 @@ function buildHeatmapCells(daily, since, until) {
2934
2684
  function renderProviderHeatmapHtml(provider, since, until, isDark, emptyCell) {
2935
2685
  const heatmapColors = buildHeatmapScale2(provider.colors, isDark);
2936
2686
  const { cells, months, totalCols } = buildHeatmapCells(provider.daily, since, until);
2937
- const cellSize = 16;
2938
- const cellGap = 4;
2939
- const dayLabelWidth = 44;
2940
- const monthLabelHeight = 24;
2941
- const heatmapWidth = dayLabelWidth + totalCols * (cellSize + cellGap);
2942
- const heatmapHeight = monthLabelHeight + 7 * (cellSize + cellGap);
2687
+ const heatmapWidth = DAY_LABEL_WIDTH + totalCols * (CELL_SIZE + CELL_GAP);
2688
+ const heatmapHeight = MONTH_LABEL_HEIGHT + 7 * (CELL_SIZE + CELL_GAP);
2943
2689
  const cellsHtml = cells.map((c) => {
2944
- const x = dayLabelWidth + c.col * (cellSize + cellGap);
2945
- const y = monthLabelHeight + c.row * (cellSize + cellGap);
2690
+ const x = DAY_LABEL_WIDTH + c.col * (CELL_SIZE + CELL_GAP);
2691
+ const y = MONTH_LABEL_HEIGHT + c.row * (CELL_SIZE + CELL_GAP);
2946
2692
  const fill = c.level === 0 ? emptyCell : heatmapColors[c.level];
2947
2693
  return `<div class="heatmap-cell" style="left:${x}px;top:${y}px;background:${fill}" data-date="${esc(c.date)}" data-tokens="${c.tokens}"></div>`;
2948
2694
  }).join(`
2949
2695
  `);
2950
2696
  const monthLabelsHtml = months.map((m) => {
2951
- const x = dayLabelWidth + m.col * (cellSize + cellGap);
2697
+ const x = DAY_LABEL_WIDTH + m.col * (CELL_SIZE + CELL_GAP);
2952
2698
  return `<span class="month-label" style="left:${x}px">${esc(m.label)}</span>`;
2953
2699
  }).join(`
2954
2700
  `);
@@ -2958,7 +2704,7 @@ function renderProviderHeatmapHtml(provider, since, until, isDark, emptyCell) {
2958
2704
  { label: "Fri", row: 5 },
2959
2705
  { label: "Sun", row: 0 }
2960
2706
  ].map((d) => {
2961
- const y = monthLabelHeight + d.row * (cellSize + cellGap) + cellSize - 2;
2707
+ const y = MONTH_LABEL_HEIGHT + d.row * (CELL_SIZE + CELL_GAP) + CELL_SIZE - 2;
2962
2708
  return `<span class="day-label" style="top:${y - 10}px">${d.label}</span>`;
2963
2709
  }).join(`
2964
2710
  `);
@@ -3080,8 +2826,8 @@ function generateHtml(output, options) {
3080
2826
  .heatmap-container { position: relative; margin-bottom: 8px; }
3081
2827
  .heatmap-cell {
3082
2828
  position: absolute;
3083
- width: 16px;
3084
- height: 16px;
2829
+ width: ${CELL_SIZE}px;
2830
+ height: ${CELL_SIZE}px;
3085
2831
  border-radius: 3px;
3086
2832
  cursor: pointer;
3087
2833
  }
@@ -3111,11 +2857,17 @@ function generateHtml(output, options) {
3111
2857
  .stat-value.accent { color: ${accent}; }
3112
2858
  .models-section { margin-top: 8px; }
3113
2859
  .models-label { color: ${muted}; font-size: 10px; font-weight: 600; letter-spacing: 2px; margin-bottom: 16px; }
3114
- .model-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
3115
- .model-name { color: ${muted}; font-size: 12px; width: 200px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3116
- .model-bar-track { flex: 1; height: 8px; background: ${barTrack}; border-radius: 4px; overflow: hidden; }
2860
+ .model-row {
2861
+ display: grid;
2862
+ grid-template-columns: ${MODEL_NAME_WIDTH}px minmax(0, 1fr) ${MODEL_PERCENT_WIDTH}px;
2863
+ align-items: center;
2864
+ column-gap: ${MODEL_BAR_GAP}px;
2865
+ margin-bottom: 16px;
2866
+ }
2867
+ .model-name { color: ${muted}; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2868
+ .model-bar-track { width: 100%; height: 8px; background: ${barTrack}; border-radius: 4px; overflow: hidden; }
3117
2869
  .model-bar-fill { height: 100%; border-radius: 4px; background: linear-gradient(90deg, ${accent}44, ${accent}); }
3118
- .model-pct { color: ${muted}; font-size: 12px; width: 40px; text-align: right; flex-shrink: 0; }
2870
+ .model-pct { color: ${muted}; font-size: 12px; text-align: right; }
3119
2871
  .refresh-btn {
3120
2872
  display: inline-flex;
3121
2873
  align-items: center;
@@ -3400,6 +3152,171 @@ async function uploadToGist(content, filename, description) {
3400
3152
  // packages/cli/src/cli.ts
3401
3153
  var FORMAT_VALUES = ["json", "svg", "png", "terminal"];
3402
3154
  var THEME_VALUES = ["dark", "light"];
3155
+ var PROVIDER_SHORTCUTS = {
3156
+ claude: "claude-code",
3157
+ codex: "codex",
3158
+ openCode: "open-code"
3159
+ };
3160
+ var PROVIDER_ALIASES = {
3161
+ anthropic: "claude-code",
3162
+ claude: "claude-code",
3163
+ "claude-code": "claude-code",
3164
+ claudecode: "claude-code",
3165
+ codex: "codex",
3166
+ openai: "codex",
3167
+ "open-code": "open-code",
3168
+ open_code: "open-code",
3169
+ opencode: "open-code"
3170
+ };
3171
+ var PROVIDER_ALIAS_GROUPS = {
3172
+ "claude-code": ["anthropic", "claude", "claudecode"],
3173
+ codex: ["openai"],
3174
+ "open-code": ["opencode", "open_code"]
3175
+ };
3176
+ function normalizeProviderToken(token) {
3177
+ const normalized = token.trim().toLowerCase().replace(/\s+/g, "-");
3178
+ return PROVIDER_ALIASES[normalized] ?? normalized;
3179
+ }
3180
+ function getRequestedProviders(config) {
3181
+ const requested = new Set;
3182
+ if (config.provider) {
3183
+ for (const token of config.provider.split(",")) {
3184
+ const normalized = normalizeProviderToken(token);
3185
+ if (normalized) {
3186
+ requested.add(normalized);
3187
+ }
3188
+ }
3189
+ }
3190
+ if (config.claude)
3191
+ requested.add(PROVIDER_SHORTCUTS.claude);
3192
+ if (config.codex)
3193
+ requested.add(PROVIDER_SHORTCUTS.codex);
3194
+ if (config.openCode)
3195
+ requested.add(PROVIDER_SHORTCUTS.openCode);
3196
+ return requested;
3197
+ }
3198
+ function providerMatchesFilter(provider, requested) {
3199
+ if (requested.size === 0)
3200
+ return true;
3201
+ const candidates = [
3202
+ normalizeProviderToken(provider.name),
3203
+ normalizeProviderToken(provider.displayName)
3204
+ ];
3205
+ return candidates.some((candidate) => requested.has(candidate));
3206
+ }
3207
+ function buildHelpText() {
3208
+ return [
3209
+ `tokenleak ${VERSION}`,
3210
+ "Visualize AI coding assistant token usage across providers.",
3211
+ "",
3212
+ "Usage:",
3213
+ " tokenleak [flags]",
3214
+ "",
3215
+ "Provider Shortcuts:",
3216
+ " --claude Only include Claude Code",
3217
+ " --codex Only include Codex",
3218
+ " --open-code Only include OpenCode",
3219
+ " --all-providers Ignore provider filters and use every available provider",
3220
+ " --list-providers Show registered providers and aliases",
3221
+ "",
3222
+ "Flags:",
3223
+ " -f, --format <format> Output format: terminal, png, svg, json",
3224
+ " -t, --theme <theme> Theme for png/svg/live output: dark, light",
3225
+ " -s, --since <date> Start date in YYYY-MM-DD format",
3226
+ " -u, --until <date> End date in YYYY-MM-DD format",
3227
+ ` -d, --days <number> Number of trailing days to include (default: ${DEFAULT_DAYS})`,
3228
+ " -o, --output <path> Write output to a file and infer format from extension",
3229
+ " -w, --width <number> Terminal render width",
3230
+ " -p, --provider <list> Provider filter list, comma-separated",
3231
+ " --compare <range> Compare against YYYY-MM-DD..YYYY-MM-DD or auto",
3232
+ " --clipboard Copy rendered output to the clipboard",
3233
+ " --open Open the generated output file",
3234
+ " --upload <target> Upload rendered output, currently: gist",
3235
+ " -L, --live-server Start the interactive local dashboard",
3236
+ " --no-color Disable ANSI colors",
3237
+ " --no-insights Hide insights in terminal mode",
3238
+ " --help Show this help",
3239
+ " --version Show version information",
3240
+ "",
3241
+ "Examples:",
3242
+ " tokenleak",
3243
+ " tokenleak --claude --days 30",
3244
+ " tokenleak --codex --format png --output codex.png",
3245
+ " tokenleak --open-code --since 2026-01-01 --until 2026-03-01",
3246
+ " tokenleak --provider claude,codex --format svg --output usage.svg",
3247
+ " tokenleak --provider anthropic,openai",
3248
+ " tokenleak --list-providers",
3249
+ " tokenleak --compare auto --format terminal",
3250
+ " tokenleak --live-server --theme light",
3251
+ "",
3252
+ "Version:",
3253
+ ` CLI ${VERSION}`,
3254
+ ` Schema ${SCHEMA_VERSION}`,
3255
+ ""
3256
+ ].join(`
3257
+ `);
3258
+ }
3259
+ function buildVersionText() {
3260
+ return `tokenleak ${VERSION}
3261
+ schema ${SCHEMA_VERSION}
3262
+ `;
3263
+ }
3264
+ function normalizeCliArg(arg) {
3265
+ const flagMap = {
3266
+ "--all-providers": "--allProviders",
3267
+ "--list-providers": "--listProviders",
3268
+ "--open-code": "--openCode",
3269
+ "--live-server": "--liveServer",
3270
+ "--no-color": "--noColor",
3271
+ "--no-insights": "--noInsights"
3272
+ };
3273
+ return flagMap[arg] ?? arg;
3274
+ }
3275
+ function normalizeCliArgv(argv) {
3276
+ const normalized = argv.map(normalizeCliArg);
3277
+ const result = [];
3278
+ for (let i = 0;i < normalized.length; i++) {
3279
+ const arg = normalized[i];
3280
+ if (arg === "--provider" || arg === "-p") {
3281
+ result.push(arg);
3282
+ const providerParts = [];
3283
+ let j = i + 1;
3284
+ while (j < normalized.length) {
3285
+ const next = normalized[j];
3286
+ if (next.startsWith("-"))
3287
+ break;
3288
+ providerParts.push(next);
3289
+ j++;
3290
+ }
3291
+ if (providerParts.length > 0) {
3292
+ result.push(providerParts.join(" "));
3293
+ i = j - 1;
3294
+ }
3295
+ continue;
3296
+ }
3297
+ result.push(arg);
3298
+ }
3299
+ return result;
3300
+ }
3301
+ function registerBuiltInProviders(registry) {
3302
+ registry.register(new ClaudeCodeProvider);
3303
+ registry.register(new CodexProvider);
3304
+ registry.register(new OpenCodeProvider);
3305
+ }
3306
+ function buildProviderList(providers, availability) {
3307
+ const lines = ["Registered providers:", ""];
3308
+ for (const provider of providers) {
3309
+ const aliases = PROVIDER_ALIAS_GROUPS[provider.name] ?? [];
3310
+ const status = availability.get(provider.name) ? "available" : "unavailable";
3311
+ lines.push(`- ${provider.name} (${provider.displayName}) [${status}]`);
3312
+ if (aliases.length > 0) {
3313
+ lines.push(` aliases: ${aliases.join(", ")}`);
3314
+ }
3315
+ }
3316
+ lines.push("");
3317
+ return lines.join(`
3318
+ `);
3319
+ }
3403
3320
  function inferFormatFromPath(filePath) {
3404
3321
  const ext = filePath.split(".").pop()?.toLowerCase();
3405
3322
  switch (ext) {
@@ -3453,6 +3370,11 @@ function resolveConfig(cliArgs) {
3453
3370
  width: 80,
3454
3371
  noColor: false,
3455
3372
  noInsights: false,
3373
+ claude: false,
3374
+ codex: false,
3375
+ openCode: false,
3376
+ allProviders: false,
3377
+ listProviders: false,
3456
3378
  clipboard: false,
3457
3379
  open: false,
3458
3380
  liveServer: false
@@ -3518,6 +3440,21 @@ function resolveConfig(cliArgs) {
3518
3440
  if (cliArgs["provider"] !== undefined) {
3519
3441
  result.provider = cliArgs["provider"];
3520
3442
  }
3443
+ if (cliArgs["claude"] !== undefined) {
3444
+ result.claude = cliArgs["claude"];
3445
+ }
3446
+ if (cliArgs["codex"] !== undefined) {
3447
+ result.codex = cliArgs["codex"];
3448
+ }
3449
+ if (cliArgs["openCode"] !== undefined) {
3450
+ result.openCode = cliArgs["openCode"];
3451
+ }
3452
+ if (cliArgs["allProviders"] !== undefined) {
3453
+ result.allProviders = cliArgs["allProviders"];
3454
+ }
3455
+ if (cliArgs["listProviders"] !== undefined) {
3456
+ result.listProviders = cliArgs["listProviders"];
3457
+ }
3521
3458
  if (cliArgs["clipboard"] !== undefined) {
3522
3459
  result.clipboard = cliArgs["clipboard"];
3523
3460
  }
@@ -3578,19 +3515,30 @@ async function runCompare(compareStr, currentRange, _registry, available) {
3578
3515
  }
3579
3516
  async function run(cliArgs) {
3580
3517
  const config = resolveConfig(cliArgs);
3518
+ if (config.allProviders && (config.provider || config.claude || config.codex || config.openCode)) {
3519
+ throw new TokenleakError("--all-providers cannot be combined with provider filters");
3520
+ }
3521
+ const registry = new ProviderRegistry;
3522
+ registerBuiltInProviders(registry);
3523
+ if (config.listProviders) {
3524
+ const providers = registry.getAll();
3525
+ const availabilityResults = await Promise.all(providers.map(async (provider) => [provider.name, await provider.isAvailable()]));
3526
+ process.stdout.write(buildProviderList(providers, new Map(availabilityResults)));
3527
+ return;
3528
+ }
3581
3529
  const dateRange = computeDateRange({
3582
3530
  since: config.since,
3583
3531
  until: config.until,
3584
3532
  days: config.days
3585
3533
  });
3586
- const registry = new ProviderRegistry;
3587
- registry.register(new ClaudeCodeProvider);
3588
- registry.register(new CodexProvider);
3589
- registry.register(new OpenCodeProvider);
3590
3534
  let available = await registry.getAvailable();
3591
- if (config.provider) {
3592
- const requested = new Set(config.provider.split(",").map((s) => s.trim().toLowerCase()));
3593
- available = available.filter((p) => requested.has(p.name.toLowerCase()) || requested.has(p.displayName.toLowerCase()));
3535
+ const requestedProviders = getRequestedProviders(config);
3536
+ if (!config.allProviders && requestedProviders.size > 0) {
3537
+ if (config.provider && (config.claude || config.codex || config.openCode)) {
3538
+ process.stderr.write(`Combining provider filters: ${Array.from(requestedProviders).join(", ")}
3539
+ `);
3540
+ }
3541
+ available = available.filter((provider) => providerMatchesFilter(provider, requestedProviders));
3594
3542
  }
3595
3543
  if (available.length === 0) {
3596
3544
  throw new TokenleakError("No provider data found");
@@ -3771,6 +3719,31 @@ var main = defineCommand({
3771
3719
  alias: "p",
3772
3720
  description: "Filter to specific provider(s), comma-separated"
3773
3721
  },
3722
+ claude: {
3723
+ type: "boolean",
3724
+ description: "Shortcut for --provider claude-code",
3725
+ default: false
3726
+ },
3727
+ codex: {
3728
+ type: "boolean",
3729
+ description: "Shortcut for --provider codex",
3730
+ default: false
3731
+ },
3732
+ openCode: {
3733
+ type: "boolean",
3734
+ description: "Shortcut for --provider open-code",
3735
+ default: false
3736
+ },
3737
+ allProviders: {
3738
+ type: "boolean",
3739
+ description: "Ignore provider filters and use every available provider",
3740
+ default: false
3741
+ },
3742
+ listProviders: {
3743
+ type: "boolean",
3744
+ description: "List registered providers and aliases",
3745
+ default: false
3746
+ },
3774
3747
  clipboard: {
3775
3748
  type: "boolean",
3776
3749
  description: "Copy output to clipboard after rendering",
@@ -3817,6 +3790,16 @@ var main = defineCommand({
3817
3790
  cliArgs["compare"] = args.compare;
3818
3791
  if (args.provider !== undefined)
3819
3792
  cliArgs["provider"] = args.provider;
3793
+ if (args.claude)
3794
+ cliArgs["claude"] = true;
3795
+ if (args.codex)
3796
+ cliArgs["codex"] = true;
3797
+ if (args.openCode)
3798
+ cliArgs["openCode"] = true;
3799
+ if (args.allProviders)
3800
+ cliArgs["allProviders"] = true;
3801
+ if (args.listProviders)
3802
+ cliArgs["listProviders"] = true;
3820
3803
  if (args.clipboard)
3821
3804
  cliArgs["clipboard"] = true;
3822
3805
  if (args.open)
@@ -3833,11 +3816,23 @@ var main = defineCommand({
3833
3816
  });
3834
3817
  var isDirectExecution = typeof Bun !== "undefined" ? Bun.main === import.meta.path : process.argv[1] !== undefined && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
3835
3818
  if (isDirectExecution) {
3819
+ const normalizedArgv = normalizeCliArgv(process.argv.slice(2));
3820
+ process.argv = [...process.argv.slice(0, 2), ...normalizedArgv];
3821
+ const argv = normalizedArgv;
3822
+ if (argv.includes("--help") || argv.includes("-h")) {
3823
+ process.stdout.write(buildHelpText());
3824
+ process.exit(0);
3825
+ }
3826
+ if (argv.includes("--version") || argv.includes("-v")) {
3827
+ process.stdout.write(buildVersionText());
3828
+ process.exit(0);
3829
+ }
3836
3830
  runMain(main);
3837
3831
  }
3838
3832
  export {
3839
3833
  run,
3840
3834
  resolveConfig,
3835
+ normalizeCliArgv,
3841
3836
  inferFormatFromPath,
3842
3837
  computeDateRange
3843
3838
  };