tokentracker-cli 0.93.4 → 0.93.6
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-BcNvxrVM.js → AchievementBadge-DsAclDw6.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DhipQygJ.js → AchievementsPage-D__EwC45.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BJyb_mIr.js → AchievementsSection-BAZNM3Cn.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-CoZv7o6t.js → BadgeDetailModal-Dgeer5oB.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-eVBNtvcG.js → BotAnimated-CwrScgrI.js} +1 -1
- package/dashboard/dist/assets/{Card-BEfG2im9.js → Card-D7GmLoqP.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-C-30t4dC.js → ClawdAnimated-BEXgb5J_.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-DF-TGB1C.js → CommandPalette-B9Mn3Qt2.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-D-0eI3Pz.js → CommunityStatsModal-eJ_a1FMd.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-BQ7Aor1C.js → ConfirmModal-CumOr2J1.js} +1 -1
- package/dashboard/dist/assets/{Controls-COThRtMS.js → Controls-DxnaHBzv.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-Ci_Gb5I1.js → DashboardPage-BpRRXCRr.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BmlmimUg.js → DevicePage-4yTYf29M.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BdALEZzc.js → DialogClose-BbderFCY.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-C5Ujj254.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-F6W1aTiE.js → DialogTitle-6epeLIn0.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-S2d3D28d.js → FadeIn-BFLojPGG.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DBBwOwc_.js → HeaderGithubStar-CPBNn61n.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-rLWQC9Va.js → HoverTooltip-DbxnLj8e.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BpPoAIry.js → IpCheckPage-CfPtXAAF.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BUm_ZMew.js → LandingPage-CocswfFj.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-DQxbOYZb.js → LeaderboardAvatar-iZLxxZX-.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-DzLrFQm8.js → LeaderboardPage-yqauB4Q1.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-Cq4B8Les.js → LeaderboardProfileModal-BWu8jbBW.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-C-Xj65xf.js → LeaderboardProfilePage-B77gOTzr.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-Bjcmr0bd.js → LimitsPage-C9NeIcft.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-9mWINkVj.js → LocalOnlyNotice-HPXWU8TT.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BlKleF8M.js → LoginCard-D592L-fh.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-BUYa13hj.js → LoginModal-C7Qm7CVf.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DbQhApOd.js → LoginPage-DkYw5ZQK.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DuAf3OOf.js → PetPage-BOhvAJp4.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CG76MkbK.js → PopoverPopup-BK9aqQn1.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-N7OTtgte.js → ProviderIcon-d0_vhIkS.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-D48--1wC.js → ResetPasswordPage-UDNlpFVu.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-zRvbFqyY.js → SegmentedControl-DE5m1pTf.js} +1 -1
- package/dashboard/dist/assets/{Select-Bi3B21Fb.js → Select-Z2xTQ55f.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Bg8I618N.js → SelectItemText-Daq-XRZg.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-CeeYx4XY.js → ServiceStatusPage-Cfr-QDZ7.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-CZF3qLEm.js → SessionsPage-nHPj6IAt.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-D2wa8i5u.js → SettingsPage-NvxUG_9m.js} +2 -2
- package/dashboard/dist/assets/{SkillsPage-OxW2w6up.js → SkillsPage-zFbZa42f.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-DmtP_r86.js → TokenGalaxy-CjAY6rey.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-DC6BLLoO.js → ToolbarRootContext-DnC8LT_8.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-OvvLCD3K.js → TrendMonitor-DAAlILwJ.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DhOC8rae.js → WidgetsPage-CXsfi-mq.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C0boSobK.js → WrappedPage-ByYzOeZ-.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-HdCplPKs.js +45 -0
- package/dashboard/dist/assets/{agent-logos-Dzyq5cDh.js → agent-logos-BW-ajJ7p.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DrLfm7Vy.js → arrow-up-right-CA5IJkqo.js} +1 -1
- package/dashboard/dist/assets/{check-DAsdH-MF.js → check-BBjF3bSm.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-CdSgby9-.js → chevron-right-DCvGp9iJ.js} +1 -1
- package/dashboard/dist/assets/{copy-TuVoW7qK.js → copy-ahF8LGeu.js} +1 -1
- package/dashboard/dist/assets/{download-BZ61xuhi.js → download-DBm8dmvt.js} +1 -1
- package/dashboard/dist/assets/{external-link-DJrRSKF9.js → external-link-C1sJSL7J.js} +1 -1
- package/dashboard/dist/assets/{flame-9JS9yMju.js → flame-B6xukEYd.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Blp6lI_n.js → format-tokens-6TqXlT3q.js} +1 -1
- package/dashboard/dist/assets/{icons-CbF0cL6i.js → icons-pGe_pInY.js} +1 -1
- package/dashboard/dist/assets/{index-I81NzV7H.js → index-Chb8mb2X.js} +1 -1
- package/dashboard/dist/assets/{index-FnWiMfqV.js → index-I9O8xChC.js} +1 -1
- package/dashboard/dist/assets/{index-Bu3I2hmd.js → index-RPV0zdZ9.js} +1 -1
- package/dashboard/dist/assets/{info-B_iryt84.js → info-DBfJv0yu.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-Cli4pkKC.js → limits-providers-DPQsdVq5.js} +1 -1
- package/dashboard/dist/assets/{link-2-pB3-7Waa.js → link-2-DNEARrPY.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-DivA-LsF.js → loader-circle-BCFHUr8X.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-WNMF5NUU.js → maximize-2-1EmlA2R-.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-BaJJxzf2.js → modifiers.esm-DGXFSuEq.js} +1 -1
- package/dashboard/dist/assets/module-D2D5r9gC.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-pAR3IHow.js → monitor-smartphone-DsPPhvsT.js} +1 -1
- package/dashboard/dist/assets/{provider-display-CyYhYVar.js → provider-display-C20LtBC5.js} +1 -1
- package/dashboard/dist/assets/{react-BldsIPA1.js → react-B4FPJgs_.js} +1 -1
- package/dashboard/dist/assets/{search-xIfdCEo0.js → search-mkIFPKzW.js} +1 -1
- package/dashboard/dist/assets/{skills-api-DZNsCHPo.js → skills-api-D24vlu2H.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-DMaj448G.js → sortable.esm-BNDcP0sW.js} +1 -1
- package/dashboard/dist/assets/{store-VTEegajU.js → store-B2nqaAqP.js} +1 -1
- package/dashboard/dist/assets/{terminal-Bj8SotRv.js → terminal-BxVMmvEx.js} +1 -1
- package/dashboard/dist/assets/{trash-2-CWgY_FBc.js → trash-2-crt9lDvj.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-BOVvh_Gv.js → use-community-stats-CTB_uHWV.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-MtLsQ5kN.js → use-limits-display-prefs-DXXZpjGY.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DjCZVinv.js → use-ordered-list-BVNb5QWU.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-aSaQtoJu.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-DASihY0W.js → use-session-efficiency-pref-Di8nY6Lr.js} +1 -1
- package/dashboard/dist/assets/{use-transform-tHlm2id8.js → use-transform-Dx9C2nBC.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BBEHNJKl.js → use-usage-limits-D9vQFB1H.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DgTCDMXb.js → useCurrency-7dkDciam.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-C3TFkbz0.js → useScrollLock-BUVUIPTX.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-BtqPANfZ.js → useTokenFormat-DFSIAsAb.js} +1 -1
- package/dashboard/dist/assets/{zap-CJNTZ87b.js → zap-Png-VJSP.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +115 -4
- package/src/commands/sync.js +159 -9
- package/src/lib/opencode-go-limits.js +11 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +252 -22
- package/src/lib/usage-limits.js +119 -1
- package/dashboard/dist/assets/DialogDescription-DAKoQCEv.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-D_dICEUB.js +0 -1575
- package/dashboard/dist/assets/module-CV1mYFsv.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-DjWNXuSi.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -24,6 +24,26 @@ const CLAUDE_MEM_OBSERVER_PROJECT_REF =
|
|
|
24
24
|
"https://local.tokentracker/claude-mem/observer-sessions";
|
|
25
25
|
const PROJECT_ABSENT_CONTEXT_RESCAN_MS = 24 * 60 * 60 * 1000;
|
|
26
26
|
const DEFAULT_CODEX_COLD_SKIP_RECENT_DAYS = 2;
|
|
27
|
+
const FILE_METADATA_CONCURRENCY = 32;
|
|
28
|
+
|
|
29
|
+
async function mapConcurrent(items, concurrency, mapper) {
|
|
30
|
+
if (!Array.isArray(items) || items.length === 0) return [];
|
|
31
|
+
const results = new Array(items.length);
|
|
32
|
+
let nextIndex = 0;
|
|
33
|
+
const workerCount = Math.min(
|
|
34
|
+
items.length,
|
|
35
|
+
Math.max(1, Math.floor(Number(concurrency) || 1)),
|
|
36
|
+
);
|
|
37
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
38
|
+
while (nextIndex < items.length) {
|
|
39
|
+
const index = nextIndex;
|
|
40
|
+
nextIndex += 1;
|
|
41
|
+
results[index] = await mapper(items[index], index);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
await Promise.all(workers);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
27
47
|
|
|
28
48
|
async function listRolloutFiles(sessionsDir, options = {}) {
|
|
29
49
|
const out = [];
|
|
@@ -41,23 +61,37 @@ async function listRolloutFiles(sessionsDir, options = {}) {
|
|
|
41
61
|
dayInventoryCache.days = {};
|
|
42
62
|
}
|
|
43
63
|
}
|
|
44
|
-
const years = await safeReadDir(sessionsDir)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
64
|
+
const years = (await safeReadDir(sessionsDir))
|
|
65
|
+
.filter((entry) => /^[0-9]{4}$/.test(entry.name) && entry.isDirectory());
|
|
66
|
+
const monthGroups = await mapConcurrent(
|
|
67
|
+
years,
|
|
68
|
+
FILE_METADATA_CONCURRENCY,
|
|
69
|
+
async (year) => {
|
|
70
|
+
const yearDir = path.join(sessionsDir, year.name);
|
|
71
|
+
const months = await safeReadDir(yearDir);
|
|
72
|
+
return months
|
|
73
|
+
.filter((entry) => /^[0-9]{2}$/.test(entry.name) && entry.isDirectory())
|
|
74
|
+
.map((entry) => path.join(yearDir, entry.name));
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
const monthDirs = monthGroups.flat();
|
|
78
|
+
const dayGroups = await mapConcurrent(
|
|
79
|
+
monthDirs,
|
|
80
|
+
FILE_METADATA_CONCURRENCY,
|
|
81
|
+
async (monthDir) => {
|
|
52
82
|
const days = await safeReadDir(monthDir);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
return days
|
|
84
|
+
.filter((entry) => /^[0-9]{2}$/.test(entry.name) && entry.isDirectory())
|
|
85
|
+
.map((entry) => path.join(monthDir, entry.name));
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
const dayDirs = dayGroups.flat();
|
|
89
|
+
const fileGroups = await mapConcurrent(
|
|
90
|
+
dayDirs,
|
|
91
|
+
FILE_METADATA_CONCURRENCY,
|
|
92
|
+
(dayDir) => listRolloutDayFiles(dayDir, { dayInventoryCache, stats }),
|
|
93
|
+
);
|
|
94
|
+
for (const files of fileGroups) out.push(...files);
|
|
61
95
|
|
|
62
96
|
out.sort((a, b) => a.localeCompare(b));
|
|
63
97
|
return out;
|
|
@@ -354,6 +388,18 @@ async function parseRolloutIncremental({
|
|
|
354
388
|
return false;
|
|
355
389
|
};
|
|
356
390
|
|
|
391
|
+
// Metadata reads are independent while parsing and bucket mutation are not.
|
|
392
|
+
// Prefetch stats with bounded concurrency, then retain the original stable
|
|
393
|
+
// file order for parsing, deduplication, cursor updates, and queue writes.
|
|
394
|
+
const rolloutStats = await mapConcurrent(
|
|
395
|
+
rolloutFiles,
|
|
396
|
+
FILE_METADATA_CONCURRENCY,
|
|
397
|
+
async (entry) => {
|
|
398
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
399
|
+
if (!filePath) return null;
|
|
400
|
+
return fs.stat(filePath).catch(() => null);
|
|
401
|
+
},
|
|
402
|
+
);
|
|
357
403
|
for (let idx = 0; idx < rolloutFiles.length; idx++) {
|
|
358
404
|
const entry = rolloutFiles[idx];
|
|
359
405
|
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
@@ -363,7 +409,7 @@ async function parseRolloutIncremental({
|
|
|
363
409
|
? defaultSource
|
|
364
410
|
: normalizeSourceInput(entry?.source) || defaultSource;
|
|
365
411
|
if (syncDiagnostics && fileSource === DEFAULT_SOURCE) syncDiagnostics.stat_candidates += 1;
|
|
366
|
-
const st =
|
|
412
|
+
const st = rolloutStats[idx];
|
|
367
413
|
if (!st || !st.isFile()) continue;
|
|
368
414
|
|
|
369
415
|
const key = filePath;
|
|
@@ -4422,8 +4468,7 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4422
4468
|
// by the existing cursor-key + #426 fingerprint dedup in
|
|
4423
4469
|
// parseOpencodeDbIncremental, so unioning here is safe — the downstream
|
|
4424
4470
|
// parser collapses identical sessionID|messageID pairs before bucketing.
|
|
4425
|
-
const
|
|
4426
|
-
const out = [];
|
|
4471
|
+
const appendRows = (rows, isV2, out) => {
|
|
4427
4472
|
for (const row of rows) {
|
|
4428
4473
|
if (!row || typeof row.data !== "string") continue;
|
|
4429
4474
|
let data;
|
|
@@ -4450,7 +4495,6 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4450
4495
|
data,
|
|
4451
4496
|
});
|
|
4452
4497
|
}
|
|
4453
|
-
return out;
|
|
4454
4498
|
};
|
|
4455
4499
|
|
|
4456
4500
|
const readGeneration = (sql) => {
|
|
@@ -4477,11 +4521,11 @@ function readOpencodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
4477
4521
|
const out = [];
|
|
4478
4522
|
|
|
4479
4523
|
const rows1 = readGeneration(OPENCODE_DB_V1_MESSAGE_SQL);
|
|
4480
|
-
if (rows1)
|
|
4524
|
+
if (rows1) appendRows(rows1, false, out);
|
|
4481
4525
|
|
|
4482
4526
|
if (probe?.hasRows) {
|
|
4483
4527
|
const rows2 = readGeneration(buildV2Sql(probe.sessionTable));
|
|
4484
|
-
if (rows2)
|
|
4528
|
+
if (rows2) appendRows(rows2, true, out);
|
|
4485
4529
|
}
|
|
4486
4530
|
|
|
4487
4531
|
return out;
|
|
@@ -4551,10 +4595,195 @@ function isZcodeNativeMessage(data) {
|
|
|
4551
4595
|
);
|
|
4552
4596
|
}
|
|
4553
4597
|
|
|
4598
|
+
const ZCODE_NATIVE_USAGE_COLUMNS = new Set([
|
|
4599
|
+
"id",
|
|
4600
|
+
"logical_request_id",
|
|
4601
|
+
"attempt_index",
|
|
4602
|
+
"session_id",
|
|
4603
|
+
"provider_id",
|
|
4604
|
+
"model_id",
|
|
4605
|
+
"status",
|
|
4606
|
+
"started_at",
|
|
4607
|
+
"input_tokens",
|
|
4608
|
+
"output_tokens",
|
|
4609
|
+
"reasoning_tokens",
|
|
4610
|
+
"cache_creation_input_tokens",
|
|
4611
|
+
"cache_read_input_tokens",
|
|
4612
|
+
]);
|
|
4613
|
+
|
|
4614
|
+
function detectZcodeNativeUsageLayout(dbPath, sqliteOptions = {}) {
|
|
4615
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return null;
|
|
4616
|
+
let rows;
|
|
4617
|
+
try {
|
|
4618
|
+
rows = readSqliteJsonRows(
|
|
4619
|
+
dbPath,
|
|
4620
|
+
`SELECT 'model_usage' AS table_name, name FROM pragma_table_info('model_usage')
|
|
4621
|
+
UNION ALL
|
|
4622
|
+
SELECT 'session' AS table_name, name FROM pragma_table_info('session')`,
|
|
4623
|
+
{
|
|
4624
|
+
label: "ZCode",
|
|
4625
|
+
timeout: 10_000,
|
|
4626
|
+
maxBuffer: 1024 * 1024,
|
|
4627
|
+
...sqliteOptions,
|
|
4628
|
+
throwOnReadFailure: true,
|
|
4629
|
+
},
|
|
4630
|
+
);
|
|
4631
|
+
} catch (_error) {
|
|
4632
|
+
return null;
|
|
4633
|
+
}
|
|
4634
|
+
const modelUsageColumns = new Set(
|
|
4635
|
+
rows
|
|
4636
|
+
.filter((row) => !row?.table_name || row.table_name === "model_usage")
|
|
4637
|
+
.map((row) => String(row?.name || "")),
|
|
4638
|
+
);
|
|
4639
|
+
if (![...ZCODE_NATIVE_USAGE_COLUMNS].every((name) => modelUsageColumns.has(name))) {
|
|
4640
|
+
return null;
|
|
4641
|
+
}
|
|
4642
|
+
const sessionColumns = new Set(
|
|
4643
|
+
rows
|
|
4644
|
+
.filter((row) => row?.table_name === "session")
|
|
4645
|
+
.map((row) => String(row?.name || "")),
|
|
4646
|
+
);
|
|
4647
|
+
return {
|
|
4648
|
+
hasSessionDirectory: sessionColumns.has("id") && sessionColumns.has("directory"),
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
function hasZcodeNativeUsageSchema(dbPath, sqliteOptions = {}) {
|
|
4653
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return false;
|
|
4654
|
+
let snapshot = null;
|
|
4655
|
+
let effectiveDbPath = dbPath;
|
|
4656
|
+
if (isUncPath(dbPath)) {
|
|
4657
|
+
try {
|
|
4658
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
4659
|
+
effectiveDbPath = snapshot.path;
|
|
4660
|
+
} catch (_error) {
|
|
4661
|
+
// Fall through to the direct read: some UNC servers support SQLite's
|
|
4662
|
+
// read-only locking semantics even when the WSL bridge does not.
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
try {
|
|
4666
|
+
return detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions) !== null;
|
|
4667
|
+
} finally {
|
|
4668
|
+
if (snapshot) snapshot.cleanup();
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
function buildZcodeNativeUsageSql({ hasSessionDirectory }) {
|
|
4673
|
+
const directorySelect = hasSessionDirectory ? ", s.directory AS directory" : "";
|
|
4674
|
+
const directoryJoin = hasSessionDirectory
|
|
4675
|
+
? " LEFT JOIN session AS s ON s.id = mu.session_id"
|
|
4676
|
+
: "";
|
|
4677
|
+
return `SELECT
|
|
4678
|
+
mu.id,
|
|
4679
|
+
mu.logical_request_id,
|
|
4680
|
+
mu.attempt_index,
|
|
4681
|
+
mu.session_id,
|
|
4682
|
+
mu.provider_id,
|
|
4683
|
+
mu.model_id,
|
|
4684
|
+
mu.started_at,
|
|
4685
|
+
mu.input_tokens,
|
|
4686
|
+
mu.output_tokens,
|
|
4687
|
+
mu.reasoning_tokens,
|
|
4688
|
+
mu.cache_creation_input_tokens,
|
|
4689
|
+
mu.cache_read_input_tokens
|
|
4690
|
+
${directorySelect}
|
|
4691
|
+
FROM model_usage AS mu${directoryJoin}
|
|
4692
|
+
WHERE mu.status = 'completed'
|
|
4693
|
+
AND trim(mu.model_id) != ''
|
|
4694
|
+
AND (
|
|
4695
|
+
mu.input_tokens > 0 OR mu.output_tokens > 0 OR mu.reasoning_tokens > 0 OR
|
|
4696
|
+
mu.cache_creation_input_tokens > 0 OR mu.cache_read_input_tokens > 0
|
|
4697
|
+
)
|
|
4698
|
+
ORDER BY mu.started_at ASC, mu.id ASC`;
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
function readZcodeNativeUsageMessages(dbPath, sqliteOptions = {}) {
|
|
4702
|
+
let snapshot = null;
|
|
4703
|
+
let effectiveDbPath = dbPath;
|
|
4704
|
+
if (isUncPath(dbPath)) {
|
|
4705
|
+
try {
|
|
4706
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
4707
|
+
effectiveDbPath = snapshot.path;
|
|
4708
|
+
} catch (_error) {
|
|
4709
|
+
// Preserve the existing direct-read fallback for transient snapshot
|
|
4710
|
+
// failures and UNC implementations that support SQLite locking.
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
let rows;
|
|
4715
|
+
try {
|
|
4716
|
+
const layout = detectZcodeNativeUsageLayout(effectiveDbPath, sqliteOptions);
|
|
4717
|
+
if (!layout) return null;
|
|
4718
|
+
rows = readSqliteJsonRows(effectiveDbPath, buildZcodeNativeUsageSql(layout), {
|
|
4719
|
+
label: "ZCode",
|
|
4720
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
4721
|
+
timeout: 30_000,
|
|
4722
|
+
...sqliteOptions,
|
|
4723
|
+
throwOnReadFailure: true,
|
|
4724
|
+
});
|
|
4725
|
+
} catch (_error) {
|
|
4726
|
+
return null;
|
|
4727
|
+
} finally {
|
|
4728
|
+
if (snapshot) snapshot.cleanup();
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
const messages = [];
|
|
4732
|
+
for (const row of rows) {
|
|
4733
|
+
const providerID = String(row?.provider_id || "").trim();
|
|
4734
|
+
const modelID = String(row?.model_id || "").trim();
|
|
4735
|
+
const sessionID = String(row?.session_id || "").trim();
|
|
4736
|
+
const logicalRequestId = String(row?.logical_request_id || "").trim();
|
|
4737
|
+
const attemptIndex = toNonNegativeInt(row?.attempt_index);
|
|
4738
|
+
const id = String(row?.id || "").trim() ||
|
|
4739
|
+
(logicalRequestId ? `${logicalRequestId}#${attemptIndex}` : "");
|
|
4740
|
+
const startedAt = coerceEpochMs(row?.started_at);
|
|
4741
|
+
if (!providerID || !modelID || !sessionID || !id || !startedAt) continue;
|
|
4742
|
+
|
|
4743
|
+
// ZCode's persisted input/output columns are inclusive parents:
|
|
4744
|
+
// cache read/write are subsets of input, and reasoning is a subset of
|
|
4745
|
+
// output. TokenTracker stores those five columns disjointly, so split the
|
|
4746
|
+
// subsets here before entering the shared OpenCode parser. This preserves
|
|
4747
|
+
// sum(input + cache read + cache write + output + reasoning) without
|
|
4748
|
+
// billing either subset twice.
|
|
4749
|
+
const rawInput = toNonNegativeInt(row?.input_tokens);
|
|
4750
|
+
const rawOutput = toNonNegativeInt(row?.output_tokens);
|
|
4751
|
+
const cacheRead = toNonNegativeInt(row?.cache_read_input_tokens);
|
|
4752
|
+
const cacheWrite = toNonNegativeInt(row?.cache_creation_input_tokens);
|
|
4753
|
+
const reasoning = toNonNegativeInt(row?.reasoning_tokens);
|
|
4754
|
+
|
|
4755
|
+
const data = {
|
|
4756
|
+
id,
|
|
4757
|
+
sessionID,
|
|
4758
|
+
role: "assistant",
|
|
4759
|
+
providerID,
|
|
4760
|
+
modelID,
|
|
4761
|
+
time: { created: startedAt, completed: startedAt },
|
|
4762
|
+
tokens: {
|
|
4763
|
+
input: Math.max(0, rawInput - cacheRead - cacheWrite),
|
|
4764
|
+
output: Math.max(0, rawOutput - reasoning),
|
|
4765
|
+
reasoning,
|
|
4766
|
+
cache: {
|
|
4767
|
+
read: cacheRead,
|
|
4768
|
+
write: cacheWrite,
|
|
4769
|
+
},
|
|
4770
|
+
},
|
|
4771
|
+
};
|
|
4772
|
+
if (typeof row?.directory === "string" && row.directory.trim()) {
|
|
4773
|
+
data.path = { cwd: row.directory.trim() };
|
|
4774
|
+
}
|
|
4775
|
+
if (!isZcodeNativeMessage(data)) continue;
|
|
4776
|
+
messages.push({ id, sessionID, timeUpdated: startedAt, data });
|
|
4777
|
+
}
|
|
4778
|
+
return messages;
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4554
4781
|
// Read only genuine ZCode assistant messages (its own GLM models via Z.ai /
|
|
4555
4782
|
// BigModel), dropping any bundled sub-agent turns. See isZcodeNativeMessage.
|
|
4556
4783
|
function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
4557
4784
|
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
4785
|
+
const nativeMessages = readZcodeNativeUsageMessages(dbPath, sqliteOptions);
|
|
4786
|
+
if (nativeMessages !== null) return nativeMessages;
|
|
4558
4787
|
const all = readOpencodeDbMessages(dbPath, sqliteOptions);
|
|
4559
4788
|
return all.filter((m) => isZcodeNativeMessage(m.data));
|
|
4560
4789
|
}
|
|
@@ -18054,6 +18283,7 @@ module.exports = {
|
|
|
18054
18283
|
readOpencodeDbMessages,
|
|
18055
18284
|
readMimoDbMessages,
|
|
18056
18285
|
readZcodeDbMessages,
|
|
18286
|
+
hasZcodeNativeUsageSchema,
|
|
18057
18287
|
resolveQoderDbPath,
|
|
18058
18288
|
resolveQoderDbPaths,
|
|
18059
18289
|
resolveQoderCnDbPaths,
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -66,6 +66,13 @@ const CLAUDE_LIMITS_CACHE_FRESH_TTL_MS = 10 * 60 * 1000;
|
|
|
66
66
|
// error, mirroring the Claude stale-fallback path.
|
|
67
67
|
const CODEX_LIMITS_CACHE_FILE = "codex-usage-limits-cache.json";
|
|
68
68
|
const CODEX_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
69
|
+
// OpenCode Go hits opencode.ai over the public internet (15s timeout) with no local CLI to
|
|
70
|
+
// fall back on. A transient 5xx / timeout previously left the panel blank until the next
|
|
71
|
+
// 2-minute poll — persist the last successful windows so a blip serves stale bars instead
|
|
72
|
+
// of a flash of "fetch failed" (mirrors Claude / Codex / Antigravity).
|
|
73
|
+
const OPENCODE_GO_LIMITS_CACHE_FILE = "opencode-go-usage-limits-cache.json";
|
|
74
|
+
const OPENCODE_GO_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
75
|
+
const OPENCODE_GO_LOCAL_ESTIMATE_CACHE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
69
76
|
// A 429 from the usage endpoint carries a long `retry-after` (often 20+ minutes). Persist
|
|
70
77
|
// the cooldown so every surface — this process, the menu bar app's embedded server, a later
|
|
71
78
|
// restart — stops calling until it expires. Hammering during the cooldown just renews the
|
|
@@ -2398,6 +2405,82 @@ function writeCodexLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
|
|
|
2398
2405
|
} catch (_error) {}
|
|
2399
2406
|
}
|
|
2400
2407
|
|
|
2408
|
+
function resolveOpencodeGoLimitsCachePath({ home } = {}) {
|
|
2409
|
+
return path.join(home || os.homedir(), ".tokentracker", "tracker", OPENCODE_GO_LIMITS_CACHE_FILE);
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function hasOpencodeGoWindow(limits) {
|
|
2413
|
+
return Boolean(limits?.primary_window || limits?.secondary_window || limits?.tertiary_window);
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
function isOpencodeGoCacheWindowUsable(window, { nowMs } = {}) {
|
|
2417
|
+
if (!window || typeof window !== "object") return false;
|
|
2418
|
+
const resetAtMs = parseTimeMs(window.reset_at);
|
|
2419
|
+
if (resetAtMs === null) return true;
|
|
2420
|
+
return resetAtMs > nowMs;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
function normalizeOpencodeGoCachedLimits(
|
|
2424
|
+
raw,
|
|
2425
|
+
{ nowMs = Date.now(), maxAgeMs } = {},
|
|
2426
|
+
) {
|
|
2427
|
+
const effectiveMaxAge = Number.isFinite(maxAgeMs)
|
|
2428
|
+
? maxAgeMs
|
|
2429
|
+
: raw?.source === "local-estimate"
|
|
2430
|
+
? OPENCODE_GO_LOCAL_ESTIMATE_CACHE_MAX_AGE_MS
|
|
2431
|
+
: OPENCODE_GO_LIMITS_CACHE_MAX_AGE_MS;
|
|
2432
|
+
const cachedAtMs = parseTimeMs(raw?.cached_at);
|
|
2433
|
+
if (!Number.isFinite(cachedAtMs)) return null;
|
|
2434
|
+
if (cachedAtMs > nowMs + 60_000) return null;
|
|
2435
|
+
if (nowMs - cachedAtMs > effectiveMaxAge) return null;
|
|
2436
|
+
|
|
2437
|
+
const cached = {
|
|
2438
|
+
configured: true,
|
|
2439
|
+
error: null,
|
|
2440
|
+
source: typeof raw?.source === "string" ? raw.source : "api",
|
|
2441
|
+
subscription_status: typeof raw?.subscription_status === "string" ? raw.subscription_status : null,
|
|
2442
|
+
plan_label: typeof raw?.plan_label === "string" ? raw.plan_label : null,
|
|
2443
|
+
primary_window: isOpencodeGoCacheWindowUsable(raw?.primary_window, { nowMs }) ? raw.primary_window : null,
|
|
2444
|
+
secondary_window: isOpencodeGoCacheWindowUsable(raw?.secondary_window, { nowMs }) ? raw.secondary_window : null,
|
|
2445
|
+
tertiary_window: isOpencodeGoCacheWindowUsable(raw?.tertiary_window, { nowMs }) ? raw.tertiary_window : null,
|
|
2446
|
+
stale: true,
|
|
2447
|
+
cached_at: raw.cached_at,
|
|
2448
|
+
};
|
|
2449
|
+
return hasOpencodeGoWindow(cached) ? cached : null;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function readOpencodeGoLimitsCache({ home, nowMs = Date.now() } = {}) {
|
|
2453
|
+
const cachePath = resolveOpencodeGoLimitsCachePath({ home });
|
|
2454
|
+
try {
|
|
2455
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
2456
|
+
return normalizeOpencodeGoCachedLimits(parsed?.opencodeGo, { nowMs });
|
|
2457
|
+
} catch (_error) {
|
|
2458
|
+
return null;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
function writeOpencodeGoLimitsCache(limits, { home, nowMs = Date.now() } = {}) {
|
|
2463
|
+
if (!limits?.configured || limits.error || !hasOpencodeGoWindow(limits)) return;
|
|
2464
|
+
const cachePath = resolveOpencodeGoLimitsCachePath({ home });
|
|
2465
|
+
const payload = {
|
|
2466
|
+
opencodeGo: {
|
|
2467
|
+
source: limits.source || "api",
|
|
2468
|
+
subscription_status: limits.subscription_status || null,
|
|
2469
|
+
plan_label: limits.plan_label || null,
|
|
2470
|
+
primary_window: limits.primary_window || null,
|
|
2471
|
+
secondary_window: limits.secondary_window || null,
|
|
2472
|
+
tertiary_window: limits.tertiary_window || null,
|
|
2473
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
2474
|
+
},
|
|
2475
|
+
};
|
|
2476
|
+
try {
|
|
2477
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
2478
|
+
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
2479
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
2480
|
+
fs.renameSync(tmpPath, cachePath);
|
|
2481
|
+
} catch (_error) {}
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2401
2484
|
function resolveClaudeRateLimitPath({ home } = {}) {
|
|
2402
2485
|
return path.join(home || os.homedir(), ".tokentracker", "tracker", CLAUDE_RATE_LIMIT_FILE);
|
|
2403
2486
|
}
|
|
@@ -3063,7 +3146,7 @@ async function fetchUsageLimitsUncached({
|
|
|
3063
3146
|
: null;
|
|
3064
3147
|
|
|
3065
3148
|
const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
|
|
3066
|
-
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode,
|
|
3149
|
+
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, claudeServiceStatus] = await Promise.all([
|
|
3067
3150
|
claudeToken && !freshClaudeCache && !claudeRetryAtMs
|
|
3068
3151
|
? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
|
|
3069
3152
|
(value) => ({ status: "fulfilled", value }),
|
|
@@ -3276,6 +3359,41 @@ async function fetchUsageLimitsUncached({
|
|
|
3276
3359
|
}
|
|
3277
3360
|
}
|
|
3278
3361
|
|
|
3362
|
+
// OpenCode Go: persist last successful windows so a transient timeout/5xx
|
|
3363
|
+
// (common on GFW-affected routes to opencode.ai) serves stale bars instead of
|
|
3364
|
+
// a flashing "fetch failed". Mirrors Codex / Claude / Antigravity.
|
|
3365
|
+
// Auth errors (401/403 invalid key, missing subscription, expired cookie) are
|
|
3366
|
+
// actionable and must not be hidden behind stale cache — they carry
|
|
3367
|
+
// `auth_error:true` from opencode-go-limits.js.
|
|
3368
|
+
const opencodeGoIsAuthError =
|
|
3369
|
+
Boolean(opencodeGoRaw?.auth_error) ||
|
|
3370
|
+
(typeof opencodeGoRaw?.error === "string" &&
|
|
3371
|
+
/Not signed in|auth cookie|Refresh the auth cookie|Failed to resolve Workspace ID.*401|Failed to resolve Workspace ID.*403/i.test(
|
|
3372
|
+
opencodeGoRaw.error,
|
|
3373
|
+
));
|
|
3374
|
+
let opencodeGo;
|
|
3375
|
+
if (opencodeGoRaw && opencodeGoRaw.configured === false) {
|
|
3376
|
+
opencodeGo = opencodeGoRaw;
|
|
3377
|
+
} else if (opencodeGoIsAuthError) {
|
|
3378
|
+
opencodeGo = opencodeGoRaw;
|
|
3379
|
+
} else if (opencodeGoRaw?.subscription_status === "inactive") {
|
|
3380
|
+
opencodeGo = opencodeGoRaw;
|
|
3381
|
+
} else if (opencodeGoRaw && !opencodeGoRaw.error && hasOpencodeGoWindow(opencodeGoRaw)) {
|
|
3382
|
+
opencodeGo = {
|
|
3383
|
+
...opencodeGoRaw,
|
|
3384
|
+
stale: false,
|
|
3385
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
3386
|
+
};
|
|
3387
|
+
writeOpencodeGoLimitsCache(opencodeGo, { home, nowMs });
|
|
3388
|
+
} else {
|
|
3389
|
+
const cached = readOpencodeGoLimitsCache({ home, nowMs });
|
|
3390
|
+
if (cached) {
|
|
3391
|
+
opencodeGo = cached;
|
|
3392
|
+
} else {
|
|
3393
|
+
opencodeGo = opencodeGoRaw || { configured: true, error: "Unknown error" };
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3279
3397
|
const data = {
|
|
3280
3398
|
fetched_at: new Date(nowMs).toISOString(),
|
|
3281
3399
|
claude: withPlanLabel(claude, claudePlanType, "Claude"),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,z as a}from"./__vite-browser-external-D_dICEUB.js";import{u as p}from"./DialogTitle-F6W1aTiE.js";import{u as c}from"./useScrollLock-C3TFkbz0.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
|