tokentracker-cli 0.81.2 → 0.82.0
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/dashboard/dist/assets/AchievementBadge-D6iPEEbz.js +1 -0
- package/dashboard/dist/assets/AchievementsPage-Jry355YE.js +2 -0
- package/dashboard/dist/assets/AchievementsSection-ah-kG00E.js +1 -0
- package/dashboard/dist/assets/BadgeDetailModal-FyvUrVvF.js +1 -0
- package/dashboard/dist/assets/Card-C3nLScju.js +1 -0
- package/dashboard/dist/assets/ClawdAnimated-CLHhE26z.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-DA3PHLV5.js +1 -0
- package/dashboard/dist/assets/ConfirmModal-CTDarLrD.js +1 -0
- package/dashboard/dist/assets/DashboardPage-DZIiWc3Y.js +1 -0
- package/dashboard/dist/assets/DevicePage-CyXjUO8W.js +1 -0
- package/dashboard/dist/assets/DialogDescription-ahxSNUcB.js +1 -0
- package/dashboard/dist/assets/DialogTitle-z86PHHyk.js +1 -0
- package/dashboard/dist/assets/FadeIn-BDWtfIsO.js +1 -0
- package/dashboard/dist/assets/HeaderGithubStar-DE1USQbb.js +1 -0
- package/dashboard/dist/assets/{IpCheckPage-CMf9CHFr.js → IpCheckPage-B2SkgT0P.js} +3 -3
- package/dashboard/dist/assets/{LandingPage-BT61_TqT.js → LandingPage-PU5gxsfY.js} +165 -165
- package/dashboard/dist/assets/LeaderboardAvatar-CaOV_hFE.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-B0JtEyRP.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-C_kxqOMn.js → LeaderboardProfileModal-DtNEbVH8.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-zgNsE0EQ.js → LeaderboardProfilePage-DRklax3-.js} +3 -3
- package/dashboard/dist/assets/LimitsPage-Dg0cJa40.js +4 -0
- package/dashboard/dist/assets/LocalOnlyNotice-si9PMKya.js +1 -0
- package/dashboard/dist/assets/LoginPage-DgLwhXKo.js +1 -0
- package/dashboard/dist/assets/PetPage-DEZbjk99.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-DX7v1XfE.js +1 -0
- package/dashboard/dist/assets/ResetPasswordPage-C6NvRO-Q.js +1 -0
- package/dashboard/dist/assets/Select-CH2xVU8X.js +1 -0
- package/dashboard/dist/assets/SelectItemText-CCW6JeeN.js +1 -0
- package/dashboard/dist/assets/SettingsPage-Cm3uT11D.js +1 -0
- package/dashboard/dist/assets/SkillsPage-CljRL6yF.js +1 -0
- package/dashboard/dist/assets/TrendMonitor-DM1_WNfw.js +19 -0
- package/dashboard/dist/assets/WidgetsPage-mJ1j6hly.js +1 -0
- package/dashboard/dist/assets/WrappedPage-Dz71ln4t.js +1 -0
- package/dashboard/dist/assets/__vite-browser-external-C7JwykID.css +1 -0
- package/dashboard/dist/assets/__vite-browser-external-Eoyj4eG-.js +1341 -0
- package/dashboard/dist/assets/agent-logos-D3K_ZRUj.js +1 -0
- package/dashboard/dist/assets/arrow-up-right-CT4-gmOS.js +1 -0
- package/dashboard/dist/assets/check-B87_qvAg.js +1 -0
- package/dashboard/dist/assets/copy-Dtr68epm.js +1 -0
- package/dashboard/dist/assets/download-DaWsA_Gj.js +1 -0
- package/dashboard/dist/assets/flame-vLBOq0X0.js +1 -0
- package/dashboard/dist/assets/format-tokens-C6Pidz3s.js +1 -0
- package/dashboard/dist/assets/icons-BaaYJxUN.js +1 -0
- package/dashboard/dist/assets/index-lKnLA6Pd.js +1 -0
- package/dashboard/dist/assets/info-B46Qt16h.js +1 -0
- package/dashboard/dist/assets/limits-providers-13af4PEg.js +1 -0
- package/dashboard/dist/assets/link-2-DeN6MJpT.js +1 -0
- package/dashboard/dist/assets/loader-circle-ByEuaaSw.js +1 -0
- package/dashboard/dist/assets/provider-display-CKbOKeV4.js +42 -0
- package/dashboard/dist/assets/terminal-DyicX7lG.js +1 -0
- package/dashboard/dist/assets/trash-2-CVCC3DtQ.js +1 -0
- package/dashboard/dist/assets/use-community-stats-CExk3D6G.js +1 -0
- package/dashboard/dist/assets/use-limits-display-prefs-CRl4uU95.js +1 -0
- package/dashboard/dist/assets/use-native-settings-Bi6E9dsE.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-DPbiKnPc.js → use-ordered-list-Dk0p2otw.js} +2 -2
- package/dashboard/dist/assets/use-session-efficiency-pref-YAG-OYIh.js +1 -0
- package/dashboard/dist/assets/use-transform-lflyghWN.js +1 -0
- package/dashboard/dist/assets/use-usage-limits-DJXvJ25Q.js +1 -0
- package/dashboard/dist/assets/useCurrency-BtHtcsO5.js +1 -0
- package/dashboard/dist/assets/useScrollLock-C2C-Yazq.js +1 -0
- package/dashboard/dist/assets/useTokenFormat-BdD057ih.js +1 -0
- package/dashboard/dist/assets/zap-DWWma29E.js +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/commands/serve.js +8 -6
- package/src/commands/sessions.js +29 -0
- package/src/commands/status.js +2 -1
- package/src/commands/sync.js +164 -36
- package/src/lib/codex-context-breakdown.js +330 -13
- package/src/lib/codex-rollout-parser.js +323 -42
- package/src/lib/context-health.js +124 -0
- package/src/lib/cursor-store.js +1289 -0
- package/src/lib/diagnostics.js +8 -5
- package/src/lib/fs.js +254 -16
- package/src/lib/git-outcomes.js +147 -0
- package/src/lib/local-api.js +151 -20
- package/src/lib/outcomes-engine.js +23 -0
- package/src/lib/pricing/index.js +8 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +101 -9
- package/src/lib/session-analytics.js +612 -0
- package/src/lib/usage-limits.js +21 -0
- package/dashboard/dist/assets/AchievementBadge-DXWHZrAL.js +0 -1
- package/dashboard/dist/assets/AchievementsPage-BhrQXniL.js +0 -2
- package/dashboard/dist/assets/AchievementsSection-gzP3m1Ml.js +0 -1
- package/dashboard/dist/assets/BadgeDetailModal-DGKPuvDU.js +0 -1
- package/dashboard/dist/assets/Card-BRtYQ-Ot.js +0 -1
- package/dashboard/dist/assets/ClawdAnimated-UsNG-qDv.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-CJQw-P4t.js +0 -1
- package/dashboard/dist/assets/ConfirmModal-ByY4GDsy.js +0 -1
- package/dashboard/dist/assets/DashboardPage-CuLVnW98.js +0 -19
- package/dashboard/dist/assets/DevicePage-D67CxpmR.js +0 -1
- package/dashboard/dist/assets/DialogClose-Ci8hkTw5.js +0 -1
- package/dashboard/dist/assets/DialogDescription-C9vXUZU0.js +0 -1
- package/dashboard/dist/assets/DialogTitle-sLJxKWjv.js +0 -1
- package/dashboard/dist/assets/FadeIn-xVds37w6.js +0 -1
- package/dashboard/dist/assets/HeaderGithubStar-kK3omgmB.js +0 -1
- package/dashboard/dist/assets/LeaderboardAvatar-DyKz6NdT.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-DJ4zihJJ.js +0 -2
- package/dashboard/dist/assets/LimitsPage-BiQpqxj8.js +0 -4
- package/dashboard/dist/assets/LocalOnlyNotice-DTrrUVbZ.js +0 -1
- package/dashboard/dist/assets/LoginPage-BOM9Q2sI.js +0 -1
- package/dashboard/dist/assets/PetPage-BURk7RG-.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-BV_aoNBd.js +0 -1
- package/dashboard/dist/assets/ResetPasswordPage-SBL2LvXC.js +0 -1
- package/dashboard/dist/assets/Select-BsRdHde3.js +0 -1
- package/dashboard/dist/assets/SelectItemText-DgkrZxIq.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DqxVsfnB.js +0 -1
- package/dashboard/dist/assets/SkillsPage-B0VfWkOh.js +0 -1
- package/dashboard/dist/assets/WidgetsPage-4QQ8wI_g.js +0 -1
- package/dashboard/dist/assets/WrappedPage-BK53khVY.js +0 -1
- package/dashboard/dist/assets/agent-logos-DXpW_Wmz.js +0 -1
- package/dashboard/dist/assets/arrow-up-right-DCc--V5G.js +0 -1
- package/dashboard/dist/assets/check-DqxuosV2.js +0 -1
- package/dashboard/dist/assets/copy-qChAT7MY.js +0 -1
- package/dashboard/dist/assets/download-W9R3sBph.js +0 -1
- package/dashboard/dist/assets/flame-0eyhMnfU.js +0 -1
- package/dashboard/dist/assets/format-tokens-CkfBUcoa.js +0 -1
- package/dashboard/dist/assets/info-DY1O85-U.js +0 -1
- package/dashboard/dist/assets/link-2-B_Z66hFT.js +0 -1
- package/dashboard/dist/assets/loader-circle-xH40lon2.js +0 -1
- package/dashboard/dist/assets/main-CeaB5N36.js +0 -1259
- package/dashboard/dist/assets/main-DAc77vwF.css +0 -1
- package/dashboard/dist/assets/provider-display-C_XKZQcF.js +0 -42
- package/dashboard/dist/assets/trash-2-DVhNJ74r.js +0 -1
- package/dashboard/dist/assets/use-community-stats-CO-nr2Vf.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-RRqmyoxt.js +0 -1
- package/dashboard/dist/assets/use-native-settings-79piPeET.js +0 -1
- package/dashboard/dist/assets/use-quality-per-dollar-pref-DyF49QRF.js +0 -1
- package/dashboard/dist/assets/use-transform-BvXyGAtb.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-BiZ_I-KE.js +0 -1
- package/dashboard/dist/assets/useCurrency-D-C1e7eE.js +0 -1
- package/dashboard/dist/assets/useScrollLock-BfU2EMvG.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-Cm4EvrQp.js +0 -1
- package/dashboard/dist/assets/zap-DlAOTwFs.js +0 -1
package/src/lib/local-api.js
CHANGED
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
getModelPricing,
|
|
36
36
|
computeRowCost,
|
|
37
37
|
ensurePricingLoaded,
|
|
38
|
+
getPricingRevision,
|
|
38
39
|
} = require("./pricing");
|
|
39
40
|
|
|
40
41
|
const {
|
|
@@ -66,6 +67,33 @@ const CLAUDE_MEM_OBSERVER_PROJECT_KEY = deriveProjectKeyFromRef(
|
|
|
66
67
|
CLAUDE_MEM_OBSERVER_PROJECT_REF,
|
|
67
68
|
);
|
|
68
69
|
const PROJECT_USAGE_MAX_ENTRIES = 10;
|
|
70
|
+
const MAX_TIME_ZONE_CACHE_ENTRIES = 16;
|
|
71
|
+
|
|
72
|
+
// The native dashboard fans one refresh out across several local endpoints.
|
|
73
|
+
// Keep one immutable, deduped view of the current queue so those endpoints do
|
|
74
|
+
// not all read and JSON.parse the same append-only file. A file identity +
|
|
75
|
+
// nanosecond timestamp signature invalidates the cache immediately on append,
|
|
76
|
+
// in-place rewrite, or atomic replacement.
|
|
77
|
+
let queueDataCache = null;
|
|
78
|
+
const dailyAggregationCache = new WeakMap();
|
|
79
|
+
const zonedPartsFormatters = new Map();
|
|
80
|
+
|
|
81
|
+
function boundedCacheSet(cache, key, value, maxEntries) {
|
|
82
|
+
if (cache.has(key)) cache.delete(key);
|
|
83
|
+
cache.set(key, value);
|
|
84
|
+
while (cache.size > maxEntries) {
|
|
85
|
+
cache.delete(cache.keys().next().value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function queueFileSignature(queuePath) {
|
|
90
|
+
const stat = fs.statSync(queuePath, { bigint: true });
|
|
91
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function timeZoneContextKey({ timeZone, offsetMinutes } = {}) {
|
|
95
|
+
return `${timeZone || ""}|${Number.isFinite(offsetMinutes) ? offsetMinutes : ""}|p${getPricingRevision()}`;
|
|
96
|
+
}
|
|
69
97
|
|
|
70
98
|
// Shared by project-usage-summary and project-usage-detail: reads the
|
|
71
99
|
// deduped project bucket log, drops the claude-mem pseudo project, and
|
|
@@ -160,6 +188,21 @@ function normalizeQueueRow(row) {
|
|
|
160
188
|
}
|
|
161
189
|
|
|
162
190
|
function readQueueData(queuePath) {
|
|
191
|
+
let signature;
|
|
192
|
+
try {
|
|
193
|
+
signature = queueFileSignature(queuePath);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
if (queueDataCache?.queuePath === queuePath) queueDataCache = null;
|
|
196
|
+
if (e?.code !== "ENOENT") {
|
|
197
|
+
console.error("[LocalAPI] readQueueData: failed to stat queue:", e?.message || e);
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (queueDataCache?.queuePath === queuePath && queueDataCache.signature === signature) {
|
|
203
|
+
return queueDataCache.rows;
|
|
204
|
+
}
|
|
205
|
+
|
|
163
206
|
let raw;
|
|
164
207
|
try {
|
|
165
208
|
raw = fs.readFileSync(queuePath, "utf8");
|
|
@@ -196,7 +239,9 @@ function readQueueData(queuePath) {
|
|
|
196
239
|
const key = `${row.source || ""}|${row.model || ""}|${row.hour_start || ""}`;
|
|
197
240
|
seen.set(key, normalizeQueueRow(row));
|
|
198
241
|
}
|
|
199
|
-
|
|
242
|
+
const rows = Array.from(seen.values());
|
|
243
|
+
queueDataCache = { queuePath, signature, rows };
|
|
244
|
+
return rows;
|
|
200
245
|
}
|
|
201
246
|
|
|
202
247
|
function rowDayKey(row, timeZoneContext) {
|
|
@@ -214,8 +259,19 @@ function rowDayKey(row, timeZoneContext) {
|
|
|
214
259
|
}
|
|
215
260
|
|
|
216
261
|
function aggregateByDay(rows, timeZoneContext = null) {
|
|
262
|
+
const normalizedRows = Array.isArray(rows) ? rows : [];
|
|
263
|
+
const cacheKey = timeZoneContextKey(timeZoneContext || {});
|
|
264
|
+
let cachedByTimeZone = dailyAggregationCache.get(normalizedRows);
|
|
265
|
+
if (cachedByTimeZone?.has(cacheKey)) {
|
|
266
|
+
const cached = cachedByTimeZone.get(cacheKey);
|
|
267
|
+
// Touch the entry so the small per-queue map behaves as an LRU.
|
|
268
|
+
cachedByTimeZone.delete(cacheKey);
|
|
269
|
+
cachedByTimeZone.set(cacheKey, cached);
|
|
270
|
+
return cached;
|
|
271
|
+
}
|
|
272
|
+
|
|
217
273
|
const byDay = new Map();
|
|
218
|
-
for (const row of
|
|
274
|
+
for (const row of normalizedRows) {
|
|
219
275
|
if (!row.hour_start) continue;
|
|
220
276
|
const day = rowDayKey(row, timeZoneContext);
|
|
221
277
|
if (!day) continue;
|
|
@@ -250,7 +306,13 @@ function aggregateByDay(rows, timeZoneContext = null) {
|
|
|
250
306
|
const model = row.model || "unknown";
|
|
251
307
|
a.models[model] = (a.models[model] || 0) + (row.total_tokens || 0);
|
|
252
308
|
}
|
|
253
|
-
|
|
309
|
+
const daily = Array.from(byDay.values()).sort((a, b) => a.day.localeCompare(b.day));
|
|
310
|
+
if (!cachedByTimeZone) {
|
|
311
|
+
cachedByTimeZone = new Map();
|
|
312
|
+
dailyAggregationCache.set(normalizedRows, cachedByTimeZone);
|
|
313
|
+
}
|
|
314
|
+
boundedCacheSet(cachedByTimeZone, cacheKey, daily, MAX_TIME_ZONE_CACHE_ENTRIES);
|
|
315
|
+
return daily;
|
|
254
316
|
}
|
|
255
317
|
|
|
256
318
|
function buildCodexCategoryFallbackFromQueue(queueRows, { from, to, timeZoneContext }) {
|
|
@@ -477,17 +539,34 @@ function getZonedParts(date, { timeZone, offsetMinutes } = {}) {
|
|
|
477
539
|
|
|
478
540
|
if (timeZone && typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
479
541
|
try {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
542
|
+
let formatter;
|
|
543
|
+
if (zonedPartsFormatters.has(timeZone)) {
|
|
544
|
+
formatter = zonedPartsFormatters.get(timeZone);
|
|
545
|
+
} else {
|
|
546
|
+
try {
|
|
547
|
+
formatter = new Intl.DateTimeFormat("en-CA", {
|
|
548
|
+
timeZone,
|
|
549
|
+
year: "numeric",
|
|
550
|
+
month: "2-digit",
|
|
551
|
+
day: "2-digit",
|
|
552
|
+
hour: "2-digit",
|
|
553
|
+
minute: "2-digit",
|
|
554
|
+
second: "2-digit",
|
|
555
|
+
hourCycle: "h23",
|
|
556
|
+
});
|
|
557
|
+
} catch {
|
|
558
|
+
// Cache invalid zone ids too; otherwise a bad local query would
|
|
559
|
+
// throw once per queue row before falling back to its fixed offset.
|
|
560
|
+
formatter = null;
|
|
561
|
+
}
|
|
562
|
+
boundedCacheSet(
|
|
563
|
+
zonedPartsFormatters,
|
|
564
|
+
timeZone,
|
|
565
|
+
formatter,
|
|
566
|
+
MAX_TIME_ZONE_CACHE_ENTRIES,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
const parts = formatter?.formatToParts(dt) || [];
|
|
491
570
|
const values = parts.reduce((acc, part) => {
|
|
492
571
|
if (part.type && part.value) acc[part.type] = part.value;
|
|
493
572
|
return acc;
|
|
@@ -685,6 +764,7 @@ function runSyncCommand(extraEnv = {}, opts = {}) {
|
|
|
685
764
|
const args = [TRACKER_BIN, "sync"];
|
|
686
765
|
if (opts.auto === true) args.push("--auto");
|
|
687
766
|
if (opts.background === true) args.push("--background");
|
|
767
|
+
if (opts.publishAccount === true) args.push("--publish-account");
|
|
688
768
|
if (opts.allLocalSources === true) args.push("--all-local-sources");
|
|
689
769
|
if (opts.drain === true) args.push("--drain");
|
|
690
770
|
const child = spawn(process.execPath, args, {
|
|
@@ -1696,6 +1776,11 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1696
1776
|
const auto = body.auto === true && !drain;
|
|
1697
1777
|
const background =
|
|
1698
1778
|
auto && (body.background === true || body.lightweight === true);
|
|
1779
|
+
// The local server is the trust boundary for cloud-sync preferences.
|
|
1780
|
+
// A persisted device token must not let a native background request
|
|
1781
|
+
// upload after the user has explicitly disabled cloud sync.
|
|
1782
|
+
const publishAccount =
|
|
1783
|
+
background && body.publishAccount === true && getCloudSyncPref();
|
|
1699
1784
|
const allLocalSources = background && body.allLocalSources === true;
|
|
1700
1785
|
if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
|
|
1701
1786
|
extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
|
|
@@ -1710,7 +1795,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1710
1795
|
extraEnv.TOKENTRACKER_INSFORGE_BASE_URL = allowedBaseUrl;
|
|
1711
1796
|
localSyncBaseUrl = allowedBaseUrl;
|
|
1712
1797
|
}
|
|
1713
|
-
if (!background
|
|
1798
|
+
if ((!background || publishAccount) &&
|
|
1799
|
+
!extraEnv.TOKENTRACKER_DEVICE_TOKEN &&
|
|
1800
|
+
getCloudSyncPref() &&
|
|
1801
|
+
getRefreshTokenForCloud()) {
|
|
1714
1802
|
let issuedToken = null;
|
|
1715
1803
|
try {
|
|
1716
1804
|
issuedToken = await issueDeviceTokenForLocalSync(qp, { baseUrl: localSyncBaseUrl });
|
|
@@ -1732,6 +1820,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1732
1820
|
drain,
|
|
1733
1821
|
auto,
|
|
1734
1822
|
background,
|
|
1823
|
+
publishAccount,
|
|
1735
1824
|
allLocalSources,
|
|
1736
1825
|
});
|
|
1737
1826
|
try {
|
|
@@ -1778,7 +1867,8 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1778
1867
|
const to = url.searchParams.get("to") || "";
|
|
1779
1868
|
const timeZoneContext = getTimeZoneContext(url);
|
|
1780
1869
|
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
1781
|
-
const
|
|
1870
|
+
const allDaily = aggregateByDay(rows, timeZoneContext);
|
|
1871
|
+
const daily = allDaily.filter((d) => d.day >= from && d.day <= to);
|
|
1782
1872
|
const totals = daily.reduce(
|
|
1783
1873
|
(acc, r) => {
|
|
1784
1874
|
acc.total_tokens += r.total_tokens;
|
|
@@ -1798,7 +1888,6 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1798
1888
|
|
|
1799
1889
|
const todayParts = getZonedParts(new Date(), timeZoneContext);
|
|
1800
1890
|
const todayStr = formatPartsDayKey(todayParts) || new Date().toISOString().slice(0, 10);
|
|
1801
|
-
const allDaily = aggregateByDay(rows, timeZoneContext);
|
|
1802
1891
|
|
|
1803
1892
|
const shiftDay = (dayStr, delta) => {
|
|
1804
1893
|
const d = new Date(`${dayStr}T00:00:00Z`);
|
|
@@ -1860,11 +1949,20 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1860
1949
|
const from = url.searchParams.get("from") || "";
|
|
1861
1950
|
const to = url.searchParams.get("to") || "";
|
|
1862
1951
|
const {
|
|
1863
|
-
|
|
1864
|
-
resolveOutcomesPath,
|
|
1952
|
+
readAllOutcomesData,
|
|
1865
1953
|
computeQualityPerDollar,
|
|
1866
1954
|
} = require("./outcomes-engine");
|
|
1867
|
-
|
|
1955
|
+
// Refresh the independent metadata-only sidecars before joining. Any
|
|
1956
|
+
// scanner failure degrades to the existing manual outcomes file.
|
|
1957
|
+
try {
|
|
1958
|
+
const { buildSessionAnalytics } = require("./session-analytics");
|
|
1959
|
+
const { buildGitOutcomes } = require("./git-outcomes");
|
|
1960
|
+
const sessions = await buildSessionAnalytics();
|
|
1961
|
+
await buildGitOutcomes(sessions);
|
|
1962
|
+
} catch (error) {
|
|
1963
|
+
console.error("[outcomes] automatic Git attribution failed:", error?.message || error);
|
|
1964
|
+
}
|
|
1965
|
+
const outcomes = readAllOutcomesData();
|
|
1868
1966
|
if (!outcomes.length) {
|
|
1869
1967
|
json(res, { available: false, from, to, by_model: [], by_tool: [], totals: null });
|
|
1870
1968
|
return true;
|
|
@@ -1875,6 +1973,39 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1875
1973
|
return true;
|
|
1876
1974
|
}
|
|
1877
1975
|
|
|
1976
|
+
// --- metadata-only Claude/Codex session efficiency ---
|
|
1977
|
+
if (p === "/functions/tokentracker-session-insights") {
|
|
1978
|
+
const from = url.searchParams.get("from") || "";
|
|
1979
|
+
const to = url.searchParams.get("to") || "";
|
|
1980
|
+
const refresh = ["1", "true"].includes(url.searchParams.get("refresh"));
|
|
1981
|
+
try {
|
|
1982
|
+
const { buildSessionAnalytics, summarizeSessions, sessionsToCsv } = require("./session-analytics");
|
|
1983
|
+
const sessions = await buildSessionAnalytics({ force: refresh });
|
|
1984
|
+
const wantsCsv = url.searchParams.get("format") === "csv";
|
|
1985
|
+
const includeSessions = wantsCsv || ["1", "true"].includes(url.searchParams.get("include_sessions"));
|
|
1986
|
+
const result = summarizeSessions(sessions, { from, to, includeSessions });
|
|
1987
|
+
if (wantsCsv) {
|
|
1988
|
+
const content = sessionsToCsv(result.sessions);
|
|
1989
|
+
res.statusCode = 200;
|
|
1990
|
+
res.setHeader("Content-Type", "text/csv; charset=utf-8");
|
|
1991
|
+
res.setHeader("Content-Disposition", "attachment; filename=tokentracker-sessions.csv");
|
|
1992
|
+
res.end(content);
|
|
1993
|
+
return true;
|
|
1994
|
+
}
|
|
1995
|
+
json(res, { from, to, ...result });
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
json(res, { available: false, error: error?.message || "Session analytics failed" }, 500);
|
|
1998
|
+
}
|
|
1999
|
+
return true;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// --- fixed context overhead audit (counts and estimates only) ---
|
|
2003
|
+
if (p === "/functions/tokentracker-context-health") {
|
|
2004
|
+
const { computeContextHealth } = require("./context-health");
|
|
2005
|
+
json(res, computeContextHealth({ home: os.homedir(), cwd: process.cwd(), env: process.env }));
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1878
2009
|
// --- usage-heatmap ---
|
|
1879
2010
|
if (p === "/functions/tokentracker-usage-heatmap") {
|
|
1880
2011
|
const weeks = parseInt(url.searchParams.get("weeks") || "52", 10);
|
|
@@ -27,6 +27,10 @@ function resolveOutcomesPath() {
|
|
|
27
27
|
return path.join(os.homedir(), ".tokentracker", "tracker", "outcomes.jsonl");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function resolveAutoOutcomesPath() {
|
|
31
|
+
return path.join(os.homedir(), ".tokentracker", "tracker", "auto-outcomes.jsonl");
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
// The ONLY fields ever lifted off an outcome record. Anything not on this list
|
|
31
35
|
// — bodies, diffs, messages, arbitrary blobs — is dropped at read time. This is
|
|
32
36
|
// the enforcement point for the metadata-only privacy invariant.
|
|
@@ -54,6 +58,10 @@ function sanitizeOutcome(raw) {
|
|
|
54
58
|
if (typeof raw.task_type === "string" && raw.task_type.trim()) {
|
|
55
59
|
out.task_type = raw.task_type.trim();
|
|
56
60
|
}
|
|
61
|
+
for (const key of ["status", "session_hash", "commit_hash", "confidence", "methodology"]) {
|
|
62
|
+
if (typeof raw[key] === "string" && raw[key].trim()) out[key] = raw[key].trim();
|
|
63
|
+
}
|
|
64
|
+
if (Number.isFinite(Number(raw.parent_count))) out.parent_count = Number(raw.parent_count);
|
|
57
65
|
return out;
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -91,6 +99,19 @@ function readOutcomesData(outcomesPath) {
|
|
|
91
99
|
return out;
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
function readAllOutcomesData(paths = [resolveOutcomesPath(), resolveAutoOutcomesPath()]) {
|
|
103
|
+
const dedup = new Map();
|
|
104
|
+
for (const outcomePath of paths) {
|
|
105
|
+
for (const row of readOutcomesData(outcomePath)) {
|
|
106
|
+
const key = row.commit_hash
|
|
107
|
+
? `commit:${row.commit_hash}`
|
|
108
|
+
: `${row.timestamp}|${row.model}|${row.tool}|${row.task_type || ""}`;
|
|
109
|
+
dedup.set(key, row);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return [...dedup.values()];
|
|
113
|
+
}
|
|
114
|
+
|
|
94
115
|
function dayOf(value) {
|
|
95
116
|
return typeof value === "string" ? value.slice(0, 10) : "";
|
|
96
117
|
}
|
|
@@ -224,7 +245,9 @@ function computeQualityPerDollar(queueRows, outcomes, { from = "", to = "" } = {
|
|
|
224
245
|
|
|
225
246
|
module.exports = {
|
|
226
247
|
resolveOutcomesPath,
|
|
248
|
+
resolveAutoOutcomesPath,
|
|
227
249
|
sanitizeOutcome,
|
|
228
250
|
readOutcomesData,
|
|
251
|
+
readAllOutcomesData,
|
|
229
252
|
computeQualityPerDollar,
|
|
230
253
|
};
|
package/src/lib/pricing/index.js
CHANGED
|
@@ -37,6 +37,7 @@ const seedRaw = loadSeedSync();
|
|
|
37
37
|
const state = {
|
|
38
38
|
loaded: false,
|
|
39
39
|
loadingPromise: null,
|
|
40
|
+
revision: 0,
|
|
40
41
|
litellmRawMap: seedRaw, // raw per-token; field shape from LiteLLM JSON
|
|
41
42
|
litellmPerMillionMap: buildLitellmPerMillionMap(seedRaw), // USD/MTok
|
|
42
43
|
source: Object.keys(seedRaw).length ? "seed-snapshot:sync" : null,
|
|
@@ -61,6 +62,7 @@ async function ensurePricingLoaded(opts = {}) {
|
|
|
61
62
|
state.litellmPerMillionMap = buildLitellmPerMillionMap(state.litellmRawMap);
|
|
62
63
|
state.source = source;
|
|
63
64
|
state.loaded = true;
|
|
65
|
+
state.revision += 1;
|
|
64
66
|
state.negativeCache.clear();
|
|
65
67
|
return state;
|
|
66
68
|
} finally {
|
|
@@ -79,9 +81,14 @@ function resetPricingForTests() {
|
|
|
79
81
|
state.litellmRawMap = seedRaw;
|
|
80
82
|
state.litellmPerMillionMap = buildLitellmPerMillionMap(seedRaw);
|
|
81
83
|
state.source = Object.keys(seedRaw).length ? "seed-snapshot:sync" : null;
|
|
84
|
+
state.revision += 1;
|
|
82
85
|
state.negativeCache.clear();
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
function getPricingRevision() {
|
|
89
|
+
return state.revision;
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
function getModelPricing(model, opts = {}) {
|
|
86
93
|
if (!model) return ZERO_PRICING;
|
|
87
94
|
let lookupSource = null;
|
|
@@ -133,6 +140,7 @@ const MODEL_PRICING = curatedOverrides.exact;
|
|
|
133
140
|
|
|
134
141
|
module.exports = {
|
|
135
142
|
ensurePricingLoaded,
|
|
143
|
+
getPricingRevision,
|
|
136
144
|
getModelPricing,
|
|
137
145
|
computeRowCost,
|
|
138
146
|
resetPricingForTests,
|