opencode-token-tracker 1.6.3 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/opencode-tokens.js +128 -61
- package/package.json +1 -1
|
@@ -905,67 +905,117 @@ function getTrendValue(point, metric) {
|
|
|
905
905
|
function formatTrendValue(value, metric) {
|
|
906
906
|
return metric === "tokens" ? formatTokens(value) : metric === "messages" ? String(Math.round(value)) : formatCost(value);
|
|
907
907
|
}
|
|
908
|
+
function formatSignedTrendValue(value, metric) {
|
|
909
|
+
const sign = value > 0 ? "+" : value < 0 ? "-" : "";
|
|
910
|
+
return `${sign}${formatTrendValue(Math.abs(value), metric)}`;
|
|
911
|
+
}
|
|
908
912
|
function metricLabel(metric) {
|
|
909
913
|
return metric === "tokens" ? "Token Trend" : metric === "messages" ? "Message Trend" : "Cost Trend";
|
|
910
914
|
}
|
|
911
|
-
function
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
915
|
+
function buildLineRows(points, width, height) {
|
|
916
|
+
const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => " "));
|
|
917
|
+
// 1. Calculate the exact integer row r[x] for every column x from 0 to width - 1
|
|
918
|
+
const r = Array.from({ length: width }, () => 0);
|
|
919
|
+
for (let x = 0; x < width; x++) {
|
|
920
|
+
// Find which segment p[i] -> p[i+1] contains x
|
|
921
|
+
let i = 0;
|
|
922
|
+
for (; i < points.length - 1; i++) {
|
|
923
|
+
if (x >= points[i].x && x <= points[i + 1].x) {
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
// Clamp to valid index
|
|
928
|
+
if (i >= points.length - 1) {
|
|
929
|
+
i = points.length - 2;
|
|
930
|
+
}
|
|
931
|
+
const p1 = points[i];
|
|
932
|
+
const p2 = points[i + 1];
|
|
933
|
+
if (p1.x === p2.x) {
|
|
934
|
+
r[x] = p1.y;
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
const t = (x - p1.x) / (p2.x - p1.x);
|
|
938
|
+
const y = p1.y + (p2.y - p1.y) * t;
|
|
939
|
+
r[x] = Math.round(y);
|
|
940
|
+
}
|
|
928
941
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
+
// 2. Plot the line characters based on r[x] and r[x+1]
|
|
943
|
+
for (let x = 0; x < width; x++) {
|
|
944
|
+
const rCurr = r[x];
|
|
945
|
+
if (x === width - 1) {
|
|
946
|
+
// Last column has no next column, draw horizontal line
|
|
947
|
+
if (grid[rCurr][x] === " ") {
|
|
948
|
+
grid[rCurr][x] = "─";
|
|
949
|
+
}
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
const rNext = r[x + 1];
|
|
953
|
+
if (rCurr === rNext) {
|
|
954
|
+
if (grid[rCurr][x] === " ") {
|
|
955
|
+
grid[rCurr][x] = "─";
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
else if (rCurr < rNext) {
|
|
959
|
+
// Going down (larger row index)
|
|
960
|
+
if (grid[rCurr][x] === " ") {
|
|
961
|
+
grid[rCurr][x] = "┐";
|
|
962
|
+
}
|
|
963
|
+
for (let y = rCurr + 1; y < rNext; y++) {
|
|
964
|
+
if (grid[y][x] === " ") {
|
|
965
|
+
grid[y][x] = "│";
|
|
942
966
|
}
|
|
943
967
|
}
|
|
968
|
+
if (grid[rNext][x] === " ") {
|
|
969
|
+
grid[rNext][x] = "└";
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
// Going up (smaller row index)
|
|
974
|
+
if (grid[rCurr][x] === " ") {
|
|
975
|
+
grid[rCurr][x] = "┘";
|
|
976
|
+
}
|
|
977
|
+
for (let y = rNext + 1; y < rCurr; y++) {
|
|
978
|
+
if (grid[y][x] === " ") {
|
|
979
|
+
grid[y][x] = "│";
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (grid[rNext][x] === " ") {
|
|
983
|
+
grid[rNext][x] = "┌";
|
|
984
|
+
}
|
|
944
985
|
}
|
|
945
986
|
}
|
|
987
|
+
return grid;
|
|
946
988
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
989
|
+
const ESC = "\x1b";
|
|
990
|
+
const RESET = `${ESC}[0m`;
|
|
991
|
+
const BOLD = `${ESC}[1m`;
|
|
992
|
+
const DIM = `${ESC}[2m`;
|
|
993
|
+
const GRAY = `${ESC}[90m`;
|
|
952
994
|
function buildTrendXAxis(points, chartPoints, chartWidth) {
|
|
953
995
|
const labelChars = Array.from({ length: chartWidth }, () => " ");
|
|
996
|
+
const tickChars = Array.from({ length: chartWidth }, () => "─");
|
|
954
997
|
const labelStep = Math.max(1, Math.ceil(points.length / 6));
|
|
955
998
|
for (let i = 0; i < chartPoints.length; i++) {
|
|
956
999
|
if (i % labelStep !== 0 && i !== chartPoints.length - 1)
|
|
957
1000
|
continue;
|
|
958
1001
|
const date = new Date(points[i][0]);
|
|
959
1002
|
const label = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
960
|
-
const
|
|
1003
|
+
const centerOfLabel = chartPoints[i].x;
|
|
1004
|
+
const start = Math.min(Math.max(0, centerOfLabel - Math.floor(label.length / 2)), Math.max(0, chartWidth - label.length));
|
|
961
1005
|
const hasSpace = labelChars.slice(Math.max(0, start - 1), Math.min(chartWidth, start + label.length + 1)).every((c) => c === " ");
|
|
962
1006
|
if (!hasSpace)
|
|
963
1007
|
continue;
|
|
964
1008
|
for (let j = 0; j < label.length; j++) {
|
|
965
1009
|
labelChars[start + j] = label[j];
|
|
966
1010
|
}
|
|
1011
|
+
if (centerOfLabel < chartWidth) {
|
|
1012
|
+
tickChars[centerOfLabel] = "┬";
|
|
1013
|
+
}
|
|
967
1014
|
}
|
|
968
|
-
return
|
|
1015
|
+
return {
|
|
1016
|
+
axisTicks: tickChars.join(""),
|
|
1017
|
+
axisLabels: labelChars.join("")
|
|
1018
|
+
};
|
|
969
1019
|
}
|
|
970
1020
|
function cmdTrend(flags) {
|
|
971
1021
|
const days = parseInt(String(flagValue(flags, "days") ?? "30"), 10);
|
|
@@ -1001,10 +1051,16 @@ function cmdTrend(flags) {
|
|
|
1001
1051
|
const values = sorted.map(([, d]) => getTrendValue(d, metric));
|
|
1002
1052
|
const maxVal = Math.max(...values, 1);
|
|
1003
1053
|
const minVal = Math.min(...values);
|
|
1054
|
+
const valRange = maxVal - minVal;
|
|
1055
|
+
// Adaptive scaling padding (e.g. 5% of range) to show beautiful ups & downs
|
|
1056
|
+
const pad = valRange === 0 ? maxVal * 0.1 : valRange * 0.05;
|
|
1057
|
+
const chartMax = maxVal + pad;
|
|
1058
|
+
const chartMin = Math.max(0, minVal - pad);
|
|
1059
|
+
const chartRange = chartMax - chartMin || 1;
|
|
1004
1060
|
const totalVal = values.reduce((sum, value) => sum + value, 0);
|
|
1005
1061
|
const avgVal = totalVal / values.length;
|
|
1006
1062
|
const deltaVal = values[values.length - 1] - values[0];
|
|
1007
|
-
const
|
|
1063
|
+
const chartHeight = sorted.length <= 3 ? 6 : Math.max(6, Math.min(Math.floor(width / 4), 12));
|
|
1008
1064
|
if (width < 35) {
|
|
1009
1065
|
// Fallback: simple sparkline
|
|
1010
1066
|
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
@@ -1013,37 +1069,48 @@ function cmdTrend(flags) {
|
|
|
1013
1069
|
return;
|
|
1014
1070
|
}
|
|
1015
1071
|
const chartWidth = Math.max(width - 12, 20);
|
|
1016
|
-
const yLabelStep = Math.max(1, Math.floor(
|
|
1017
|
-
const gridRows = new Set();
|
|
1018
|
-
for (let row = 0; row < H; row++) {
|
|
1019
|
-
if (row === 0 || row === H - 1 || (H - 1 - row) % yLabelStep === 0) {
|
|
1020
|
-
gridRows.add(row);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1072
|
+
const yLabelStep = Math.max(1, Math.floor(chartHeight / 4));
|
|
1023
1073
|
const chartPoints = values.map((value, i) => ({
|
|
1024
1074
|
x: values.length === 1 ? Math.floor(chartWidth / 2) : Math.round((i / (values.length - 1)) * (chartWidth - 1)),
|
|
1025
|
-
y: Math.round((value /
|
|
1075
|
+
y: Math.max(0, chartHeight - 1 - Math.round(((value - chartMin) / chartRange) * (chartHeight - 1))),
|
|
1026
1076
|
}));
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1077
|
+
const grid = buildLineRows(chartPoints, chartWidth, chartHeight);
|
|
1078
|
+
// Metric premium color themes
|
|
1079
|
+
let metricColor = "\x1b[36m"; // default Cyan
|
|
1080
|
+
if (metric === "cost") {
|
|
1081
|
+
metricColor = "\x1b[32m"; // Green
|
|
1082
|
+
}
|
|
1083
|
+
else if (metric === "messages") {
|
|
1084
|
+
metricColor = "\x1b[33m"; // Yellow
|
|
1085
|
+
}
|
|
1086
|
+
const peakStr = `${metricColor}${BOLD}${formatTrendValue(maxVal, metric)}${RESET}`;
|
|
1087
|
+
const avgStr = `${metricColor}${BOLD}${formatTrendValue(avgVal, metric)}${RESET}`;
|
|
1088
|
+
let deltaColor = "\x1b[32m"; // Green
|
|
1089
|
+
if (metric === "cost") {
|
|
1090
|
+
deltaColor = deltaVal > 0 ? "\x1b[31m" : "\x1b[32m"; // Red for cost increase, Green for decrease
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
deltaColor = deltaVal > 0 ? "\x1b[32m" : "\x1b[31m"; // Green for token/msg increase, Red for decrease
|
|
1031
1094
|
}
|
|
1032
|
-
|
|
1095
|
+
const deltaStr = `${deltaColor}${BOLD}${formatSignedTrendValue(deltaVal, metric)}${RESET}`;
|
|
1033
1096
|
const lines = [];
|
|
1034
|
-
lines.push(`${metricLabel(metric)} · ${sorted.length} days · peak ${
|
|
1035
|
-
lines.push(
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1097
|
+
lines.push(`${metricColor}${BOLD}${metricLabel(metric)}${RESET} · ${sorted.length} days · peak ${peakStr} · avg ${avgStr} · Δ ${deltaStr}`);
|
|
1098
|
+
lines.push(`${GRAY}range ${formatTrendValue(minVal, metric)} → ${formatTrendValue(maxVal, metric)}${RESET}`);
|
|
1099
|
+
// Colorize the line chart points and connection characters
|
|
1100
|
+
const colorRows = grid.map(row => row.map(char => char === " " ? " " : `${metricColor}${char}${RESET}`).join(""));
|
|
1101
|
+
const { axisTicks, axisLabels } = buildTrendXAxis(sorted, chartPoints, chartWidth);
|
|
1102
|
+
for (let row = 0; row < chartHeight; row++) {
|
|
1103
|
+
const valueRatio = 1 - row / (chartHeight - 1);
|
|
1104
|
+
const valAtRow = chartMin + valueRatio * chartRange;
|
|
1105
|
+
const hasLabel = row === 0 || row === chartHeight - 1 || row % yLabelStep === 0;
|
|
1106
|
+
const label = hasLabel ? formatTrendValue(valAtRow, metric) : "";
|
|
1107
|
+
const tick = hasLabel ? "┤" : "│";
|
|
1108
|
+
const line = `${padLeft(label, 9)} ${GRAY}${tick}${RESET}${colorRows[row]}`;
|
|
1042
1109
|
lines.push(line);
|
|
1043
1110
|
}
|
|
1044
|
-
const axis = `${" ".repeat(9)} └${
|
|
1111
|
+
const axis = `${" ".repeat(9)} ${GRAY}└${axisTicks}${RESET}`;
|
|
1045
1112
|
lines.push(axis);
|
|
1046
|
-
lines.push(`${" ".repeat(11)}${
|
|
1113
|
+
lines.push(`${" ".repeat(11)}${GRAY}${axisLabels}${RESET}`);
|
|
1047
1114
|
console.log();
|
|
1048
1115
|
for (const l of lines)
|
|
1049
1116
|
console.log(` ${l}`);
|
package/package.json
CHANGED