opencode-token-tracker 1.6.1 → 1.6.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.
@@ -945,10 +945,16 @@ function cmdTrend(flags) {
945
945
  console.log(`\n ${spark}\n`);
946
946
  return;
947
947
  }
948
- // Build chart
948
+ // Build chart — pixel-based rendering
949
949
  const cols = values.map((v) => ({ value: v, y: Math.round((v / maxVal) * (H - 1)) }));
950
950
  const chartWidth = Math.max(width - 12, 20);
951
- const xStep = Math.max(2, Math.floor(chartWidth / sorted.length));
951
+ // Map data points to pixel x-positions (evenly spaced across chartWidth)
952
+ const px = [];
953
+ const py = [];
954
+ for (let i = 0; i < cols.length; i++) {
955
+ px.push(cols.length === 1 ? Math.floor(chartWidth / 2) : Math.round((i / (cols.length - 1)) * (chartWidth - 1)));
956
+ py.push(cols[i].y);
957
+ }
952
958
  const yLabelStep = Math.max(1, Math.floor(H / 5));
953
959
  const lines = [];
954
960
  for (let row = H - 1; row >= 0; row--) {
@@ -959,56 +965,75 @@ function cmdTrend(flags) {
959
965
  : "";
960
966
  line += padLeft(label, 9);
961
967
  line += row === 0 ? " ┼" : " ┤";
962
- for (let ci = 0; ci < cols.length; ci++) {
963
- const col = cols[ci];
964
- const nextY = ci < cols.length - 1 ? cols[ci + 1].y : col.y;
965
- if (col.y === row) {
966
- if (ci > 0) {
967
- const prevY = cols[ci - 1].y;
968
- if (prevY < col.y && nextY <= col.y)
969
- line += "╭";
970
- else if (prevY > col.y && nextY >= col.y)
971
- line += "╰";
972
- else if (prevY < col.y || nextY < col.y)
973
- line += "╭";
974
- else if (prevY > col.y || nextY > col.y)
975
- line += "╰";
976
- else
977
- line += "─";
968
+ // Build a sparse array of characters at specific x-positions for this row
969
+ const chars = [];
970
+ // Data points that land on this row
971
+ for (let i = 0; i < px.length; i++) {
972
+ if (py[i] === row) {
973
+ if (cols.length === 1) {
974
+ chars.push({ x: px[i], c: "─" });
978
975
  }
979
976
  else {
980
- line += nextY > col.y ? "╭" : nextY < col.y ? "╰" : "─";
981
- }
982
- if (xStep > 1 && ci < cols.length - 1 && nextY === row) {
983
- line += "─".repeat(xStep - 1);
977
+ const prevSlope = i > 0 ? py[i] - py[i - 1] : 0;
978
+ const nextSlope = i < px.length - 1 ? py[i + 1] - py[i] : 0;
979
+ let c = "─";
980
+ if (i === 0)
981
+ c = nextSlope > 0 ? "╭" : nextSlope < 0 ? "╰" : "─";
982
+ else if (i === px.length - 1)
983
+ c = prevSlope > 0 ? "╮" : prevSlope < 0 ? "╯" : "─";
984
+ else if (prevSlope > 0 && nextSlope > 0)
985
+ c = "╭";
986
+ else if (prevSlope < 0 && nextSlope < 0)
987
+ c = "╰";
988
+ else if (prevSlope > 0 && nextSlope < 0)
989
+ c = "╮";
990
+ else if (prevSlope < 0 && nextSlope > 0)
991
+ c = "╯";
992
+ chars.push({ x: px[i], c });
984
993
  }
985
994
  }
986
- else if (ci > 0 && ci < cols.length) {
987
- const prevY = cols[ci - 1].y;
988
- if ((prevY < row && col.y > row) || (prevY > row && col.y < row)) {
989
- line += prevY < col.y ? "╱" : "╲";
990
- }
991
- else {
992
- line += " ".repeat(xStep > 1 && nextY !== row ? 1 : Math.min(xStep, 1));
993
- }
995
+ }
996
+ // Line segments crossing this row (exact x of intersection)
997
+ for (let i = 1; i < px.length; i++) {
998
+ const y0 = py[i - 1], y1 = py[i];
999
+ // Skip if segment doesn't cross this row
1000
+ if ((y0 <= row && y1 <= row) || (y0 >= row && y1 >= row))
1001
+ continue;
1002
+ if (y0 === y1)
1003
+ continue;
1004
+ const t = (row - y0) / (y1 - y0);
1005
+ const cx = Math.round(px[i - 1] + t * (px[i] - px[i - 1]));
1006
+ const slope = y1 - y0;
1007
+ chars.push({ x: cx, c: slope > 0 ? "╱" : "╲" });
1008
+ }
1009
+ // Render the row: sort chars by x and fill gaps with spaces
1010
+ chars.sort((a, b) => a.x - b.x);
1011
+ let prevX = 0;
1012
+ for (const { x, c } of chars) {
1013
+ while (prevX < x) {
1014
+ line += " ";
1015
+ prevX++;
994
1016
  }
1017
+ line += c;
1018
+ prevX = x + 1;
995
1019
  }
996
1020
  lines.push(line);
997
1021
  }
998
1022
  // Bottom axis
999
1023
  let axis = " ".repeat(9) + " └";
1000
- axis += "─".repeat(xStep * cols.length);
1024
+ axis += "─".repeat(chartWidth);
1001
1025
  lines.push(axis);
1002
1026
  // X axis labels
1003
1027
  const labelStep = Math.max(1, Math.ceil(sorted.length / 6));
1004
1028
  let xLabels = " ".repeat(11);
1005
- for (let i = 0; i < cols.length; i++) {
1006
- if (i % labelStep === 0 || i === cols.length - 1) {
1029
+ for (let i = 0; i < px.length; i++) {
1030
+ if (i % labelStep === 0 || i === px.length - 1) {
1007
1031
  const d = new Date(sorted[i][0]);
1008
1032
  const ds = `${d.getMonth() + 1}/${d.getDate()}`;
1033
+ const pos = px[i] + 0;
1034
+ while (xLabels.length - 11 < pos)
1035
+ xLabels += " ";
1009
1036
  xLabels += ds;
1010
- if (i < cols.length - 1)
1011
- xLabels += " ".repeat(Math.max(1, xStep - ds.length + 1));
1012
1037
  }
1013
1038
  }
1014
1039
  lines.push(xLabels);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-token-tracker",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
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",