@alint-js/cli 0.0.36 → 0.0.37
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/index.mjs +1 -1
- package/dist/{cli-Ct9DDkxy.mjs → cli-Dm3rTcjL.mjs} +417 -134
- package/dist/index.mjs +1 -1
- package/package.json +3 -3
package/dist/bin/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import Gitignore from "gitignore-fs";
|
|
|
16
16
|
import { Minimatch, minimatch } from "minimatch";
|
|
17
17
|
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
|
|
18
18
|
//#region package.json
|
|
19
|
-
var version = "0.0.
|
|
19
|
+
var version = "0.0.37";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/cli/commands/command.ts
|
|
22
22
|
function defineCommand(node) {
|
|
@@ -67,13 +67,14 @@ function formatCommandHelp(sections, nodes, argv, rootHelp) {
|
|
|
67
67
|
body: commands.map((command) => ` ${command.pattern.padEnd(longestCommand)} ${command.description}`).join("\n"),
|
|
68
68
|
title: "Commands"
|
|
69
69
|
};
|
|
70
|
+
const optionSection = formatOptionsSection(node.options ?? []);
|
|
70
71
|
const usageIndex = normalizedSections.findIndex((section) => section.title === "Usage");
|
|
71
|
-
|
|
72
|
-
return [
|
|
72
|
+
const withCommands = usageIndex === -1 ? [commandSection, ...normalizedSections] : [
|
|
73
73
|
...normalizedSections.slice(0, usageIndex + 1),
|
|
74
74
|
commandSection,
|
|
75
75
|
...normalizedSections.slice(usageIndex + 1)
|
|
76
76
|
];
|
|
77
|
+
return optionSection ? [...withCommands, optionSection] : withCommands;
|
|
77
78
|
}
|
|
78
79
|
function formatExamples(examples) {
|
|
79
80
|
return examples.map((example) => example.split("\n").map((line) => ` ${line}`).join("\n")).join("\n\n");
|
|
@@ -193,11 +194,14 @@ function shouldSkipOptionValue(arg) {
|
|
|
193
194
|
"--config",
|
|
194
195
|
"--cwd",
|
|
195
196
|
"--format",
|
|
197
|
+
"--interval",
|
|
198
|
+
"--metric",
|
|
196
199
|
"--model",
|
|
197
200
|
"--provider-endpoint",
|
|
198
201
|
"--provider-header",
|
|
199
202
|
"--provider-id",
|
|
200
203
|
"--provider-model",
|
|
204
|
+
"--rule",
|
|
201
205
|
"--rule-concurrency",
|
|
202
206
|
"--since",
|
|
203
207
|
"--timeout-ms",
|
|
@@ -873,7 +877,7 @@ function createRuleGroups(state) {
|
|
|
873
877
|
}
|
|
874
878
|
function estimateEta(durationMs, terminal, planned) {
|
|
875
879
|
if (terminal <= 0 || planned <= 0 || durationMs <= 0) return "eta ?";
|
|
876
|
-
return `eta ~${formatDuration
|
|
880
|
+
return `eta ~${formatDuration(durationMs * (planned - terminal) / terminal)}`;
|
|
877
881
|
}
|
|
878
882
|
function fitRow(row, columns) {
|
|
879
883
|
if (columns <= 0) return "";
|
|
@@ -927,7 +931,7 @@ function formatContentRows(groups, state, options, now, contentBudget) {
|
|
|
927
931
|
}
|
|
928
932
|
return rows;
|
|
929
933
|
}
|
|
930
|
-
function formatDuration
|
|
934
|
+
function formatDuration(ms) {
|
|
931
935
|
return `${(Math.max(ms, 0) / 1e3).toFixed(1)}s`;
|
|
932
936
|
}
|
|
933
937
|
function formatFooters(state, options, now) {
|
|
@@ -937,7 +941,7 @@ function formatFooters(state, options, now) {
|
|
|
937
941
|
const eta = estimateEta(elapsed, terminal, state.execution.planned);
|
|
938
942
|
const projectedTokens = terminal > 0 && state.execution.planned > 0 ? Math.ceil((state.totalTokens + state.cachedTokens) * state.execution.planned / terminal) : void 0;
|
|
939
943
|
return [
|
|
940
|
-
fitRow(`${terminal}/${state.execution.planned}${progressBar} ${formatDuration
|
|
944
|
+
fitRow(`${terminal}/${state.execution.planned}${progressBar} ${formatDuration(elapsed)} -> ${eta === "eta ?" ? "~?" : eta.replace("eta ", "")}`, options.columns),
|
|
941
945
|
fitRow(`${state.execution.running} concurrency / ${state.execution.queued} queued / ${state.execution.cached} cached / ${state.execution.failed} failed`, options.columns),
|
|
942
946
|
fitRow(`${state.totalTokens.toLocaleString("en-US")} tokens (${state.cachedTokens.toLocaleString("en-US")} cached) -> ${projectedTokens === void 0 ? "~?" : `~${projectedTokens.toLocaleString("en-US")} tokens`}`, options.columns)
|
|
943
947
|
];
|
|
@@ -965,7 +969,7 @@ function formatTargetRow(jobState, options, now, last) {
|
|
|
965
969
|
const inputPath = options.cwd ? relative$1(options.cwd, jobState.job.inputPath) || "." : jobState.job.inputPath;
|
|
966
970
|
const target = jobState.job.target.name ? `${jobState.job.target.kind} ${jobState.job.target.name}` : jobState.job.target.kind;
|
|
967
971
|
const startedAt = jobState.retry?.startedAt ?? jobState.startedAt ?? now;
|
|
968
|
-
return fitRow(`${last ? " └─" : " ├─"} ${inputPath} > ${target} ${jobState.retry ? `${jobState.retry.attempt}/${jobState.retry.maxAttempts} retrying elapsed ${formatDuration
|
|
972
|
+
return fitRow(`${last ? " └─" : " ├─"} ${inputPath} > ${target} ${jobState.retry ? `${jobState.retry.attempt}/${jobState.retry.maxAttempts} retrying elapsed ${formatDuration(now - startedAt)}` : `running ${formatDuration(now - startedAt)}`}`, options.columns);
|
|
969
973
|
}
|
|
970
974
|
function styleRow(row, state, warnCount, errorCount, options) {
|
|
971
975
|
let styledRow = row;
|
|
@@ -1386,20 +1390,29 @@ function isCi(env = process.env) {
|
|
|
1386
1390
|
//#region src/cli/stats/since.ts
|
|
1387
1391
|
/**
|
|
1388
1392
|
* Parse a `--since` time into an inclusive lower-bound timestamp.
|
|
1389
|
-
* This is a tiny helper only to meet the needs.
|
|
1390
1393
|
*
|
|
1391
1394
|
* Supported formats:
|
|
1392
|
-
* - `Nd`/`
|
|
1393
|
-
*
|
|
1395
|
+
* - `Nh`/`Nd`/`Nw`/`Nm`/`Ny` — now minus N hours/days/weeks/months/years.
|
|
1396
|
+
* Weeks, months, and years are fixed approximations (7/30/365 days). `m` is
|
|
1397
|
+
* months here, not minutes — a day is the finest window this tool tracks.
|
|
1398
|
+
* - `YYYY-MM`/`YYYY-MM-DD` — start of that UTC month/day.
|
|
1394
1399
|
*
|
|
1395
1400
|
* Edge cases:
|
|
1396
1401
|
* - An empty time means "no lower bound".
|
|
1397
|
-
* - An unrecognized time
|
|
1402
|
+
* - An unrecognized time throws.
|
|
1398
1403
|
*/
|
|
1404
|
+
const HOUR_MS = 36e5;
|
|
1405
|
+
const UNIT_MS = {
|
|
1406
|
+
d: 24 * HOUR_MS,
|
|
1407
|
+
h: HOUR_MS,
|
|
1408
|
+
m: 720 * HOUR_MS,
|
|
1409
|
+
w: 168 * HOUR_MS,
|
|
1410
|
+
y: 365 * 24 * HOUR_MS
|
|
1411
|
+
};
|
|
1399
1412
|
function parseSince(time, now) {
|
|
1400
1413
|
if (time === void 0 || time === "") return;
|
|
1401
|
-
const relative = /^(\d+)([
|
|
1402
|
-
if (relative) return now - Number(relative[1]) *
|
|
1414
|
+
const relative = /^(\d+)([dhmwy])$/u.exec(time);
|
|
1415
|
+
if (relative) return now - Number(relative[1]) * UNIT_MS[relative[2]];
|
|
1403
1416
|
const calendar = /^(\d{4})-(\d{2})(?:-(\d{2}))?$/u.exec(time);
|
|
1404
1417
|
if (calendar) {
|
|
1405
1418
|
const year = Number(calendar[1]);
|
|
@@ -1407,16 +1420,32 @@ function parseSince(time, now) {
|
|
|
1407
1420
|
const day = calendar[3] === void 0 ? 1 : Number(calendar[3]);
|
|
1408
1421
|
return Date.UTC(year, month - 1, day);
|
|
1409
1422
|
}
|
|
1410
|
-
throw new Error(`Invalid --since "${time}": use e.g. 7d,
|
|
1423
|
+
throw new Error(`Invalid --since "${time}": use e.g. 24h, 7d, 2w, 3m, 1y, 2025-01, or 2025-01-15.`);
|
|
1411
1424
|
}
|
|
1412
1425
|
//#endregion
|
|
1413
1426
|
//#region src/cli/stats/jsonl.ts
|
|
1414
1427
|
const DEFAULT_RETENTION_MONTHS = 12;
|
|
1415
1428
|
const STATS_FILE_PATTERN = /^stats-(\d{4})-(\d{2})\.jsonl$/u;
|
|
1429
|
+
const DAY_MS = 864e5;
|
|
1430
|
+
const MONTH_LABELS$1 = [
|
|
1431
|
+
"Jan",
|
|
1432
|
+
"Feb",
|
|
1433
|
+
"Mar",
|
|
1434
|
+
"Apr",
|
|
1435
|
+
"May",
|
|
1436
|
+
"Jun",
|
|
1437
|
+
"Jul",
|
|
1438
|
+
"Aug",
|
|
1439
|
+
"Sep",
|
|
1440
|
+
"Oct",
|
|
1441
|
+
"Nov",
|
|
1442
|
+
"Dec"
|
|
1443
|
+
];
|
|
1416
1444
|
function createJsonlStatsStore(options) {
|
|
1417
1445
|
const retentionMonths = options.retentionMonths ?? DEFAULT_RETENTION_MONTHS;
|
|
1418
1446
|
return {
|
|
1419
1447
|
query: (filter) => query(options.dir, filter ?? {}, Date.now),
|
|
1448
|
+
querySeries: (filter) => querySeries(options.dir, filter ?? {}, Date.now),
|
|
1420
1449
|
record: (input) => record(options.dir, input, Date.now(), retentionMonths)
|
|
1421
1450
|
};
|
|
1422
1451
|
}
|
|
@@ -1435,6 +1464,44 @@ function accumulate(rows, key, inTok, outTok, totalTok, countRun) {
|
|
|
1435
1464
|
rows.set(key, row);
|
|
1436
1465
|
return row;
|
|
1437
1466
|
}
|
|
1467
|
+
function bucketLabel(startMs, interval) {
|
|
1468
|
+
const date = new Date(startMs);
|
|
1469
|
+
if (interval === "month") return MONTH_LABELS$1[date.getUTCMonth()];
|
|
1470
|
+
return `${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
1471
|
+
}
|
|
1472
|
+
function bucketStart(ts, interval) {
|
|
1473
|
+
const date = new Date(ts);
|
|
1474
|
+
if (interval === "month") return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
|
|
1475
|
+
const dayStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
1476
|
+
if (interval === "week") return dayStart - (new Date(dayStart).getUTCDay() + 6) % 7 * DAY_MS;
|
|
1477
|
+
return dayStart;
|
|
1478
|
+
}
|
|
1479
|
+
function chooseInterval(since, nowMs, timestamps) {
|
|
1480
|
+
if (since !== void 0) return intervalForDuration(nowMs - since);
|
|
1481
|
+
if (timestamps.length < 2) return "day";
|
|
1482
|
+
return intervalForDuration(Math.max(...timestamps) - Math.min(...timestamps));
|
|
1483
|
+
}
|
|
1484
|
+
function fillBuckets(buckets, interval) {
|
|
1485
|
+
if (buckets.size === 0) return [];
|
|
1486
|
+
const starts = [...buckets.keys()].sort((left, right) => left - right);
|
|
1487
|
+
const last = starts.at(-1);
|
|
1488
|
+
const filled = [];
|
|
1489
|
+
for (let startMs = starts[0]; startMs <= last; startMs = nextBucketStart(startMs, interval)) filled.push(buckets.get(startMs) ?? {
|
|
1490
|
+
inTok: 0,
|
|
1491
|
+
key: bucketLabel(startMs, interval),
|
|
1492
|
+
outTok: 0,
|
|
1493
|
+
runs: 0,
|
|
1494
|
+
startMs,
|
|
1495
|
+
totalTok: 0
|
|
1496
|
+
});
|
|
1497
|
+
return filled;
|
|
1498
|
+
}
|
|
1499
|
+
function intervalForDuration(ms) {
|
|
1500
|
+
const days = ms / DAY_MS;
|
|
1501
|
+
if (days <= 21) return "day";
|
|
1502
|
+
if (days <= 120) return "week";
|
|
1503
|
+
return "month";
|
|
1504
|
+
}
|
|
1438
1505
|
function isEnoent(error) {
|
|
1439
1506
|
return error instanceof Error && error.code === "ENOENT";
|
|
1440
1507
|
}
|
|
@@ -1442,6 +1509,13 @@ function monthIndexOf(ts) {
|
|
|
1442
1509
|
const date = new Date(ts);
|
|
1443
1510
|
return date.getUTCFullYear() * 12 + date.getUTCMonth();
|
|
1444
1511
|
}
|
|
1512
|
+
function nextBucketStart(startMs, interval) {
|
|
1513
|
+
if (interval === "month") {
|
|
1514
|
+
const date = new Date(startMs);
|
|
1515
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1);
|
|
1516
|
+
}
|
|
1517
|
+
return startMs + (interval === "week" ? 7 * DAY_MS : DAY_MS);
|
|
1518
|
+
}
|
|
1445
1519
|
function parseRunLine(line) {
|
|
1446
1520
|
try {
|
|
1447
1521
|
const value = JSON.parse(line);
|
|
@@ -1460,42 +1534,84 @@ async function prune(dir, ts, retentionMonths) {
|
|
|
1460
1534
|
async function query(dir, filter, now) {
|
|
1461
1535
|
const dimension = filter.by ?? "model";
|
|
1462
1536
|
const since = parseSince(filter.since, now());
|
|
1537
|
+
const rules = ruleSet(filter.rules);
|
|
1463
1538
|
const runs = await readRuns(dir);
|
|
1464
1539
|
const rows = /* @__PURE__ */ new Map();
|
|
1465
1540
|
let totalRuns = 0;
|
|
1466
1541
|
let totalIn = 0;
|
|
1467
1542
|
let totalOut = 0;
|
|
1468
1543
|
let totalTok = 0;
|
|
1469
|
-
let totalDuration;
|
|
1470
1544
|
for (const run of runs) {
|
|
1471
|
-
if (since
|
|
1472
|
-
|
|
1545
|
+
if (!runInWindow(run, since, filter.cwd)) continue;
|
|
1546
|
+
const slice = sliceRun(run, rules);
|
|
1547
|
+
if (!slice.touched) continue;
|
|
1473
1548
|
totalRuns += 1;
|
|
1474
|
-
totalIn +=
|
|
1475
|
-
totalOut +=
|
|
1476
|
-
totalTok +=
|
|
1549
|
+
totalIn += slice.inTok;
|
|
1550
|
+
totalOut += slice.outTok;
|
|
1551
|
+
totalTok += slice.totalTok;
|
|
1477
1552
|
if (dimension === "dir") {
|
|
1478
|
-
accumulate(rows, run.cwd,
|
|
1553
|
+
accumulate(rows, run.cwd, slice.inTok, slice.outTok, slice.totalTok, true);
|
|
1479
1554
|
continue;
|
|
1480
1555
|
}
|
|
1481
1556
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
1482
|
-
for (const usageRecord of
|
|
1557
|
+
for (const usageRecord of slice.usageRecords) {
|
|
1483
1558
|
const key = recordKey(usageRecord, dimension);
|
|
1484
1559
|
accumulate(rows, key, usageRecord.inTok, usageRecord.outTok, usageRecord.totalTok, !seenKeys.has(key));
|
|
1485
1560
|
seenKeys.add(key);
|
|
1486
1561
|
}
|
|
1487
|
-
if (dimension !== "rule") continue;
|
|
1488
|
-
for (const { durationMs, ruleId } of run.ruleDurations ?? []) {
|
|
1489
|
-
const row = accumulate(rows, ruleId, 0, 0, 0, !seenKeys.has(ruleId));
|
|
1490
|
-
seenKeys.add(ruleId);
|
|
1491
|
-
row.durationMs = (row.durationMs ?? 0) + durationMs;
|
|
1492
|
-
totalDuration = (totalDuration ?? 0) + durationMs;
|
|
1493
|
-
}
|
|
1494
1562
|
}
|
|
1495
1563
|
return {
|
|
1496
1564
|
dimension,
|
|
1497
1565
|
rows: [...rows.values()].sort((left, right) => right.totalTok - left.totalTok),
|
|
1498
|
-
|
|
1566
|
+
totalIn,
|
|
1567
|
+
totalOut,
|
|
1568
|
+
totalRuns,
|
|
1569
|
+
totalTok
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
async function querySeries(dir, filter, now) {
|
|
1573
|
+
const nowMs = now();
|
|
1574
|
+
const since = parseSince(filter.since, nowMs);
|
|
1575
|
+
const rules = ruleSet(filter.rules);
|
|
1576
|
+
const runs = await readRuns(dir);
|
|
1577
|
+
const sliced = [];
|
|
1578
|
+
for (const run of runs) {
|
|
1579
|
+
if (!runInWindow(run, since, filter.cwd)) continue;
|
|
1580
|
+
const slice = sliceRun(run, rules);
|
|
1581
|
+
if (slice.touched) sliced.push({
|
|
1582
|
+
slice,
|
|
1583
|
+
ts: run.ts
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
const interval = filter.interval ?? chooseInterval(since, nowMs, sliced.map((entry) => entry.ts));
|
|
1587
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1588
|
+
let totalRuns = 0;
|
|
1589
|
+
let totalIn = 0;
|
|
1590
|
+
let totalOut = 0;
|
|
1591
|
+
let totalTok = 0;
|
|
1592
|
+
for (const { slice, ts } of sliced) {
|
|
1593
|
+
const startMs = bucketStart(ts, interval);
|
|
1594
|
+
const bucket = buckets.get(startMs) ?? {
|
|
1595
|
+
inTok: 0,
|
|
1596
|
+
key: bucketLabel(startMs, interval),
|
|
1597
|
+
outTok: 0,
|
|
1598
|
+
runs: 0,
|
|
1599
|
+
startMs,
|
|
1600
|
+
totalTok: 0
|
|
1601
|
+
};
|
|
1602
|
+
bucket.runs += 1;
|
|
1603
|
+
bucket.inTok += slice.inTok;
|
|
1604
|
+
bucket.outTok += slice.outTok;
|
|
1605
|
+
bucket.totalTok += slice.totalTok;
|
|
1606
|
+
buckets.set(startMs, bucket);
|
|
1607
|
+
totalRuns += 1;
|
|
1608
|
+
totalIn += slice.inTok;
|
|
1609
|
+
totalOut += slice.outTok;
|
|
1610
|
+
totalTok += slice.totalTok;
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
buckets: fillBuckets(buckets, interval),
|
|
1614
|
+
interval,
|
|
1499
1615
|
totalIn,
|
|
1500
1616
|
totalOut,
|
|
1501
1617
|
totalRuns,
|
|
@@ -1529,6 +1645,29 @@ function recordKey(record, dimension) {
|
|
|
1529
1645
|
if (dimension === "operation") return record.operation ?? record.ruleId;
|
|
1530
1646
|
return `${record.providerId}/${record.modelId}`;
|
|
1531
1647
|
}
|
|
1648
|
+
function ruleSet(rules) {
|
|
1649
|
+
return rules && rules.length > 0 ? new Set(rules) : void 0;
|
|
1650
|
+
}
|
|
1651
|
+
function runInWindow(run, since, cwd) {
|
|
1652
|
+
return (since === void 0 || run.ts >= since) && (cwd === void 0 || run.cwd === cwd);
|
|
1653
|
+
}
|
|
1654
|
+
function sliceRun(run, rules) {
|
|
1655
|
+
if (rules === void 0) return {
|
|
1656
|
+
inTok: run.usage.inTok,
|
|
1657
|
+
outTok: run.usage.outTok,
|
|
1658
|
+
totalTok: run.usage.totalTok,
|
|
1659
|
+
touched: true,
|
|
1660
|
+
usageRecords: run.usage.records
|
|
1661
|
+
};
|
|
1662
|
+
const usageRecords = run.usage.records.filter((record) => rules.has(record.ruleId));
|
|
1663
|
+
return {
|
|
1664
|
+
inTok: sumBy(usageRecords, (record) => record.inTok),
|
|
1665
|
+
outTok: sumBy(usageRecords, (record) => record.outTok),
|
|
1666
|
+
totalTok: sumBy(usageRecords, (record) => record.totalTok),
|
|
1667
|
+
touched: usageRecords.length > 0,
|
|
1668
|
+
usageRecords
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1532
1671
|
function statsFileName(ts) {
|
|
1533
1672
|
const date = new Date(ts);
|
|
1534
1673
|
return `stats-${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}.jsonl`;
|
|
@@ -1549,6 +1688,11 @@ async function statsFiles(dir) {
|
|
|
1549
1688
|
} : void 0;
|
|
1550
1689
|
}).filter((entry) => entry !== void 0).sort((left, right) => left.month - right.month);
|
|
1551
1690
|
}
|
|
1691
|
+
function sumBy(items, select) {
|
|
1692
|
+
let total = 0;
|
|
1693
|
+
for (const item of items) total += select(item);
|
|
1694
|
+
return total;
|
|
1695
|
+
}
|
|
1552
1696
|
//#endregion
|
|
1553
1697
|
//#region src/cli/stats/record.ts
|
|
1554
1698
|
function createRunStat(input) {
|
|
@@ -1556,7 +1700,6 @@ function createRunStat(input) {
|
|
|
1556
1700
|
cwd: input.cwd,
|
|
1557
1701
|
durationMs: input.durationMs,
|
|
1558
1702
|
ruleCounts: input.ruleCounts,
|
|
1559
|
-
ruleDurations: input.ruleDurations,
|
|
1560
1703
|
usage: {
|
|
1561
1704
|
inTok: input.usage.inputTokens,
|
|
1562
1705
|
outTok: input.usage.outputTokens,
|
|
@@ -1594,16 +1737,10 @@ function createStatsCollector() {
|
|
|
1594
1737
|
};
|
|
1595
1738
|
let startedAt;
|
|
1596
1739
|
let endedAt;
|
|
1597
|
-
const ruleMs = /* @__PURE__ */ new Map();
|
|
1598
1740
|
return {
|
|
1599
1741
|
counts,
|
|
1600
1742
|
durationMs: () => startedAt !== void 0 && endedAt !== void 0 ? endedAt - startedAt : void 0,
|
|
1601
1743
|
reporter: {
|
|
1602
|
-
onJobEnd: (payload) => {
|
|
1603
|
-
if (payload.startedAt === void 0 || payload.endedAt === void 0) return;
|
|
1604
|
-
const elapsed = Math.max(payload.endedAt - payload.startedAt, 0);
|
|
1605
|
-
ruleMs.set(payload.job.ruleId, (ruleMs.get(payload.job.ruleId) ?? 0) + elapsed);
|
|
1606
|
-
},
|
|
1607
1744
|
onRunEnd: (payload) => {
|
|
1608
1745
|
counts.cached = payload.execution.cached;
|
|
1609
1746
|
counts.cancelled = payload.execution.cancelled;
|
|
@@ -1615,11 +1752,7 @@ function createStatsCollector() {
|
|
|
1615
1752
|
onRunStart: (payload) => {
|
|
1616
1753
|
startedAt = payload.startedAt;
|
|
1617
1754
|
}
|
|
1618
|
-
}
|
|
1619
|
-
ruleDurations: () => ruleMs.size === 0 ? void 0 : [...ruleMs].map(([ruleId, durationMs]) => ({
|
|
1620
|
-
durationMs,
|
|
1621
|
-
ruleId
|
|
1622
|
-
}))
|
|
1755
|
+
}
|
|
1623
1756
|
};
|
|
1624
1757
|
}
|
|
1625
1758
|
function mergeProgressReporters(base, extra) {
|
|
@@ -1700,7 +1833,6 @@ async function writeRunStats(target, collector, result, cwd) {
|
|
|
1700
1833
|
failed: result.execution.failed,
|
|
1701
1834
|
planned: result.execution.planned
|
|
1702
1835
|
},
|
|
1703
|
-
ruleDurations: collector.ruleDurations(),
|
|
1704
1836
|
usage: result.usage
|
|
1705
1837
|
}));
|
|
1706
1838
|
} catch {}
|
|
@@ -2354,25 +2486,21 @@ const DIMENSION_LABEL = {
|
|
|
2354
2486
|
operation: "operation",
|
|
2355
2487
|
rule: "rule"
|
|
2356
2488
|
};
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
function formatMetric(row, metric) {
|
|
2363
|
-
if (metric === "runs") return String(row.runs);
|
|
2364
|
-
if (metric === "duration") return formatDuration(row.durationMs);
|
|
2365
|
-
return formatTokens(row.totalTok);
|
|
2489
|
+
function formatMetric(row, metric, exact = false) {
|
|
2490
|
+
return formatMetricValue(metricValue(row, metric), metric, exact);
|
|
2491
|
+
}
|
|
2492
|
+
function formatMetricValue(value, metric, exact = false) {
|
|
2493
|
+
return metric === "runs" ? String(value) : formatTokens(value, exact);
|
|
2366
2494
|
}
|
|
2367
2495
|
function formatStatsAggregate(aggregate, options = {}) {
|
|
2368
2496
|
const paint = (text, style) => options.color === true ? style(text) : text;
|
|
2369
2497
|
const metric = options.metric ?? "tokens";
|
|
2370
|
-
const
|
|
2498
|
+
const exact = options.exact === true;
|
|
2499
|
+
const lines = [summaryLine(aggregate, paint, exact), ""];
|
|
2371
2500
|
if (aggregate.rows.length === 0) {
|
|
2372
2501
|
lines.push("No stats recorded yet.");
|
|
2373
2502
|
return `${lines.join("\n")}\n`;
|
|
2374
2503
|
}
|
|
2375
|
-
const withTime = aggregate.dimension === "rule";
|
|
2376
2504
|
const header = [
|
|
2377
2505
|
DIMENSION_LABEL[aggregate.dimension],
|
|
2378
2506
|
"runs",
|
|
@@ -2380,18 +2508,13 @@ function formatStatsAggregate(aggregate, options = {}) {
|
|
|
2380
2508
|
"output",
|
|
2381
2509
|
"total"
|
|
2382
2510
|
];
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
formatTokens(row.totalTok)
|
|
2391
|
-
];
|
|
2392
|
-
if (withTime) cells.push(formatDuration(row.durationMs));
|
|
2393
|
-
return cells;
|
|
2394
|
-
});
|
|
2511
|
+
const rows = sortRows(aggregate.rows, metric).map((row) => [
|
|
2512
|
+
row.key,
|
|
2513
|
+
String(row.runs),
|
|
2514
|
+
formatTokens(row.inTok, exact),
|
|
2515
|
+
formatTokens(row.outTok, exact),
|
|
2516
|
+
formatTokens(row.totalTok, exact)
|
|
2517
|
+
]);
|
|
2395
2518
|
const widths = header.map((cell, index) => Math.max(cell.length, ...rows.map((row) => row[index].length)));
|
|
2396
2519
|
const renderRow = (cells, isHeader) => cells.map((cell, index) => {
|
|
2397
2520
|
const aligned = index === 0 ? cell.padEnd(widths[index]) : cell.padStart(widths[index]);
|
|
@@ -2400,30 +2523,39 @@ function formatStatsAggregate(aggregate, options = {}) {
|
|
|
2400
2523
|
lines.push(renderRow(header, true), ...rows.map((row) => renderRow(row, false)));
|
|
2401
2524
|
return `${lines.join("\n")}\n`;
|
|
2402
2525
|
}
|
|
2403
|
-
function formatTokens(value) {
|
|
2404
|
-
return value.toLocaleString("en-US");
|
|
2526
|
+
function formatTokens(value, exact = false) {
|
|
2527
|
+
if (exact || value < 1e4) return value.toLocaleString("en-US");
|
|
2528
|
+
if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
|
|
2529
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(2)}M`;
|
|
2530
|
+
return `${(value / 1e3).toFixed(2)}k`;
|
|
2405
2531
|
}
|
|
2406
2532
|
function metricValue(row, metric) {
|
|
2407
|
-
|
|
2408
|
-
if (metric === "duration") return row.durationMs ?? 0;
|
|
2409
|
-
return row.totalTok;
|
|
2533
|
+
return metric === "runs" ? row.runs : row.totalTok;
|
|
2410
2534
|
}
|
|
2411
2535
|
function sortRows(rows, metric) {
|
|
2412
2536
|
return [...rows].sort((left, right) => metricValue(right, metric) - metricValue(left, metric));
|
|
2413
2537
|
}
|
|
2414
|
-
function summaryLine(
|
|
2415
|
-
const runLabel =
|
|
2416
|
-
|
|
2417
|
-
return `${aggregate.totalRuns} ${runLabel} — ${formatTokens(aggregate.totalIn)} in / ${formatTokens(aggregate.totalOut)} out / ${paint(`${formatTokens(aggregate.totalTok)} total`, colors.cyan)}${time}`;
|
|
2418
|
-
}
|
|
2419
|
-
function formatDuration(ms) {
|
|
2420
|
-
return ms === void 0 ? "—" : `${(Math.max(ms, 0) / 1e3).toFixed(1)}s`;
|
|
2538
|
+
function summaryLine(totals, paint, exact = false) {
|
|
2539
|
+
const runLabel = totals.totalRuns === 1 ? "run" : "runs";
|
|
2540
|
+
return `${totals.totalRuns} ${runLabel} — ${formatTokens(totals.totalIn, exact)} in / ${formatTokens(totals.totalOut, exact)} out / ${paint(`${formatTokens(totals.totalTok, exact)} total`, colors.cyan)}`;
|
|
2421
2541
|
}
|
|
2422
2542
|
//#endregion
|
|
2423
|
-
//#region src/cli/commands/stats/
|
|
2424
|
-
const
|
|
2425
|
-
const
|
|
2426
|
-
const
|
|
2543
|
+
//#region src/cli/commands/stats/timeline.ts
|
|
2544
|
+
const CHART_HEIGHT = 6;
|
|
2545
|
+
const BAR_GAP = 2;
|
|
2546
|
+
const COLUMN_STRIDE = 3;
|
|
2547
|
+
const VERTICAL_BLOCKS = [
|
|
2548
|
+
" ",
|
|
2549
|
+
"▁",
|
|
2550
|
+
"▂",
|
|
2551
|
+
"▃",
|
|
2552
|
+
"▄",
|
|
2553
|
+
"▅",
|
|
2554
|
+
"▆",
|
|
2555
|
+
"▇",
|
|
2556
|
+
"█"
|
|
2557
|
+
];
|
|
2558
|
+
const HORIZONTAL_BLOCKS = [
|
|
2427
2559
|
"",
|
|
2428
2560
|
"▏",
|
|
2429
2561
|
"▎",
|
|
@@ -2433,36 +2565,143 @@ const PARTIAL_BLOCKS = [
|
|
|
2433
2565
|
"▊",
|
|
2434
2566
|
"▉"
|
|
2435
2567
|
];
|
|
2436
|
-
|
|
2568
|
+
const FULL_BLOCK = "█";
|
|
2569
|
+
const HORIZONTAL_BAR_MIN = 8;
|
|
2570
|
+
const HORIZONTAL_BAR_MAX = 40;
|
|
2571
|
+
const SUMMARY_GAP = 3;
|
|
2572
|
+
const SUMMARY_MAX_COLUMNS = 4;
|
|
2573
|
+
const MONTH_LABELS = [
|
|
2574
|
+
"Jan",
|
|
2575
|
+
"Feb",
|
|
2576
|
+
"Mar",
|
|
2577
|
+
"Apr",
|
|
2578
|
+
"May",
|
|
2579
|
+
"Jun",
|
|
2580
|
+
"Jul",
|
|
2581
|
+
"Aug",
|
|
2582
|
+
"Sep",
|
|
2583
|
+
"Oct",
|
|
2584
|
+
"Nov",
|
|
2585
|
+
"Dec"
|
|
2586
|
+
];
|
|
2587
|
+
function formatStatsTimeline(series, options = {}) {
|
|
2437
2588
|
const paint = (text, style) => options.color === true ? style(text) : text;
|
|
2438
2589
|
const metric = options.metric ?? "tokens";
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2590
|
+
const exact = options.exact === true;
|
|
2591
|
+
const ctx = {
|
|
2592
|
+
exact,
|
|
2593
|
+
interval: series.interval,
|
|
2594
|
+
metric,
|
|
2595
|
+
paint
|
|
2596
|
+
};
|
|
2597
|
+
const lines = [
|
|
2598
|
+
summaryLine(series, paint, exact),
|
|
2599
|
+
"",
|
|
2600
|
+
caption(series, metric, options.rules)
|
|
2601
|
+
];
|
|
2602
|
+
if (series.buckets.length === 0) {
|
|
2441
2603
|
lines.push("No stats recorded yet.");
|
|
2442
2604
|
return `${lines.join("\n")}\n`;
|
|
2443
2605
|
}
|
|
2444
|
-
const
|
|
2445
|
-
const maxValue = Math.max(...
|
|
2446
|
-
const
|
|
2447
|
-
const
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2606
|
+
const values = series.buckets.map((bucket) => metricValue(bucket, metric));
|
|
2607
|
+
const maxValue = Math.max(...values);
|
|
2608
|
+
const axisWidth = Math.max(formatMetricValue(maxValue, metric, exact).length, 1);
|
|
2609
|
+
const verticalWidth = axisWidth + 2 + series.buckets.length * COLUMN_STRIDE;
|
|
2610
|
+
const width = options.columns ?? 80;
|
|
2611
|
+
if (options.vertical === true && verticalWidth <= width) {
|
|
2612
|
+
const chart = renderVertical(series.buckets, values, maxValue, axisWidth, ctx);
|
|
2613
|
+
const list = summaryList(series.buckets, width, ctx);
|
|
2614
|
+
return `${[
|
|
2615
|
+
...lines,
|
|
2616
|
+
...chart,
|
|
2617
|
+
"",
|
|
2618
|
+
...list
|
|
2619
|
+
].join("\n")}\n`;
|
|
2620
|
+
}
|
|
2621
|
+
return `${[...lines, ...renderHorizontal(series.buckets, values, maxValue, width, ctx)].join("\n")}\n`;
|
|
2622
|
+
}
|
|
2623
|
+
function axisRows(buckets, interval, chartWidth) {
|
|
2624
|
+
if (interval === "month") return [endpointAxis(buckets, chartWidth)];
|
|
2625
|
+
const days = Array.from({ length: chartWidth + 4 }).fill(" ");
|
|
2626
|
+
const months = Array.from({ length: chartWidth + 4 }).fill(" ");
|
|
2627
|
+
buckets.forEach((bucket, index) => {
|
|
2628
|
+
const date = new Date(bucket.startMs);
|
|
2629
|
+
const at = BAR_GAP + index * COLUMN_STRIDE;
|
|
2630
|
+
const previous = index === 0 ? void 0 : new Date(buckets[index - 1].startMs);
|
|
2631
|
+
writeAt(days, at, String(date.getUTCDate()).padStart(2, "0"));
|
|
2632
|
+
if (startsNewMonth(previous, date)) writeAt(months, at, MONTH_LABELS[date.getUTCMonth()]);
|
|
2633
|
+
});
|
|
2634
|
+
return [days.join("").replace(/\s+$/u, ""), months.join("").replace(/\s+$/u, "")];
|
|
2635
|
+
}
|
|
2636
|
+
function caption(series, metric, rules) {
|
|
2637
|
+
const filter = rules && rules.length > 0 ? ` — ${rules.join(", ")}` : "";
|
|
2638
|
+
return `${metric} by ${series.interval}${filter}`;
|
|
2639
|
+
}
|
|
2640
|
+
function endpointAxis(buckets, chartWidth) {
|
|
2641
|
+
const first = buckets[0].key;
|
|
2642
|
+
if (buckets.length === 1) return `${" ".repeat(BAR_GAP)}${first}`;
|
|
2643
|
+
const last = buckets.at(-1).key;
|
|
2644
|
+
const lastStart = chartWidth - last.length;
|
|
2645
|
+
const firstEnd = BAR_GAP + first.length;
|
|
2646
|
+
return lastStart > firstEnd ? `${" ".repeat(BAR_GAP)}${first}${" ".repeat(lastStart - firstEnd)}${last}` : `${" ".repeat(BAR_GAP)}${first} … ${last}`;
|
|
2647
|
+
}
|
|
2648
|
+
function renderHorizontal(buckets, values, maxValue, width, ctx) {
|
|
2649
|
+
const labels = buckets.map((bucket) => formatMetric(bucket, ctx.metric, ctx.exact));
|
|
2650
|
+
const keyWidth = Math.max(...buckets.map((bucket) => bucket.key.length));
|
|
2651
|
+
const valueWidth = Math.max(...labels.map((label) => label.length));
|
|
2652
|
+
const barWidth = Math.max(HORIZONTAL_BAR_MIN, Math.min(HORIZONTAL_BAR_MAX, width - keyWidth - valueWidth - 6));
|
|
2653
|
+
return buckets.map((bucket, index) => {
|
|
2654
|
+
const bar = renderHorizontalBar(maxValue === 0 ? 0 : values[index] / maxValue, barWidth);
|
|
2655
|
+
return `${bucket.key.padEnd(keyWidth)} ${ctx.paint(bar, colors.cyan)} ${labels[index].padStart(valueWidth)}`;
|
|
2458
2656
|
});
|
|
2459
|
-
return `${lines.join("\n")}\n`;
|
|
2460
2657
|
}
|
|
2461
|
-
function
|
|
2462
|
-
const eighths = fraction <= 0 ? 0 : Math.max(1, Math.round(fraction *
|
|
2658
|
+
function renderHorizontalBar(fraction, width) {
|
|
2659
|
+
const eighths = fraction <= 0 ? 0 : Math.max(1, Math.round(fraction * width * 8));
|
|
2463
2660
|
const full = Math.floor(eighths / 8);
|
|
2464
|
-
|
|
2465
|
-
|
|
2661
|
+
return `${FULL_BLOCK.repeat(full)}${HORIZONTAL_BLOCKS[eighths % 8]}`.padEnd(width);
|
|
2662
|
+
}
|
|
2663
|
+
function renderVertical(buckets, values, maxValue, axisWidth, ctx) {
|
|
2664
|
+
const columnAt = (index) => BAR_GAP + index * COLUMN_STRIDE;
|
|
2665
|
+
const chartWidth = columnAt(buckets.length - 1) + 1;
|
|
2666
|
+
const eighths = values.map((value) => {
|
|
2667
|
+
if (value <= 0 || maxValue === 0) return 0;
|
|
2668
|
+
return Math.max(1, Math.round(value / maxValue * CHART_HEIGHT * 8));
|
|
2669
|
+
});
|
|
2670
|
+
const gutter = " ".repeat(axisWidth);
|
|
2671
|
+
const lines = [];
|
|
2672
|
+
for (let row = CHART_HEIGHT - 1; row >= 0; row -= 1) {
|
|
2673
|
+
const cells = Array.from({ length: chartWidth }).fill(" ");
|
|
2674
|
+
eighths.forEach((total, index) => {
|
|
2675
|
+
cells[columnAt(index)] = VERTICAL_BLOCKS[Math.max(0, Math.min(8, total - row * 8))];
|
|
2676
|
+
});
|
|
2677
|
+
const label = row === CHART_HEIGHT - 1 ? formatMetricValue(maxValue, ctx.metric, ctx.exact).padStart(axisWidth) : gutter;
|
|
2678
|
+
const axis = row === CHART_HEIGHT - 1 ? "┤" : "│";
|
|
2679
|
+
lines.push(`${label} ${axis}${ctx.paint(cells.join("").replace(/\s+$/u, ""), colors.cyan)}`);
|
|
2680
|
+
}
|
|
2681
|
+
const baseline = Array.from({ length: chartWidth }).fill("─");
|
|
2682
|
+
buckets.forEach((_, index) => {
|
|
2683
|
+
baseline[columnAt(index)] = "┴";
|
|
2684
|
+
});
|
|
2685
|
+
lines.push(`${"0".padStart(axisWidth)} ┼${baseline.join("")}`);
|
|
2686
|
+
for (const labelRow of axisRows(buckets, ctx.interval, chartWidth)) lines.push(`${gutter} ${labelRow}`);
|
|
2687
|
+
return lines;
|
|
2688
|
+
}
|
|
2689
|
+
function startsNewMonth(previous, date) {
|
|
2690
|
+
if (previous === void 0) return true;
|
|
2691
|
+
return previous.getUTCFullYear() !== date.getUTCFullYear() || previous.getUTCMonth() !== date.getUTCMonth();
|
|
2692
|
+
}
|
|
2693
|
+
function summaryList(buckets, width, ctx) {
|
|
2694
|
+
const labels = buckets.map((bucket) => formatMetric(bucket, ctx.metric, ctx.exact));
|
|
2695
|
+
const keyWidth = Math.max(...buckets.map((bucket) => bucket.key.length));
|
|
2696
|
+
const valueWidth = Math.max(...labels.map((label) => label.length));
|
|
2697
|
+
const cellWidth = keyWidth + 2 + valueWidth;
|
|
2698
|
+
const perRow = Math.max(1, Math.min(SUMMARY_MAX_COLUMNS, Math.floor((width + SUMMARY_GAP) / (cellWidth + SUMMARY_GAP))));
|
|
2699
|
+
const rows = [];
|
|
2700
|
+
for (let start = 0; start < buckets.length; start += perRow) rows.push(buckets.slice(start, start + perRow).map((bucket, offset) => `${bucket.key.padEnd(keyWidth)} ${labels[start + offset].padStart(valueWidth)}`).join(" ".repeat(SUMMARY_GAP)));
|
|
2701
|
+
return rows;
|
|
2702
|
+
}
|
|
2703
|
+
function writeAt(cells, at, text) {
|
|
2704
|
+
for (let index = 0; index < text.length && at + index < cells.length; index += 1) cells[at + index] = text[index];
|
|
2466
2705
|
}
|
|
2467
2706
|
//#endregion
|
|
2468
2707
|
//#region src/cli/commands/stats/stats.ts
|
|
@@ -2472,23 +2711,48 @@ const DIMENSIONS = /* @__PURE__ */ new Set([
|
|
|
2472
2711
|
"operation",
|
|
2473
2712
|
"rule"
|
|
2474
2713
|
]);
|
|
2475
|
-
const METRICS = /* @__PURE__ */ new Set([
|
|
2476
|
-
|
|
2477
|
-
"
|
|
2478
|
-
"
|
|
2714
|
+
const METRICS = /* @__PURE__ */ new Set(["runs", "tokens"]);
|
|
2715
|
+
const INTERVALS = /* @__PURE__ */ new Set([
|
|
2716
|
+
"day",
|
|
2717
|
+
"month",
|
|
2718
|
+
"week"
|
|
2479
2719
|
]);
|
|
2480
2720
|
async function runStatsCommand(options, io) {
|
|
2481
2721
|
try {
|
|
2482
|
-
const dimension = parseDimension(options.by);
|
|
2483
2722
|
const metric = parseMetric(options.metric);
|
|
2484
|
-
|
|
2485
|
-
const
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2723
|
+
const rules = parseRules(options.rule);
|
|
2724
|
+
const cwd = options.here === true ? io.cwd : options.cwd;
|
|
2725
|
+
const color = io.stdout.isTTY === true;
|
|
2726
|
+
const exact = options.exactNumbers === true;
|
|
2727
|
+
const store = await resolveStore(io);
|
|
2728
|
+
if (options.chart === true) {
|
|
2729
|
+
const series = await store.querySeries({
|
|
2730
|
+
cwd,
|
|
2731
|
+
interval: parseInterval(options.interval),
|
|
2732
|
+
rules,
|
|
2733
|
+
since: options.since
|
|
2734
|
+
});
|
|
2735
|
+
io.stdout.write(options.json === true ? `${JSON.stringify(series, null, 2)}\n` : formatStatsTimeline(series, {
|
|
2736
|
+
color,
|
|
2737
|
+
columns: io.stdout.columns,
|
|
2738
|
+
exact,
|
|
2739
|
+
metric,
|
|
2740
|
+
rules,
|
|
2741
|
+
vertical: options.vertical === true
|
|
2742
|
+
}));
|
|
2743
|
+
return 0;
|
|
2744
|
+
}
|
|
2745
|
+
const aggregate = await store.query({
|
|
2746
|
+
by: parseDimension(options.by),
|
|
2747
|
+
cwd,
|
|
2748
|
+
rules,
|
|
2489
2749
|
since: options.since
|
|
2490
2750
|
});
|
|
2491
|
-
io.stdout.write(
|
|
2751
|
+
io.stdout.write(options.json === true ? `${JSON.stringify(aggregate, null, 2)}\n` : formatStatsAggregate(aggregate, {
|
|
2752
|
+
color,
|
|
2753
|
+
exact,
|
|
2754
|
+
metric
|
|
2755
|
+
}));
|
|
2492
2756
|
return 0;
|
|
2493
2757
|
} catch (error) {
|
|
2494
2758
|
io.stderr.write(`${errorMessageFrom$1(error) ?? "Failed to read stats."}\n`);
|
|
@@ -2500,21 +2764,24 @@ function parseDimension(value) {
|
|
|
2500
2764
|
if (DIMENSIONS.has(value)) return value;
|
|
2501
2765
|
throw new Error(`Invalid --by "${value}": use rule, operation, model, or dir.`);
|
|
2502
2766
|
}
|
|
2767
|
+
function parseInterval(value) {
|
|
2768
|
+
if (value === void 0) return;
|
|
2769
|
+
if (INTERVALS.has(value)) return value;
|
|
2770
|
+
throw new Error(`Invalid --interval "${value}": use day, week, or month.`);
|
|
2771
|
+
}
|
|
2503
2772
|
function parseMetric(value) {
|
|
2504
2773
|
if (value === void 0) return "tokens";
|
|
2505
2774
|
if (METRICS.has(value)) return value;
|
|
2506
|
-
throw new Error(`Invalid --metric "${value}": use tokens
|
|
2775
|
+
throw new Error(`Invalid --metric "${value}": use tokens or runs.`);
|
|
2507
2776
|
}
|
|
2508
|
-
function
|
|
2509
|
-
if (
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
metric
|
|
2517
|
-
});
|
|
2777
|
+
function parseRules(value) {
|
|
2778
|
+
if (value === void 0) return;
|
|
2779
|
+
const rules = value.split(",").map((rule) => rule.trim()).filter(Boolean);
|
|
2780
|
+
return rules.length > 0 ? rules : void 0;
|
|
2781
|
+
}
|
|
2782
|
+
async function resolveStore(io) {
|
|
2783
|
+
const [setupConfig, config] = await Promise.all([loadMergedSetupConfig(io), loadAlintConfig(io.cwd)]);
|
|
2784
|
+
return createJsonlStatsStore({ dir: statsLocation(resolveConfigRunner(config)?.stats) ?? statsLocation(setupConfig.runner?.stats) ?? getStatsDir(io.env) });
|
|
2518
2785
|
}
|
|
2519
2786
|
function statsLocation(stats) {
|
|
2520
2787
|
return typeof stats === "object" ? stats.location : void 0;
|
|
@@ -2532,11 +2799,31 @@ const commandTree = [
|
|
|
2532
2799
|
name: "stats",
|
|
2533
2800
|
options: [
|
|
2534
2801
|
{
|
|
2535
|
-
description: "
|
|
2802
|
+
description: "Render a usage timeline instead of the table",
|
|
2803
|
+
flags: "--chart"
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
description: "Group the table by rule, operation, model, or dir",
|
|
2536
2807
|
flags: "--by <dimension>"
|
|
2537
2808
|
},
|
|
2538
2809
|
{
|
|
2539
|
-
description: "
|
|
2810
|
+
description: "Chart bucket: day, week, or month (default: auto)",
|
|
2811
|
+
flags: "--interval <interval>"
|
|
2812
|
+
},
|
|
2813
|
+
{
|
|
2814
|
+
description: "Draw the chart as vertical bars instead of horizontal",
|
|
2815
|
+
flags: "--vertical"
|
|
2816
|
+
},
|
|
2817
|
+
{
|
|
2818
|
+
description: "Rank/plot by tokens or runs",
|
|
2819
|
+
flags: "--metric <metric>"
|
|
2820
|
+
},
|
|
2821
|
+
{
|
|
2822
|
+
description: "Only include these rules (comma-separated)",
|
|
2823
|
+
flags: "--rule <ids>"
|
|
2824
|
+
},
|
|
2825
|
+
{
|
|
2826
|
+
description: "Only include runs since this time (e.g. 24h, 7d, 4w, 3m, 2025-01)",
|
|
2540
2827
|
flags: "--since <time>"
|
|
2541
2828
|
},
|
|
2542
2829
|
{
|
|
@@ -2548,12 +2835,8 @@ const commandTree = [
|
|
|
2548
2835
|
flags: "--here"
|
|
2549
2836
|
},
|
|
2550
2837
|
{
|
|
2551
|
-
description: "
|
|
2552
|
-
flags: "--
|
|
2553
|
-
},
|
|
2554
|
-
{
|
|
2555
|
-
description: "Rank by tokens, runs, or duration (duration needs --by rule)",
|
|
2556
|
-
flags: "--metric <metric>"
|
|
2838
|
+
description: "Show full counts instead of compact k/M/B",
|
|
2839
|
+
flags: "--exact-numbers"
|
|
2557
2840
|
},
|
|
2558
2841
|
{
|
|
2559
2842
|
description: "Output JSON",
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-
|
|
1
|
+
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-Dm3rTcjL.mjs";
|
|
2
2
|
import { ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated } from "@alint-js/config";
|
|
3
3
|
import { defineConfig } from "@alint-js/core";
|
|
4
4
|
export { defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alint-js/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.37",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.mts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"pathe": "^2.0.3",
|
|
28
28
|
"table": "^6.9.0",
|
|
29
29
|
"tinyrainbow": "^3.1.0",
|
|
30
|
-
"@alint-js/
|
|
31
|
-
"@alint-js/
|
|
30
|
+
"@alint-js/config": "0.0.37",
|
|
31
|
+
"@alint-js/core": "0.0.37"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@pnpm/find-workspace-dir": "^1000.1.5",
|