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.
- package/package.json +1 -1
- package/tokenleak +446 -451
package/package.json
CHANGED
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.
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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/
|
|
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&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
|
|
2134
|
-
var
|
|
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
|
|
2138
|
-
var
|
|
1835
|
+
var DAY_LABEL_WIDTH = 44;
|
|
1836
|
+
var MONTH_LABEL_HEIGHT = 24;
|
|
2139
1837
|
var PROVIDER_SECTION_GAP = 36;
|
|
2140
|
-
var
|
|
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: "#
|
|
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: "#
|
|
2150
|
-
barTrack: "#
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
2279
|
-
const x =
|
|
2280
|
-
const y =
|
|
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="${
|
|
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="${
|
|
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: "
|
|
2004
|
+
{ label: "Sat", row: 6 }
|
|
2298
2005
|
].map((d) => {
|
|
2299
|
-
const y =
|
|
2300
|
-
return `<text x="0" y="${y}" fill="__MUTED__" font-size="11" font-family="${escapeXml(
|
|
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 =
|
|
2304
|
-
const height =
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2400
|
-
const valueColor = stat.accent ?
|
|
2401
|
-
sections.push(`<text x="${x}" y="${startY + 28}" fill="${escapeXml(valueColor)}" font-size="22" font-family="${escapeXml(
|
|
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(
|
|
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 =
|
|
2415
|
-
const
|
|
2416
|
-
const
|
|
2417
|
-
|
|
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
|
|
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(
|
|
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="${
|
|
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="
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
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.
|
|
2281
|
+
alignedStart.setUTCDate(alignedStart.getUTCDate() - alignedStart.getUTCDay());
|
|
2529
2282
|
const weeks = [];
|
|
2530
|
-
const
|
|
2531
|
-
while (
|
|
2283
|
+
const cursor = new Date(alignedStart);
|
|
2284
|
+
while (cursor <= endDate) {
|
|
2532
2285
|
const week = [];
|
|
2533
|
-
for (let
|
|
2534
|
-
week.push(new Date(
|
|
2535
|
-
|
|
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
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
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[
|
|
2565
|
-
if (!date || date
|
|
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
|
|
2570
|
-
const tokens = usageMap.get(
|
|
2571
|
-
const block =
|
|
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
|
|
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
|
|
2692
|
-
const
|
|
2693
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
2938
|
-
const
|
|
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 =
|
|
2945
|
-
const y =
|
|
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 =
|
|
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 =
|
|
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:
|
|
3084
|
-
height:
|
|
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 {
|
|
3115
|
-
|
|
3116
|
-
|
|
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;
|
|
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
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
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
|
};
|