opencode-token-tracker 1.6.2 → 1.6.4
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 +112 -129
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import {
|
|
2
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { BUILTIN_PRICING, DEFAULT_CONFIG, findModelConfigPricing, formatCost, formatTokens, getStartOfDay, getStartOfMonth, getStartOfWeek, validateConfig } from "../lib/shared.js";
|
|
6
6
|
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
7
7
|
const CONFIG_FILE = join(CONFIG_DIR, "token-tracker.json");
|
|
8
8
|
const LOG_FILE = join(CONFIG_DIR, "logs", "token-tracker", "tokens.jsonl");
|
|
@@ -10,15 +10,15 @@ const LOG_FILE = join(CONFIG_DIR, "logs", "token-tracker", "tokens.jsonl");
|
|
|
10
10
|
// Helpers
|
|
11
11
|
// ============================================================================
|
|
12
12
|
function padRight(str, len) {
|
|
13
|
-
return str.length >= len ? str : str
|
|
13
|
+
return str.length >= len ? str : `${str}${" ".repeat(len - str.length)}`;
|
|
14
14
|
}
|
|
15
15
|
function padLeft(str, len) {
|
|
16
|
-
return str.length >= len ? str : " ".repeat(len - str.length)
|
|
16
|
+
return str.length >= len ? str : `${" ".repeat(len - str.length)}${str}`;
|
|
17
17
|
}
|
|
18
18
|
function truncateSessionId(sessionId) {
|
|
19
19
|
if (!sessionId)
|
|
20
20
|
return "unknown";
|
|
21
|
-
return sessionId.length > 16 ? sessionId.slice(0, 14)
|
|
21
|
+
return sessionId.length > 16 ? `${sessionId.slice(0, 14)}…` : sessionId;
|
|
22
22
|
}
|
|
23
23
|
function parseArgs(args) {
|
|
24
24
|
const positional = [];
|
|
@@ -70,9 +70,6 @@ function flagValue(flags, name) {
|
|
|
70
70
|
const v = flags.get(name);
|
|
71
71
|
return typeof v === "string" ? v : undefined;
|
|
72
72
|
}
|
|
73
|
-
function flagBool(flags, name) {
|
|
74
|
-
return flags.has(name);
|
|
75
|
-
}
|
|
76
73
|
// ============================================================================
|
|
77
74
|
// Data Loading
|
|
78
75
|
// ============================================================================
|
|
@@ -178,10 +175,11 @@ function groupBy(entries, keyFn) {
|
|
|
178
175
|
const groups = new Map();
|
|
179
176
|
for (const e of entries) {
|
|
180
177
|
const key = keyFn(e);
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
let stats = groups.get(key);
|
|
179
|
+
if (!stats) {
|
|
180
|
+
stats = createEmptyStats();
|
|
181
|
+
groups.set(key, stats);
|
|
183
182
|
}
|
|
184
|
-
const stats = groups.get(key);
|
|
185
183
|
stats.input += e.input ?? 0;
|
|
186
184
|
stats.output += e.output ?? 0;
|
|
187
185
|
stats.reasoning += e.reasoning ?? 0;
|
|
@@ -277,7 +275,6 @@ function cmdStats(period, breakdown) {
|
|
|
277
275
|
since = getStartOfMonth(now);
|
|
278
276
|
title = "This Month's Usage";
|
|
279
277
|
break;
|
|
280
|
-
case "all":
|
|
281
278
|
default:
|
|
282
279
|
since = undefined;
|
|
283
280
|
title = "All-Time Usage";
|
|
@@ -337,15 +334,15 @@ function cmdPricing() {
|
|
|
337
334
|
const p = BUILTIN_PRICING[model];
|
|
338
335
|
if (!p)
|
|
339
336
|
continue;
|
|
340
|
-
const overridden = config.models
|
|
341
|
-
console.log(` ${padRight(model
|
|
337
|
+
const overridden = config.models[model] ? " *" : "";
|
|
338
|
+
console.log(` ${padRight(`${model}${overridden}`, modelWidth)} ${padLeft(`$${p.input.toString()}`, priceWidth)} ${padLeft(`$${p.output.toString()}`, priceWidth)} ${padLeft(p.cacheRead ? `$${p.cacheRead.toString()}` : "-", priceWidth)} ${padLeft(p.cacheWrite ? `$${p.cacheWrite.toString()}` : "-", priceWidth)}`);
|
|
342
339
|
}
|
|
343
340
|
console.log();
|
|
344
341
|
}
|
|
345
342
|
console.log(` Default (unknown models)`);
|
|
346
343
|
console.log(` ${"-".repeat(modelWidth + priceWidth * 4 + 12)}`);
|
|
347
|
-
const def = BUILTIN_PRICING
|
|
348
|
-
console.log(` ${padRight("_default", modelWidth)} ${padLeft(
|
|
344
|
+
const def = BUILTIN_PRICING._default;
|
|
345
|
+
console.log(` ${padRight("_default", modelWidth)} ${padLeft(`$${def.input.toString()}`, priceWidth)} ${padLeft(`$${def.output.toString()}`, priceWidth)} ${padLeft("-", priceWidth)} ${padLeft("-", priceWidth)}`);
|
|
349
346
|
console.log();
|
|
350
347
|
if (Object.keys(config.models || {}).length > 0) {
|
|
351
348
|
console.log(` * = overridden in config`);
|
|
@@ -363,10 +360,11 @@ function cmdModels() {
|
|
|
363
360
|
const model = e.model ?? "unknown";
|
|
364
361
|
const provider = e.provider ?? "unknown";
|
|
365
362
|
const key = `${model}|${provider}`;
|
|
366
|
-
|
|
367
|
-
|
|
363
|
+
let info = modelProviders.get(key);
|
|
364
|
+
if (!info) {
|
|
365
|
+
info = { provider, count: 0, lastUsed: 0 };
|
|
366
|
+
modelProviders.set(key, info);
|
|
368
367
|
}
|
|
369
|
-
const info = modelProviders.get(key);
|
|
370
368
|
info.count++;
|
|
371
369
|
info.lastUsed = Math.max(info.lastUsed, e._ts);
|
|
372
370
|
}
|
|
@@ -569,7 +567,7 @@ function cmdConfig(positional) {
|
|
|
569
567
|
return;
|
|
570
568
|
}
|
|
571
569
|
}
|
|
572
|
-
applyConfigSet(key, value
|
|
570
|
+
applyConfigSet(key, value);
|
|
573
571
|
console.log(`\n Set ${key} = ${JSON.stringify(value)}\n`);
|
|
574
572
|
return;
|
|
575
573
|
}
|
|
@@ -584,7 +582,7 @@ function cmdConfig(positional) {
|
|
|
584
582
|
console.log(`\n Unknown key: ${key}\n Available: ${Object.keys(SETTABLE_KEYS).join(", ")}\n`);
|
|
585
583
|
return;
|
|
586
584
|
}
|
|
587
|
-
applyConfigUnset(key
|
|
585
|
+
applyConfigUnset(key);
|
|
588
586
|
console.log(`\n Unset ${key} (reverted to default)\n`);
|
|
589
587
|
return;
|
|
590
588
|
}
|
|
@@ -604,7 +602,7 @@ function cmdConfig(positional) {
|
|
|
604
602
|
if (existsSync(CONFIG_FILE)) {
|
|
605
603
|
console.log(` Contents:`);
|
|
606
604
|
console.log(` ${"-".repeat(60)}`);
|
|
607
|
-
console.log(JSON.stringify(config, null, 2).split("\n").map(l =>
|
|
605
|
+
console.log(JSON.stringify(config, null, 2).split("\n").map(l => ` ${l}`).join("\n"));
|
|
608
606
|
console.log();
|
|
609
607
|
}
|
|
610
608
|
console.log(` Commands:`);
|
|
@@ -648,7 +646,7 @@ function resolveConfigKey(config, key) {
|
|
|
648
646
|
}
|
|
649
647
|
return obj[spec.path[spec.path.length - 1]] ?? spec.default;
|
|
650
648
|
}
|
|
651
|
-
function applyConfigSet(key, value
|
|
649
|
+
function applyConfigSet(key, value) {
|
|
652
650
|
const spec = SETTABLE_KEYS[key];
|
|
653
651
|
const fullConfig = loadOrInitConfig();
|
|
654
652
|
let obj = fullConfig;
|
|
@@ -660,7 +658,7 @@ function applyConfigSet(key, value, config) {
|
|
|
660
658
|
obj[spec.path[spec.path.length - 1]] = value;
|
|
661
659
|
saveConfig(fullConfig);
|
|
662
660
|
}
|
|
663
|
-
function applyConfigUnset(key
|
|
661
|
+
function applyConfigUnset(key) {
|
|
664
662
|
const spec = SETTABLE_KEYS[key];
|
|
665
663
|
const fullConfig = loadOrInitConfig();
|
|
666
664
|
let obj = fullConfig;
|
|
@@ -687,9 +685,9 @@ function saveConfig(raw) {
|
|
|
687
685
|
mkdirSync(dir, { recursive: true });
|
|
688
686
|
}
|
|
689
687
|
if (existsSync(CONFIG_FILE)) {
|
|
690
|
-
copyFileSync(CONFIG_FILE, CONFIG_FILE
|
|
688
|
+
copyFileSync(CONFIG_FILE, `${CONFIG_FILE}.bak`);
|
|
691
689
|
}
|
|
692
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(raw, null, 2)
|
|
690
|
+
writeFileSync(CONFIG_FILE, `${JSON.stringify(raw, null, 2)}\n`);
|
|
693
691
|
}
|
|
694
692
|
// ============================================================================
|
|
695
693
|
// Export
|
|
@@ -738,7 +736,7 @@ function cmdExport(flags) {
|
|
|
738
736
|
e.cacheWrite ?? 0,
|
|
739
737
|
e.cost ?? 0,
|
|
740
738
|
].map(csvEscape).join(","));
|
|
741
|
-
output = [headers.join(","), ...rows].join("\n")
|
|
739
|
+
output = `${[headers.join(","), ...rows].join("\n")}\n`;
|
|
742
740
|
}
|
|
743
741
|
if (outputFile) {
|
|
744
742
|
writeFileSync(outputFile, output);
|
|
@@ -901,9 +899,58 @@ function cmdHelp() {
|
|
|
901
899
|
opencode-tokens config get toast.enabled # Check if toast is enabled
|
|
902
900
|
`);
|
|
903
901
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
902
|
+
const BRAILLE_DOTS = [0x01, 0x02, 0x04, 0x40, 0x08, 0x10, 0x20, 0x80];
|
|
903
|
+
function getTrendValue(point, metric) {
|
|
904
|
+
return metric === "tokens" ? point.tokens : metric === "messages" ? point.messages : point.cost;
|
|
905
|
+
}
|
|
906
|
+
function formatTrendValue(value, metric) {
|
|
907
|
+
return metric === "tokens" ? formatTokens(value) : metric === "messages" ? String(Math.round(value)) : formatCost(value);
|
|
908
|
+
}
|
|
909
|
+
function formatSignedTrendValue(value, metric) {
|
|
910
|
+
const sign = value > 0 ? "+" : value < 0 ? "-" : "";
|
|
911
|
+
return `${sign}${formatTrendValue(Math.abs(value), metric)}`;
|
|
912
|
+
}
|
|
913
|
+
function metricLabel(metric) {
|
|
914
|
+
return metric === "tokens" ? "Token Trend" : metric === "messages" ? "Message Trend" : "Cost Trend";
|
|
915
|
+
}
|
|
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));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return cells.map(row => row.map(mask => mask === 0 ? " " : String.fromCharCode(0x2800 + mask)).join(""));
|
|
935
|
+
}
|
|
936
|
+
function buildTrendXAxis(points, chartPoints, chartWidth) {
|
|
937
|
+
const labelChars = Array.from({ length: chartWidth }, () => " ");
|
|
938
|
+
const labelStep = Math.max(1, Math.ceil(points.length / 6));
|
|
939
|
+
for (let i = 0; i < chartPoints.length; i++) {
|
|
940
|
+
if (i % labelStep !== 0 && i !== chartPoints.length - 1)
|
|
941
|
+
continue;
|
|
942
|
+
const date = new Date(points[i][0]);
|
|
943
|
+
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));
|
|
945
|
+
const hasSpace = labelChars.slice(Math.max(0, start - 1), Math.min(chartWidth, start + label.length + 1)).every((c) => c === " ");
|
|
946
|
+
if (!hasSpace)
|
|
947
|
+
continue;
|
|
948
|
+
for (let j = 0; j < label.length; j++) {
|
|
949
|
+
labelChars[start + j] = label[j];
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return labelChars.join("");
|
|
953
|
+
}
|
|
907
954
|
function cmdTrend(flags) {
|
|
908
955
|
const days = parseInt(String(flagValue(flags, "days") ?? "30"), 10);
|
|
909
956
|
const metric = flagValue(flags, "metric") ?? "cost";
|
|
@@ -914,14 +961,14 @@ function cmdTrend(flags) {
|
|
|
914
961
|
console.log(`\n (no data in period)\n`);
|
|
915
962
|
return;
|
|
916
963
|
}
|
|
917
|
-
// Aggregate by day
|
|
918
964
|
const dayMap = new Map();
|
|
919
965
|
for (const e of entries) {
|
|
920
966
|
const dayStart = getStartOfDay(new Date(e._ts));
|
|
921
|
-
|
|
922
|
-
|
|
967
|
+
let d = dayMap.get(dayStart);
|
|
968
|
+
if (!d) {
|
|
969
|
+
d = { cost: 0, tokens: 0, messages: 0 };
|
|
970
|
+
dayMap.set(dayStart, d);
|
|
923
971
|
}
|
|
924
|
-
const d = dayMap.get(dayStart);
|
|
925
972
|
d.cost += e.cost ?? 0;
|
|
926
973
|
d.tokens += (e.input ?? 0) + (e.output ?? 0) + (e.reasoning ?? 0);
|
|
927
974
|
d.messages += 1;
|
|
@@ -930,116 +977,53 @@ function cmdTrend(flags) {
|
|
|
930
977
|
if (sorted.length < 2) {
|
|
931
978
|
const only = sorted[0];
|
|
932
979
|
if (only) {
|
|
933
|
-
const v =
|
|
980
|
+
const v = formatTrendValue(getTrendValue(only[1], metric), metric);
|
|
934
981
|
console.log(`\n ${new Date(only[0]).toISOString().slice(0, 10)}: ${v}\n`);
|
|
935
982
|
}
|
|
936
983
|
return;
|
|
937
984
|
}
|
|
938
|
-
const values = sorted.map(([, d]) =>
|
|
985
|
+
const values = sorted.map(([, d]) => getTrendValue(d, metric));
|
|
939
986
|
const maxVal = Math.max(...values, 1);
|
|
940
|
-
const
|
|
987
|
+
const minVal = Math.min(...values);
|
|
988
|
+
const totalVal = values.reduce((sum, value) => sum + value, 0);
|
|
989
|
+
const avgVal = totalVal / values.length;
|
|
990
|
+
const deltaVal = values[values.length - 1] - values[0];
|
|
991
|
+
const chartHeight = sorted.length <= 3 ? 6 : Math.max(6, Math.min(Math.floor(width / 4), 12));
|
|
941
992
|
if (width < 35) {
|
|
942
993
|
// Fallback: simple sparkline
|
|
943
994
|
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
944
995
|
const spark = values.map(v => chars[Math.min(Math.floor((v / maxVal) * 7), 7)]).join("");
|
|
945
|
-
console.log(`\n ${spark}\n`);
|
|
996
|
+
console.log(`\n ${metricLabel(metric)} ${spark}\n`);
|
|
946
997
|
return;
|
|
947
998
|
}
|
|
948
|
-
// Build chart — pixel-based rendering
|
|
949
|
-
const cols = values.map((v) => ({ value: v, y: Math.round((v / maxVal) * (H - 1)) }));
|
|
950
999
|
const chartWidth = Math.max(width - 12, 20);
|
|
951
|
-
|
|
952
|
-
const
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
const
|
|
1000
|
+
const yLabelStep = Math.max(1, Math.floor(chartHeight / 4));
|
|
1001
|
+
const dotWidth = chartWidth * 2;
|
|
1002
|
+
const dotHeight = chartHeight * 4;
|
|
1003
|
+
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))),
|
|
1006
|
+
}));
|
|
1007
|
+
const rows = buildBrailleRows(chartPoints, chartWidth, chartHeight);
|
|
1008
|
+
const labelPoints = chartPoints.map(point => ({ x: Math.floor(point.x / 2), y: point.y }));
|
|
959
1009
|
const lines = [];
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const
|
|
964
|
-
|
|
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)}`);
|
|
1012
|
+
for (let row = 0; row < chartHeight; row++) {
|
|
1013
|
+
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)
|
|
965
1017
|
: "";
|
|
966
|
-
line
|
|
967
|
-
line += row === 0 ? " ┼" : " ┤";
|
|
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: "─" });
|
|
975
|
-
}
|
|
976
|
-
else {
|
|
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 });
|
|
993
|
-
}
|
|
994
|
-
}
|
|
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++;
|
|
1016
|
-
}
|
|
1017
|
-
line += c;
|
|
1018
|
-
prevX = x + 1;
|
|
1019
|
-
}
|
|
1018
|
+
const line = `${padLeft(label, 9)} ┤${rows[row]}`;
|
|
1020
1019
|
lines.push(line);
|
|
1021
1020
|
}
|
|
1022
|
-
|
|
1023
|
-
let axis = " ".repeat(9) + " └";
|
|
1024
|
-
axis += "─".repeat(chartWidth);
|
|
1021
|
+
const axis = `${" ".repeat(9)} └${"─".repeat(chartWidth)}`;
|
|
1025
1022
|
lines.push(axis);
|
|
1026
|
-
|
|
1027
|
-
const labelStep = Math.max(1, Math.ceil(sorted.length / 6));
|
|
1028
|
-
let xLabels = " ".repeat(11);
|
|
1029
|
-
for (let i = 0; i < px.length; i++) {
|
|
1030
|
-
if (i % labelStep === 0 || i === px.length - 1) {
|
|
1031
|
-
const d = new Date(sorted[i][0]);
|
|
1032
|
-
const ds = `${d.getMonth() + 1}/${d.getDate()}`;
|
|
1033
|
-
const pos = px[i] + 0;
|
|
1034
|
-
while (xLabels.length - 11 < pos)
|
|
1035
|
-
xLabels += " ";
|
|
1036
|
-
xLabels += ds;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
lines.push(xLabels);
|
|
1023
|
+
lines.push(`${" ".repeat(11)}${buildTrendXAxis(sorted, labelPoints, chartWidth)}`);
|
|
1040
1024
|
console.log();
|
|
1041
1025
|
for (const l of lines)
|
|
1042
|
-
console.log(
|
|
1026
|
+
console.log(` ${l}`);
|
|
1043
1027
|
console.log();
|
|
1044
1028
|
}
|
|
1045
1029
|
// ============================================================================
|
|
@@ -1075,8 +1059,7 @@ function main() {
|
|
|
1075
1059
|
}
|
|
1076
1060
|
// Default: stats
|
|
1077
1061
|
let period = "all";
|
|
1078
|
-
|
|
1079
|
-
breakdown = flagValue(parsed.flags, "by") || (parsed.flags.has("b") ? String(parsed.flags.get("b")) : undefined);
|
|
1062
|
+
const breakdown = flagValue(parsed.flags, "by") || (parsed.flags.has("b") ? String(parsed.flags.get("b")) : undefined);
|
|
1080
1063
|
for (const p of ["today", "week", "month", "all"]) {
|
|
1081
1064
|
if (parsed.positional.includes(p)) {
|
|
1082
1065
|
period = p;
|
package/package.json
CHANGED