opencode-token-tracker 1.6.4 → 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 +125 -35
- package/package.json +1 -1
|
@@ -899,7 +899,6 @@ function cmdHelp() {
|
|
|
899
899
|
opencode-tokens config get toast.enabled # Check if toast is enabled
|
|
900
900
|
`);
|
|
901
901
|
}
|
|
902
|
-
const BRAILLE_DOTS = [0x01, 0x02, 0x04, 0x40, 0x08, 0x10, 0x20, 0x80];
|
|
903
902
|
function getTrendValue(point, metric) {
|
|
904
903
|
return metric === "tokens" ? point.tokens : metric === "messages" ? point.messages : point.cost;
|
|
905
904
|
}
|
|
@@ -913,43 +912,110 @@ function formatSignedTrendValue(value, metric) {
|
|
|
913
912
|
function metricLabel(metric) {
|
|
914
913
|
return metric === "tokens" ? "Token Trend" : metric === "messages" ? "Message Trend" : "Cost Trend";
|
|
915
914
|
}
|
|
916
|
-
function
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
+
}
|
|
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] = "│";
|
|
966
|
+
}
|
|
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
|
+
}
|
|
932
985
|
}
|
|
933
986
|
}
|
|
934
|
-
return
|
|
987
|
+
return grid;
|
|
935
988
|
}
|
|
989
|
+
const ESC = "\x1b";
|
|
990
|
+
const RESET = `${ESC}[0m`;
|
|
991
|
+
const BOLD = `${ESC}[1m`;
|
|
992
|
+
const DIM = `${ESC}[2m`;
|
|
993
|
+
const GRAY = `${ESC}[90m`;
|
|
936
994
|
function buildTrendXAxis(points, chartPoints, chartWidth) {
|
|
937
995
|
const labelChars = Array.from({ length: chartWidth }, () => " ");
|
|
996
|
+
const tickChars = Array.from({ length: chartWidth }, () => "─");
|
|
938
997
|
const labelStep = Math.max(1, Math.ceil(points.length / 6));
|
|
939
998
|
for (let i = 0; i < chartPoints.length; i++) {
|
|
940
999
|
if (i % labelStep !== 0 && i !== chartPoints.length - 1)
|
|
941
1000
|
continue;
|
|
942
1001
|
const date = new Date(points[i][0]);
|
|
943
1002
|
const label = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
944
|
-
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));
|
|
945
1005
|
const hasSpace = labelChars.slice(Math.max(0, start - 1), Math.min(chartWidth, start + label.length + 1)).every((c) => c === " ");
|
|
946
1006
|
if (!hasSpace)
|
|
947
1007
|
continue;
|
|
948
1008
|
for (let j = 0; j < label.length; j++) {
|
|
949
1009
|
labelChars[start + j] = label[j];
|
|
950
1010
|
}
|
|
1011
|
+
if (centerOfLabel < chartWidth) {
|
|
1012
|
+
tickChars[centerOfLabel] = "┬";
|
|
1013
|
+
}
|
|
951
1014
|
}
|
|
952
|
-
return
|
|
1015
|
+
return {
|
|
1016
|
+
axisTicks: tickChars.join(""),
|
|
1017
|
+
axisLabels: labelChars.join("")
|
|
1018
|
+
};
|
|
953
1019
|
}
|
|
954
1020
|
function cmdTrend(flags) {
|
|
955
1021
|
const days = parseInt(String(flagValue(flags, "days") ?? "30"), 10);
|
|
@@ -985,6 +1051,12 @@ function cmdTrend(flags) {
|
|
|
985
1051
|
const values = sorted.map(([, d]) => getTrendValue(d, metric));
|
|
986
1052
|
const maxVal = Math.max(...values, 1);
|
|
987
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;
|
|
988
1060
|
const totalVal = values.reduce((sum, value) => sum + value, 0);
|
|
989
1061
|
const avgVal = totalVal / values.length;
|
|
990
1062
|
const deltaVal = values[values.length - 1] - values[0];
|
|
@@ -998,29 +1070,47 @@ function cmdTrend(flags) {
|
|
|
998
1070
|
}
|
|
999
1071
|
const chartWidth = Math.max(width - 12, 20);
|
|
1000
1072
|
const yLabelStep = Math.max(1, Math.floor(chartHeight / 4));
|
|
1001
|
-
const dotWidth = chartWidth * 2;
|
|
1002
|
-
const dotHeight = chartHeight * 4;
|
|
1003
1073
|
const chartPoints = values.map((value, i) => ({
|
|
1004
|
-
x: values.length === 1 ? Math.floor(
|
|
1005
|
-
y: Math.max(0,
|
|
1074
|
+
x: values.length === 1 ? Math.floor(chartWidth / 2) : Math.round((i / (values.length - 1)) * (chartWidth - 1)),
|
|
1075
|
+
y: Math.max(0, chartHeight - 1 - Math.round(((value - chartMin) / chartRange) * (chartHeight - 1))),
|
|
1006
1076
|
}));
|
|
1007
|
-
const
|
|
1008
|
-
|
|
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
|
|
1094
|
+
}
|
|
1095
|
+
const deltaStr = `${deltaColor}${BOLD}${formatSignedTrendValue(deltaVal, metric)}${RESET}`;
|
|
1009
1096
|
const lines = [];
|
|
1010
|
-
lines.push(`${metricLabel(metric)} · ${sorted.length} days · peak ${
|
|
1011
|
-
lines.push(
|
|
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);
|
|
1012
1102
|
for (let row = 0; row < chartHeight; row++) {
|
|
1013
1103
|
const valueRatio = 1 - row / (chartHeight - 1);
|
|
1014
|
-
const valAtRow = valueRatio *
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const line = `${padLeft(label, 9)}
|
|
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]}`;
|
|
1019
1109
|
lines.push(line);
|
|
1020
1110
|
}
|
|
1021
|
-
const axis = `${" ".repeat(9)} └${
|
|
1111
|
+
const axis = `${" ".repeat(9)} ${GRAY}└${axisTicks}${RESET}`;
|
|
1022
1112
|
lines.push(axis);
|
|
1023
|
-
lines.push(`${" ".repeat(11)}${
|
|
1113
|
+
lines.push(`${" ".repeat(11)}${GRAY}${axisLabels}${RESET}`);
|
|
1024
1114
|
console.log();
|
|
1025
1115
|
for (const l of lines)
|
|
1026
1116
|
console.log(` ${l}`);
|
package/package.json
CHANGED