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.
@@ -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 addBraillePoint(cells, x, y) {
917
- const cellX = Math.floor(x / 2);
918
- const cellY = Math.floor(y / 4);
919
- const dotX = x % 2;
920
- const dotY = y % 4;
921
- cells[cellY][cellX] |= BRAILLE_DOTS[dotX * 4 + dotY];
922
- }
923
- function buildBrailleRows(points, width, height) {
924
- const cells = Array.from({ length: height }, () => Array.from({ length: width }, () => 0));
925
- for (let i = 1; i < points.length; i++) {
926
- const from = points[i - 1];
927
- const to = points[i];
928
- const steps = Math.max(Math.abs(to.x - from.x), Math.abs(to.y - from.y), 1);
929
- for (let step = 0; step <= steps; step++) {
930
- const t = step / steps;
931
- addBraillePoint(cells, Math.round(from.x + (to.x - from.x) * t), Math.round(from.y + (to.y - from.y) * t));
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 cells.map(row => row.map(mask => mask === 0 ? " " : String.fromCharCode(0x2800 + mask)).join(""));
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 start = Math.min(Math.max(0, chartPoints[i].x - Math.floor(label.length / 2)), Math.max(0, chartWidth - label.length));
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 labelChars.join("");
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(dotWidth / 2) : Math.round((i / (values.length - 1)) * (dotWidth - 1)),
1005
- y: Math.max(0, dotHeight - 1 - Math.round((value / maxVal) * (dotHeight - 1))),
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 rows = buildBrailleRows(chartPoints, chartWidth, chartHeight);
1008
- const labelPoints = chartPoints.map(point => ({ x: Math.floor(point.x / 2), y: point.y }));
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 ${formatTrendValue(maxVal, metric)} · avg ${formatTrendValue(avgVal, metric)} · Δ ${formatSignedTrendValue(deltaVal, metric)}`);
1011
- lines.push(`range ${formatTrendValue(minVal, metric)} → ${formatTrendValue(maxVal, metric)}`);
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 * maxVal;
1015
- const label = row === 0 || row === chartHeight - 1 || row % yLabelStep === 0
1016
- ? formatTrendValue(valAtRow, metric)
1017
- : "";
1018
- const line = `${padLeft(label, 9)} ┤${rows[row]}`;
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)} └${"─".repeat(chartWidth)}`;
1111
+ const axis = `${" ".repeat(9)} ${GRAY}└${axisTicks}${RESET}`;
1022
1112
  lines.push(axis);
1023
- lines.push(`${" ".repeat(11)}${buildTrendXAxis(sorted, labelPoints, chartWidth)}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-token-tracker",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Real-time token usage and cost tracking plugin for OpenCode with Toast notifications and CLI stats",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",